
Business Investment Advisor
- 70 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
business-investment-advisor is a skill that screens investment opportunities, analyzes portfolio risk, and generates due-diligence checklists.
About
This skill screens investment opportunities, analyzes portfolio composition and risk, and generates due-diligence checklists. It ships three Python tools that read JSON inputs and output ranked opportunities, risk reports, and scored checklists. It targets business owners, angel investors, and corporate development teams evaluating investments from $50K to $50M.
- Screens and ranks investment opportunities by ROI, risk, and payback
- Analyzes portfolio diversification, concentration risk, and sector exposure
- Generates structured due-diligence checklists with 0-10 scoring
Business Investment Advisor by the numbers
- 70 all-time installs (skills.sh)
- Ranked #554 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
business-investment-advisor capabilities & compatibility
- Capabilities
- investment screening · portfolio analysis · due diligence
- Use cases
- data analysis · research
- Pricing
- Free
What business-investment-advisor says it does
investment analysis toolkit for screening opportunities, analyzing portfolio composition, and generating due diligence checklists.
Designed for business owners, angel investors, and corporate development teams evaluating investments from $50K to $50M.
Analyze portfolio diversification and risk exposure
npx skills add https://github.com/borghei/claude-skills --skill business-investment-advisorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 70 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Screen and rank investment opportunities, assess portfolio risk, and run due diligence.
Who is it for?
Business owners, angel investors, and corp-dev teams evaluating investments from $50K to $50M.
Skip if: Executing trades or moving money.
When should I use this skill?
You need to screen investments, analyze a portfolio, or run due diligence on a target.
What you get
Ranked opportunities with scores, a portfolio risk report, and a scored due-diligence checklist.
- ranked opportunities
- portfolio risk report
- due diligence checklist
By the numbers
- 3 Python tools included
- designed for $50K to $50M investments
- due diligence items scored 0-10
Files
Business Investment Advisor Skill
Overview
Production-ready investment analysis toolkit for screening opportunities, analyzing portfolio composition, and generating due diligence checklists. Designed for business owners, angel investors, and corporate development teams evaluating investments from $50K to $50M.
Quick Start
# Screen investments by criteria (ROI, risk, payback)
python scripts/investment_screener.py opportunities.json --min-roi 15 --max-payback 36
# Analyze portfolio diversification and risk exposure
python scripts/portfolio_analyzer.py portfolio.json
# Generate due diligence checklist for an investment target
python scripts/due_diligence_checklist.py --type saas --stage series-a --amount 500000Tools Overview
| Tool | Purpose | Input | Output |
|---|---|---|---|
investment_screener.py | Filter & rank investments | JSON with opportunity data | Ranked opportunities + scores |
portfolio_analyzer.py | Portfolio risk & diversification | JSON with holdings | Risk report + recommendations |
due_diligence_checklist.py | DD checklist generation | Investment parameters | Structured checklist + scoring |
Workflows
Workflow 1: Opportunity Evaluation Pipeline
1. Compile investment opportunities into JSON format (see Common Patterns) 2. Run investment_screener.py with your criteria filters 3. Review ranked results focusing on composite score 4. For top candidates, run due_diligence_checklist.py to generate investigation plan 5. After DD completion, update portfolio model and run portfolio_analyzer.py
Workflow 2: Portfolio Health Check
1. Export current holdings to JSON format 2. Run portfolio_analyzer.py to assess diversification 3. Review concentration risk, sector exposure, and liquidity analysis 4. Use recommendations to identify rebalancing opportunities 5. Screen new opportunities with investment_screener.py to fill gaps
Workflow 3: Due Diligence Sprint
1. Run due_diligence_checklist.py with target parameters 2. Assign checklist items to team members with deadlines 3. Score each item as investigation progresses (0-10) 4. Re-run with --score-file to get weighted DD score 5. Use composite score to support go/no-go decision
Reference Documentation
See references/investment-frameworks.md for detailed frameworks including:
- Investment scoring methodology
- Risk assessment matrix
- Portfolio diversification guidelines
- Due diligence phase frameworks
- Industry-specific evaluation criteria
Common Patterns
Pattern: Investment Opportunities JSON
{
"opportunities": [
{
"name": "TechCo SaaS",
"type": "equity",
"sector": "technology",
"stage": "series-a",
"amount": 250000,
"expected_roi_pct": 25.0,
"risk_level": "high",
"payback_months": 36,
"revenue": 1200000,
"revenue_growth_pct": 85.0,
"gross_margin_pct": 78.0,
"burn_rate_monthly": 80000,
"runway_months": 18
}
]
}Pattern: Portfolio Holdings JSON
{
"portfolio": {
"total_invested": 2000000,
"holdings": [
{
"name": "Investment A",
"type": "equity",
"sector": "technology",
"invested": 250000,
"current_value": 375000,
"date_invested": "2024-06-15",
"stage": "series-a",
"liquidity": "illiquid",
"status": "active"
}
]
}
}Risk Level Definitions
| Level | Expected Return | Loss Probability | Typical Payback |
|---|---|---|---|
| Low | 5-10% | < 10% | < 24 months |
| Medium | 10-20% | 10-30% | 24-48 months |
| High | 20-40% | 30-50% | 36-60 months |
| Very High | 40%+ | > 50% | 48+ months |
{
"_comment": "opportunities.json — 5 investment opportunities for the screener tool. Each includes financial metrics, risk factors, and thesis for evaluation.",
"screened_date": "2026-03-31",
"opportunities": [
{
"id": "OPP-001",
"name": "NovaTech AI Solutions",
"type": "series_b",
"sector": "Enterprise Software / AI",
"stage": "Growth",
"ask_amount": 5000000,
"pre_money_valuation": 45000000,
"currency": "USD",
"financials": {
"arr": 8200000,
"arr_growth_yoy": 0.142,
"gross_margin": 0.78,
"net_revenue_retention": 1.25,
"burn_rate_monthly": 420000,
"runway_months": 14,
"ltv_cac_ratio": 4.2,
"payback_months": 11
},
"thesis": "Category-leading AI ops platform with strong enterprise traction. 85 customers including 12 Fortune 500. Risk: concentrated revenue — top 3 customers = 40% of ARR.",
"risk_factors": ["customer_concentration", "competitive_market", "short_runway"],
"target_return_multiple": 8.0
},
{
"id": "OPP-002",
"name": "GreenGrid Energy Storage",
"type": "series_a",
"sector": "Clean Energy / Hardware",
"stage": "Early Growth",
"ask_amount": 12000000,
"pre_money_valuation": 38000000,
"currency": "USD",
"financials": {
"arr": 3100000,
"arr_growth_yoy": 0.280,
"gross_margin": 0.42,
"net_revenue_retention": 1.08,
"burn_rate_monthly": 850000,
"runway_months": 9,
"ltv_cac_ratio": 2.1,
"payback_months": 18
},
"thesis": "Novel solid-state battery tech with 3 patents granted. First utility-scale contracts signed. Risk: hardware margins, capital intensity, 9-month runway.",
"risk_factors": ["capital_intensive", "hardware_margins", "regulatory_risk", "short_runway"],
"target_return_multiple": 12.0
},
{
"id": "OPP-003",
"name": "MedSecure Compliance",
"type": "series_b",
"sector": "Healthcare IT / Compliance",
"stage": "Growth",
"ask_amount": 8000000,
"pre_money_valuation": 62000000,
"currency": "USD",
"financials": {
"arr": 14500000,
"arr_growth_yoy": 0.085,
"gross_margin": 0.82,
"net_revenue_retention": 1.12,
"burn_rate_monthly": 310000,
"runway_months": 28,
"ltv_cac_ratio": 5.8,
"payback_months": 8
},
"thesis": "HIPAA/SOC2 compliance automation with 340 healthcare clients. Sticky product with low churn. Risk: slowing growth (8.5% YoY), regulatory dependency.",
"risk_factors": ["slowing_growth", "regulatory_dependency", "niche_market"],
"target_return_multiple": 4.0
},
{
"id": "OPP-004",
"name": "UrbanFleet Logistics",
"type": "series_c",
"sector": "Logistics / Marketplace",
"stage": "Late Growth",
"ask_amount": 25000000,
"pre_money_valuation": 180000000,
"currency": "USD",
"financials": {
"arr": 42000000,
"arr_growth_yoy": 0.065,
"gross_margin": 0.31,
"net_revenue_retention": 0.94,
"burn_rate_monthly": 2800000,
"runway_months": 11,
"ltv_cac_ratio": 1.8,
"payback_months": 22
},
"thesis": "Last-mile delivery platform in 18 metro areas. Strong brand. Risk: negative unit economics in 6 markets, NRR below 100%, thin margins.",
"risk_factors": ["negative_unit_economics", "thin_margins", "high_burn", "net_retention_below_100"],
"target_return_multiple": 3.0
},
{
"id": "OPP-005",
"name": "Cobalt Cybersecurity",
"type": "series_a",
"sector": "Cybersecurity",
"stage": "Early Growth",
"ask_amount": 6000000,
"pre_money_valuation": 28000000,
"currency": "USD",
"financials": {
"arr": 4800000,
"arr_growth_yoy": 0.220,
"gross_margin": 0.74,
"net_revenue_retention": 1.32,
"burn_rate_monthly": 380000,
"runway_months": 18,
"ltv_cac_ratio": 3.9,
"payback_months": 13
},
"thesis": "AI-powered threat detection for mid-market. 132% NRR driven by expansion. Former CrowdStrike leadership team. Risk: crowded market, mid-market churn.",
"risk_factors": ["competitive_market", "mid_market_churn_risk"],
"target_return_multiple": 10.0
}
]
}
{
"_comment": "portfolio.json — Sample investment portfolio for the portfolio analyzer. Contains 10 positions across asset classes with purchase history and current valuations.",
"portfolio_name": "Meridian Growth Fund",
"owner": "Meridian Capital Partners",
"as_of_date": "2026-03-31",
"base_currency": "USD",
"positions": [
{
"ticker": "AAPL",
"name": "Apple Inc.",
"asset_class": "equity",
"sector": "Technology",
"shares": 450,
"avg_cost_basis": 178.50,
"current_price": 215.30,
"currency": "USD",
"purchase_date": "2024-06-15",
"account": "taxable"
},
{
"ticker": "MSFT",
"name": "Microsoft Corporation",
"asset_class": "equity",
"sector": "Technology",
"shares": 300,
"avg_cost_basis": 340.00,
"current_price": 428.75,
"currency": "USD",
"purchase_date": "2024-03-22",
"account": "taxable"
},
{
"ticker": "JNJ",
"name": "Johnson & Johnson",
"asset_class": "equity",
"sector": "Healthcare",
"shares": 200,
"avg_cost_basis": 162.30,
"current_price": 155.40,
"currency": "USD",
"purchase_date": "2025-01-10",
"account": "ira"
},
{
"ticker": "VTI",
"name": "Vanguard Total Stock Market ETF",
"asset_class": "etf",
"sector": "Broad Market",
"shares": 800,
"avg_cost_basis": 225.00,
"current_price": 262.80,
"currency": "USD",
"purchase_date": "2023-11-05",
"account": "ira"
},
{
"ticker": "BND",
"name": "Vanguard Total Bond Market ETF",
"asset_class": "fixed_income",
"sector": "Bonds",
"shares": 600,
"avg_cost_basis": 73.50,
"current_price": 72.10,
"currency": "USD",
"purchase_date": "2024-09-18",
"account": "ira"
},
{
"ticker": "AMZN",
"name": "Amazon.com Inc.",
"asset_class": "equity",
"sector": "Technology",
"shares": 250,
"avg_cost_basis": 145.20,
"current_price": 198.60,
"currency": "USD",
"purchase_date": "2024-07-30",
"account": "taxable"
},
{
"ticker": "XOM",
"name": "Exxon Mobil Corporation",
"asset_class": "equity",
"sector": "Energy",
"shares": 350,
"avg_cost_basis": 105.80,
"current_price": 112.45,
"currency": "USD",
"purchase_date": "2025-04-12",
"account": "taxable"
},
{
"ticker": "VXUS",
"name": "Vanguard Total International Stock ETF",
"asset_class": "etf",
"sector": "International",
"shares": 500,
"avg_cost_basis": 56.20,
"current_price": 61.35,
"currency": "USD",
"purchase_date": "2024-01-15",
"account": "ira"
},
{
"ticker": "O",
"name": "Realty Income Corporation",
"asset_class": "reit",
"sector": "Real Estate",
"shares": 400,
"avg_cost_basis": 58.90,
"current_price": 55.20,
"currency": "USD",
"purchase_date": "2025-06-20",
"account": "taxable"
},
{
"ticker": "GLD",
"name": "SPDR Gold Shares",
"asset_class": "commodity",
"sector": "Precious Metals",
"shares": 150,
"avg_cost_basis": 185.00,
"current_price": 224.60,
"currency": "USD",
"purchase_date": "2024-11-08",
"account": "taxable"
}
]
}
Investment Analysis Frameworks
Investment Scoring Methodology
Composite Score Components
The investment screener uses a weighted composite score (0-100):
| Component | Weight | Measures |
|---|---|---|
| Return Score | 30% | Expected ROI relative to risk-adjusted benchmarks |
| Risk Score | 25% | Risk level, runway, diversification benefit |
| Growth Score | 20% | Revenue growth rate, market position |
| Efficiency Score | 15% | Gross margin, burn efficiency, unit economics |
| Payback Score | 10% | Time to recover invested capital |
Return Score Calculation
- ROI > 30%: Score 90-100
- ROI 20-30%: Score 70-89
- ROI 10-20%: Score 50-69
- ROI 5-10%: Score 30-49
- ROI < 5%: Score 0-29
Adjust down by 10-20 points for "high" or "very_high" risk investments to create risk-adjusted return score.
Growth Score Calculation
- Revenue growth > 100%: Score 90-100 (hypergrowth)
- Revenue growth 50-100%: Score 70-89 (strong growth)
- Revenue growth 20-50%: Score 50-69 (healthy growth)
- Revenue growth 0-20%: Score 30-49 (moderate)
- Revenue declining: Score 0-29
Risk Assessment Matrix
Financial Risk Factors
1. Runway risk - Months of cash remaining
- > 18 months: Low risk
- 12-18 months: Medium risk
- 6-12 months: High risk
- < 6 months: Critical risk
2. Concentration risk - Revenue dependency
- Top customer < 10% revenue: Low
- Top customer 10-25%: Medium
- Top customer > 25%: High
3. Burn rate risk - Cash consumption relative to growth
- Burn multiple < 1: Efficient
- Burn multiple 1-2: Acceptable
- Burn multiple > 2: Concerning
Market Risk Factors
1. Market size - Total addressable market relative to investment thesis 2. Competition - Number and strength of competitors 3. Regulatory - Regulatory barriers or pending regulation 4. Technology - Platform risk, technical debt, defensibility
Execution Risk Factors
1. Team completeness - Key roles filled 2. Product maturity - MVP, growth, or scale stage 3. Customer validation - Paying customers, retention data 4. Operational scalability - Can operations scale with growth
Portfolio Diversification Guidelines
Sector Allocation Targets
| Investor Type | Max Single Sector | Min Sectors | Max Single Investment |
|---|---|---|---|
| Conservative | 40% | 4+ | 15% |
| Moderate | 50% | 3+ | 20% |
| Aggressive | 60% | 2+ | 30% |
Stage Diversification
Balanced portfolio across investment stages:
- Seed/Angel: 20-30% (highest risk, highest return potential)
- Series A: 30-40% (validated product, scaling risk)
- Series B+: 20-30% (lower risk, moderate returns)
- Mature/Stable: 10-20% (cash flow, portfolio anchor)
Liquidity Management
- Maintain 10-20% in liquid or semi-liquid positions
- Plan for 5-7 year hold periods on illiquid investments
- Stagger investment timing to avoid liquidity crunches
- Reserve 20% of allocated capital for follow-on rounds
Due Diligence Framework
Phase 1: Initial Screening (1-2 days)
Quick assessment to determine if full DD is warranted:
- Business model viability
- Market size validation
- Team background check
- Financial summary review
- Red flag identification
Phase 2: Deep Dive (1-3 weeks)
Comprehensive investigation across all dimensions:
Financial DD:
- Historical financial statements (3 years if available)
- Revenue recognition practices
- Unit economics validation
- Cash flow projections
- Cap table review
- Debt and liability analysis
Commercial DD:
- Customer interviews (5-10 customers)
- Market sizing methodology review
- Competitive landscape mapping
- Pricing strategy analysis
- Sales pipeline review
Technical DD:
- Architecture review
- Code quality assessment
- Infrastructure scalability
- Security posture
- Technical debt estimation
- IP ownership verification
Legal DD:
- Corporate structure
- Material contracts review
- IP assignments and patents
- Regulatory compliance
- Pending or threatened litigation
- Employment agreements
Phase 3: Negotiation Preparation (3-5 days)
- Valuation modeling (DCF, comparables, precedent transactions)
- Term sheet drafting
- Key terms identification
- Deal structure optimization
- Governance requirements
Industry-Specific Criteria
SaaS Companies
- NRR > 110%
- Gross margin > 70%
- CAC payback < 18 months
- LTV:CAC > 3x
- Monthly churn < 3%
- Rule of 40 compliance
E-Commerce
- Gross margin > 40%
- Customer repeat rate > 30%
- Inventory turnover > 6x
- CAC:First order value < 0.5
- Organic traffic > 40%
Marketplace
- GMV growth > 50%
- Take rate stability
- Supply/demand balance
- Network effects evidence
- Disintermediation risk
Hardware/Manufacturing
- Gross margin > 35%
- Design-to-production timeline
- Supply chain resilience
- IP moat strength
- Certification status
#!/usr/bin/env python3
"""
Due Diligence Checklist Generator
Generates comprehensive due diligence checklists for investment targets,
customized by company type, stage, and investment amount. Supports scoring
completed checklists to produce a weighted DD score.
Usage:
python due_diligence_checklist.py --type saas --stage series-a --amount 500000
python due_diligence_checklist.py --type ecommerce --stage seed --format json
python due_diligence_checklist.py --type saas --stage series-b --score-file scores.json
"""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional
# Due diligence categories with items, weights, and applicability
DD_CATEGORIES = {
"financial": {
"weight": 0.25,
"label": "Financial Due Diligence",
"items": [
{"id": "fin_01", "item": "Historical financial statements (P&L, BS, CF) - 3 years", "priority": "high", "min_stage": "seed"},
{"id": "fin_02", "item": "Revenue recognition policy and audit trail", "priority": "high", "min_stage": "series-a"},
{"id": "fin_03", "item": "Unit economics validation (LTV, CAC, margins)", "priority": "high", "min_stage": "seed"},
{"id": "fin_04", "item": "Cash flow projections and burn rate analysis", "priority": "high", "min_stage": "seed"},
{"id": "fin_05", "item": "Cap table review and dilution modeling", "priority": "high", "min_stage": "seed"},
{"id": "fin_06", "item": "Accounts receivable aging and collectability", "priority": "medium", "min_stage": "series-a"},
{"id": "fin_07", "item": "Debt schedule and covenant compliance", "priority": "medium", "min_stage": "series-a"},
{"id": "fin_08", "item": "Tax returns and compliance history", "priority": "medium", "min_stage": "series-a"},
{"id": "fin_09", "item": "Working capital analysis", "priority": "medium", "min_stage": "series-b"},
{"id": "fin_10", "item": "Audit opinion and management letter review", "priority": "low", "min_stage": "series-b"},
],
},
"commercial": {
"weight": 0.20,
"label": "Commercial Due Diligence",
"items": [
{"id": "com_01", "item": "Customer concentration analysis (top 10 customers)", "priority": "high", "min_stage": "seed"},
{"id": "com_02", "item": "Customer reference calls (minimum 5)", "priority": "high", "min_stage": "series-a"},
{"id": "com_03", "item": "Market size validation (TAM/SAM/SOM)", "priority": "high", "min_stage": "seed"},
{"id": "com_04", "item": "Competitive landscape mapping", "priority": "high", "min_stage": "seed"},
{"id": "com_05", "item": "Sales pipeline and conversion funnel analysis", "priority": "medium", "min_stage": "series-a"},
{"id": "com_06", "item": "Pricing strategy and competitive positioning", "priority": "medium", "min_stage": "seed"},
{"id": "com_07", "item": "Customer churn analysis and NPS/CSAT data", "priority": "high", "min_stage": "series-a"},
{"id": "com_08", "item": "Channel partner and distribution analysis", "priority": "low", "min_stage": "series-b"},
{"id": "com_09", "item": "Contract terms and renewal rate analysis", "priority": "medium", "min_stage": "series-a"},
],
},
"technical": {
"weight": 0.15,
"label": "Technical Due Diligence",
"items": [
{"id": "tech_01", "item": "Architecture review and scalability assessment", "priority": "high", "min_stage": "seed"},
{"id": "tech_02", "item": "Code quality and technical debt assessment", "priority": "medium", "min_stage": "series-a"},
{"id": "tech_03", "item": "Security posture and vulnerability assessment", "priority": "high", "min_stage": "series-a"},
{"id": "tech_04", "item": "Infrastructure and DevOps maturity", "priority": "medium", "min_stage": "series-a"},
{"id": "tech_05", "item": "Data architecture and privacy compliance", "priority": "high", "min_stage": "seed"},
{"id": "tech_06", "item": "Third-party dependency and vendor lock-in risk", "priority": "medium", "min_stage": "series-a"},
{"id": "tech_07", "item": "Disaster recovery and business continuity plans", "priority": "low", "min_stage": "series-b"},
{"id": "tech_08", "item": "Product roadmap feasibility assessment", "priority": "medium", "min_stage": "seed"},
],
},
"legal": {
"weight": 0.15,
"label": "Legal Due Diligence",
"items": [
{"id": "leg_01", "item": "Corporate structure and formation documents", "priority": "high", "min_stage": "seed"},
{"id": "leg_02", "item": "IP ownership and assignment agreements", "priority": "high", "min_stage": "seed"},
{"id": "leg_03", "item": "Material contracts review", "priority": "high", "min_stage": "series-a"},
{"id": "leg_04", "item": "Employment agreements and non-competes", "priority": "medium", "min_stage": "series-a"},
{"id": "leg_05", "item": "Pending or threatened litigation", "priority": "high", "min_stage": "seed"},
{"id": "leg_06", "item": "Regulatory compliance assessment", "priority": "medium", "min_stage": "series-a"},
{"id": "leg_07", "item": "Option pool and equity incentive plan review", "priority": "medium", "min_stage": "seed"},
{"id": "leg_08", "item": "Insurance coverage adequacy", "priority": "low", "min_stage": "series-b"},
],
},
"team": {
"weight": 0.15,
"label": "Team & Organization",
"items": [
{"id": "team_01", "item": "Founder/CEO background and reference checks", "priority": "high", "min_stage": "seed"},
{"id": "team_02", "item": "Key personnel identification and retention risk", "priority": "high", "min_stage": "seed"},
{"id": "team_03", "item": "Organizational chart and key role gaps", "priority": "medium", "min_stage": "series-a"},
{"id": "team_04", "item": "Compensation benchmarking and equity allocation", "priority": "medium", "min_stage": "series-a"},
{"id": "team_05", "item": "Culture assessment and employee satisfaction", "priority": "low", "min_stage": "series-b"},
{"id": "team_06", "item": "Board composition and governance structure", "priority": "medium", "min_stage": "series-a"},
{"id": "team_07", "item": "Hiring plan and talent pipeline assessment", "priority": "medium", "min_stage": "series-a"},
],
},
"operational": {
"weight": 0.10,
"label": "Operational Due Diligence",
"items": [
{"id": "ops_01", "item": "Key operational metrics and KPI tracking", "priority": "high", "min_stage": "seed"},
{"id": "ops_02", "item": "Customer onboarding and support processes", "priority": "medium", "min_stage": "series-a"},
{"id": "ops_03", "item": "Vendor and supply chain dependencies", "priority": "medium", "min_stage": "series-a"},
{"id": "ops_04", "item": "Operational scalability assessment", "priority": "medium", "min_stage": "series-a"},
{"id": "ops_05", "item": "Quality assurance and testing processes", "priority": "low", "min_stage": "series-b"},
],
},
}
# Type-specific additional items
TYPE_EXTRAS = {
"saas": [
{"id": "saas_01", "category": "commercial", "item": "MRR/ARR breakdown and growth trend", "priority": "high"},
{"id": "saas_02", "category": "commercial", "item": "Net revenue retention and expansion analysis", "priority": "high"},
{"id": "saas_03", "category": "technical", "item": "Multi-tenancy architecture and data isolation", "priority": "medium"},
],
"ecommerce": [
{"id": "ecom_01", "category": "commercial", "item": "Inventory turnover and fulfillment metrics", "priority": "high"},
{"id": "ecom_02", "category": "commercial", "item": "Customer repeat purchase rate and cohort analysis", "priority": "high"},
{"id": "ecom_03", "category": "operational", "item": "Supply chain resilience and vendor diversification", "priority": "high"},
],
"marketplace": [
{"id": "mkt_01", "category": "commercial", "item": "Supply/demand balance and liquidity metrics", "priority": "high"},
{"id": "mkt_02", "category": "commercial", "item": "Take rate and GMV growth analysis", "priority": "high"},
{"id": "mkt_03", "category": "commercial", "item": "Disintermediation risk assessment", "priority": "medium"},
],
"hardware": [
{"id": "hw_01", "category": "operational", "item": "Manufacturing process and yield rates", "priority": "high"},
{"id": "hw_02", "category": "legal", "item": "Patent portfolio and freedom-to-operate analysis", "priority": "high"},
{"id": "hw_03", "category": "operational", "item": "Certification status (UL, CE, FCC, etc.)", "priority": "high"},
],
}
STAGE_ORDER = {"seed": 0, "series-a": 1, "series-b": 2, "growth": 3, "late": 4}
def filter_items_by_stage(items: List[Dict], stage: str) -> List[Dict]:
"""Filter DD items applicable to the investment stage."""
stage_val = STAGE_ORDER.get(stage, 0)
return [item for item in items if STAGE_ORDER.get(item.get("min_stage", "seed"), 0) <= stage_val]
def generate_checklist(company_type: str, stage: str, amount: float) -> Dict[str, Any]:
"""Generate a due diligence checklist."""
checklist = {"type": company_type, "stage": stage, "amount": amount, "categories": {}}
total_items = 0
for cat_key, cat_data in DD_CATEGORIES.items():
filtered = filter_items_by_stage(cat_data["items"], stage)
items_out = []
for item in filtered:
items_out.append({
"id": item["id"],
"item": item["item"],
"priority": item["priority"],
"score": None,
"notes": "",
})
checklist["categories"][cat_key] = {
"label": cat_data["label"],
"weight": cat_data["weight"],
"items": items_out,
}
total_items += len(items_out)
# Add type-specific items
extras = TYPE_EXTRAS.get(company_type, [])
for extra in extras:
cat_key = extra["category"]
if cat_key in checklist["categories"]:
checklist["categories"][cat_key]["items"].append({
"id": extra["id"],
"item": extra["item"],
"priority": extra["priority"],
"score": None,
"notes": "",
})
total_items += 1
# Adjust depth based on investment size
depth = "standard"
if amount >= 1_000_000:
depth = "comprehensive"
elif amount < 100_000:
depth = "lightweight"
# Remove low-priority items for small investments
for cat_key in checklist["categories"]:
checklist["categories"][cat_key]["items"] = [
i for i in checklist["categories"][cat_key]["items"]
if i["priority"] != "low"
]
total_items = sum(
len(c["items"]) for c in checklist["categories"].values()
)
checklist["depth"] = depth
checklist["total_items"] = total_items
return checklist
def score_checklist(checklist: Dict, scores: Dict[str, float]) -> Dict[str, Any]:
"""Score a completed checklist and produce weighted DD score."""
category_scores = {}
total_weighted = 0.0
total_weight = 0.0
for cat_key, cat_data in checklist["categories"].items():
item_scores = []
for item in cat_data["items"]:
score = scores.get(item["id"])
if score is not None:
item["score"] = score
item_scores.append(score)
if item_scores:
avg = sum(item_scores) / len(item_scores)
category_scores[cat_key] = {
"label": cat_data["label"],
"average_score": round(avg, 1),
"items_scored": len(item_scores),
"items_total": len(cat_data["items"]),
"weight": cat_data["weight"],
}
total_weighted += avg * cat_data["weight"]
total_weight += cat_data["weight"]
composite = total_weighted / total_weight if total_weight > 0 else 0
# Recommendation
if composite >= 8:
recommendation = "PROCEED - Strong due diligence results across all categories"
elif composite >= 6:
recommendation = "PROCEED WITH CONDITIONS - Generally positive, address flagged items"
elif composite >= 4:
recommendation = "CAUTION - Material concerns identified, require resolution before closing"
else:
recommendation = "DO NOT PROCEED - Significant red flags identified"
return {
"composite_score": round(composite, 1),
"category_scores": category_scores,
"recommendation": recommendation,
"items_scored": sum(c["items_scored"] for c in category_scores.values()),
"items_total": checklist["total_items"],
}
def format_currency(amount: float) -> str:
if amount >= 1_000_000:
return f"${amount/1_000_000:.1f}M"
elif amount >= 1_000:
return f"${amount/1_000:.0f}K"
return f"${amount:,.0f}"
def print_human(checklist: Dict, scoring: Optional[Dict] = None) -> None:
"""Print checklist in human-readable format."""
print("=" * 72)
print(f" Due Diligence Checklist")
print("=" * 72)
print(f"\n Type: {checklist['type']}")
print(f" Stage: {checklist['stage']}")
print(f" Amount: {format_currency(checklist['amount'])}")
print(f" Depth: {checklist['depth']}")
print(f" Items: {checklist['total_items']}")
for cat_key, cat_data in checklist["categories"].items():
items = cat_data["items"]
if not items:
continue
print(f"\n --- {cat_data['label']} (Weight: {cat_data['weight']*100:.0f}%) ---")
for item in items:
priority_marker = {"high": "[H]", "medium": "[M]", "low": "[L]"}
marker = priority_marker.get(item["priority"], "[ ]")
score_str = f" [{item['score']}/10]" if item.get("score") is not None else " [ /10]"
print(f" {marker} {item['id']}: {item['item']}{score_str}")
if scoring:
print(f"\n {'=' * 68}")
print(f" DD SCORING RESULTS")
print(f" {'=' * 68}")
print(f"\n Composite Score: {scoring['composite_score']}/10.0")
print(f" Items Scored: {scoring['items_scored']}/{scoring['items_total']}")
print(f"\n Category Breakdown:")
for cat_key, cs in scoring["category_scores"].items():
bar = "#" * int(cs["average_score"])
print(f" {cs['label']:<30} {cs['average_score']:>4.1f}/10 {bar}")
print(f"\n --> {scoring['recommendation']}")
print()
def main():
parser = argparse.ArgumentParser(
description="Generate due diligence checklist for investment targets"
)
parser.add_argument("--type", choices=["saas", "ecommerce", "marketplace", "hardware", "general"],
default="general", help="Company type (default: general)")
parser.add_argument("--stage", choices=["seed", "series-a", "series-b", "growth", "late"],
default="series-a", help="Investment stage (default: series-a)")
parser.add_argument("--amount", type=float, default=500000,
help="Investment amount in USD (default: 500000)")
parser.add_argument("--format", choices=["human", "json"], default="human", help="Output format")
parser.add_argument("--score-file", help="JSON file with item scores to compute DD score")
args = parser.parse_args()
checklist = generate_checklist(args.type, args.stage, args.amount)
scoring = None
if args.score_file:
with open(args.score_file, "r", encoding="utf-8") as f:
scores = json.load(f)
scoring = score_checklist(checklist, scores)
if args.format == "json":
output = {"checklist": checklist}
if scoring:
output["scoring"] = scoring
print(json.dumps(output, indent=2, default=str))
else:
print_human(checklist, scoring)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Investment Screener
Screens and ranks investment opportunities by criteria including ROI, risk level,
payback period, growth rate, and efficiency metrics. Produces a composite score
for comparison.
Expected JSON input: {"opportunities": [{"name", "type", "sector", "amount",
"expected_roi_pct", "risk_level", "payback_months", ...}]}
Usage:
python investment_screener.py opportunities.json
python investment_screener.py opportunities.json --min-roi 15
python investment_screener.py opportunities.json --max-payback 36 --max-risk medium
python investment_screener.py opportunities.json --sector technology --format json
"""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional, Tuple
RISK_LEVELS = {"low": 1, "medium": 2, "high": 3, "very_high": 4}
RISK_LABELS = {1: "Low", 2: "Medium", 3: "High", 4: "Very High"}
def parse_risk(risk_str: str) -> int:
"""Convert risk string to numeric level."""
return RISK_LEVELS.get(risk_str.lower().replace(" ", "_").replace("-", "_"), 2)
def score_return(roi_pct: float, risk_level: int) -> float:
"""Score expected return (0-100), risk-adjusted."""
if roi_pct >= 30:
base = 90 + min((roi_pct - 30) / 7, 10)
elif roi_pct >= 20:
base = 70 + (roi_pct - 20) * 2
elif roi_pct >= 10:
base = 50 + (roi_pct - 10) * 2
elif roi_pct >= 5:
base = 30 + (roi_pct - 5) * 4
else:
base = max(0, roi_pct * 6)
# Risk adjustment: penalize high risk relative to return
risk_penalty = (risk_level - 2) * 8 # medium is baseline (0 penalty)
return max(0, min(100, base - risk_penalty))
def score_risk(risk_level: int, runway_months: Optional[float] = None) -> float:
"""Score risk (0-100, higher is better/lower risk)."""
risk_base = {1: 90, 2: 70, 3: 45, 4: 20}
base = risk_base.get(risk_level, 50)
if runway_months is not None:
if runway_months >= 18:
base += 10
elif runway_months >= 12:
base += 5
elif runway_months < 6:
base -= 15
else:
base -= 5
return max(0, min(100, base))
def score_growth(growth_pct: Optional[float]) -> float:
"""Score revenue growth (0-100)."""
if growth_pct is None:
return 50 # neutral if unknown
if growth_pct >= 100:
return 90 + min((growth_pct - 100) / 10, 10)
elif growth_pct >= 50:
return 70 + (growth_pct - 50) * 0.4
elif growth_pct >= 20:
return 50 + (growth_pct - 20) * 0.67
elif growth_pct >= 0:
return 30 + growth_pct * 1.0
else:
return max(0, 30 + growth_pct)
def score_efficiency(gross_margin_pct: Optional[float], burn_rate: Optional[float],
revenue: Optional[float]) -> float:
"""Score operational efficiency (0-100)."""
scores = []
if gross_margin_pct is not None:
if gross_margin_pct >= 75:
scores.append(90)
elif gross_margin_pct >= 50:
scores.append(70)
elif gross_margin_pct >= 30:
scores.append(50)
else:
scores.append(30)
if burn_rate is not None and revenue is not None and burn_rate > 0:
burn_multiple = (burn_rate * 12) / max(revenue, 1)
if burn_multiple < 1:
scores.append(90)
elif burn_multiple < 2:
scores.append(65)
else:
scores.append(35)
return sum(scores) / len(scores) if scores else 50
def score_payback(payback_months: Optional[float]) -> float:
"""Score payback period (0-100, shorter is better)."""
if payback_months is None:
return 50
if payback_months <= 12:
return 90 + min((12 - payback_months), 10)
elif payback_months <= 24:
return 70 + (24 - payback_months) * 1.67
elif payback_months <= 36:
return 50 + (36 - payback_months) * 1.67
elif payback_months <= 60:
return 20 + (60 - payback_months) * 1.25
else:
return max(0, 20 - (payback_months - 60) * 0.5)
def calculate_composite(opp: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate composite score for an opportunity."""
risk_level = parse_risk(opp.get("risk_level", "medium"))
ret_score = score_return(opp.get("expected_roi_pct", 0), risk_level)
risk_score = score_risk(risk_level, opp.get("runway_months"))
growth_score = score_growth(opp.get("revenue_growth_pct"))
eff_score = score_efficiency(
opp.get("gross_margin_pct"),
opp.get("burn_rate_monthly"),
opp.get("revenue"),
)
payback_score = score_payback(opp.get("payback_months"))
# Weighted composite
composite = (
ret_score * 0.30
+ risk_score * 0.25
+ growth_score * 0.20
+ eff_score * 0.15
+ payback_score * 0.10
)
return {
"name": opp.get("name", "Unknown"),
"sector": opp.get("sector", "unknown"),
"type": opp.get("type", "unknown"),
"stage": opp.get("stage", "unknown"),
"amount": opp.get("amount", 0),
"expected_roi_pct": opp.get("expected_roi_pct", 0),
"risk_level": RISK_LABELS.get(risk_level, "Unknown"),
"payback_months": opp.get("payback_months"),
"scores": {
"return": round(ret_score, 1),
"risk": round(risk_score, 1),
"growth": round(growth_score, 1),
"efficiency": round(eff_score, 1),
"payback": round(payback_score, 1),
},
"composite_score": round(composite, 1),
"recommendation": get_recommendation(composite, risk_level),
}
def get_recommendation(composite: float, risk_level: int) -> str:
"""Generate recommendation based on composite score."""
if composite >= 80:
return "STRONG BUY - Excellent opportunity across all dimensions"
elif composite >= 65:
return "BUY - Good opportunity, proceed with due diligence"
elif composite >= 50:
return "HOLD - Moderate opportunity, investigate further before committing"
elif composite >= 35:
return "CAUTION - Below-average opportunity, significant concerns"
else:
return "PASS - Poor risk-return profile, not recommended"
def apply_filters(opportunities: List[Dict], args: argparse.Namespace) -> List[Dict]:
"""Apply screening filters to opportunities."""
filtered = []
for opp in opportunities:
if args.min_roi is not None and opp.get("expected_roi_pct", 0) < args.min_roi:
continue
if args.max_payback is not None and opp.get("payback_months"):
if opp["payback_months"] > args.max_payback:
continue
if args.max_risk is not None:
max_risk_val = parse_risk(args.max_risk)
opp_risk_val = parse_risk(opp.get("risk_level", "medium"))
if opp_risk_val > max_risk_val:
continue
if args.sector is not None:
if opp.get("sector", "").lower() != args.sector.lower():
continue
if args.min_amount is not None and opp.get("amount", 0) < args.min_amount:
continue
if args.max_amount is not None and opp.get("amount", 0) > args.max_amount:
continue
filtered.append(opp)
return filtered
def format_currency(amount: float) -> str:
if amount >= 1_000_000:
return f"${amount/1_000_000:.1f}M"
elif amount >= 1_000:
return f"${amount/1_000:.0f}K"
return f"${amount:,.0f}"
def print_human(results: List[Dict[str, Any]], total_before_filter: int) -> None:
"""Print screening results in human-readable format."""
print("=" * 72)
print(" Investment Screening Results")
print("=" * 72)
print(f"\n Screened: {len(results)} of {total_before_filter} opportunities passed filters\n")
if not results:
print(" No opportunities matched the specified criteria.")
return
# Sort by composite score descending
results.sort(key=lambda x: x["composite_score"], reverse=True)
print(f" {'Rank':<5} {'Name':<22} {'Score':>6} {'ROI':>7} {'Risk':<10} {'Payback':>8} {'Amount':>10}")
print(f" {'-'*5} {'-'*22} {'-'*6} {'-'*7} {'-'*10} {'-'*8} {'-'*10}")
for i, r in enumerate(results, 1):
pb = f"{r['payback_months']}mo" if r['payback_months'] else "N/A"
print(
f" {i:<5} {r['name']:<22} {r['composite_score']:>5.1f} "
f"{r['expected_roi_pct']:>6.1f}% {r['risk_level']:<10} {pb:>8} "
f"{format_currency(r['amount']):>10}"
)
# Detailed view for top 3
print(f"\n --- Top Opportunities Detail ---")
for i, r in enumerate(results[:3], 1):
print(f"\n #{i} {r['name']} (Score: {r['composite_score']})")
print(f" Sector: {r['sector']} | Stage: {r['stage']} | Type: {r['type']}")
s = r["scores"]
print(f" Return: {s['return']} | Risk: {s['risk']} | Growth: {s['growth']} | "
f"Efficiency: {s['efficiency']} | Payback: {s['payback']}")
print(f" --> {r['recommendation']}")
print()
def main():
parser = argparse.ArgumentParser(
description="Screen and rank investment opportunities by ROI, risk, payback, and more"
)
parser.add_argument("file", help="JSON file with investment opportunities")
parser.add_argument("--format", choices=["human", "json"], default="human", help="Output format")
parser.add_argument("--min-roi", type=float, help="Minimum expected ROI percentage")
parser.add_argument("--max-payback", type=float, help="Maximum payback period in months")
parser.add_argument("--max-risk", choices=["low", "medium", "high", "very_high"],
help="Maximum acceptable risk level")
parser.add_argument("--sector", help="Filter by sector")
parser.add_argument("--min-amount", type=float, help="Minimum investment amount")
parser.add_argument("--max-amount", type=float, help="Maximum investment amount")
parser.add_argument("--top", type=int, help="Show only top N results")
args = parser.parse_args()
with open(args.file, "r", encoding="utf-8") as f:
data = json.load(f)
opportunities = data.get("opportunities", [])
if not opportunities:
print("Error: No opportunities found in input", file=sys.stderr)
sys.exit(1)
total = len(opportunities)
filtered = apply_filters(opportunities, args)
results = [calculate_composite(opp) for opp in filtered]
results.sort(key=lambda x: x["composite_score"], reverse=True)
if args.top:
results = results[:args.top]
if args.format == "json":
print(json.dumps({"total_screened": total, "passed_filters": len(results),
"results": results}, indent=2))
else:
print_human(results, total)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Portfolio Analyzer
Analyzes investment portfolio for diversification, concentration risk, sector
exposure, performance metrics, and liquidity profile.
Expected JSON input: {"portfolio": {"total_invested": N, "holdings": [...]}}
Usage:
python portfolio_analyzer.py portfolio.json
python portfolio_analyzer.py portfolio.json --format json
python portfolio_analyzer.py portfolio.json --profile moderate
"""
import argparse
import json
import math
import sys
from collections import defaultdict
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
PROFILES = {
"conservative": {"max_single": 0.15, "min_sectors": 4, "max_sector": 0.40, "min_liquid": 0.20},
"moderate": {"max_single": 0.20, "min_sectors": 3, "max_sector": 0.50, "min_liquid": 0.15},
"aggressive": {"max_single": 0.30, "min_sectors": 2, "max_sector": 0.60, "min_liquid": 0.10},
}
def parse_date(date_str: str) -> Optional[datetime]:
"""Parse date string."""
if not date_str or date_str.strip() == "":
return None
for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%m/%d/%Y"):
try:
return datetime.strptime(date_str.strip(), fmt)
except ValueError:
continue
return None
def calculate_hhi(weights: List[float]) -> float:
"""Calculate Herfindahl-Hirschman Index for concentration measurement.
HHI ranges from 1/N (perfect diversification) to 1.0 (complete concentration).
"""
return sum(w ** 2 for w in weights)
def analyze_holdings(holdings: List[Dict], total_invested: float) -> Dict[str, Any]:
"""Analyze individual holdings."""
analyzed = []
for h in holdings:
invested = h.get("invested", 0)
current = h.get("current_value", invested)
weight = invested / total_invested if total_invested > 0 else 0
gain_loss = current - invested
gain_loss_pct = (gain_loss / invested * 100) if invested > 0 else 0
# Calculate holding period
date_invested = parse_date(h.get("date_invested", ""))
holding_months = None
if date_invested:
now = datetime.now()
holding_months = (now.year - date_invested.year) * 12 + (now.month - date_invested.month)
# Annualized return
annual_return = None
if holding_months and holding_months > 0 and invested > 0:
total_return = current / invested
years = holding_months / 12
if years > 0 and total_return > 0:
annual_return = (total_return ** (1 / years) - 1) * 100
analyzed.append({
"name": h.get("name", "Unknown"),
"type": h.get("type", "unknown"),
"sector": h.get("sector", "unknown"),
"stage": h.get("stage", "unknown"),
"invested": invested,
"current_value": current,
"weight_pct": round(weight * 100, 1),
"gain_loss": round(gain_loss, 2),
"gain_loss_pct": round(gain_loss_pct, 1),
"annual_return_pct": round(annual_return, 1) if annual_return else None,
"holding_months": holding_months,
"liquidity": h.get("liquidity", "unknown"),
"status": h.get("status", "active"),
})
return analyzed
def analyze_diversification(holdings: List[Dict], total_invested: float,
profile: Dict) -> Dict[str, Any]:
"""Analyze portfolio diversification."""
if not holdings or total_invested <= 0:
return {"status": "empty", "issues": ["Portfolio is empty"]}
# Sector analysis
sector_totals = defaultdict(float)
for h in holdings:
sector_totals[h.get("sector", "unknown")] += h.get("invested", 0)
sector_weights = {s: v / total_invested for s, v in sector_totals.items()}
# Type analysis
type_totals = defaultdict(float)
for h in holdings:
type_totals[h.get("type", "unknown")] += h.get("invested", 0)
type_weights = {t: v / total_invested for t, v in type_totals.items()}
# Stage analysis
stage_totals = defaultdict(float)
for h in holdings:
stage_totals[h.get("stage", "unknown")] += h.get("invested", 0)
stage_weights = {s: v / total_invested for s, v in stage_totals.items()}
# Concentration metrics
holding_weights = [h.get("invested", 0) / total_invested for h in holdings]
hhi = calculate_hhi(holding_weights)
max_weight = max(holding_weights) if holding_weights else 0
top_3_weight = sum(sorted(holding_weights, reverse=True)[:3])
# Effective number of holdings (inverse HHI)
effective_n = 1 / hhi if hhi > 0 else 0
# Issues detection
issues = []
recommendations = []
if max_weight > profile["max_single"]:
largest = max(holdings, key=lambda h: h.get("invested", 0))
issues.append(f"Single investment concentration: {largest.get('name')} is "
f"{max_weight*100:.1f}% (limit: {profile['max_single']*100:.0f}%)")
recommendations.append(f"Reduce position in {largest.get('name')} or increase total portfolio size")
max_sector_weight = max(sector_weights.values()) if sector_weights else 0
if max_sector_weight > profile["max_sector"]:
top_sector = max(sector_weights, key=sector_weights.get)
issues.append(f"Sector concentration: {top_sector} is {max_sector_weight*100:.1f}% "
f"(limit: {profile['max_sector']*100:.0f}%)")
recommendations.append(f"Diversify away from {top_sector} sector")
if len(sector_totals) < profile["min_sectors"]:
issues.append(f"Insufficient sector diversification: {len(sector_totals)} sectors "
f"(minimum: {profile['min_sectors']})")
recommendations.append("Add investments in underrepresented sectors")
# Liquidity analysis
liquid_total = sum(h.get("invested", 0) for h in holdings
if h.get("liquidity", "").lower() in ("liquid", "semi-liquid"))
liquid_pct = liquid_total / total_invested if total_invested > 0 else 0
if liquid_pct < profile["min_liquid"]:
issues.append(f"Low liquidity: {liquid_pct*100:.1f}% liquid/semi-liquid "
f"(minimum: {profile['min_liquid']*100:.0f}%)")
recommendations.append("Increase allocation to liquid or semi-liquid investments")
return {
"sector_allocation": {s: round(w * 100, 1) for s, w in sorted(sector_weights.items())},
"type_allocation": {t: round(w * 100, 1) for t, w in sorted(type_weights.items())},
"stage_allocation": {s: round(w * 100, 1) for s, w in sorted(stage_weights.items())},
"concentration": {
"hhi": round(hhi, 4),
"effective_holdings": round(effective_n, 1),
"max_single_weight_pct": round(max_weight * 100, 1),
"top_3_weight_pct": round(top_3_weight * 100, 1),
},
"liquidity": {
"liquid_pct": round(liquid_pct * 100, 1),
"illiquid_pct": round((1 - liquid_pct) * 100, 1),
},
"issues": issues,
"recommendations": recommendations,
"health_score": max(0, 100 - len(issues) * 15),
}
def analyze_performance(analyzed_holdings: List[Dict], total_invested: float) -> Dict[str, Any]:
"""Analyze portfolio performance."""
total_current = sum(h["current_value"] for h in analyzed_holdings)
total_gain = total_current - total_invested
total_return_pct = (total_gain / total_invested * 100) if total_invested > 0 else 0
winners = [h for h in analyzed_holdings if h["gain_loss"] > 0]
losers = [h for h in analyzed_holdings if h["gain_loss"] < 0]
flat = [h for h in analyzed_holdings if h["gain_loss"] == 0]
best = max(analyzed_holdings, key=lambda h: h["gain_loss_pct"]) if analyzed_holdings else None
worst = min(analyzed_holdings, key=lambda h: h["gain_loss_pct"]) if analyzed_holdings else None
return {
"total_invested": round(total_invested, 2),
"total_current_value": round(total_current, 2),
"total_gain_loss": round(total_gain, 2),
"total_return_pct": round(total_return_pct, 1),
"winners": len(winners),
"losers": len(losers),
"flat": len(flat),
"best_performer": {"name": best["name"], "return_pct": best["gain_loss_pct"]} if best else None,
"worst_performer": {"name": worst["name"], "return_pct": worst["gain_loss_pct"]} if worst else None,
}
def format_currency(amount: float) -> str:
if abs(amount) >= 1_000_000:
return f"${amount/1_000_000:.2f}M"
elif abs(amount) >= 1_000:
return f"${amount/1_000:.1f}K"
return f"${amount:,.0f}"
def print_human(holdings_data: List[Dict], diversification: Dict, performance: Dict,
profile_name: str) -> None:
"""Print portfolio analysis in human-readable format."""
print("=" * 72)
print(f" Portfolio Analysis Report (Profile: {profile_name})")
print("=" * 72)
# Performance summary
p = performance
print(f"\n --- Performance Summary ---")
print(f" Total Invested: {format_currency(p['total_invested'])}")
print(f" Current Value: {format_currency(p['total_current_value'])}")
gain_sign = "+" if p['total_gain_loss'] >= 0 else ""
print(f" Gain/Loss: {gain_sign}{format_currency(p['total_gain_loss'])} ({gain_sign}{p['total_return_pct']:.1f}%)")
print(f" Winners/Losers/Flat: {p['winners']}/{p['losers']}/{p['flat']}")
if p["best_performer"]:
print(f" Best Performer: {p['best_performer']['name']} (+{p['best_performer']['return_pct']:.1f}%)")
if p["worst_performer"]:
print(f" Worst Performer: {p['worst_performer']['name']} ({p['worst_performer']['return_pct']:.1f}%)")
# Holdings detail
print(f"\n --- Holdings ---")
print(f" {'Name':<20} {'Invested':>10} {'Current':>10} {'Return':>8} {'Weight':>7} {'Status':<8}")
print(f" {'-'*20} {'-'*10} {'-'*10} {'-'*8} {'-'*7} {'-'*8}")
for h in sorted(holdings_data, key=lambda x: x["weight_pct"], reverse=True):
ret = f"{h['gain_loss_pct']:+.1f}%"
print(f" {h['name']:<20} {format_currency(h['invested']):>10} "
f"{format_currency(h['current_value']):>10} {ret:>8} "
f"{h['weight_pct']:>6.1f}% {h['status']:<8}")
# Diversification
d = diversification
print(f"\n --- Sector Allocation ---")
for sector, weight in sorted(d["sector_allocation"].items(), key=lambda x: -x[1]):
bar = "#" * int(weight / 2)
print(f" {sector:<18} {weight:>5.1f}% {bar}")
print(f"\n --- Concentration ---")
c = d["concentration"]
print(f" HHI Index: {c['hhi']:.4f} (lower is more diversified)")
print(f" Effective Holdings: {c['effective_holdings']:.1f}")
print(f" Largest Position: {c['max_single_weight_pct']:.1f}%")
print(f" Top 3 Concentration: {c['top_3_weight_pct']:.1f}%")
print(f"\n --- Liquidity ---")
print(f" Liquid/Semi-liquid: {d['liquidity']['liquid_pct']:.1f}%")
print(f" Illiquid: {d['liquidity']['illiquid_pct']:.1f}%")
# Issues and recommendations
if d["issues"]:
print(f"\n --- Issues ({len(d['issues'])}) ---")
for issue in d["issues"]:
print(f" [!] {issue}")
if d["recommendations"]:
print(f"\n --- Recommendations ---")
for rec in d["recommendations"]:
print(f" --> {rec}")
print(f"\n Portfolio Health Score: {d['health_score']}/100")
print()
def main():
parser = argparse.ArgumentParser(
description="Analyze portfolio diversification, risk exposure, and performance"
)
parser.add_argument("file", help="JSON file with portfolio holdings")
parser.add_argument("--format", choices=["human", "json"], default="human", help="Output format")
parser.add_argument("--profile", choices=["conservative", "moderate", "aggressive"],
default="moderate", help="Investor risk profile (default: moderate)")
args = parser.parse_args()
with open(args.file, "r", encoding="utf-8") as f:
data = json.load(f)
portfolio = data.get("portfolio", data)
total_invested = portfolio.get("total_invested", 0)
holdings = portfolio.get("holdings", [])
if not holdings:
print("Error: No holdings found in portfolio data", file=sys.stderr)
sys.exit(1)
# If total_invested not set, calculate from holdings
if total_invested <= 0:
total_invested = sum(h.get("invested", 0) for h in holdings)
profile = PROFILES[args.profile]
analyzed = analyze_holdings(holdings, total_invested)
diversification = analyze_diversification(holdings, total_invested, profile)
performance = analyze_performance(analyzed, total_invested)
if args.format == "json":
output = {
"profile": args.profile,
"holdings": analyzed,
"diversification": diversification,
"performance": performance,
}
print(json.dumps(output, indent=2, default=str))
else:
print_human(analyzed, diversification, performance, args.profile)
if __name__ == "__main__":
main()
Related skills
FAQ
What size investments is it designed for?
It is designed for business owners, angel investors, and corporate development teams evaluating investments from $50K to $50M.
What are the three tools?
investment_screener.py to filter and rank, portfolio_analyzer.py for risk and diversification, and due_diligence_checklist.py to generate checklists.