Comprehensive Portfolio Management Example¶
Complete end-to-end demonstration of all toolkit features from CSV ingestion through backtesting with advanced controls.
Table of Contents¶
- Basic Workflow - Simple start-to-finish example
- Advanced Workflow - All features combined
- Production Workflow - Multi-strategy robust setup
1. Basic Workflow¶
Scenario: Build and backtest a simple equal-weight portfolio of GBP-denominated LSE ETFs
Step 1: Prepare Raw Data¶
# Scan Stooq directory and create master instrument database
python scripts/prepare_tradeable_data.py \
--data-dir data/stooq \
--tradeable-dir tradeable_instruments \
--metadata-output data/metadata/stooq_index.csv \
--match-report data/metadata/tradeable_matches.csv \
--prices-output data/processed/tradeable_prices \
--incremental
First run: ~3-5 minutes (indexes 70k+ files)
Subsequent runs: \<5 seconds with --incremental flag
Outputs:
data/metadata/stooq_index.csv- all discovered instrumentsdata/metadata/tradeable_matches.csv- matched tradeable instruments with quality metricsdata/processed/tradeable_prices/- consolidated price files
Step 2: Select High-Quality Assets¶
# Filter to LSE GBP assets with 2+ years of clean data
python scripts/select_assets.py \
--match-report data/metadata/tradeable_matches.csv \
--output data/processed/selected_assets.csv \
--min-history-days 730 \
--markets "LSE,GBR-LSE" \
--currencies "GBP" \
--data-status "ok"
Filters applied:
- Minimum 730 days (2 years) of history
- LSE market only
- GBP currency only
- Clean data quality
Output: selected_assets.csv (~100-200 assets)
Step 3: Classify Assets¶
# Automatically classify assets by type and geography
python scripts/classify_assets.py \
--input data/processed/selected_assets.csv \
--output data/processed/classified_assets.csv \
--summary
Classifications added:
- Asset class (equity, bond, commodity, real_estate)
- Sub-class (growth, value, dividend, etc.)
- Geography (north_america, europe, asia)
- Confidence score
Output: classified_assets.csv with classification columns
Step 4: Calculate Returns¶
# Compute monthly simple returns
python scripts/calculate_returns.py \
--prices data/processed/tradeable_prices \
--assets data/processed/classified_assets.csv \
--output data/processed/returns.csv \
--method simple \
--frequency monthly \
--handle-missing forward_fill \
--max-forward-fill-days 5
Configuration:
- Simple returns: (P₁ - P₀) / P₀
- Monthly frequency
- Forward-fill missing data (max 5 days)
Output: returns.csv - returns matrix (dates × assets)
Step 5: Construct Portfolio¶
# Build equal-weight portfolio with position limits
python scripts/construct_portfolio.py \
--returns data/processed/returns.csv \
--strategy equal_weight \
--max-weight 0.10 \
--output outputs/basic/portfolio_weights.csv
Strategy: Equal weight (1/N)
Constraint: Max 10% per asset
Output: portfolio_weights.csv (~50-100 assets with weights)
Step 6: Backtest¶
# Historical simulation 2020-2023 with quarterly rebalancing
python scripts/run_backtest.py equal_weight \
--returns data/processed/returns.csv \
--start-date 2020-01-01 \
--end-date 2023-12-31 \
--initial-capital 100000 \
--rebalance-frequency quarterly \
--commission 0.001 \
--slippage 0.0005 \
--output-dir outputs/basic/backtest \
--save-trades \
--visualize
Backtest config:
- £100,000 initial capital
- Quarterly rebalancing
- 0.1% commission per trade
- 0.05% slippage (market impact)
Outputs:
outputs/basic/backtest/
├── config.json # Backtest configuration
├── metrics.json # Performance summary
├── equity_curve.csv # Portfolio value over time
├── trades.csv # Detailed trade log
├── viz_drawdown.csv # Drawdown series
├── viz_rolling_metrics.csv # Rolling Sharpe/volatility
└── viz_transaction_costs.csv # Cost analysis
Expected Results:
- Total Return: 15-30%
- Sharpe Ratio: 0.8-1.5
- Max Drawdown: 15-25%
- Annual Volatility: 10-15%
2. Advanced Workflow¶
Scenario: Multi-factor momentum + low-vol strategy with turnover control and PIT eligibility
This example demonstrates ALL advanced features in one workflow.
Step 1: Universe-Driven Data Pipeline¶
Instead of running 4 separate scripts, use the universe management CLI:
# Define universe in config/universes.yaml (see below)
# Then load it with ONE command:
python scripts/manage_universes.py load satellite_factor \
--output-dir outputs/advanced/satellite_factor
Universe definition (config/universes.yaml):
satellite_factor:
description: "Factor-tilted growth/value/momentum strategy"
filter_criteria:
data_status: ["ok", "warning"]
min_history_days: 600
min_price_rows: 600
markets: ["LSE", "NYSE", "NSQ"]
currencies: ["GBP", "USD"]
categories:
- "lse etfs/1"
- "lse etfs/2"
- "nasdaq stocks/2"
- "nyse stocks/1"
classification_requirements:
asset_class: ["equity"]
sub_class: ["growth", "value", "momentum", "small_cap"]
return_config:
method: "simple"
frequency: "monthly"
handle_missing: "forward_fill"
max_forward_fill_days: 5
min_coverage: 0.80
constraints:
min_assets: 30
max_assets: 100
preselection:
method: "combined" # Momentum + Low-vol
top_k: 40
lookback: 252
skip: 1
momentum_weight: 0.65
low_vol_weight: 0.35
membership_policy:
enabled: true
buffer_rank: 10
min_holding_periods: 3
max_turnover: 0.30
max_new_assets: 8
max_removed_assets: 5
This ONE command automatically:
- ✅ Selects assets (filters by market, currency, quality)
- ✅ Classifies assets (equity only, factor sub-classes)
- ✅ Calculates returns (monthly, with forward-fill)
- ✅ Validates configuration
- ✅ Outputs:
satellite_factor_returns.csv
Step 2: Construct Risk Parity Portfolio¶
# Risk parity with asset class constraints
python scripts/construct_portfolio.py \
--returns outputs/advanced/satellite_factor/satellite_factor_returns.csv \
--classifications outputs/advanced/satellite_factor/satellite_factor_classifications.csv \
--strategy risk_parity \
--max-weight 0.15 \
--min-weight 0.01 \
--max-equity 0.90 \
--output outputs/advanced/portfolio_riskparity.csv
Constraints:
- Max 15% per asset
- Min 1% per asset (avoid dust positions)
- Max 90% total equity (leave room for defensive)
Step 3: Advanced Backtest with ALL Features¶
python scripts/run_backtest.py risk_parity \
--returns outputs/advanced/satellite_factor/satellite_factor_returns.csv \
--classifications outputs/advanced/satellite_factor/satellite_factor_classifications.csv \
--start-date 2018-01-01 \
--end-date 2023-12-31 \
--initial-capital 250000 \
--rebalance-frequency monthly \
--commission 0.0015 \
--slippage 0.0008 \
--min-commission 10.0 \
\
--preselect-method combined \
--preselect-top-k 40 \
--preselect-lookback 252 \
--preselect-skip 1 \
--preselect-momentum-weight 0.65 \
--preselect-low-vol-weight 0.35 \
\
--membership-enabled \
--membership-buffer-rank 10 \
--membership-min-hold 3 \
--membership-max-turnover 0.30 \
--membership-max-new 8 \
--membership-max-removed 5 \
\
--use-pit-eligibility \
--min-history-days 252 \
--min-price-rows 240 \
\
--enable-cache \
--cache-dir .cache/advanced_backtest \
\
--output-dir outputs/advanced/backtest \
--save-trades \
--visualize
What this does:
1. Preselection (Factor-Based Universe Reduction):
- Combines momentum (65%) and low-volatility (35%)
- Selects top 40 assets before optimization
- 252-day lookback, skips most recent day
- Reduces 100 assets → 40 assets = 1000× faster optimization
2. Portfolio Construction:
- Risk parity strategy (equal risk contribution)
- Constraints: 1-15% per asset, max 90% equity
- Operates on 40 preselected assets
3. Membership Policy (Turnover Control):
- Buffer rank 10: protects holdings ranked up to 50th (40 + 10 buffer)
- Min 3 holding periods (3 months minimum hold)
- Max 30% turnover per rebalance
- Max 8 new assets, max 5 removals per month
- Result: Reduces transaction costs by 40-60%
4. Point-in-Time Eligibility (Bias Prevention):
- Requires 252 days of history at rebalance date
- Requires 240 price observations
- Automatically filters out recently-listed assets
- Prevents survivorship bias
5. Statistics Caching (Performance):
- Caches covariance matrices between rebalances
- 35x speedup on cache hits
- 90% hit rate with monthly rebalancing
- Particularly important for 40-asset covariance
6. Transaction Costs:
- 0.15% commission (higher than basic example)
- 0.08% slippage (market impact)
- £10 minimum commission per trade
- Realistic for retail trading
7. Comprehensive Outputs:
- Equity curve, trades, metrics
- Drawdown analysis
- Rolling Sharpe ratio
- Transaction cost breakdown
- Visualization-ready CSVs
Expected Results:
- Total Return: 25-40% (factor tilt boost)
- Sharpe Ratio: 1.0-1.8
- Max Drawdown: 20-30%
- Turnover: 15-20% per month (vs. 50%+ without membership policy)
- Transaction Costs: 0.5-1.0% per year (vs. 1.5-2.5% without turnover control)
Step 4: Compare Strategies¶
# Compare equal-weight, risk parity, and mean-variance
python scripts/construct_portfolio.py \
--returns outputs/advanced/satellite_factor/satellite_factor_returns.csv \
--compare \
--output outputs/advanced/strategy_comparison.csv
Output: CSV with strategies as columns, weights for each asset
Step 5: Batch Backtest Multiple Configurations¶
# Use Python API for batch testing
from pathlib import Path
from datetime import date
from decimal import Decimal
import pandas as pd
from portfolio_management.backtesting import (
BacktestConfig, BacktestEngine, RebalanceFrequency
)
from portfolio_management.portfolio import (
EqualWeightStrategy, RiskParityStrategy, MeanVarianceStrategy,
Preselection, PreselectionConfig, PreselectionMethod,
MembershipPolicy
)
# Load data
returns = pd.read_csv("outputs/advanced/satellite_factor/returns.csv",
index_col=0, parse_dates=True)
prices = pd.read_csv("outputs/advanced/satellite_factor/prices.csv",
index_col=0, parse_dates=True)
# Define configurations
configs = [
{"strategy": "equal_weight", "preselect": None, "membership": None},
{"strategy": "equal_weight", "preselect": "momentum", "membership": None},
{"strategy": "equal_weight", "preselect": "combined", "membership": True},
{"strategy": "risk_parity", "preselect": None, "membership": None},
{"strategy": "risk_parity", "preselect": "combined", "membership": True},
]
results = []
for config in configs:
# Setup preselection
preselection = None
if config["preselect"]:
method = PreselectionMethod.MOMENTUM if config["preselect"] == "momentum" else PreselectionMethod.COMBINED
presel_config = PreselectionConfig(method=method, top_k=40, lookback=252)
preselection = Preselection(presel_config)
# Setup membership
membership = None
if config["membership"]:
membership = MembershipPolicy(
buffer_rank=10, min_holding_periods=3,
max_turnover=0.30, max_new_assets=8
)
# Setup strategy
if config["strategy"] == "equal_weight":
strategy = EqualWeightStrategy()
elif config["strategy"] == "risk_parity":
strategy = RiskParityStrategy()
# Run backtest
backtest_config = BacktestConfig(
start_date=date(2018, 1, 1),
end_date=date(2023, 12, 31),
initial_capital=Decimal("250000"),
rebalance_frequency=RebalanceFrequency.MONTHLY,
)
engine = BacktestEngine(
config=backtest_config,
strategy=strategy,
prices=prices,
returns=returns,
preselection=preselection,
membership_policy=membership,
)
equity_curve, metrics, _ = engine.run()
results.append({
"name": f"{config['strategy']}_presel{config['preselect']}_memb{config['membership']}",
"sharpe": metrics["sharpe_ratio"],
"return": metrics["total_return"],
"drawdown": metrics["max_drawdown"],
})
# Save comparison
pd.DataFrame(results).to_csv("outputs/advanced/batch_comparison.csv", index=False)
3. Production Workflow¶
Scenario: Robust multi-strategy portfolio construction for production deployment
Step 1: Define Multiple Universes¶
config/universes_production.yaml:
universes:
core_defensive:
description: "Core defensive allocation (bonds + low-vol equities)"
filter_criteria:
data_status: ["ok"]
min_history_days: 1260 # 5 years
markets: ["LSE"]
currencies: ["GBP"]
classification_requirements:
asset_class: ["fixed_income", "equity"]
sub_class: ["government", "corporate", "dividend", "low_volatility"]
return_config:
method: "simple"
frequency: "monthly"
min_coverage: 0.95
constraints:
min_assets: 20
max_assets: 40
core_growth:
description: "Core growth allocation (diversified equity)"
filter_criteria:
data_status: ["ok"]
min_history_days: 1260
markets: ["LSE", "NYSE", "NSQ"]
currencies: ["GBP", "USD"]
classification_requirements:
asset_class: ["equity"]
geography: ["north_america", "europe", "global"]
return_config:
method: "simple"
frequency: "monthly"
min_coverage: 0.90
constraints:
min_assets: 40
max_assets: 80
preselection:
method: "low_vol"
top_k: 50
lookback: 252
membership_policy:
enabled: true
buffer_rank: 15
min_holding_periods: 6 # 6 months minimum
max_turnover: 0.20 # 20% max
satellite_momentum:
description: "Satellite momentum strategy"
filter_criteria:
data_status: ["ok", "warning"]
min_history_days: 756 # 3 years
markets: ["LSE", "NYSE", "NSQ"]
classification_requirements:
asset_class: ["equity"]
return_config:
method: "log" # Log returns for momentum
frequency: "monthly"
min_coverage: 0.85
constraints:
min_assets: 20
max_assets: 50
preselection:
method: "momentum"
top_k: 30
lookback: 252
skip: 21 # Skip 1 month (12-1 momentum)
membership_policy:
enabled: true
buffer_rank: 5
min_holding_periods: 2
max_turnover: 0.50 # More active for momentum
Step 2: Build All Universes¶
# Defensive universe
python scripts/manage_universes.py load core_defensive \
--universe-file config/universes_production.yaml \
--output-dir outputs/production/core_defensive
# Growth universe
python scripts/manage_universes.py load core_growth \
--universe-file config/universes_production.yaml \
--output-dir outputs/production/core_growth
# Momentum universe
python scripts/manage_universes.py load satellite_momentum \
--universe-file config/universes_production.yaml \
--output-dir outputs/production/satellite_momentum
Step 3: Construct Portfolios for Each Universe¶
# Defensive: Min-volatility mean-variance
python scripts/construct_portfolio.py \
--returns outputs/production/core_defensive/core_defensive_returns.csv \
--classifications outputs/production/core_defensive/core_defensive_classifications.csv \
--strategy mean_variance_min_volatility \
--max-weight 0.20 \
--min-bond 0.40 \
--output outputs/production/portfolio_defensive.csv
# Growth: Risk parity
python scripts/construct_portfolio.py \
--returns outputs/production/core_growth/core_growth_returns.csv \
--strategy risk_parity \
--max-weight 0.15 \
--output outputs/production/portfolio_growth.csv
# Momentum: Equal weight (after momentum preselection)
python scripts/construct_portfolio.py \
--returns outputs/production/satellite_momentum/satellite_momentum_returns.csv \
--strategy equal_weight \
--max-weight 0.10 \
--output outputs/production/portfolio_momentum.csv
Step 4: Backtest Each Strategy¶
# Defensive backtest
python scripts/run_backtest.py mean_variance_min_volatility \
--returns outputs/production/core_defensive/core_defensive_returns.csv \
--start-date 2015-01-01 \
--end-date 2023-12-31 \
--initial-capital 400000 \
--rebalance-frequency quarterly \
--commission 0.001 \
--slippage 0.0005 \
--enable-cache \
--output-dir outputs/production/backtest_defensive
# Growth backtest (with preselection and membership from universe config)
python scripts/run_backtest.py risk_parity \
--universe-file config/universes_production.yaml:core_growth \
--returns outputs/production/core_growth/core_growth_returns.csv \
--start-date 2015-01-01 \
--end-date 2023-12-31 \
--initial-capital 400000 \
--rebalance-frequency monthly \
--commission 0.0012 \
--slippage 0.0006 \
--use-pit-eligibility \
--enable-cache \
--output-dir outputs/production/backtest_growth
# Momentum backtest (with preselection and membership from universe config)
python scripts/run_backtest.py equal_weight \
--universe-file config/universes_production.yaml:satellite_momentum \
--returns outputs/production/satellite_momentum/satellite_momentum_returns.csv \
--start-date 2015-01-01 \
--end-date 2023-12-31 \
--initial-capital 200000 \
--rebalance-frequency monthly \
--commission 0.0015 \
--slippage 0.0008 \
--use-pit-eligibility \
--enable-cache \
--output-dir outputs/production/backtest_momentum
Step 5: Combine Results and Analyze¶
# Aggregate backtest results
import pandas as pd
import json
from pathlib import Path
strategies = ["defensive", "growth", "momentum"]
allocations = [0.40, 0.40, 0.20] # 40/40/20 split
results = []
for strategy in strategies:
metrics_file = Path(f"outputs/production/backtest_{strategy}/metrics.json")
with open(metrics_file) as f:
metrics = json.load(f)
results.append(metrics)
# Calculate blended portfolio metrics
blended_return = sum(r["total_return"] * a for r, a in zip(results, allocations))
blended_volatility = sum(r["annual_volatility"] * a for r, a in zip(results, allocations))
print(f"\n=== Production Portfolio Summary ===")
print(f"Defensive (40%): Return={results[0]['total_return']:.2%}, Sharpe={results[0]['sharpe_ratio']:.2f}")
print(f"Growth (40%): Return={results[1]['total_return']:.2%}, Sharpe={results[1]['sharpe_ratio']:.2f}")
print(f"Momentum (20%): Return={results[2]['total_return']:.2%}, Sharpe={results[2]['sharpe_ratio']:.2f}")
print(f"\nBlended Portfolio: Return={blended_return:.2%}, Volatility={blended_volatility:.2%}")
Step 6: Validation and Reporting¶
# Validate all universes
python scripts/manage_universes.py validate config/universes_production.yaml
# Compare universe configurations
python scripts/manage_universes.py compare \
config/universes_production.yaml:core_defensive \
config/universes_production.yaml:core_growth
# Generate comprehensive report (custom script)
python scripts/generate_production_report.py \
--backtest-dirs outputs/production/backtest_* \
--output outputs/production/final_report.html
4. Visualization & Reporting Workflow¶
Scenario: Generate professional charts and analysis reports from backtest results
Step 1: Run Backtest (Programmatic)¶
from portfolio_management.backtesting import BacktestEngine, BacktestConfig
# Load config
config = BacktestConfig.from_yaml("config/momentum_strategy_config.yaml")
# Run backtest
engine = BacktestEngine(config)
result = engine.run()
print(f"Backtest complete: {len(result.rebalance_events)} rebalances")
Step 2: Generate Equity Curve¶
from portfolio_management.reporting.visualization import prepare_equity_curve
import matplotlib.pyplot as plt
# Prepare normalized equity curve (base 100)
equity_data = prepare_equity_curve(result.equity)
# Plot
plt.figure(figsize=(12, 6))
plt.plot(equity_data.index, equity_data['equity_normalized'], linewidth=2)
plt.title('Portfolio Growth (Normalized to 100)', fontsize=16)
plt.ylabel('Portfolio Value', fontsize=12)
plt.xlabel('Date', fontsize=12)
plt.grid(True, alpha=0.3)
plt.savefig('outputs/equity_curve.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ Equity curve saved")
Step 3: Analyze Drawdowns¶
from portfolio_management.reporting.visualization import prepare_drawdown_series
# Prepare drawdown data
drawdown_data = prepare_drawdown_series(result.equity)
# Find key statistics
max_dd = drawdown_data['drawdown_pct'].min()
max_underwater = drawdown_data['underwater_days'].max()
print(f"Maximum Drawdown: {max_dd:.2%}")
print(f"Longest Underwater Period: {max_underwater} days")
# Plot
plt.figure(figsize=(12, 6))
plt.fill_between(
drawdown_data.index,
drawdown_data['drawdown_pct'],
0,
color='red',
alpha=0.3
)
plt.plot(drawdown_data.index, drawdown_data['drawdown_pct'], color='darkred', linewidth=1)
plt.title('Portfolio Drawdowns', fontsize=16)
plt.ylabel('Drawdown (%)', fontsize=12)
plt.xlabel('Date', fontsize=12)
plt.grid(True, alpha=0.3)
plt.savefig('outputs/drawdowns.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ Drawdown chart saved")
Step 4: Create Summary Report¶
from portfolio_management.reporting.visualization import create_summary_report
import json
# Generate comprehensive summary
summary = create_summary_report(result.equity, result.rebalance_events)
# Print formatted summary
print("\n" + "="*50)
print("BACKTEST SUMMARY REPORT")
print("="*50)
print("\nPERFORMANCE METRICS:")
for key, value in summary['performance'].items():
if isinstance(value, float):
print(f" {key}: {value:.2%}" if abs(value) < 10 else f" {key}: {value:.2f}")
else:
print(f" {key}: {value}")
print("\nRISK METRICS:")
for key, value in summary['risk'].items():
if isinstance(value, float):
print(f" {key}: {value:.2%}" if abs(value) < 10 else f" {key}: {value:.2f}")
else:
print(f" {key}: {value}")
print("\nTRADING ACTIVITY:")
for key, value in summary['trading'].items():
if isinstance(value, float):
print(f" {key}: ${value:,.2f}")
else:
print(f" {key}: {value}")
print("\nPORTFOLIO EVOLUTION:")
for key, value in summary['portfolio'].items():
print(f" {key}: ${value:,.2f}")
# Save to JSON
with open('outputs/summary.json', 'w') as f:
json.dump(summary, f, indent=2, default=str)
print("\n✓ Summary saved to outputs/summary.json")
Step 5: Rolling Metrics Analysis¶
from portfolio_management.reporting.visualization import prepare_rolling_metrics
# Calculate 60-day rolling metrics
rolling = prepare_rolling_metrics(result.equity, window=60)
# Plot rolling Sharpe ratio
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
ax1.plot(rolling.index, rolling['rolling_sharpe'], linewidth=2, color='blue')
ax1.axhline(0, color='red', linestyle='--', alpha=0.5)
ax1.set_title('Rolling 60-Day Sharpe Ratio', fontsize=16)
ax1.set_ylabel('Sharpe Ratio', fontsize=12)
ax1.grid(True, alpha=0.3)
ax2.plot(rolling.index, rolling['rolling_volatility_annual'], linewidth=2, color='orange')
ax2.set_title('Rolling 60-Day Volatility (Annualized)', fontsize=16)
ax2.set_ylabel('Volatility (%)', fontsize=12)
ax2.set_xlabel('Date', fontsize=12)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('outputs/rolling_metrics.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ Rolling metrics saved")
Step 6: Multi-Strategy Comparison¶
from portfolio_management.reporting.visualization import prepare_metrics_comparison
# Run multiple strategies
momentum_result = BacktestEngine(
BacktestConfig.from_yaml("config/momentum_strategy_config.yaml")
).run()
lowvol_result = BacktestEngine(
BacktestConfig.from_yaml("config/lowvol_strategy_config.yaml")
).run()
multifactor_result = BacktestEngine(
BacktestConfig.from_yaml("config/multifactor_strategy_config.yaml")
).run()
# Create comparison table
comparison = prepare_metrics_comparison([
("Momentum", momentum_result.metrics),
("Low Volatility", lowvol_result.metrics),
("Multi-Factor", multifactor_result.metrics)
])
print("\n" + "="*80)
print("STRATEGY COMPARISON")
print("="*80)
print(comparison.to_string())
print()
# Save to CSV
comparison.to_csv('outputs/strategy_comparison.csv')
# Find best strategy by Sharpe ratio
best_strategy = comparison.sort_values('Sharpe Ratio', ascending=False).index[0]
print(f"✓ Best risk-adjusted strategy: {best_strategy}")
print(f" Sharpe Ratio: {comparison.loc[best_strategy, 'Sharpe Ratio']:.2f}")
Step 7: Allocation Tracking¶
from portfolio_management.reporting.visualization import prepare_allocation_history
# Track portfolio allocations over time
allocation = prepare_allocation_history(result.rebalance_events)
# Plot stacked area chart
plt.figure(figsize=(12, 6))
plt.stackplot(
allocation.index,
allocation['cash_pct'],
allocation['holdings_pct'],
labels=['Cash', 'Holdings'],
alpha=0.7,
colors=['lightblue', 'steelblue']
)
plt.title('Portfolio Allocation Over Time', fontsize=16)
plt.ylabel('Allocation (%)', fontsize=12)
plt.xlabel('Date', fontsize=12)
plt.legend(loc='upper left', fontsize=10)
plt.grid(True, alpha=0.3)
plt.savefig('outputs/allocations.png', dpi=300, bbox_inches='tight')
plt.close()
print("✓ Allocation chart saved")
Step 8: Create Interactive Dashboard (Streamlit)¶
# Save as: dashboard.py
import streamlit as st
from portfolio_management.backtesting import BacktestEngine, BacktestConfig
from portfolio_management.reporting.visualization import *
st.set_page_config(layout="wide", page_title="Backtest Dashboard")
st.title("📊 Portfolio Backtest Dashboard")
# Load configuration
config_file = st.sidebar.selectbox(
"Select Strategy",
["momentum_strategy_config.yaml", "lowvol_strategy_config.yaml"]
)
if st.sidebar.button("Run Backtest"):
with st.spinner("Running backtest..."):
config = BacktestConfig.from_yaml(f"config/{config_file}")
result = BacktestEngine(config).run()
# Store in session state
st.session_state['result'] = result
st.success("✓ Backtest complete!")
if 'result' in st.session_state:
result = st.session_state['result']
# Summary metrics
summary = create_summary_report(result.equity, result.rebalance_events)
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Return", f"{summary['performance']['total_return']:.2%}")
col2.metric("Sharpe Ratio", f"{summary['risk']['sharpe_ratio']:.2f}")
col3.metric("Max Drawdown", f"{summary['risk']['max_drawdown']:.2%}")
col4.metric("Win Rate", f"{summary['risk']['win_rate']:.2%}")
# Charts
st.subheader("Equity Curve")
equity_data = prepare_equity_curve(result.equity)
st.line_chart(equity_data['equity_normalized'])
st.subheader("Drawdowns")
drawdown_data = prepare_drawdown_series(result.equity)
st.area_chart(drawdown_data['drawdown_pct'])
st.subheader("Rolling Sharpe (60-Day)")
rolling = prepare_rolling_metrics(result.equity, window=60)
st.line_chart(rolling['rolling_sharpe'])
Run dashboard:
Complete Visualization Script¶
# Save as: scripts/generate_visual_report.py
"""Generate comprehensive visual backtest report."""
import sys
from pathlib import Path
import matplotlib.pyplot as plt
from portfolio_management.backtesting import BacktestEngine, BacktestConfig
from portfolio_management.reporting.visualization import *
def generate_report(config_path: str, output_dir: str):
"""Generate complete visual backtest report."""
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# Run backtest
print(f"Running backtest: {config_path}")
config = BacktestConfig.from_yaml(config_path)
result = BacktestEngine(config).run()
# 1. Equity curve
print("Generating equity curve...")
equity_data = prepare_equity_curve(result.equity)
plt.figure(figsize=(12, 6))
plt.plot(equity_data.index, equity_data['equity_normalized'], linewidth=2)
plt.title('Portfolio Growth (Base 100)')
plt.ylabel('Value')
plt.grid(True, alpha=0.3)
plt.savefig(output_path / 'equity_curve.png', dpi=300, bbox_inches='tight')
plt.close()
# 2. Drawdowns
print("Generating drawdown analysis...")
drawdown_data = prepare_drawdown_series(result.equity)
plt.figure(figsize=(12, 6))
plt.fill_between(drawdown_data.index, drawdown_data['drawdown_pct'], 0,
color='red', alpha=0.3)
plt.title('Drawdown Analysis')
plt.ylabel('Drawdown %')
plt.grid(True, alpha=0.3)
plt.savefig(output_path / 'drawdowns.png', dpi=300, bbox_inches='tight')
plt.close()
# 3. Rolling metrics
print("Generating rolling metrics...")
rolling = prepare_rolling_metrics(result.equity, window=60)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
ax1.plot(rolling.index, rolling['rolling_sharpe'])
ax1.set_title('Rolling 60-Day Sharpe Ratio')
ax1.grid(True, alpha=0.3)
ax2.plot(rolling.index, rolling['rolling_volatility_annual'])
ax2.set_title('Rolling 60-Day Volatility')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(output_path / 'rolling_metrics.png', dpi=300, bbox_inches='tight')
plt.close()
# 4. Summary report
print("Generating summary report...")
summary = create_summary_report(result.equity, result.rebalance_events)
with open(output_path / 'summary.txt', 'w') as f:
f.write("BACKTEST SUMMARY REPORT\n")
f.write("=" * 50 + "\n\n")
for category, metrics in summary.items():
f.write(f"{category.upper()}:\n")
for key, value in metrics.items():
f.write(f" {key}: {value}\n")
f.write("\n")
print(f"\n✓ Report generated in: {output_path}")
print(f" - equity_curve.png")
print(f" - drawdowns.png")
print(f" - rolling_metrics.png")
print(f" - summary.txt")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python generate_visual_report.py <config.yaml> <output_dir>")
sys.exit(1)
generate_report(sys.argv[1], sys.argv[2])
Run visualization script:
python scripts/generate_visual_report.py \
config/momentum_strategy_config.yaml \
outputs/visual_report
Summary of Features Demonstrated¶
Basic Workflow¶
✅ Data preparation with incremental resume ✅ Asset selection with quality filters ✅ Asset classification ✅ Returns calculation ✅ Simple portfolio construction ✅ Basic backtesting
Advanced Workflow¶
✅ Universe management (config-driven pipeline) ✅ Factor-based preselection (momentum + low-vol) ✅ Membership policy (turnover control) ✅ Point-in-time eligibility (bias prevention) ✅ Statistics caching (performance) ✅ Risk parity strategy ✅ Asset class constraints ✅ Strategy comparison ✅ Batch backtesting
Production Workflow¶
✅ Multiple universe definitions ✅ Defensive/growth/momentum allocation ✅ Mean-variance optimization ✅ Long-term backtesting (8+ years) ✅ Portfolio blending ✅ Validation and reporting
Visualization & Reporting Workflow¶
✅ Equity curve normalization (base 100) ✅ Drawdown analysis with underwater periods ✅ Comprehensive performance summaries ✅ Multi-strategy comparison tables ✅ Rolling metrics (Sharpe, volatility, drawdown) ✅ Allocation tracking over time ✅ Interactive Streamlit dashboards ✅ Automated report generation ✅ Professional-grade chart output
Next Steps¶
- Explore Examples: See
examples/directory for more use cases - Read Documentation: See
docs/for detailed feature guides docs/visualization.md- Complete visualization API referencedocs/backtesting.md- Backtesting configurationdocs/performance/- Performance optimization guides- Run Tests:
pytest tests/to validate your setup - Customize: Edit
config/universes.yamlfor your requirements - Deploy: Use production workflow for live trading preparation
For questions and troubleshooting, see docs/troubleshooting.md.