
Investment Management
- 3 installs
- 3 repo stars
- Updated August 5, 2026
- broomva/skills
investment-management is a Claude Code skill for portfolio construction, quantitative analysis, and trade execution across stocks, ETFs, bonds, and crypto using built-in investing frameworks.
About
investment-management is a Claude skill for portfolio construction, analysis, and execution across traditional and alternative assets. It screens securities, runs deep research, factor analysis, and backtests, applies philosophy-driven scoring frameworks, and executes and tracks trades through APIs like Alpaca, Coinbase, and Polymarket. A developer uses it to research, decide, execute, and rebalance investments with structured frameworks. It sits atop a finance-substrate accounting layer and a wealth-management planning layer.
- Screens stocks/ETFs/crypto and runs factor models, backtests, and risk metrics
- Embeds decision frameworks from Buffett, Dalio, Bogle, Munger, Marks, and Taleb
- Executes and tracks trades via Alpaca, Coinbase, and Polymarket
Investment Management by the numbers
- 3 all-time installs (skills.sh)
- Ranked #847 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
investment-management capabilities & compatibility
Needs broker/exchange API keys (Alpaca, Coinbase, Polymarket) for execution; data sources include free tiers.
- Capabilities
- stock screening · factor analysis · backtesting · trade execution · portfolio tracking
- Works with
- stripe
- Use cases
- trading · data analysis · research
- Pricing
- Bring your own API key
What investment-management says it does
Full-stack investment management: from philosophy to execution.
EXECUTE: Trade via APIs (Alpaca, Coinbase, Polymarket)
Search and filter stocks, ETFs, crypto, and other securities by criteria.
npx skills add https://github.com/broomva/skills --skill investment-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 3 |
| Last updated | August 5, 2026 |
| Repository | broomva/skills ↗ |
What it does
Screen, analyze, backtest, and execute a portfolio across stocks, ETFs, and crypto using structured investing frameworks.
Who is it for?
Building, analyzing, backtesting, and executing a multi-asset portfolio with structured investing frameworks.
Skip if: Users seeking a single click-to-buy consumer app; this is a programmatic analysis and execution toolkit needing platform APIs.
When should I use this skill?
You say 'portfolio', 'screen stocks', 'backtest', 'factor model', 'rebalance', 'Alpaca', or 'Polymarket'.
What you get
A researched, framework-scored portfolio that can be screened, backtested, executed, and rebalanced across platforms.
- Screens, research reports, factor and backtest analyses, and executed/tracked portfolio positions
By the numbers
- 6 skill modes (screen, research, factor, and more)
- 6 investor frameworks (Buffett, Dalio, Bogle, Marks, Taleb, plus Munger)
Files
Investment Management
Full-stack investment management: from philosophy to execution. Research, analyze, decide, execute, track, and optimize across traditional and alternative asset classes.
Financial Management Stack
finance-substrate (Layer 1: Accounting & Tax)
├── Bank transaction import, certificate parsing
├── Form 210 tax projection, DIAN integration
├── Parafiscales, patrimonio, retenciones
└── Gmail document collector
↓
wealth-management (Layer 2: Planning & Projections)
├── Compound growth projections
├── Goal-based planning, Monte Carlo
├── Portfolio summary & asset allocation
└── Budget planning with tax savings
↓
investment-management (Layer 3: Analysis & Execution) ← THIS SKILL
├── RESEARCH: Market data, fundamentals, screening
├── ANALYZE: Factor models, backtests, risk metrics
├── DECIDE: Philosophy-driven frameworks, scoring
├── EXECUTE: Trade via APIs (Alpaca, Coinbase, Polymarket)
├── TRACK: Multi-platform position aggregation
└── OPTIMIZE: Rebalancing, tax-loss harvesting, lot managementInvestment Philosophies (Built-in Frameworks)
The skill embeds decision frameworks from legendary investors. Each philosophy is a scoring lens that can be applied to any investment.
Value (Buffett/Graham/Munger)
| Principle | Implementation |
|---|---|
| Economic moat analysis | Score competitive advantage: brand, network, switching cost, scale |
| Margin of safety | Require 25%+ discount to intrinsic value (DCF, owner earnings) |
| Circle of competence | Flag unfamiliar sectors; require deeper research threshold |
| Owner earnings | Net income + depreciation - capex (not GAAP earnings) |
| Quality over price | ROIC > WACC, consistent ROE > 15%, low debt/equity |
Systematic (Dalio/AQR)
| Principle | Implementation |
|---|---|
| All-Weather allocation | Risk-parity: equal risk contribution from growth, inflation, deflation |
| Factor exposure | Decompose returns into market, value, momentum, quality, size |
| Risk parity | Weight by inverse volatility, target equal risk contribution |
| Regime awareness | Detect growth/inflation quadrant, adjust allocation |
| Correlation regime | Monitor rolling correlations; diversification fails in crises |
Passive/Index (Bogle)
| Principle | Implementation |
|---|---|
| Three-fund portfolio | Total market + international + bonds; rebalance annually |
| Minimize costs | Flag any fund with expense ratio > 0.20% |
| Tax efficiency | Index funds in taxable, bonds in tax-deferred |
| Stay the course | Reject market timing; dollar-cost average |
| Simple beats complex | Baseline comparison for every active strategy |
Second-Level Thinking (Marks)
| Principle | Implementation |
|---|---|
| Consensus vs reality | Flag positions where market consensus is priced in |
| Risk is not volatility | Focus on permanent capital loss, not price fluctuation |
| Market cycles | Track Shiller CAPE, yield spreads, sentiment indicators |
| Asymmetric outcomes | Seek situations where upside >> downside |
| Know what you don't know | Confidence-weighted recommendations |
Barbell (Taleb)
| Principle | Implementation |
|---|---|
| 85% ultra-safe + 15% high-convexity | Split portfolio into safe (CDTs, treasuries) + optionality (crypto, VC, prediction markets) |
| Antifragile positions | Identify investments that benefit from volatility |
| Avoid the middle | Skip mediocre risk-return profiles |
| Small bets, big payoffs | Position size by max loss tolerance, not expected return |
Skill Modes
1. screen — Security Screening
Search and filter stocks, ETFs, crypto, and other securities by criteria.
Script: scripts/screener.py
Screening criteria:
- Fundamental: P/E, P/B, EV/EBITDA, FCF yield, ROIC, ROE, debt/equity, dividend yield
- Technical: RSI, MACD signal, price vs 200-day MA, 52-week range position
- Quality: earnings consistency, revenue growth, margin stability
- Momentum: 1/3/6/12-month returns, relative strength
- Value composite: Piotroski F-Score, Greenblatt Magic Formula rank
- Size: market cap filters
Data sources: yfinance, Financial Modeling Prep, OpenBB
2. research — Deep Investment Research
In-depth analysis of a specific security or market.
Script: scripts/research.py
Analysis includes:
- Company overview and business model
- Financial statement analysis (3-5 year trends)
- Valuation: DCF, comparable companies, dividend discount
- Competitive landscape and moat assessment
- Risk factors and bear case
- Catalyst identification
- Philosophy alignment score (which frameworks support/oppose)
Data sources: yfinance fundamentals, SEC EDGAR (edgartools), news sentiment (FinBERT)
3. factor — Factor Analysis
Decompose portfolio returns into systematic factor exposures.
Script: scripts/factor_analysis.py
Factors analyzed:
- Fama-French 5 factors: Market, Size (SMB), Value (HML), Profitability (RMW), Investment (CMA)
- Momentum (UMD)
- Quality (QMJ from AQR)
- Alpha: residual return not explained by factors
Output: Factor loadings, R², alpha significance, factor exposure drift over time
4. backtest — Strategy Backtesting
Test investment strategies against historical data.
Script: scripts/backtest.py
Built-in strategies:
- Buy and hold (benchmark)
- Equal-weight rebalanced
- Risk parity (inverse vol)
- Momentum (top N by 12-1 month return)
- Value (top N by composite score)
- All-Weather (Dalio's 4-quadrant allocation)
- Custom (user-defined rules)
Metrics: CAGR, Sharpe, Sortino, max drawdown, Calmar, win rate, average gain/loss
5. optimize — Portfolio Optimization
Find optimal portfolio weights given constraints.
Script: scripts/portfolio_optimizer.py
Methods:
- Mean-variance (Markowitz efficient frontier)
- Black-Litterman (market equilibrium + personal views)
- Hierarchical Risk Parity (HRP, no covariance inversion)
- Risk budgeting (equal risk contribution)
- Maximum Sharpe, minimum volatility, target return
- CVaR optimization (tail-risk aware)
Constraints: Long-only, sector limits, position size caps, turnover limits, tax-awareness
Libraries: PyPortfolioOpt, Riskfolio-Lib, cvxpy
6. risk — Risk Analysis
Comprehensive risk assessment of current or proposed portfolio.
Script: scripts/risk_analysis.py
Metrics:
- Value at Risk (parametric, historical, Monte Carlo)
- Conditional VaR (Expected Shortfall)
- Maximum drawdown analysis
- Stress tests: 2008 GFC, 2020 COVID, 2022 rates, COP devaluation
- Correlation regime analysis (rolling correlations)
- GARCH volatility forecast
- Concentration risk and single-name exposure
7. trade — Trade Execution
Execute trades via supported platform APIs.
Script: scripts/trade.py
Supported platforms:
| Platform | Assets | Auth | Library |
|---|---|---|---|
| Alpaca | US stocks, ETFs | API key pair | alpaca-trade-api |
| Coinbase | Crypto | CDP API key + JWT | coinbase-advanced-py |
| Polymarket | Prediction markets | Wallet signature | py-clob-client |
| Interactive Brokers | Everything | TWS API | ib_async |
Features:
- Paper trading mode (default) — no real money until explicitly confirmed
- Order types: market, limit, stop-loss
- Position sizing: Kelly criterion, fixed fractional, risk-budget
- Pre-trade checks: liquidity, spread, portfolio impact
8. track — Multi-Platform Position Tracking
Aggregate positions across all investment platforms.
Script: scripts/tracker.py
Sources (in priority order): 1. API-connected platforms (Alpaca, Coinbase) — real-time 2. finance-substrate certificates (Skandia, Davivienda, etc.) — periodic 3. Browser-automated platforms (Tyba, Trii, Davivienda Corredores) — via agent-browser 4. Manual entries (portfolio.json) — user-maintained
Output: Unified position list with cost basis, current value, unrealized gain/loss, allocation %
9. rebalance — Intelligent Rebalancing
Generate and optionally execute rebalancing trades.
Script: scripts/rebalancer.py
Rebalancing modes:
- Cash-flow (direct new contributions to underweight positions)
- Threshold (trigger when drift exceeds band)
- Tax-aware (prefer loss-harvesting sells, defer gains)
- Calendar (monthly/quarterly/annual schedule)
Tax-loss harvesting:
- Scan for positions with unrealized losses
- Identify replacement securities (correlated but not identical)
- Track wash sale windows (30 days US; no equivalent in Colombia)
- Estimate tax savings
10. data — Market Data Retrieval
Fetch market data from multiple sources.
Script: scripts/market_data.py
Sources:
| Source | Data | Cost | Library |
|---|---|---|---|
| yfinance | US/intl stocks, fundamentals | Free | yfinance |
| Financial Modeling Prep | Financial statements, ratios | Free tier | fmpsdk |
| CoinGecko | Crypto prices, market data | Free tier | pycoingecko |
| FRED | Macro indicators (800K series) | Free | fredapi |
| datos.gov.co | TRM (USD/COP) | Free | requests |
| Banco de la República | CPI, interest rates | Free | requests |
| Alpha Vantage | Technical data, forex | Free tier | alpha_vantage |
| Polymarket | Prediction market odds | Free | py-clob-client |
11. score — Investment Scoring
Score a security through multiple investment philosophy lenses.
Script: scripts/scorer.py
Scoring dimensions:
- Value score (Graham/Buffett): P/E, P/B, FCF yield, moat rating
- Quality score (Munger): ROIC, margin stability, debt discipline
- Momentum score: price momentum, earnings momentum, analyst revisions
- Risk score (Marks): downside volatility, max drawdown, tail risk
- Growth score (Lynch): revenue growth, PEG ratio, addressable market
- Composite score: weighted average across all dimensions
Output: 0-100 score per dimension, overall composite, philosophy alignment
Asset Class Coverage
Traditional
| Class | Screening | Research | Trading | Tracking |
|---|---|---|---|---|
| US Stocks | yfinance, FMP | EDGAR, fundamentals | Alpaca, IBKR | API |
| International Stocks | yfinance | Limited fundamentals | IBKR | API |
| ETFs | yfinance | Holdings analysis | Alpaca, IBKR | API |
| Bonds/Fixed Income | FRED yield curves | Duration, credit | IBKR | Manual |
| CDTs (Colombia) | Superfinanciera rates | Yield comparison | Manual | Certificates |
Alternative
| Class | Screening | Research | Trading | Tracking |
|---|---|---|---|---|
| Crypto | CoinGecko, CMC | On-chain, sentiment | Coinbase, Binance | API |
| Prediction Markets | Polymarket API | Market analysis | Polymarket CLOB | API |
| Real Estate | Manual | Cap rate, appreciation | Manual | Exogena/manual |
| VC/Startups | Manual | Due diligence framework | Manual | Manual |
| Colombian Equities | Yahoo (BVC tickers) | Limited | Davivienda Corredores (browser) | Browser/manual |
Tax-Advantaged (Colombia)
| Vehicle | Max Benefit | Tracking |
|---|---|---|
| AFC (Davivienda) | 1,340 UVT cap | finance-substrate certs |
| Pensión Voluntaria (Skandia) | Combined w/ AFC | finance-substrate certs |
| Cesantías | Forced savings | finance-substrate certs |
Platform Integration Architecture
investment-management
│
├── API-first (real-time)
│ ├── Alpaca ──── US stocks, ETFs (paper + live)
│ ├── Coinbase ── Crypto (BTC, ETH, SOL, etc.)
│ ├── Polymarket ── Prediction markets
│ ├── CoinGecko ── Crypto market data
│ ├── FRED ──── Macro indicators
│ └── yfinance ── Stock data, fundamentals
│
├── Browser-automated (agent-browser)
│ ├── Davivienda Corredores ── Colombian equities
│ ├── Tyba ── Colombian robo-advisor
│ ├── Skandia Portal ── Pension fund data
│ └── MiDataCredito ── Credit profile
│
├── File-based (finance-substrate)
│ ├── certificates.jsonl ── Bank saldos, investment funds
│ ├── exogena.jsonl ── Third-party reported assets
│ └── salary-history.jsonl ── Income trajectory
│
└── Manual (portfolio.json)
├── Private investments
├── Real estate
└── VC/startup positionsQuantitative Toolkit
Libraries Used
| Purpose | Library | Install |
|---|---|---|
| Portfolio optimization | PyPortfolioOpt | pip install pyportfolioopt |
| Advanced risk optimization | Riskfolio-Lib | pip install riskfolio-lib |
| Custom optimization | cvxpy | pip install cvxpy |
| Volatility modeling | arch | pip install arch |
| Technical indicators | pandas-ta | pip install pandas_ta |
| Factor data | pandas-datareader | pip install pandas-datareader |
| Backtesting | VectorBT | pip install vectorbt |
| Sentiment | transformers (FinBERT) | pip install transformers |
| Fundamental data | edgartools | pip install edgartools |
| Macro data | fredapi | pip install fredapi |
Key Formulas
| Formula | Expression | Use |
|---|---|---|
| Sharpe Ratio | (R_p - R_f) / σ_p | Risk-adjusted return |
| Sortino Ratio | (R_p - R_f) / σ_downside | Downside risk-adjusted |
| Kelly Fraction | (bp - q) / b | Optimal position size |
| Intrinsic Value (DCF) | Σ FCF_t / (1+r)^t + TV | Valuation |
| WACC | E/(E+D)×Re + D/(E+D)×Rd×(1-T) | Discount rate |
| Black-Scholes | C = S·N(d1) - K·e^(-rT)·N(d2) | Option pricing |
| VaR (parametric) | μ - z_α × σ | Tail risk |
| CVaR | E[L \ | L > VaR] |
| HHI (concentration) | Σ w_i² | Diversification |
Data Directory
~/.investment-management/
├── portfolio.json # Master holdings (all platforms)
├── targets.json # Target allocation by strategy
├── watchlist.json # Securities under observation
├── trades/ # Trade history
│ └── trades.jsonl # All executed trades
├── research/ # Security research cache
│ └── research-{ticker}.json
├── backtests/ # Backtest results
│ └── backtest-{strategy}-{date}.json
├── scores/ # Investment scores
│ └── scores-{date}.json
├── market-data/ # Cached market data
│ ├── prices/ # Historical price cache
│ └── fundamentals/ # Fundamental data cache
└── config/
├── api-keys.json # Platform API keys (encrypted)
├── strategies.json # Active strategy configurations
└── rebalance-rules.json # Rebalancing parametersCompound: Autoany Integration (EGRI)
This skill is EGRI-aware. When a user's request implies iterative optimization or strategy search, the agent should scaffold a problem-spec from the templates below and delegate to /autoany for autonomous improvement.
Optimization Triggers
Invoke /autoany when the user asks to:
- "Optimize" — allocation, weights, strategy parameters
- "Find the best" — strategy, allocation, screening criteria
- "Compare strategies" — systematic comparison across N variants
- "Backtest variations" — test parameter sweeps
- "What if I changed" — sensitivity analysis via mutations
- "Stress test" — combined with optimization intent
EGRI Problem-Spec Templates
| Template | Artifact | Evaluator | Score | Use When |
|---|---|---|---|---|
strategy-optimization | strategy.yaml (weights, rules) | backtest.py --egri | Sharpe ratio | Optimizing portfolio allocation |
screen-evolution | screen_criteria.yaml (thresholds) | screener.py + eval_screen.py | Forward return | Evolving stock selection criteria |
Templates are at templates/egri/. The strategy artifact schema is at templates/egri/strategy-artifact.yaml.
Delegation Flow
1. User request → agent detects optimization intent
2. Load personal context from finance-substrate (patrimonio, salary, TRM)
3. Pull historical market data (market_data.py)
4. Scaffold problem-spec from template, fill in:
- Starting allocation as baseline artifact
- Period, constraints from policy.yaml
- Goal metrics from user's request
5. Invoke /autoany with scaffolded problem-spec
6. EGRI loop runs: Proposer → Executor (backtest.py) → Evaluator → Selector
7. Return promoted strategy + ledger summary to user
8. Log results for cross-session strategy inheritance (autoany-lago)EGRI Evaluator Bridge
scripts/eval_backtest.py wraps backtest.py for use as an EGRI evaluator:
- Takes a strategy YAML artifact + period
- Returns structured
Outcome(score, constraints_passed, violations) - Applies constraint expressions from the problem-spec
- Can be used standalone:
python3 eval_backtest.py --strategy-file strategy.yaml --period 10y
Safety Constraints (enforced in EGRI loops)
- EGRI loops over historical data are autonomous (sandbox mode)
- EGRI loops that propose live trades require human gate (HumanGate selector)
- Max drawdown constraint:
-15%(from policy S6) - Position concentration:
<= 25%(from policy S2) - Budget: 50 trials max, 1 hour total
- All trials logged to ledger for audit and cross-run inheritance
Dependencies
- Python 3.10+
finance-substrateskill (accounting, tax, certificates)wealth-managementskill (projections, goals, portfolio summary)autoany(optional, for EGRI optimization loops)- Market data:
yfinance,fredapi,pycoingecko(free) - Optimization:
pypfopt,riskfolio-lib,cvxpy(optional, for advanced modes) - Trading:
alpaca-trade-api,coinbase-advanced-py(optional, for execution) - Technical:
pandas-ta,arch(optional, for quantitative modes) - No paid services required. All data stays local.
File Structure
investment-management/
├── SKILL.md # This file
├── skill.json # Schema definition (11 modes)
├── scripts/
│ ├── screener.py # Mode 1: security screening
│ ├── research.py # Mode 2: deep research
│ ├── factor_analysis.py # Mode 3: factor decomposition
│ ├── backtest.py # Mode 4: strategy backtesting
│ ├── portfolio_optimizer.py # Mode 5: portfolio optimization
│ ├── risk_analysis.py # Mode 6: risk metrics
│ ├── trade.py # Mode 7: trade execution
│ ├── tracker.py # Mode 8: position tracking
│ ├── rebalancer.py # Mode 9: intelligent rebalancing
│ ├── market_data.py # Mode 10: data retrieval
│ ├── scorer.py # Mode 11: investment scoring
│ └── eval_backtest.py # EGRI evaluator wrapper (autoany bridge)
├── references/
│ ├── investment-philosophies.md # Legendary investor frameworks
│ ├── platform-apis.md # API reference for all platforms
│ ├── quantitative-toolkit.md # Libraries, formulas, models
│ ├── colombian-markets.md # BVC, CDTs, FICs, regulations
│ └── alternative-investments.md # Crypto, prediction markets, RE, VC
├── templates/
│ ├── strategies.json # Pre-built strategy configurations
│ ├── scoring-weights.json # Philosophy scoring weights
│ └── egri/ # EGRI problem-spec templates (autoany)
│ ├── strategy-optimization.yaml # Portfolio strategy optimization
│ ├── screen-evolution.yaml # Screening criteria evolution
│ └── strategy-artifact.yaml # Strategy YAML schema (mutable artifact)
├── .control/
│ └── policy.yaml # Trading limits, risk gates
└── README.mdinvestment-management
Full-stack investment management skill: from philosophy to execution. Research, analyze, decide, execute, track, and optimize across traditional and alternative asset classes.
Compounds on finance-substrate (accounting/tax) and wealth-management (projections/goals) for a complete financial management framework.
Quick Start
# Install
npx skills add broomva/investment-management -y -g
# Market data (always works, no dependencies)
python3 scripts/market_data.py --trm
# With yfinance installed: pip install yfinance
python3 scripts/market_data.py --ticker AAPL --type price --period 1y
python3 scripts/screener.py --philosophy value --universe custom --tickers AAPL,MSFT,JPM,BAC,KO
python3 scripts/scorer.py --ticker AAPL --philosophy balancedModes
| # | Mode | Script | Type | Status |
|---|---|---|---|---|
| 1 | screen | screener.py | Screening | Working |
| 2 | research | research.py | Analysis | Planned |
| 3 | factor | factor_analysis.py | Quant | Planned |
| 4 | backtest | backtest.py | Quant | Planned |
| 5 | optimize | portfolio_optimizer.py | Quant | Planned |
| 6 | risk | risk_analysis.py | Analysis | Planned |
| 7 | trade | trade.py | Execution | Planned |
| 8 | track | tracker.py | Tracking | Planned |
| 9 | rebalance | rebalancer.py | Execution | Planned |
| 10 | data | market_data.py | Data | Working |
| 11 | score | scorer.py | Analysis | Working |
Investment Philosophies
Built-in scoring and screening frameworks from legendary investors:
- Value (Graham/Buffett): Margin of safety, moats, owner earnings
- Quality (Munger): ROIC, margin stability, debt discipline
- Growth (Lynch): PEG ratio, tenbaggers, revenue acceleration
- Systematic (Dalio/AQR): Factor exposure, risk parity, All-Weather
- Passive (Bogle): Three-fund portfolio, low-cost indexing
- Barbell (Taleb): 85% ultra-safe + 15% high-convexity
Dependencies
- Python 3.10+ (core scripts work with stdlib only)
yfinance— Stock data (optional,pip install yfinance)pycoingecko— Crypto data (optional,pip install pycoingecko)fredapi— Macro data (optional,pip install fredapi)- No paid services required. All data stays local.
Investment Philosophies, Strategies & Portfolio Construction: A Comprehensive Research Report
---
1. Value Investing Legends
Warren Buffett / Berkshire Hathaway
Core Philosophy: Buy wonderful companies at fair prices, hold them forever. Businesses must have durable competitive advantages ("economic moats") — brand power, network effects, switching costs, cost advantages, or regulatory barriers. The circle of competence principle demands you only invest in businesses you deeply understand, and the margin of safety ensures you pay meaningfully less than intrinsic value.
Key Metrics/Ratios:
- Owner earnings (net income + depreciation - capex)
- Return on equity (ROE) consistently above 15%
- Debt-to-equity ratio (prefers low leverage)
- Free cash flow yield
- Price-to-intrinsic-value discount (seeks 25%+ margin of safety)
- Revenue and earnings growth consistency over 10+ years
Typical Portfolio Allocation:
- Concentrated: 5-10 core positions comprising 70-80% of equity portfolio
- Heavy cash reserves (Berkshire often holds $100B+ in cash/T-bills waiting for opportunities)
- No hedging, no shorting, no leverage in equity book
- Insurance float as a source of zero-cost leverage
- Sector bias toward financials, consumer staples, technology (Apple), energy
When It Works Best: Long bull markets where quality compounds; periods following market dislocations when great businesses are on sale; low-interest-rate environments that raise the present value of durable earnings streams.
When It Fails: Prolonged speculative manias where momentum dominates fundamentals (e.g., 1999 dot-com); rapid technological disruption that erodes moats faster than anticipated; inflationary periods that compress multiples on even high-quality businesses.
---
Benjamin Graham
Core Philosophy: The father of value investing. Stocks are fractional ownership of businesses, not lottery tickets. "Mr. Market" is an emotional counterparty who offers prices daily — you are free to accept or ignore. Intrinsic value is calculated from tangible assets and normalized earnings, and the margin of safety (buying at a deep discount to intrinsic value) is the central principle of intelligent investing.
Key Metrics/Ratios:
- Net-net working capital (current assets minus total liabilities, buy below 2/3 of this value)
- Price-to-book value below 1.0 (ideally below 0.67)
- Price-to-earnings below 15 (or P/E x P/B < 22.5, the "Graham Number")
- Current ratio above 2.0
- Earnings stability: positive EPS in each of the past 10 years
- Dividend record: uninterrupted dividends for 20+ years
- Earnings growth: minimum 33% increase in per-share earnings over 10 years
Typical Portfolio Allocation:
- Highly diversified: 30+ positions to reduce single-stock risk
- Split between "defensive" (large, prominent, conservatively financed companies) and "enterprising" (net-nets, special situations, workout/arbitrage positions)
- Graham's recommended split: 25-75% stocks, with the balance in bonds, adjusted inversely to market valuation
- Never 100% equities, never 100% bonds
When It Works Best: Bear markets and post-crisis recoveries where many companies trade below liquidation value; small-cap and micro-cap universes where net-nets are more common; mean-reverting environments.
When It Fails: Asset-light economies (software, services) where book value is meaningless; long bull markets where nothing trades at net-net levels; "value traps" where cheap assets are permanently impaired. The classic net-net strategy has fewer opportunities in modern developed markets.
---
Charlie Munger
Core Philosophy: Investing requires a "latticework of mental models" drawn from multiple disciplines — psychology, physics, biology, economics, history, mathematics. You need only a few great ideas in a lifetime; the key is recognizing them and betting big. Invert problems ("avoid stupidity rather than seeking brilliance"), guard against cognitive biases, and demand quality above cheapness.
Key Metrics/Ratios:
- Return on invested capital (ROIC) — the single most important measure of business quality
- Sustainable competitive advantages (qualitative assessment)
- Management integrity and capital allocation skill
- Long-term earnings power rather than current-period metrics
- "What can go wrong?" — inversion-based risk assessment
Typical Portfolio Allocation:
- Extreme concentration: 3-5 positions (Munger's personal portfolio was often 3 stocks)
- Long holding periods (decades)
- No diversification for diversification's sake — "diversification is protection against ignorance"
- Cash as a permanent option on future opportunities
- Zero use of derivatives, leverage, or shorting
When It Works Best: When combined with deep domain expertise and patience; when the investor has the temperament to endure volatility; when a few truly exceptional businesses are available at reasonable prices.
When It Fails: When conviction turns to stubbornness (concentration risk is extreme); when the few positions are correlated; when the investor's "mental models" have blind spots.
---
Seth Klarman / Baupost Group
Core Philosophy: Margin of safety is everything — "value investing is the discipline of buying securities at a significant discount from their current underlying values and holding them until more of their value is realized." Be a contrarian: buy what others are forced to sell, avoid what others are chasing. Process over outcome — rigorous analysis matters more than any single trade.
Key Metrics/Ratios:
- Absolute return orientation (not relative to a benchmark)
- Downside analysis: "what is the worst case and can I survive it?"
- Catalyst identification: what will unlock value (restructuring, spin-off, activist involvement)?
- Liquidity premium analysis (willing to invest in illiquid securities for higher returns)
- Sum-of-the-parts valuation for complex situations
- Distressed debt recovery analysis
Typical Portfolio Allocation:
- Large cash positions (30-50% of fund, sometimes more) when opportunities are scarce
- Diversified across asset classes: equities, distressed debt, real estate, private investments
- Heavy allocation to special situations: spin-offs, restructurings, bankruptcies, liquidations
- Geographic diversification (Baupost invests globally)
- Position sizes typically 1-5% each, rarely exceeding 10%
When It Works Best: Market dislocations, credit crises, forced selling events (margin calls, index deletions, regulatory changes); environments with rising dispersion and falling correlations.
When It Fails: Prolonged bull markets where cash is a drag; momentum-driven environments; when "cheap" gets cheaper (value traps in secular decline).
---
Howard Marks / Oaktree Capital
Core Philosophy: "Second-level thinking" — first-level thinkers say "it's a good company, let's buy." Second-level thinkers say "it's a good company, but everyone thinks it's great and the stock is overpriced; sell." Markets oscillate between greed and fear in predictable cycles, and superior returns come from understanding where you are in the cycle and acting contrary to the crowd. Risk is not volatility — risk is the probability of permanent capital loss.
Key Metrics/Ratios:
- Risk-adjusted return (not raw return)
- Credit spreads and yield premiums as indicators of risk appetite
- Market sentiment indicators: VIX, put/call ratios, fund flows, margin debt
- "Where are we in the cycle?" — qualitative framework
- Distressed debt: recovery value, enterprise value/debt coverage, attachment point analysis
- Asymmetry: is the upside/downside skew favorable?
Typical Portfolio Allocation:
- Primarily distressed debt and high-yield bonds (Oaktree's core)
- Equity positions as secondary
- Aggressiveness calibrated to cycle position: deploy aggressively in fear, defensively in greed
- Diversified across many distressed issuers (50-100+ positions)
- Typically fully invested in distressed markets, more conservative in ebullient markets
When It Works Best: Credit cycles (distressed debt outperforms massively in recoveries); periods of market dislocation; when fear creates forced selling; in asset classes with mean-reverting characteristics.
When It Fails: "This time is different" paradigm shifts where old cycles break; prolonged periods of low defaults and tight spreads where there is nothing distressed to buy; when you are early (being contrarian and wrong looks the same for a while).
---
2. Growth & Momentum
Peter Lynch / Fidelity Magellan
Core Philosophy: Individual investors have an edge over professionals because they encounter great companies in daily life before Wall Street discovers them. Classify stocks into categories (slow growers, stalwarts, fast growers, cyclicals, turnarounds, asset plays) and apply the right framework to each. The PEG ratio (P/E divided by earnings growth rate) identifies growth at a reasonable price. "Tenbaggers" — stocks that increase 10x — are the goal, and you find them by being curious and doing your homework.
Key Metrics/Ratios:
- PEG ratio: ideally below 1.0 (P/E less than earnings growth rate)
- Earnings growth rate: 20-25% annual is ideal for fast growers
- Debt-to-equity: prefer low leverage
- Inventory levels relative to sales (rising inventory is a warning sign)
- Insider buying
- Institutional ownership: prefer less-followed names
- Cash position and free cash flow
Typical Portfolio Allocation:
- Extremely diversified: Lynch held 1,000+ stocks at Magellan
- Heavy small/mid-cap tilt (where tenbaggers are found)
- Sector-agnostic — followed companies, not sectors
- Willing to hold cyclicals through downturns if thesis intact
- Frequent rebalancing based on category reclassification
When It Works Best: Bull markets with broad participation; small/mid-cap rallies; environments rewarding stock-picking skill; when individual companies have identifiable growth catalysts that the market hasn't priced.
When It Fails: Bear markets (diversification helps but doesn't eliminate drawdowns); indexing eras where stock-picking doesn't beat benchmarks; when market leadership is extremely narrow (e.g., mega-cap tech dominance).
---
Philip Fisher
Core Philosophy: Buy outstanding companies with above-average growth potential managed by people of exceptional ability, and hold them for a very long time. The "scuttlebutt" method involves talking to customers, competitors, suppliers, former employees, and industry experts to build a qualitative picture of a company's competitive position and management quality. Growth is more important than cheapness — paying a fair price for an outstanding company is better than a cheap price for a mediocre one.
Key Metrics/Ratios:
- Revenue growth rate and sustainability
- Profit margins relative to industry
- R&D spending as percentage of sales
- Quality of management (subjective: integrity, vision, execution)
- Market size and growth potential (total addressable market)
- Competitive positioning (Fisher's 15-point checklist)
Typical Portfolio Allocation:
- Concentrated: 10-30 stocks maximum
- Heavy growth/technology tilt (Fisher was early in Motorola, Texas Instruments, Dow Chemical)
- Extremely long holding periods — Fisher held Motorola for 21 years
- Almost never sells (only three reasons to sell: a mistake was made in analysis, the company no longer qualifies, or a significantly better opportunity arises)
When It Works Best: Secular growth environments (technology adoption curves, demographic tailwinds); when great companies are re-investing at high ROIC; in innovation-driven markets.
When It Fails: Market-wide multiple compression; when growth stocks become massively overvalued; when technological disruption kills the moat of the "excellent" company you're holding.
---
William O'Neil / CAN SLIM
Core Philosophy: A systematic growth-momentum hybrid. CAN SLIM is a seven-factor screen: Current quarterly earnings (up 25%+), Annual earnings growth (25%+ over 5 years), New products/management/highs, Supply and demand (smaller float preferred, rising volume on breakouts), Leader vs. laggard (relative strength rank 80+), Institutional sponsorship (quality funds accumulating), Market direction (only buy in confirmed uptrends). Combines fundamental quality with technical timing.
Key Metrics/Ratios:
- Quarterly EPS growth (minimum 25%, prefer 50%+)
- Annual EPS growth (25%+ for 3-5 years)
- Relative Strength Rating (must be 80+, prefer 90+)
- Volume: breakout on 50%+ above-average volume
- Up/Down volume ratio
- Accumulation/Distribution rating
- Cup-with-handle and other base patterns for entry timing
Typical Portfolio Allocation:
- Concentrated: 4-8 stocks in a typical portfolio
- 100% equities during confirmed uptrends; up to 100% cash during downtrends
- Aggressive position sizing in leaders (25% of portfolio in best idea)
- Strict loss-cutting: sell any stock that drops 7-8% below purchase price (no exceptions)
- Pyramid into winners, cut losers quickly
When It Works Best: Trending bull markets with sector rotation; when growth stocks are in favor; markets with identifiable leadership (a few stocks driving the advance).
When It Fails: Choppy, range-bound markets (frequent false breakouts trigger stop-losses); bear markets (the system says go to cash, which works, but re-entry timing is difficult); when value outperforms growth for extended periods.
---
Cathie Wood / ARK Invest
Core Philosophy: Focus exclusively on disruptive innovation across five platforms: DNA sequencing/gene editing, robotics, energy storage, AI, and blockchain. These technologies follow Wright's Law (cost declines as a function of cumulative production) and S-curve adoption patterns. Traditional valuation metrics (P/E, EV/EBITDA) are misleading for companies whose total addressable market is expanding exponentially. Use 5-year Monte Carlo simulations for valuation.
Key Metrics/Ratios:
- Total addressable market (TAM) growth trajectory
- Wright's Law cost curves (cost decline per doubling of cumulative production)
- Revenue growth rate (prefer 25%+ annually)
- Innovation-specific KPIs: genome sequencing cost per base, battery cost per kWh, autonomous miles driven
- Network value (Metcalfe's Law for platform businesses)
- Gross margins trending toward software-like levels (70%+)
Typical Portfolio Allocation:
- Concentrated in 30-50 high-conviction names across ARK funds
- Top 10 holdings often represent 40-50% of portfolio
- 100% equities, no hedging
- Heavy overlap across ARK's thematic ETFs
- Sector concentration: technology, healthcare/genomics, fintech
- Frequent rebalancing — buys dips aggressively, trims into strength
When It Works Best: Low interest rate environments that favor long-duration growth assets; early stages of technology adoption cycles; when narrative and momentum align (2020-early 2021); when disruptive companies are genuinely displacing incumbents.
When It Fails: Rising interest rate environments (long-duration assets get crushed); when profitability matters more than growth; when speculative fervor unwinds; when the innovation thesis takes longer to play out than expected (many ARK holdings fell 70-80% in 2022).
---
3. Quantitative & Systematic
Ray Dalio / Bridgewater Associates
Core Philosophy: The economy and markets are machines driven by cause-effect relationships that can be understood and modeled. The "All-Weather" portfolio is built on risk parity: balance risk across four economic environments (rising growth, falling growth, rising inflation, falling inflation) so the portfolio performs reasonably well in all conditions. "Radical transparency" and "idea meritocracy" govern the firm's culture. Principles-based decision-making: encode your decision rules, test them against history, and follow them systematically.
Key Metrics/Ratios:
- Risk contribution by asset class (equal risk, not equal capital)
- Sharpe ratio optimization (maximize risk-adjusted returns)
- Economic regime indicators (growth and inflation expectations)
- Real yields (TIPS breakevens)
- Credit spreads
- Leverage ratio (All-Weather uses moderate leverage to equalize risk contributions)
- Correlation matrices across asset classes
Typical Portfolio Allocation (All-Weather):
- 30% stocks (US and international)
- 40% long-term Treasury bonds (20+ years)
- 15% intermediate-term Treasury bonds (7-10 years)
- 7.5% gold
- 7.5% commodities (broad basket)
- Leverage applied to bonds and commodities to equalize risk contributions
- Unleveraged retail version: similar weights but lower expected return
When It Works Best: Uncertain economic environments where no single regime dominates; long time horizons; when the investor wants steady returns with limited drawdowns; during periods of bond-equity diversification (negative correlation).
When It Fails: Simultaneous stock and bond declines (2022 was devastating for risk parity); when inflation surges and bonds sell off while equities also decline; when leverage amplifies losses during correlated sell-offs; prolonged bull markets where the stock-heavy portfolio far outperforms.
---
Jim Simons / Renaissance Technologies
Core Philosophy: Markets have statistically significant, exploitable patterns that persist over time due to structural and behavioral factors. These signals are often small, noisy, and short-lived — extractable only through massive computational power, rigorous statistical testing, and impeccable execution. Renaissance employs physicists, mathematicians, and computer scientists (not MBAs or traditional analysts). The Medallion Fund (internal) has compounded at ~66% annually before fees since 1988, the greatest track record in investing history.
Key Metrics/Ratios:
- Signal-to-noise ratio of predictive factors
- Statistical significance (t-stats, p-values) of trading signals
- Sharpe ratio (Medallion's is estimated at 3-6+, extraordinary)
- Transaction costs as a percentage of expected alpha
- Decay rate of alpha signals
- Capacity constraints (Medallion caps AUM at ~$10B and returns profits to employees)
Typical Portfolio Allocation:
- Thousands of positions across equities, futures, currencies, commodities
- Extremely short holding periods (hours to days, sometimes seconds)
- High turnover (1000%+ annually)
- Market-neutral or low net exposure
- Heavy use of leverage (rumored 5-20x)
- Diversification across signals, instruments, and timeframes
When It Works Best: Markets with sufficient liquidity and volatility to generate trading opportunities; when structural inefficiencies persist (behavioral biases, market microstructure); when the fund's infrastructure and talent pipeline remain intact.
When It Fails: External Renaissance funds (RIEF, RIDA) have far less impressive track records, suggesting Medallion's edge is capacity-constrained and not easily scalable. Flash crashes and liquidity crises can cause severe losses for leveraged, market-neutral strategies. Regulatory changes to market microstructure can eliminate specific signal sources.
---
AQR Capital Management / Cliff Asness
Core Philosophy: Academic finance research identifies persistent, pervasive, and robust factors that explain returns across asset classes. The core factors are: Value (buy cheap, sell expensive), Momentum (buy recent winners, sell recent losers), Quality (buy profitable, growing, safe companies), and Carry (buy high-yielding assets, sell low-yielding). These factors work across stocks, bonds, currencies, and commodities. Diversify across factors and asset classes for better risk-adjusted returns.
Key Metrics/Ratios:
- Value: book-to-price, earnings yield, cash flow yield
- Momentum: 12-month return minus most recent month (12-1 momentum)
- Quality: profitability (gross profits/assets), growth, safety (low leverage, low earnings volatility)
- Carry: yield spread (dividend yield, bond yield, interest rate differential)
- Factor exposure analysis (ensure portfolio has intended factor tilts)
- Information ratio and Sharpe ratio by factor
Typical Portfolio Allocation:
- Long-short factor portfolios (academic pure form)
- Long-only factor tilt overlays (practical implementation)
- Multi-asset: factors applied across stocks, bonds, currencies, commodities
- Risk-balanced across factors (equal risk contribution from each)
- Leverage used in long-short implementations
- Global diversification (factors work in all developed and most emerging markets)
When It Works Best: Long time horizons (factors can underperform for years before mean-reverting); when factor premia are at historically wide levels; diversified across many factors simultaneously (reduces drawdowns from any single factor's underperformance).
When It Fails: Factor crashes (momentum crashed in 2009; value has underperformed for a decade 2010-2020); when crowding in factor strategies compresses premia; single-factor exposure can have enormous drawdowns; when macro regimes shift fundamentally (e.g., zero interest rates disrupted the value factor).
---
Two Sigma
Core Philosophy: Apply machine learning, distributed computing, and alternative data to find non-obvious patterns in financial markets. Unlike Renaissance's pure statistical arbitrage, Two Sigma integrates ML models (random forests, neural networks, NLP on earnings calls and news) with traditional quantitative methods. The firm emphasizes engineering culture — data infrastructure and model deployment are as important as the models themselves.
Key Metrics/Ratios:
- Model accuracy and out-of-sample performance
- Feature importance rankings from ML models
- Alpha decay (how quickly signals lose predictive power)
- Sharpe ratios by strategy and timeframe
- Data coverage and freshness (alternative data: satellite imagery, credit card transactions, web traffic, social sentiment)
- Portfolio turnover and transaction cost analysis
Typical Portfolio Allocation:
- Market-neutral equity portfolios (long/short, dollar-neutral, beta-neutral)
- Macro strategies (rates, FX, commodities)
- Venture investments in technology/AI companies
- Thousands of positions, highly diversified
- Moderate holding periods (days to weeks, longer than Medallion)
When It Works Best: Data-rich environments; markets with many securities and sufficient liquidity; when alternative data provides a genuine informational edge; when ML models can detect non-linear relationships that linear factor models miss.
When It Fails: Regime changes that invalidate historical patterns (COVID crash, policy shifts); when many quant funds use similar signals and crowd trades; when alternative data becomes widely available and the edge commoditizes; overfitting to noise in training data.
---
4. Index & Passive
Jack Bogle / Vanguard
Core Philosophy: The average investor cannot consistently beat the market after fees. Therefore, the optimal strategy is to own the entire market at the lowest possible cost and hold for the long term. Costs are the single most reliable predictor of future relative returns — lower expense ratios, lower turnover, and lower taxes. "Don't look for the needle in the haystack. Just buy the haystack."
Key Metrics/Ratios:
- Expense ratio (should be below 0.10%, ideally below 0.05%)
- Tracking error to benchmark (should be near zero)
- Tax efficiency (broad index funds are inherently tax-efficient)
- Portfolio turnover (low turnover = low costs = higher after-tax returns)
- Dollar-cost averaging regularity
Typical Portfolio Allocation (Three-Fund Portfolio):
- US total stock market index: 40-60%
- International total stock market index: 20-30%
- US total bond market index: 20-40%
- Adjust stock/bond ratio based on age (common rule: bond allocation = age)
- Rebalance annually or at 5% drift thresholds
- No alternatives, no individual stocks, no tactical allocation
When It Works Best: Always — by definition, index investors capture the market return minus minimal costs; over 15+ year periods, 85-90% of active managers underperform their benchmark after fees; works in all market conditions as a wealth-preserving, behavior-managing approach.
When It Fails: "Fails" is relative — indexing never achieves above-average returns. During prolonged bear markets, drawdowns match the market (no defensive mechanism). In highly concentrated markets (US market dominated by 7 tech stocks in 2024), index investors have extreme concentration they may not intend. Indexing also creates potential systemic risks (price-insensitive buying).
---
Burton Malkiel / Random Walk
Core Philosophy: Stock prices incorporate and reflect all available information, making consistent outperformance through either fundamental analysis or technical analysis extremely difficult. Price changes are largely random walks; past performance does not predict future performance. The Efficient Market Hypothesis (EMH) in its semi-strong form implies that index investing is the rational default strategy.
Key Metrics/Ratios:
- Alpha (risk-adjusted outperformance) — Malkiel argues alpha is near-zero on average for active managers
- Expense ratios and transaction costs
- Tax drag
- Autocorrelation of returns (testing for randomness)
- Survivorship bias in fund performance data
Typical Portfolio Allocation:
- Similar to Bogle's three-fund portfolio
- Malkiel adds slight tilts: emerging markets exposure, REIT allocation
- Life-cycle approach: aggressive (90/10 stock/bond) in youth, conservative (40/60) near retirement
- Rebalance periodically
- Tax-loss harvesting as one of the few "free lunches"
When It Works Best: Efficient, liquid, large-cap markets (US, developed international) where information is rapidly incorporated; long time horizons where market efficiency dominates.
When It Fails: Markets that are less efficient (small-caps, frontier markets, distressed debt, private markets) may offer exploitable inefficiencies; momentum and value factors have shown persistent anomalies that challenge the pure random walk hypothesis; behavioral finance has identified systematic biases that create exploitable patterns.
---
Paul Merriman
Core Philosophy: Small-cap value stocks have historically outperformed large-cap growth stocks by 2-4% annually over long periods (the Fama-French small and value premiums). The "Ultimate Buy & Hold" portfolio tilts heavily toward small-cap value across US and international markets, capturing these premiums while maintaining diversification. Combines the simplicity of passive investing with factor-based tilts.
Key Metrics/Ratios:
- Small-cap value premium (historical 2-4% annualized excess return over large-cap growth)
- Fama-French three-factor and five-factor model loadings
- Portfolio weighted average market cap (smaller is better, per the thesis)
- Price-to-book ratio of portfolio (lower indicates value tilt)
- Diversification across 10+ asset classes
Typical Portfolio Allocation (Ultimate Buy & Hold):
- US large-cap blend: 10%
- US large-cap value: 10%
- US small-cap blend: 10%
- US small-cap value: 10%
- US REITs: 10%
- International large-cap: 10%
- International large-cap value: 10%
- International small-cap blend: 10%
- International small-cap value: 10%
- Emerging markets: 10%
- (Bonds held separately based on age/risk tolerance)
When It Works Best: Long time horizons (20+ years) where the small-cap value premium has time to compound; investors who can tolerate higher volatility for higher expected returns; periods when value outperforms growth.
When It Fails: The small-cap value premium has been inconsistent (underperformed significantly 2010-2020); higher turnover and less tax-efficient than simple total market index; more volatile in bear markets (small-cap value can draw down more severely); the premium may have been partially arbitraged away as more investors target it.
---
Lazy Portfolios
Boglehead Three-Fund Portfolio:
- US total stock market: 40-60%
- International total stock market: 20-30%
- US total bond market: 20-40%
- Philosophy: Maximum simplicity, minimum cost, broad diversification.
Golden Butterfly (Tyler at PortfolioCharts):
- US total stock market: 20%
- US small-cap value: 20%
- Long-term Treasury bonds: 20%
- Short-term Treasury bonds: 20%
- Gold: 20%
- Philosophy: Permanent-portfolio-inspired with small-cap value tilt; designed to minimize worst-case drawdowns and withdrawal-period failures.
All-Seasons Portfolio (Ray Dalio simplified for retail):
- US stocks: 30%
- Long-term Treasury bonds: 40%
- Intermediate-term Treasury bonds: 15%
- Gold: 7.5%
- Commodities: 7.5%
- Philosophy: Risk parity without leverage; perform adequately in all economic environments.
When Lazy Portfolios Work Best: For investors who want a "set and forget" approach; those who lack the temperament for active management; long horizons with regular contributions.
When They Fail: Bond-heavy portfolios suffer in rising rate environments (2022); over-simplification can miss asset classes that improve risk-adjusted returns; rebalancing discipline is required but psychologically difficult during drawdowns.
---
5. Alternative Investments
Family Offices & Endowment Model (Yale/David Swensen)
Core Philosophy: Swensen pioneered the "endowment model" at Yale: shift away from traditional stocks and bonds toward alternative assets (private equity, venture capital, real estate, natural resources, hedge funds) that offer an illiquidity premium and higher long-term returns. Institutions with perpetual time horizons should exploit their ability to lock up capital. Governance matters — invest with best-in-class managers, negotiate favorable terms, and maintain rigorous oversight.
Key Metrics/Ratios:
- Illiquidity premium (estimated 2-4% annual excess return for PE vs. public equities)
- Internal rate of return (IRR) for PE and VC investments
- Total value to paid-in capital (TVPI) and distributions to paid-in capital (DPI)
- Vintage year diversification
- Manager selection alpha (top-quartile PE returns far exceed bottom-quartile)
- J-curve: early negative returns before value creation
Typical Portfolio Allocation (Yale Endowment circa 2020s):
- Venture capital: 23%
- Leveraged buyouts (PE): 17%
- Absolute return (hedge funds): 23%
- Real estate: 10%
- Natural resources: 5%
- US equities: 2%
- International equities: 12%
- Bonds/cash: 8%
- Only ~22% in traditional stocks and bonds
When It Works Best: Institutions with truly long time horizons (20+ years), no liquidity needs, and access to top-tier fund managers; periods when private markets offer genuine premiums over public markets; when the investor has sophisticated governance and due diligence capabilities.
When It Fails: Many endowments that copied Yale's model failed because they lacked access to top-tier managers (returns are bimodal: top managers generate alpha, bottom managers destroy value); liquidity crises (2008-2009 forced many endowments to sell illiquid positions at fire-sale prices); PE return dispersion is enormous; fees erode returns for investors without negotiating power.
---
Venture Capital
Core Philosophy: VC returns follow a power law distribution — a small number of investments generate the vast majority of returns. The goal is to identify and invest in companies that can become 100x+ outliers. Portfolio construction must accept that 50-70% of investments will fail or return less than 1x, 20-30% will return 1-5x, and 1-5% will return 10-100x+ and drive the fund's returns. Access to the best deals (proprietary deal flow) and winning competitive allocation are as important as picking skill.
Key Metrics/Ratios:
- IRR and TVPI/DPI by fund vintage
- Loss ratio (percentage of investments returning <1x)
- Follow-on reserve ratio (typically 50% of fund reserved for pro-rata follow-on)
- Ownership percentage at entry
- Portfolio construction math: a $100M fund investing $2M per company needs 2-3 investments to return 50x+ to drive fund performance
Typical Portfolio Construction:
- Seed/early-stage: 20-40 investments per fund
- Series A/B: 15-25 investments per fund
- Growth/late-stage: 10-15 investments per fund
- 50% initial investment, 50% follow-on reserves
- Target ownership: 10-20% at seed, 15-25% at Series A
- Power law emphasis: double down on winners, write off losers quickly
Key Thinkers:
- Marc Andreessen / a16z: Software is eating the world; build a platform (recruiting, marketing, BD) not just a fund; bet on technical founders
- Peter Thiel: Zero-to-one thinking; seek monopolies, avoid competition; "definite optimism" — the future is buildable; concentrated bets on contrarian truths
When It Works Best: Technology adoption cycles; low-interest-rate environments that fund startups aggressively; when the investor has proprietary deal flow and can win competitive rounds; emerging technology paradigms (AI, blockchain, biotech).
When It Fails: When access to top-tier funds is unavailable (median VC fund returns are poor); rising rates that compress multiples and shut down exit markets (IPO drought); when follow-on funding dries up and portfolio companies can't raise; vintage years during frothy markets often produce poor returns.
---
Real Estate
Core Philosophy: Real estate offers income (rental yield), appreciation, leverage (mortgage financing), tax advantages (depreciation, 1031 exchanges), and inflation hedging. Unlike stocks, real estate can be improved through active management (renovations, operational efficiency, tenant management).
BRRRR Method (Buy, Rehab, Rent, Refinance, Repeat):
- Buy undervalued/distressed property
- Rehab to force appreciation
- Rent at market rates
- Refinance to extract invested capital (cash-out refinance at new appraised value)
- Repeat with extracted capital — effectively investing with "infinite returns" on equity
Key Metrics/Ratios:
- Cap rate (NOI / purchase price) — typically 4-10% depending on market/asset class
- Cash-on-cash return (annual cash flow / cash invested)
- Net operating income (NOI)
- Debt service coverage ratio (DSCR): NOI / debt payments (minimum 1.25x)
- Price per square foot vs. comparable properties
- Gross rent multiplier
- Internal rate of return (IRR) including appreciation and tax benefits
REITs vs. Direct Ownership:
- REITs: liquid, diversified, passive, 90%+ income distribution requirement; lower returns but zero management burden; publicly traded REITs correlate with stock market short-term but real estate fundamentals long-term
- Direct: higher potential returns through leverage and active management; tax advantages (depreciation shield); illiquid; management-intensive; concentration risk
When It Works Best: Inflationary environments (rents and property values rise with inflation); low/falling interest rate environments (leverage becomes cheaper); growing population/job markets; when active management skill can unlock value through renovation or repositioning.
When It Fails: Rising interest rates (increases financing costs, compresses cap rates, reduces affordability); overbuilt markets with excess supply; economic recessions that increase vacancy rates; remote work trends affecting office/commercial real estate; leverage amplifies losses in downturns.
---
Crypto & Digital Assets
Bitcoin Thesis (Michael Saylor / MicroStrategy):
- Bitcoin is "digital gold" — a store of value and inflation hedge with a fixed supply of 21 million coins
- Saylor's strategy: convert corporate treasury cash into Bitcoin using leverage (convertible bonds)
- Key metrics: stock-to-flow ratio, hash rate, on-chain activity, realized cap vs. market cap
- Works best when: monetary debasement concerns rise, fiat currencies weaken, institutional adoption accelerates
- Fails when: regulatory crackdowns, sustained risk-off environments, correlation with equities increases (destroys the "uncorrelated" thesis)
DeFi Yield:
- Decentralized lending, liquidity provision, yield farming
- Key metrics: total value locked (TVL), annual percentage yield (APY), impermanent loss, smart contract audit status, protocol revenue
- Staking: proof-of-stake networks offer 3-8% nominal yields for validating transactions
- Works best when: DeFi ecosystem is growing, new protocols launch with subsidized yields
- Fails when: smart contract exploits, regulatory action, yield compression as TVL grows, "rug pulls"
Prediction Markets (Polymarket):
- Markets for binary/multi-outcome events (elections, economic data, sports)
- Enable hedging, information aggregation, and speculative trading
- Key metrics: market volume, bid-ask spread, calibration accuracy
- Works best as an information tool and hedging instrument rather than primary investment strategy
Typical Crypto Portfolio Allocation (for those who choose exposure):
- Conservative: 1-5% of total portfolio in Bitcoin/Ethereum
- Moderate: 5-15% across major protocols, staking, and select DeFi
- Aggressive: 15-30%+ with venture-style bets on early protocols
- Maximum position sizing discipline is critical due to extreme volatility (50-80% drawdowns are common)
---
Futures & Commodities: Trend Following
Core Philosophy: Prices trend — they go up for longer and down for longer than random walk theory predicts — due to behavioral biases (herding, anchoring, slow information diffusion) and structural factors (central bank policy, supply/demand cycles). Managed futures (CTA) strategies systematically buy assets in uptrends and sell/short assets in downtrends across commodities, currencies, rates, and equity indices.
Key Metrics/Ratios:
- Moving average crossovers (e.g., 50-day vs. 200-day)
- Breakout signals (Donchian channels, Bollinger Band breakouts)
- Volatility-adjusted position sizing (target constant risk per position)
- Sharpe ratio of trend-following strategies: typically 0.5-1.0
- Crisis alpha: correlation to equities during drawdowns (should be negative — trend following tends to profit during sustained market declines)
Carry Trade:
- Borrow in low-yielding currencies/assets, invest in high-yielding currencies/assets
- Key metric: yield differential (carry)
- Works in stable, low-volatility environments
- Fails catastrophically during sudden risk-off events (carry trades unwind violently — "going up by the stairs, down by the elevator")
Typical Allocation:
- Managed futures as a 10-20% portfolio diversifier
- Applied across 50-100+ futures markets globally
- 1-2x leverage typical, risk-adjusted per position
- Carry overlaid as a secondary signal alongside trend
When Trend Following Works Best: Sustained directional moves in any asset class; crisis periods (2008, 2022 — managed futures were positive when stocks and bonds fell); inflationary/deflationary regimes with persistent trends.
When It Fails: Choppy, mean-reverting, range-bound markets (death by a thousand cuts from whipsaws); low-volatility environments with no sustained trends; when central banks suppress volatility through intervention.
---
6. Risk Management Frameworks
Kelly Criterion
Core Philosophy: The Kelly Criterion provides the mathematically optimal fraction of capital to bet on a favorable opportunity, maximizing the long-term geometric growth rate of wealth. The formula is: f* = (bp - q) / b, where b is the odds, p is the probability of winning, and q = 1-p. In practice, most professional investors use "fractional Kelly" (25-50% of full Kelly) because the full Kelly bet is extremely volatile and requires perfect knowledge of probabilities.
Key Metrics/Ratios:
- Edge: expected return of the bet
- Odds: payoff ratio (win amount / loss amount)
- Probability of success (estimated, not known — hence fractional Kelly)
- Kelly fraction: percentage of capital to allocate
- Geometric growth rate of the portfolio
Application:
- Full Kelly maximizes long-term wealth but with enormous drawdowns (can lose 50%+ temporarily)
- Half-Kelly achieves 75% of the growth rate with dramatically lower volatility
- Quarter-Kelly is common for strategies with uncertain edge estimates
- Never exceed full Kelly — overbetting guarantees ruin over time
When It Works Best: When edge and probabilities can be estimated with reasonable accuracy; repeated bets with consistent edge; poker, sports betting, and systematic trading.
When It Fails: When probability estimates are wrong (garbage in, garbage out); single, irreversible decisions; when correlations between bets are ignored; when tail risk is underestimated.
---
Modern Portfolio Theory (Markowitz) / Efficient Frontier
Core Philosophy: Investors should construct portfolios that maximize expected return for a given level of risk (or minimize risk for a given expected return). The efficient frontier is the set of optimal portfolios. Diversification is the "only free lunch in finance" — combining assets with imperfect correlation reduces portfolio risk without proportionally reducing returns. The key insight is that a portfolio's risk depends not just on individual asset volatility but on correlations between assets.
Key Metrics/Ratios:
- Expected return (mean)
- Variance / standard deviation (risk measure)
- Covariance / correlation matrix between all assets
- Sharpe ratio: (return - risk-free rate) / standard deviation
- Efficient frontier: the curved set of optimal portfolios in risk-return space
- Tangency portfolio: the portfolio on the efficient frontier with the highest Sharpe ratio
Application:
- Mean-variance optimization to find optimal weights
- Constraint-based optimization (minimum/maximum allocation, sector limits)
- Used as a foundation by virtually all institutional portfolio construction
When It Works Best: When return distributions are approximately normal; when correlations are stable; for strategic (long-term) asset allocation decisions; as a framework for thinking about risk-return tradeoffs.
When It Fails: Returns are not normally distributed (fat tails, skewness); correlations spike during crises (precisely when diversification is needed most); highly sensitive to input assumptions (small changes in expected returns produce wildly different optimal portfolios — "error maximization"); backward-looking data may not predict future correlations.
---
Black-Litterman Model
Core Philosophy: A Bayesian approach to portfolio construction that combines the market equilibrium (CAPM-implied expected returns from market capitalization weights) with the investor's subjective views about expected returns. This solves the key problem with Markowitz: the extreme sensitivity to expected return inputs. By starting from market equilibrium and adjusting based on investor confidence in their views, Black-Litterman produces more stable, intuitive portfolios.
Key Metrics/Ratios:
- Market-implied equilibrium returns (reverse-optimized from market cap weights)
- Investor views matrix (expressed as expected returns on specific assets or asset pairs)
- Confidence levels on each view (uncertainty matrix)
- Tau parameter: scaling factor for uncertainty in equilibrium returns
- Posterior expected returns: Bayesian combination of equilibrium and views
Application:
- Start with global market cap weights as the neutral position
- Express views: "US equities will outperform international by 2%" with a confidence level
- Model blends market equilibrium with views proportional to confidence
- Result: portfolios that tilt toward views but remain diversified and stable
When It Works Best: Institutional investors with specific market views who want a systematic framework to implement them; when the investor wants to avoid the instability of unconstrained mean-variance optimization; multi-asset allocation.
When It Fails: If investor views are consistently wrong, the model systematically tilts toward losing positions; requires careful calibration of confidence levels (overconfidence in bad views is worse than no views); still dependent on stable covariance estimates.
---
Tail Risk Hedging / Nassim Taleb: Barbell Strategy
Core Philosophy: The world is dominated by "Black Swans" — rare, high-impact, unpredictable events. Most risk models dangerously underestimate tail risk because they assume normal distributions. The "barbell strategy" places 85-90% of capital in extremely safe assets (Treasury bills, cash) and 10-15% in highly speculative, convex bets (deep out-of-the-money options, early-stage VC, moonshot investments). Nothing in the middle. The goal is "antifragility" — benefiting from volatility and disorder rather than being harmed by it.
Key Metrics/Ratios:
- Tail risk: probability and magnitude of extreme losses (beyond 3 standard deviations)
- Convexity: asymmetric payoff profile (limited downside, unlimited upside)
- Kurtosis: fatness of return distribution tails (higher kurtosis = more tail risk)
- Cost of hedging: premium paid for protective options relative to portfolio value
- Maximum drawdown: the single most important risk metric (not Sharpe ratio or volatility)
- Recovery time from drawdowns
Application (Universa Investments, Taleb's fund):
- 85-90% in T-bills and safe bonds
- 10-15% in far out-of-the-money puts on equity indices and other tail-risk hedges
- Tail hedges are expected to lose money slowly in normal markets but generate 100x+ returns during crashes
- Portfolio-level result: modest positive returns in bull markets, enormous returns during crashes
When It Works Best: During black swan events (2008, 2020 COVID crash — Universa reportedly returned 4,144% in March 2020); for investors who psychologically cannot handle large drawdowns; as a hedge overlay on an existing portfolio.
When It Fails: In prolonged calm markets, the "bleed" from constantly buying options that expire worthless can be significant; the opportunity cost of holding 85-90% in T-bills during strong bull markets is enormous; timing of the hedges must be roughly right (options have finite expiration); the strategy requires extreme patience and tolerance for prolonged underperformance.
---
Drawdown-Based Risk Management
Core Philosophy: Maximum drawdown (peak-to-trough decline) is a more meaningful risk measure than standard deviation for real investors, because it captures the actual worst-case experience. Drawdown-based metrics directly measure what investors care about: "how much can I lose, and how long until I recover?"
Key Metrics/Ratios:
- Maximum drawdown: largest peak-to-trough decline in portfolio value
- Calmar ratio: annualized return / maximum drawdown (higher is better; >1.0 is strong)
- Recovery time: how long to return to previous peak
- Ulcer Index: average depth and duration of drawdowns (penalizes both magnitude and persistence)
- Sterling ratio: excess return / average annual drawdown
- Conditional drawdown at risk (CDaR): expected drawdown in the worst X% of scenarios
Application:
- Set a maximum acceptable drawdown (e.g., 20%) and size positions/leverage accordingly
- Use drawdown-based stop-losses: reduce exposure when portfolio drawdown exceeds threshold
- Risk budgeting based on expected maximum drawdown rather than volatility
- Stress testing against historical worst cases (2008, 2020, 2022) and hypothetical scenarios
When It Works Best: For investors with specific loss tolerance; wealth management where drawdown tolerance is a function of spending needs; hedge funds that face redemption risk at certain drawdown levels.
When It Fails: Backward-looking (the next worst drawdown may exceed all historical precedents); overly conservative drawdown limits may force selling at bottoms; whipsawing in volatile-but-ultimately-recovering markets.
---
7. Portfolio Construction Models
Strategic vs. Tactical Asset Allocation
Strategic Asset Allocation (SAA):
- Set long-term target weights based on investor goals, risk tolerance, and time horizon
- Rebalance periodically (quarterly, annually, or at drift thresholds)
- Based on long-term capital market assumptions (expected returns, volatilities, correlations)
- Philosophy: markets are efficient enough that timing is futile; earn the risk premium by staying invested
- Typical implementation: policy portfolio with fixed percentages (e.g., 60/40)
Tactical Asset Allocation (TAA):
- Systematically deviate from strategic weights based on market conditions
- Overweight asset classes expected to outperform, underweight those expected to underperform
- Signals: valuation (CAPE ratio), momentum, economic indicators, sentiment
- Typical bands: +/- 5-10% deviation from strategic weights
- Requires rebalancing triggers and discipline to return to strategic weights when signals normalize
When SAA Works Best: Long time horizons; investors who lack the skill or temperament for timing; low-cost environments. When SAA Fails: Misses opportunities to reduce exposure before major drawdowns.
When TAA Works Best: When valuation signals are extreme (CAPE > 30 or < 10); when momentum is clearly directional; for skilled investors with disciplined processes. When TAA Fails: Transaction costs and tax drag can exceed the benefit; most investors' timing is value-destructive; requires conviction to act against the crowd.
---
Core-Satellite Approach
Core Philosophy: Combine the reliability of passive indexing (the "core") with the potential for alpha from active strategies (the "satellites"). The core captures market beta cheaply; satellites seek alpha through stock-picking, factor tilts, alternatives, or tactical positions.
Typical Structure:
- Core (60-80%): Broad market index funds (total stock market, total international, total bond)
- Satellites (20-40%): Individual stocks, sector ETFs, factor funds, alternatives, active managers
- Core provides stability, diversification, and low cost; satellites provide potential outperformance and customization
Key Metrics:
- Tracking error of total portfolio vs. benchmark (moderate, reflecting satellite bets)
- Active share (percentage of portfolio differing from benchmark)
- Information ratio of satellite positions
- Cost blended across core and satellite (total expense ratio)
When It Works Best: Investors who believe in market efficiency but want some active exposure; those who want to control costs while expressing high-conviction views; institutional portfolios balancing passive mandate with internal active teams.
When It Fails: If satellites consistently underperform, they drag down the portfolio vs. pure indexing; requires discipline to limit satellite allocation; behavioral risk of over-trading satellites.
---
Risk Budgeting
Core Philosophy: Allocate a total "risk budget" (expressed as volatility, VaR, or expected shortfall) across portfolio positions. Each position receives a risk allocation based on its expected contribution to return and the investor's confidence. This framework ensures that risk is deployed intentionally rather than emerging accidentally from capital allocation.
Key Concepts:
- Marginal contribution to risk: how much risk does adding a unit of this asset contribute to total portfolio risk?
- Risk parity: equal risk contribution from each asset class (Bridgewater's All-Weather)
- Active risk budget: how much tracking error is allocated to each active bet?
- Risk attribution: decompose realized risk into contributions from asset allocation, security selection, factor exposure, and currency
Application:
- Set total portfolio risk target (e.g., 10% annualized volatility or 20% maximum drawdown)
- Allocate risk across asset classes/strategies based on expected Sharpe ratios and diversification benefits
- Monitor risk consumption vs. budget; reduce positions that consume disproportionate risk relative to expected return
- Adjust budgets dynamically as market conditions change
When It Works Best: Institutional investors managing multiple strategies/managers; when risk transparency is critical; multi-asset portfolios where capital allocation doesn't reflect risk allocation.
When It Fails: Risk measures are backward-looking and may understate forward risk; correlation estimates break down in crises; overly rigid budgets may force selling at precisely the wrong time.
---
Factor-Based Allocation
Core Philosophy: Instead of allocating capital to asset classes (stocks, bonds, commodities), allocate to the underlying factors that drive returns. All assets can be decomposed into factor exposures: equity risk premium, term premium, credit premium, value, momentum, quality, low volatility, carry, liquidity. Factor allocation provides better diversification because factors have lower correlations with each other than asset classes do (asset classes often share common factor exposures).
Key Factors:
- Market (equity risk premium): compensation for bearing equity market risk
- Size: small-cap premium over large-cap
- Value: cheap stocks outperform expensive stocks (long-term, with drawdowns)
- Momentum: recent winners continue outperforming recent losers
- Quality: profitable, stable, growing companies outperform
- Low volatility: lower-risk stocks deliver higher risk-adjusted returns
- Term: long-term bonds earn a premium over short-term
- Credit: riskier bonds earn a premium over safe bonds
- Carry: high-yielding assets outperform low-yielding assets
- Liquidity: illiquid assets earn a premium
Typical Implementation:
- Map current portfolio to factor exposures using regression
- Identify unintended factor bets and deliberate factor tilts
- Use factor-targeted ETFs or long-short factor portfolios
- Diversify across uncorrelated factors to reduce portfolio volatility
When It Works Best: When factors provide genuine diversification; for institutional investors with long horizons and risk tolerance for multi-year factor drawdowns; when factor premia are at historically wide levels.
When It Fails: Individual factors can underperform for decades (value underperformed 2010-2020); factor crowding can compress premia; the "factor zoo" problem (hundreds of published factors, many are data-mined noise); factor correlations can increase during stress events.
---
Goals-Based Investing (Bucketing)
Core Philosophy: Instead of managing one portfolio against a benchmark, divide wealth into "buckets" aligned with specific goals and time horizons. Each bucket has its own asset allocation matched to the goal's timing, importance, and required return. This framework aligns the portfolio with what the money is actually for, reducing the behavioral risk of panic selling during drawdowns in long-term buckets.
Typical Bucket Structure:
- Bucket 1 — Safety / Near-Term (0-3 years): Cash, money market funds, short-term bonds, CDs. Goal: capital preservation for living expenses and emergency needs. Yield: nominal, but certainty is paramount.
- Bucket 2 — Income / Medium-Term (3-10 years): Investment-grade bonds, dividend stocks, balanced funds. Goal: moderate growth with controlled drawdowns for medium-term needs (education, home purchase, planned expenditures).
- Bucket 3 — Growth / Long-Term (10+ years): Equities (domestic and international), alternatives, real estate. Goal: maximize long-term wealth; can tolerate volatility because the money won't be touched for a decade or more.
Key Metrics:
- Probability of meeting each goal (Monte Carlo simulation)
- Shortfall risk: probability that bucket value falls below goal amount at target date
- Required return for each bucket to meet its goal
- Liquidity coverage: months of expenses covered by Bucket 1
When It Works Best: Individual investors and wealth management clients with multiple goals; retirees managing withdrawal sequences; families with staggered time horizons (college in 5 years, retirement in 20); reducing behavioral errors by psychologically separating short-term and long-term money.
When It Fails: Adds complexity vs. a single diversified portfolio; requires regular monitoring and refilling of near-term buckets; suboptimal from a pure mean-variance perspective (the total allocation may not be on the efficient frontier); can lead to under-investment if too much is parked in safety buckets.
---
Synthesis: Key Principles for an Investment Management AI Skill
Across all the philosophies and frameworks above, several meta-principles emerge:
1. Cost is the most reliable predictor of future returns. Every strategy benefits from cost minimization — lower expense ratios, lower transaction costs, lower tax drag.
2. Risk management is more important than return generation. Every legendary investor prioritizes not losing money. Position sizing (Kelly), drawdown limits, margin of safety, and diversification are universal.
3. Time horizon determines strategy suitability. Long horizons favor equities, illiquidity premiums, and factor tilts. Short horizons demand capital preservation.
4. Behavioral discipline is the highest-alpha strategy. The gap between investor returns and investment returns (the "behavior gap") exceeds most fee differentials. Systematic rules combat emotional decision-making.
5. No single approach dominates all environments. Value fails when growth leads; momentum fails in chop; risk parity fails when stocks and bonds correlate. The best portfolios combine complementary approaches.
6. Simplicity is underrated. A three-fund portfolio consistently beats the median actively managed portfolio. Complexity should be added only when it provably improves risk-adjusted returns after costs.
7. Factor diversification beats asset-class diversification. Understanding the underlying drivers of return (value, momentum, quality, carry) provides better diversification than naive asset-class splits.
8. Margin of safety is universal. Whether it manifests as Buffett's price discount, Taleb's barbell, Dalio's all-weather balance, or Bogle's cost minimization, every approach demands a buffer against the unknown.
---
This report synthesizes publicly available investment philosophies from published books, papers, interviews, and fund disclosures. It does not constitute investment advice. All strategies involve risk of loss.
#!/usr/bin/env python3
"""
Strategy Backtesting Engine (Mode 4) — investment-management skill.
Test investment strategies against historical data. Supports built-in strategies
(buy-and-hold, equal-weight, risk parity, momentum, value, all-weather) and
custom YAML-defined strategies. Outputs structured metrics consumable by
autoany EGRI evaluator loops.
Usage:
python3 backtest.py --strategy buy-and-hold --tickers SPY --period 10y
python3 backtest.py --strategy equal-weight --tickers SPY,QQQ,BND,GLD --period 5y
python3 backtest.py --strategy risk-parity --tickers SPY,TLT,GLD,VNQ --period 10y
python3 backtest.py --strategy momentum --tickers SPY,QQQ,EEM,TLT,GLD --top 3 --period 5y
python3 backtest.py --strategy-file strategy.yaml --period 10y
python3 backtest.py --strategy all-weather --period 10y --json
python3 backtest.py --strategy buy-and-hold --tickers SPY --period 10y --benchmark SPY
EGRI evaluator mode (structured JSON output for autoany):
python3 backtest.py --strategy-file strategy.yaml --period 10y --egri
"""
from __future__ import annotations
import argparse
import json
import math
import sys
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
# ---------------------------------------------------------------------------
# Optional dependencies
# ---------------------------------------------------------------------------
_yfinance = None
_np = None
_yaml = None
try:
import yfinance as _yfinance
except ImportError:
pass
try:
import numpy as _np
except ImportError:
pass
try:
import yaml as _yaml
except ImportError:
pass
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
TRADING_DAYS_PER_YEAR = 252
RISK_FREE_RATE = 0.04 # ~4% (current US T-bill rate)
CACHE_DIR = Path.home() / ".investment-management" / "market-data" / "prices"
# Dalio's All-Weather allocation
ALL_WEATHER = {
"SPY": 0.30, # US equities
"TLT": 0.40, # Long-term bonds
"IEF": 0.15, # Intermediate bonds
"GLD": 0.075, # Gold
"DBC": 0.075, # Commodities
}
# ---------------------------------------------------------------------------
# Data classes
# ---------------------------------------------------------------------------
@dataclass
class BacktestMetrics:
"""Core metrics from a backtest run."""
strategy: str
tickers: list[str]
period: str
start_date: str
end_date: str
trading_days: int
years: float
# Returns
total_return_pct: float
cagr_pct: float
# Risk-adjusted
sharpe_ratio: float
sortino_ratio: float
calmar_ratio: float
# Risk
annualized_volatility_pct: float
max_drawdown_pct: float
max_drawdown_start: str
max_drawdown_end: str
max_drawdown_recovery: str | None
# Win/loss
win_rate_pct: float
avg_daily_return_pct: float
avg_winning_day_pct: float
avg_losing_day_pct: float
best_day_pct: float
worst_day_pct: float
# Portfolio
starting_value: float
ending_value: float
weights: dict[str, float]
rebalance_count: int
def to_egri_outcome(self) -> dict:
"""Format as autoany EGRI Outcome for evaluator consumption."""
return {
"score": self.sharpe_ratio,
"constraints_passed": self.max_drawdown_pct > -15.0,
"violations": (
[f"max_drawdown={self.max_drawdown_pct:.1f}% exceeds -15% limit"]
if self.max_drawdown_pct <= -15.0 else []
),
"metrics": {
"sharpe_ratio": self.sharpe_ratio,
"sortino_ratio": self.sortino_ratio,
"calmar_ratio": self.calmar_ratio,
"cagr_pct": self.cagr_pct,
"max_drawdown_pct": self.max_drawdown_pct,
"volatility_pct": self.annualized_volatility_pct,
"total_return_pct": self.total_return_pct,
"win_rate_pct": self.win_rate_pct,
},
}
@dataclass
class StrategyConfig:
"""Strategy definition — can be loaded from YAML or built-in."""
name: str
tickers: list[str]
weights: dict[str, float]
rebalance_frequency: str = "quarterly" # daily, monthly, quarterly, annually, never
rebalance_threshold_pct: float = 5.0
momentum_lookback: int = 252 # trading days for momentum calc
momentum_top_n: int = 3
stop_loss_pct: float | None = None
# For value strategy
value_metric: str = "pe_ratio" # pe_ratio, pb_ratio, fcf_yield
# ---------------------------------------------------------------------------
# Data fetching
# ---------------------------------------------------------------------------
def _parse_period(period: str) -> tuple[datetime, datetime]:
"""Convert period string like '5y', '10y', '3m', '1y' to date range."""
end = datetime.now()
val = int(period[:-1])
unit = period[-1].lower()
if unit == "y":
start = end - timedelta(days=val * 365)
elif unit == "m":
start = end - timedelta(days=val * 30)
elif unit == "d":
start = end - timedelta(days=val)
else:
start = end - timedelta(days=val * 365)
return start, end
def fetch_prices(tickers: list[str], period: str) -> dict[str, list[tuple[str, float]]]:
"""Fetch adjusted close prices for tickers. Returns {ticker: [(date_str, price), ...]}."""
if _yfinance is None:
print("Error: yfinance required. Install with: pip install yfinance", file=sys.stderr)
raise SystemExit(1)
CACHE_DIR.mkdir(parents=True, exist_ok=True)
result = {}
for ticker in tickers:
cache_file = CACHE_DIR / f"{ticker}_{period}.json"
cache_fresh = False
if cache_file.exists():
age_hours = (datetime.now().timestamp() - cache_file.stat().st_mtime) / 3600
if age_hours < 24:
cache_fresh = True
with open(cache_file) as f:
result[ticker] = json.load(f)
if not cache_fresh:
try:
data = _yfinance.download(ticker, period=period, progress=False, auto_adjust=True)
if data.empty:
print(f"Warning: no data for {ticker}", file=sys.stderr)
continue
prices = []
for idx, row in data.iterrows():
date_str = idx.strftime("%Y-%m-%d") if hasattr(idx, "strftime") else str(idx)[:10]
close = float(row["Close"]) if not _np or not _np.isnan(float(row["Close"])) else None
if close is not None:
prices.append((date_str, close))
result[ticker] = prices
with open(cache_file, "w") as f:
json.dump(prices, f)
except Exception as e:
print(f"Warning: failed to fetch {ticker}: {e}", file=sys.stderr)
return result
def align_prices(price_data: dict[str, list[tuple[str, float]]]) -> tuple[list[str], dict[str, list[float]]]:
"""Align all tickers to the same dates. Returns (dates, {ticker: [prices]})."""
# Find common dates
date_sets = [set(d for d, _ in prices) for prices in price_data.values()]
if not date_sets:
return [], {}
common_dates = sorted(set.intersection(*date_sets))
# Build aligned price arrays
aligned = {}
for ticker, prices in price_data.items():
price_map = dict(prices)
aligned[ticker] = [price_map[d] for d in common_dates]
return common_dates, aligned
# ---------------------------------------------------------------------------
# Portfolio simulation
# ---------------------------------------------------------------------------
def compute_portfolio_returns(
dates: list[str],
prices: dict[str, list[float]],
config: StrategyConfig,
) -> tuple[list[float], list[float], int]:
"""
Simulate portfolio returns given prices and strategy config.
Returns (daily_returns, portfolio_values, rebalance_count).
"""
tickers = [t for t in config.tickers if t in prices]
if not tickers:
return [], [], 0
n_days = len(dates)
weights = _normalize_weights({t: config.weights.get(t, 0) for t in tickers})
# Initialize portfolio
initial_value = 10000.0
holdings = {t: initial_value * weights[t] / prices[t][0] for t in tickers}
portfolio_values = [initial_value]
daily_returns = []
rebalance_count = 0
# Rebalance schedule
last_rebalance_month = None
last_rebalance_quarter = None
for i in range(1, n_days):
# Compute current portfolio value
value = sum(holdings[t] * prices[t][i] for t in tickers)
prev_value = portfolio_values[-1]
daily_ret = (value - prev_value) / prev_value if prev_value > 0 else 0
daily_returns.append(daily_ret)
portfolio_values.append(value)
# Stop loss check
if config.stop_loss_pct is not None:
peak = max(portfolio_values)
drawdown = (value - peak) / peak
if drawdown < config.stop_loss_pct / 100.0:
# Liquidate to cash — hold flat
for t in tickers:
holdings[t] = 0
continue
# Check rebalance trigger
should_rebalance = False
date_str = dates[i]
if config.rebalance_frequency == "daily":
should_rebalance = True
elif config.rebalance_frequency == "monthly":
month = date_str[:7]
if month != last_rebalance_month:
should_rebalance = True
last_rebalance_month = month
elif config.rebalance_frequency == "quarterly":
quarter = date_str[:4] + "Q" + str((int(date_str[5:7]) - 1) // 3)
if quarter != last_rebalance_quarter:
should_rebalance = True
last_rebalance_quarter = quarter
elif config.rebalance_frequency == "annually":
year = date_str[:4]
if last_rebalance_month is None or year != last_rebalance_month:
should_rebalance = True
last_rebalance_month = year
# Threshold check — only rebalance if drift exceeds threshold
if should_rebalance and config.rebalance_threshold_pct > 0:
current_weights = {t: holdings[t] * prices[t][i] / value for t in tickers}
max_drift = max(abs(current_weights[t] - weights[t]) for t in tickers)
if max_drift * 100 < config.rebalance_threshold_pct:
should_rebalance = False
if should_rebalance and config.rebalance_frequency != "never":
# For momentum strategy, update weights
if config.name == "momentum" and i >= config.momentum_lookback:
momentum_scores = {}
for t in tickers:
if i >= config.momentum_lookback:
ret = (prices[t][i] / prices[t][i - config.momentum_lookback]) - 1
momentum_scores[t] = ret
top_n = sorted(momentum_scores, key=momentum_scores.get, reverse=True)[:config.momentum_top_n]
weights = {t: (1.0 / config.momentum_top_n if t in top_n else 0) for t in tickers}
# Rebalance holdings
for t in tickers:
holdings[t] = value * weights[t] / prices[t][i] if prices[t][i] > 0 else 0
rebalance_count += 1
return daily_returns, portfolio_values, rebalance_count
def _normalize_weights(weights: dict[str, float]) -> dict[str, float]:
"""Normalize weights to sum to 1.0."""
total = sum(weights.values())
if total == 0:
n = len(weights)
return {t: 1.0 / n for t in weights}
return {t: w / total for t, w in weights.items()}
# ---------------------------------------------------------------------------
# Metrics computation
# ---------------------------------------------------------------------------
def compute_metrics(
strategy_name: str,
tickers: list[str],
weights: dict[str, float],
period: str,
dates: list[str],
daily_returns: list[float],
portfolio_values: list[float],
rebalance_count: int,
) -> BacktestMetrics:
"""Compute all backtest metrics from daily returns."""
n = len(daily_returns)
if n == 0:
return _empty_metrics(strategy_name, tickers, weights, period)
years = n / TRADING_DAYS_PER_YEAR
# Returns
total_return = (portfolio_values[-1] / portfolio_values[0]) - 1
cagr = (portfolio_values[-1] / portfolio_values[0]) ** (1 / years) - 1 if years > 0 else 0
# Volatility
mean_ret = sum(daily_returns) / n
variance = sum((r - mean_ret) ** 2 for r in daily_returns) / (n - 1) if n > 1 else 0
daily_vol = math.sqrt(variance)
ann_vol = daily_vol * math.sqrt(TRADING_DAYS_PER_YEAR)
# Downside deviation (for Sortino)
downside_returns = [min(0, r - RISK_FREE_RATE / TRADING_DAYS_PER_YEAR) for r in daily_returns]
downside_var = sum(r ** 2 for r in downside_returns) / n if n > 0 else 0
downside_dev = math.sqrt(downside_var) * math.sqrt(TRADING_DAYS_PER_YEAR)
# Sharpe ratio
excess_return = cagr - RISK_FREE_RATE
sharpe = excess_return / ann_vol if ann_vol > 0 else 0
# Sortino ratio
sortino = excess_return / downside_dev if downside_dev > 0 else 0
# Max drawdown
peak = portfolio_values[0]
max_dd = 0
dd_start_idx = 0
dd_end_idx = 0
current_dd_start = 0
recovery_idx = None
for i, val in enumerate(portfolio_values):
if val > peak:
peak = val
current_dd_start = i
dd = (val - peak) / peak
if dd < max_dd:
max_dd = dd
dd_start_idx = current_dd_start
dd_end_idx = i
# Find recovery point
if dd_end_idx < len(portfolio_values) - 1:
peak_at_dd = portfolio_values[dd_start_idx]
for i in range(dd_end_idx, len(portfolio_values)):
if portfolio_values[i] >= peak_at_dd:
recovery_idx = i
break
# Calmar ratio
calmar = cagr / abs(max_dd) if max_dd != 0 else 0
# Win/loss stats
winning = [r for r in daily_returns if r > 0]
losing = [r for r in daily_returns if r < 0]
win_rate = len(winning) / n * 100 if n > 0 else 0
return BacktestMetrics(
strategy=strategy_name,
tickers=tickers,
period=period,
start_date=dates[0] if dates else "",
end_date=dates[-1] if dates else "",
trading_days=n,
years=round(years, 2),
total_return_pct=round(total_return * 100, 2),
cagr_pct=round(cagr * 100, 2),
sharpe_ratio=round(sharpe, 3),
sortino_ratio=round(sortino, 3),
calmar_ratio=round(calmar, 3),
annualized_volatility_pct=round(ann_vol * 100, 2),
max_drawdown_pct=round(max_dd * 100, 2),
max_drawdown_start=dates[dd_start_idx] if dates else "",
max_drawdown_end=dates[dd_end_idx] if dates else "",
max_drawdown_recovery=dates[recovery_idx] if recovery_idx is not None and dates else None,
win_rate_pct=round(win_rate, 1),
avg_daily_return_pct=round(mean_ret * 100, 4),
avg_winning_day_pct=round(sum(winning) / len(winning) * 100, 4) if winning else 0,
avg_losing_day_pct=round(sum(losing) / len(losing) * 100, 4) if losing else 0,
best_day_pct=round(max(daily_returns) * 100, 2),
worst_day_pct=round(min(daily_returns) * 100, 2),
starting_value=round(portfolio_values[0], 2),
ending_value=round(portfolio_values[-1], 2),
weights=weights,
rebalance_count=rebalance_count,
)
def _empty_metrics(name: str, tickers: list[str], weights: dict, period: str) -> BacktestMetrics:
return BacktestMetrics(
strategy=name, tickers=tickers, period=period,
start_date="", end_date="", trading_days=0, years=0,
total_return_pct=0, cagr_pct=0, sharpe_ratio=0, sortino_ratio=0, calmar_ratio=0,
annualized_volatility_pct=0, max_drawdown_pct=0, max_drawdown_start="",
max_drawdown_end="", max_drawdown_recovery=None,
win_rate_pct=0, avg_daily_return_pct=0, avg_winning_day_pct=0, avg_losing_day_pct=0,
best_day_pct=0, worst_day_pct=0, starting_value=0, ending_value=0,
weights=weights, rebalance_count=0,
)
# ---------------------------------------------------------------------------
# Strategy builders
# ---------------------------------------------------------------------------
def build_strategy(name: str, tickers: list[str], **kwargs) -> StrategyConfig:
"""Build a strategy config from a named preset."""
if name == "buy-and-hold":
weights = {tickers[0]: 1.0} if len(tickers) == 1 else {t: 1.0 / len(tickers) for t in tickers}
return StrategyConfig(name=name, tickers=tickers, weights=weights, rebalance_frequency="never")
elif name == "equal-weight":
weights = {t: 1.0 / len(tickers) for t in tickers}
return StrategyConfig(name=name, tickers=tickers, weights=weights, rebalance_frequency="quarterly")
elif name == "risk-parity":
# Inverse-volatility weighting — computed from data in the simulation
# Start equal, will be dynamically adjusted if data allows
weights = {t: 1.0 / len(tickers) for t in tickers}
return StrategyConfig(name=name, tickers=tickers, weights=weights, rebalance_frequency="quarterly")
elif name == "momentum":
weights = {t: 1.0 / len(tickers) for t in tickers}
top_n = kwargs.get("top", min(3, len(tickers)))
return StrategyConfig(
name=name, tickers=tickers, weights=weights,
rebalance_frequency="monthly", momentum_top_n=top_n,
)
elif name == "all-weather":
tickers_aw = list(ALL_WEATHER.keys())
return StrategyConfig(
name=name, tickers=tickers_aw, weights=dict(ALL_WEATHER),
rebalance_frequency="quarterly",
)
elif name == "custom":
weights = kwargs.get("weights", {t: 1.0 / len(tickers) for t in tickers})
freq = kwargs.get("rebalance_frequency", "quarterly")
threshold = kwargs.get("rebalance_threshold_pct", 5.0)
stop_loss = kwargs.get("stop_loss_pct", None)
return StrategyConfig(
name=name, tickers=tickers, weights=weights,
rebalance_frequency=freq, rebalance_threshold_pct=threshold,
stop_loss_pct=stop_loss,
)
else:
# Default to equal-weight
weights = {t: 1.0 / len(tickers) for t in tickers}
return StrategyConfig(name=name, tickers=tickers, weights=weights, rebalance_frequency="quarterly")
def load_strategy_yaml(path: str) -> StrategyConfig:
"""Load strategy from a YAML file (autoany artifact format)."""
with open(path) as f:
if _yaml:
data = _yaml.safe_load(f)
else:
# Minimal YAML-like parser for simple configs
data = _parse_simple_yaml(f.read())
return StrategyConfig(
name=data.get("name", "custom"),
tickers=list(data.get("weights", {}).keys()) or data.get("tickers", []),
weights=data.get("weights", {}),
rebalance_frequency=data.get("rebalance", {}).get("frequency", "quarterly")
if isinstance(data.get("rebalance"), dict) else data.get("rebalance_frequency", "quarterly"),
rebalance_threshold_pct=float(data.get("rebalance", {}).get("threshold_pct", 5.0))
if isinstance(data.get("rebalance"), dict) else float(data.get("rebalance_threshold_pct", 5.0)),
stop_loss_pct=data.get("stop_loss_pct"),
momentum_lookback=int(data.get("momentum_lookback", 252)),
momentum_top_n=int(data.get("momentum_top_n", 3)),
)
def _parse_simple_yaml(text: str) -> dict:
"""Minimal YAML parser for simple key-value and nested dict configs."""
result = {}
current_key = None
current_dict = None
for line in text.split("\n"):
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip())
if indent == 0 and ":" in stripped:
key, _, val = stripped.partition(":")
key = key.strip()
val = val.strip()
if val:
# Try to parse as number
try:
result[key] = float(val)
if result[key] == int(result[key]):
result[key] = int(result[key])
except ValueError:
result[key] = val
current_key = None
current_dict = None
else:
current_key = key
current_dict = {}
result[key] = current_dict
elif indent > 0 and current_dict is not None and ":" in stripped:
key, _, val = stripped.partition(":")
key = key.strip()
val = val.strip()
try:
current_dict[key] = float(val)
if current_dict[key] == int(current_dict[key]):
current_dict[key] = int(current_dict[key])
except ValueError:
current_dict[key] = val
return result
# ---------------------------------------------------------------------------
# Risk-parity weight computation
# ---------------------------------------------------------------------------
def compute_risk_parity_weights(prices: dict[str, list[float]], lookback: int = 60) -> dict[str, float]:
"""Compute inverse-volatility weights from recent price data."""
vols = {}
for ticker, price_list in prices.items():
if len(price_list) < lookback + 1:
vols[ticker] = 1.0
continue
recent = price_list[-lookback:]
returns = [(recent[i] / recent[i-1]) - 1 for i in range(1, len(recent))]
mean_r = sum(returns) / len(returns)
var = sum((r - mean_r) ** 2 for r in returns) / (len(returns) - 1)
vols[ticker] = math.sqrt(var) if var > 0 else 0.001
inv_vols = {t: 1.0 / v for t, v in vols.items()}
total = sum(inv_vols.values())
return {t: v / total for t, v in inv_vols.items()}
# ---------------------------------------------------------------------------
# Run backtest
# ---------------------------------------------------------------------------
def run_backtest(
config: StrategyConfig,
period: str = "10y",
benchmark_ticker: str | None = None,
) -> dict:
"""Run a full backtest and return results dict."""
# Determine all tickers needed
all_tickers = list(set(config.tickers + ([benchmark_ticker] if benchmark_ticker else [])))
# Fetch data
price_data = fetch_prices(all_tickers, period)
dates, prices = align_prices(price_data)
if not dates:
return {"error": "No price data available for the requested tickers/period"}
# For risk-parity, compute weights from data
if config.name == "risk-parity":
rp_weights = compute_risk_parity_weights(
{t: prices[t] for t in config.tickers if t in prices}
)
config.weights = rp_weights
# Run main strategy
daily_returns, portfolio_values, rebalance_count = compute_portfolio_returns(
dates, prices, config,
)
metrics = compute_metrics(
config.name, config.tickers, config.weights, period,
dates, daily_returns, portfolio_values, rebalance_count,
)
result = {"strategy": asdict(metrics)}
# Run benchmark if requested
if benchmark_ticker and benchmark_ticker in prices:
bench_config = build_strategy("buy-and-hold", [benchmark_ticker])
bench_returns, bench_values, _ = compute_portfolio_returns(dates, prices, bench_config)
bench_metrics = compute_metrics(
f"benchmark ({benchmark_ticker})", [benchmark_ticker],
{benchmark_ticker: 1.0}, period,
dates, bench_returns, bench_values, 0,
)
result["benchmark"] = asdict(bench_metrics)
# Alpha and information ratio
if len(daily_returns) == len(bench_returns) and len(daily_returns) > 1:
excess = [s - b for s, b in zip(daily_returns, bench_returns)]
mean_excess = sum(excess) / len(excess)
var_excess = sum((e - mean_excess) ** 2 for e in excess) / (len(excess) - 1)
tracking_error = math.sqrt(var_excess) * math.sqrt(TRADING_DAYS_PER_YEAR)
info_ratio = (mean_excess * TRADING_DAYS_PER_YEAR) / tracking_error if tracking_error > 0 else 0
result["comparison"] = {
"alpha_pct": round((metrics.cagr_pct - bench_metrics.cagr_pct), 2),
"tracking_error_pct": round(tracking_error * 100, 2),
"information_ratio": round(info_ratio, 3),
"excess_sharpe": round(metrics.sharpe_ratio - bench_metrics.sharpe_ratio, 3),
}
return result
# ---------------------------------------------------------------------------
# Display
# ---------------------------------------------------------------------------
def print_metrics(metrics: dict, title: str = ""):
"""Pretty-print backtest metrics."""
m = metrics
print(f"\n{'='*72}")
print(f" BACKTEST: {title or m['strategy']}")
print(f"{'='*72}")
print(f" Period: {m['start_date']} → {m['end_date']} ({m['years']:.1f} years, {m['trading_days']} days)")
print(f" Tickers: {', '.join(m['tickers'])}")
# Weights
w_str = ", ".join(f"{t}: {w*100:.1f}%" for t, w in m['weights'].items() if w > 0)
print(f" Weights: {w_str}")
print()
# Returns
print(f" ── RETURNS ───────────────────────────────────────────────────")
print(f" Total Return: {m['total_return_pct']:>8.1f}%")
print(f" CAGR: {m['cagr_pct']:>8.2f}%")
print(f" Starting Value: ${m['starting_value']:>10,.2f}")
print(f" Ending Value: ${m['ending_value']:>10,.2f}")
print()
# Risk
print(f" ── RISK ──────────────────────────────────────────────────────")
print(f" Volatility (ann): {m['annualized_volatility_pct']:>8.2f}%")
print(f" Max Drawdown: {m['max_drawdown_pct']:>8.2f}%")
print(f" Start: {m['max_drawdown_start']}")
print(f" End: {m['max_drawdown_end']}")
rec = m['max_drawdown_recovery'] or "NOT RECOVERED"
print(f" Recovery: {rec}")
print()
# Risk-adjusted
print(f" ── RISK-ADJUSTED ─────────────────────────────────────────────")
print(f" Sharpe Ratio: {m['sharpe_ratio']:>8.3f}")
print(f" Sortino Ratio: {m['sortino_ratio']:>8.3f}")
print(f" Calmar Ratio: {m['calmar_ratio']:>8.3f}")
print()
# Trading stats
print(f" ── TRADING STATS ─────────────────────────────────────────────")
print(f" Win Rate: {m['win_rate_pct']:>8.1f}%")
print(f" Avg Daily Return: {m['avg_daily_return_pct']:>8.4f}%")
print(f" Best Day: {m['best_day_pct']:>8.2f}%")
print(f" Worst Day: {m['worst_day_pct']:>8.2f}%")
print(f" Rebalances: {m['rebalance_count']:>8d}")
def print_comparison(result: dict):
"""Print strategy vs benchmark comparison."""
if "comparison" not in result:
return
c = result["comparison"]
print(f"\n ── VS BENCHMARK ──────────────────────────────────────────────")
print(f" Alpha: {c['alpha_pct']:>+8.2f}%")
print(f" Tracking Error: {c['tracking_error_pct']:>8.2f}%")
print(f" Information Ratio: {c['information_ratio']:>8.3f}")
print(f" Excess Sharpe: {c['excess_sharpe']:>+8.3f}")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Strategy backtesting engine — investment-management skill Mode 4",
)
parser.add_argument("--strategy", default="buy-and-hold",
choices=["buy-and-hold", "equal-weight", "risk-parity",
"momentum", "all-weather", "custom"],
help="Built-in strategy preset")
parser.add_argument("--strategy-file", help="Load strategy from YAML file (autoany artifact)")
parser.add_argument("--tickers", help="Comma-separated tickers (e.g. SPY,QQQ,BND)")
parser.add_argument("--period", default="10y", help="Lookback period (e.g. 5y, 10y, 3m)")
parser.add_argument("--benchmark", help="Benchmark ticker for comparison (e.g. SPY)")
parser.add_argument("--top", type=int, default=3, help="Top N for momentum strategy")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--egri", action="store_true",
help="Output as EGRI Outcome (for autoany evaluator)")
args = parser.parse_args()
# Load strategy
if args.strategy_file:
config = load_strategy_yaml(args.strategy_file)
elif args.strategy == "all-weather":
config = build_strategy("all-weather", [])
else:
if not args.tickers:
print("Error: --tickers required for this strategy", file=sys.stderr)
raise SystemExit(1)
tickers = [t.strip().upper() for t in args.tickers.split(",")]
config = build_strategy(args.strategy, tickers, top=args.top)
# Run backtest
result = run_backtest(config, args.period, args.benchmark)
if "error" in result:
print(f"Error: {result['error']}", file=sys.stderr)
raise SystemExit(1)
# Output
if args.egri:
# EGRI evaluator output — structured Outcome for autoany
metrics = BacktestMetrics(**result["strategy"])
outcome = metrics.to_egri_outcome()
print(json.dumps(outcome, indent=2))
elif args.json:
print(json.dumps(result, indent=2))
else:
print_metrics(result["strategy"], config.name)
if "benchmark" in result:
print_metrics(result["benchmark"])
print_comparison(result)
print()
# Save results
results_dir = Path.home() / ".investment-management" / "backtests"
results_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.now().strftime("%Y-%m-%d_%H%M%S")
result_file = results_dir / f"backtest-{config.name}-{ts}.json"
with open(result_file, "w") as f:
json.dump(result, f, indent=2)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
EGRI Evaluator Wrapper — bridges backtest.py to autoany.
Thin wrapper that runs a backtest and outputs a structured EGRI Outcome.
Used as the evaluator in strategy-optimization and screen-evolution
problem specs. Validates constraints and formats results for the
autoany EGRI loop.
Usage:
python3 eval_backtest.py --strategy-file strategy.yaml --period 10y
python3 eval_backtest.py --strategy-file strategy.yaml --period 10y \
--max-drawdown -15 --min-sharpe 0.5
# With constraint overrides from problem spec:
python3 eval_backtest.py --strategy-file strategy.yaml --period 10y \
--constraints "max_drawdown_pct > -15, max_position_weight <= 0.25"
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from pathlib import Path
def run_backtest(strategy_file: str, period: str) -> dict:
"""Run backtest.py and parse the EGRI output."""
script = Path(__file__).parent / "backtest.py"
cmd = [
sys.executable, str(script),
"--strategy-file", strategy_file,
"--period", period,
"--egri",
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
return {
"score": 0,
"constraints_passed": False,
"violations": [f"backtest failed: {result.stderr.strip()[:200]}"],
"metrics": {},
}
return json.loads(result.stdout)
def check_constraints(outcome: dict, constraints: list[str]) -> dict:
"""Apply additional constraints to the EGRI outcome."""
metrics = outcome.get("metrics", {})
violations = list(outcome.get("violations", []))
passed = outcome.get("constraints_passed", True)
for constraint in constraints:
constraint = constraint.strip()
if not constraint:
continue
# Parse constraint: "metric_name op value"
for op in ["<=", ">=", "<", ">", "=="]:
if op in constraint:
parts = constraint.split(op)
if len(parts) == 2:
metric_name = parts[0].strip()
try:
threshold = float(parts[1].strip())
except ValueError:
continue
actual = metrics.get(metric_name)
if actual is None:
continue
violated = False
if op == "<=" and actual > threshold:
violated = True
elif op == ">=" and actual < threshold:
violated = True
elif op == "<" and actual >= threshold:
violated = True
elif op == ">" and actual <= threshold:
violated = True
elif op == "==" and abs(actual - threshold) > 0.001:
violated = True
if violated:
violations.append(f"{metric_name}={actual} violates {constraint}")
passed = False
break
outcome["constraints_passed"] = passed
outcome["violations"] = violations
return outcome
def main():
parser = argparse.ArgumentParser(
description="EGRI evaluator wrapper for strategy backtesting",
)
parser.add_argument("--strategy-file", required=True, help="Path to strategy YAML artifact")
parser.add_argument("--period", default="10y", help="Backtest period")
parser.add_argument("--constraints", default="", help="Comma-separated constraint expressions")
parser.add_argument("--max-drawdown", type=float, help="Max drawdown constraint (e.g. -15)")
parser.add_argument("--min-sharpe", type=float, help="Min Sharpe ratio constraint")
args = parser.parse_args()
# Run backtest
outcome = run_backtest(args.strategy_file, args.period)
# Build constraint list
constraints = [c.strip() for c in args.constraints.split(",") if c.strip()]
if args.max_drawdown is not None:
constraints.append(f"max_drawdown_pct > {args.max_drawdown}")
if args.min_sharpe is not None:
constraints.append(f"sharpe_ratio >= {args.min_sharpe}")
# Apply constraints
if constraints:
outcome = check_constraints(outcome, constraints)
print(json.dumps(outcome, indent=2))
if __name__ == "__main__":
main()
# tradingview-bridge — service-local control policy (v0.2.0 — PR 2).
# Composes with the investment-management skill's .control/policy.yaml and
# the umbrella workspace .control/policy.yaml (broomva/workspace).
service: tradingview-bridge
version: 0.2.0
canonical-adr: https://github.com/broomva/workspace/blob/main/docs/specs/2026-05-22-broker-selection-cross-asset.html
linear-ticket: https://github.com/broomva/workspace/blob/main/tasks/bro-167-cross-asset-trading-platform.md
gates:
paper-only-mode:
enforce: startup
blocker: true
description: >
Service exits with non-zero status at startup if
TVBRIDGE_TRADING_MODE != "paper".
test-coverage:
- tests/test_webhook_smoke.py::test_paper_only_assertion_blocks_live
source-ip-allowlist:
enforce: runtime
blocker: true
description: >
Every POST /webhook request must originate from an IP in
TVBRIDGE_TV_ALLOWED_IPS (TradingView's published webhook source IPs).
test-coverage:
- tests/test_webhook_smoke.py::test_webhook_wrong_ip
shared-secret-constant-time:
enforce: runtime
blocker: true
description: >
The `secret` field in the alert body must match
TVBRIDGE_TV_WEBHOOK_SECRET under hmac.compare_digest.
test-coverage:
- tests/test_auth.py::test_require_valid_secret_rejects
- tests/test_webhook_smoke.py::test_webhook_wrong_secret
- tests/test_webhook_smoke.py::test_webhook_missing_secret
auth-before-schema-validation:
enforce: runtime
blocker: false
description: >
Auth (secret check) runs BEFORE pydantic schema validation.
test-coverage:
- tests/test_webhook_smoke.py::test_webhook_validation_error_after_auth
# --- PR 2 newly-active gates ---
webhook-rate-limit:
enforce: runtime
blocker: true
description: >
Per-IP rate limit (default 60 req/min) via TokenBucketLimiter (60s
sliding window). Excess requests get 429 without consuming the
secret/schema budget.
test-coverage:
- tests/test_ratelimit.py
- tests/test_webhook_smoke.py::test_webhook_429_on_rate_limit
alert-idempotency:
enforce: runtime
blocker: false
description: >
SQLite store keyed on alert_id; duplicate alerts get 200 with
status=duplicate + same order_id from the original dispatch. Prevents
double-fire on TradingView retries.
test-coverage:
- tests/test_idempotency.py
- tests/test_webhook_smoke.py::test_webhook_duplicate_alert_returns_duplicate
broker-mode-mock-default:
enforce: startup
blocker: false
description: >
TVBRIDGE_BROKER_MODE defaults to "mock" — no broker contact in CI or
local dev without explicit opt-in. Operator flips to "real-paper"
explicitly + provides per-broker credentials.
test-coverage:
- tests/test_clients_interface.py::test_real_client_mock_mode_places_order
real-paper-requires-creds:
enforce: runtime
blocker: true
description: >
When TVBRIDGE_BROKER_MODE=real-paper, each broker client checks for
its required env vars and raises NotConfiguredError if missing. The
dispatcher translates this into a `rejected` DispatchResult; no
cryptic SDK exceptions leak to the response.
test-coverage:
- tests/test_clients_interface.py::test_real_client_real_paper_without_creds_raises
# --- Autonomous operator gates (BRO-1247) ---
operator-dogfood-precondition:
enforce: runtime
blocker: true
description: >
The dogfood-as-precondition interlock. The operator manages positions
ONLY when its self-dogfood canary passed this tick AND no hard halt is
active (OperatorState.position_management_allowed). P11 crystallized
into runtime: never manage money on a pipeline you cannot confirm works.
test-coverage:
- tests/test_operator_state.py::test_fresh_state_blocks_position_management
- tests/test_operator_loop.py::test_failing_canary_trips_interlock
- tests/test_operator_loop.py::test_passing_tick_allows_management
operator-hard-halt-requires-reset:
enforce: runtime
blocker: true
description: >
After `halt_after_failures` consecutive canary failures the operator
enters a STICKY hard halt. A subsequently-passing canary does NOT clear
it — only an explicit `operate reset` (operator-acknowledged recovery)
does. Prevents an autonomous loop from silently resuming after an
intermittent pipeline fault.
test-coverage:
- tests/test_operator_state.py::test_hard_halt_survives_a_passing_canary
- tests/test_operator_loop.py::test_hard_halt_blocks_until_reset
operator-paper-only:
enforce: startup
blocker: true
description: >
The operator CLI runs assert_paper_only() before constructing its loop —
it refuses to operate outside paper mode, identical to the bridge's
startup guard.
test-coverage:
- tests/test_operator_cli.py::test_cli_refuses_live_mode
operator-canary-excluded-from-positions:
enforce: runtime
blocker: true
description: >
Canary orders (strategy_name prefixed `__canary__`) exercise the full
dispatch pipeline but are filtered at the single order-ledger write point,
so they never pollute net positions. The self-dogfood cannot move the
book it is meant to verify.
test-coverage:
- tests/test_orders.py::test_canary_orders_filtered
operator-canary-read-only-in-real-venue:
enforce: runtime
blocker: true
description: >
CRITICAL. In any real-venue broker mode (tradingview-paper, real-paper)
the self-dogfood canary is READ-ONLY — it verifies the venue via
health_check(), and NEVER places an order. The place-an-order canary is
used only in `mock` mode (harmless MockClient). Without this, running the
operator against a real account would drop a canary order every tick.
Proven live: 2 operator ticks in tradingview-paper mode placed 0 orders;
the canary passed via venue_health.
test-coverage:
- tests/test_operator_canary.py::test_read_only_canary_never_dispatches
- tests/test_operator_canary.py::test_read_only_canary_fails_on_unhealthy_venue
operator-reconciles-real-book:
enforce: runtime
blocker: false
description: >
In a single real-venue mode the operator reconciles its order ledger
against the broker's actual positions (TradingViewPaperClient.list_positions).
Drift — a position the operator did not place (broker_only) or a believed
position the broker no longer shows (ledger_only) — is surfaced via
operator_position_drift, never silent. Live read quality is subject to the
tab-churn limitation (degrades gracefully to empty); the reconcile logic
is unit-tested.
test-coverage:
- tests/test_operator_positions.py::test_reconcile_matched_and_broker_only_drift
- tests/test_operator_loop.py::test_medium_tick_reconciles_against_broker
operator-position-cap-no-silent-exceed:
enforce: runtime
blocker: false
description: >
When open positions exceed max_open_positions the operator logs
operator_position_cap_exceeded (P11 no-silent-caps) rather than silently
tolerating it. Non-blocking in paper/mock; promotion to blocker tracked
for the real-paper rebalance follow-up.
test-coverage:
- tests/test_operator_loop.py::test_position_cap_does_not_crash
# --- TradingView-Paper adapter gates (BRO-1250) ---
tradingview-paper-is-simulated:
enforce: by-design
blocker: true
description: >
TVBRIDGE_BROKER_MODE=tradingview-paper drives TradingView's built-in
Paper Trading simulator only — a simulated broker with no real capital.
It composes with the bridge's paper-only startup assertion. There is no
code path from this mode to a live-money broker.
test-coverage:
- tests/test_clients_tradingview_paper.py::test_broker_name
tradingview-paper-write-actions-bounded:
enforce: runtime
blocker: true
description: >
Full order lifecycle: market buy/sell, close (per symbol), flatten (all),
cancel. Each maps to an explicit UI flow; a missing control raises and the
dispatcher renders a clean `rejected` — never a silent partial action.
A double-submit guard ensures a quick button that places immediately is
never followed by a second ticket-submit click. limit/stop + modify are
deferred.
test-coverage:
- tests/test_clients_tradingview_paper.py::test_no_double_submit_when_quick_button_places_immediately
- tests/test_clients_tradingview_paper.py::test_close_action_via_place_order
- tests/test_clients_tradingview_paper.py::test_flatten_action_via_place_order
- tests/test_clients_tradingview_paper.py::test_close_action_no_position_becomes_rejected
tradingview-paper-list-orders-no-phantom:
enforce: runtime
blocker: false
description: >
list_orders() is gated on the `Orders N` tab count and reads working
orders from row cells — never from the order-entry ticket submit button
(which also reads as `Buy N SYM @ price`). Caught live: a phantom order
was reported when 0 were working. Regression-tested.
test-coverage:
- tests/test_clients_tradingview_paper.py::test_list_orders_ignores_ticket_button_when_count_zero
tradingview-paper-browser-failure-is-rejected:
enforce: runtime
blocker: true
description: >
If Interceptor / the browser session is unreachable, the client raises
NotConfiguredError → `rejected` (not a crash, not a blind retry). The
account is never touched on an unverified control surface.
test-coverage:
- tests/test_clients_tradingview_paper.py::test_browser_error_becomes_not_configured
- tests/test_clients_tradingview_paper.py::test_missing_side_control_raises_not_configured
tradingview-paper-selectors-by-name:
enforce: runtime
blocker: false
description: >
The client never hardcodes per-session element refs (e21, …). It resolves
controls by semantic role+name each call (find_ref), so a TradingView DOM
reshuffle degrades to a clean `rejected` with a re-pin hint rather than
clicking the wrong element.
test-coverage:
- tests/test_interceptor_driver.py
# Still deferred — promoted to follow-up for traceability.
deferred:
per-broker-position-cap:
target-pr: 3
description: >
S9 candidate — per-broker drawdown + concentration caps. Requires
a broker-positions reader (REST poll) which depends on real-paper
wiring (PR 2b).
formulario-4-reminder:
target-pr: 3
description: >
S7 candidate — any IBKR outbound capital instruction above threshold
blocks until Formulario 4 (Banco República) is logged. Depends on
capital-transfer detection, also PR 2b.
cfd-broker-blocked:
target-pr: 4
description: >
S8 candidate — hard-block any executor call to a CFD broker
endpoint. Not relevant in PR 2 because the dispatcher only routes
to IBKR/Kraken/Polymarket (no CFD path exists).
bookkeeping-journal-required:
target-pr: 3
description: >
Currently graceful no-op when CLI not found (this allows tests + CI
to pass without the workspace bookkeeping CLI). PR 3 promotes to
blocker when running in production deployment.
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg-info/
*.egg
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/
.venv/
venv/
env/
.env
.env.local
dist/
build/
*.log
.DS_Store
"""TradingView Pine Script alert webhook bridge for the broomva trading stack.
PR 1 — receiver only, no execution. Paper-only enforced at startup.
"""
__version__ = "0.1.0"
Related skills
FAQ
Which platforms can it execute trades on?
It integrates with Alpaca, Coinbase, and Polymarket, plus agent-browser for Colombian platforms.
What analysis does the factor mode do?
It decomposes portfolio returns into factors, and the skill also supports screening, deep research, backtests, and risk metrics.