Skip to content

Comprehensive Portfolio Management Example

Complete end-to-end demonstration of all toolkit features from CSV ingestion through backtesting with advanced controls.


Table of Contents

  1. Basic Workflow - Simple start-to-finish example
  2. Advanced Workflow - All features combined
  3. 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 instruments
  • data/metadata/tradeable_matches.csv - matched tradeable instruments with quality metrics
  • data/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:

  1. ✅ Selects assets (filters by market, currency, quality)
  2. ✅ Classifies assets (equity only, factor sub-classes)
  3. ✅ Calculates returns (monthly, with forward-fill)
  4. ✅ Validates configuration
  5. ✅ 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:

streamlit run dashboard.py

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

  1. Explore Examples: See examples/ directory for more use cases
  2. Read Documentation: See docs/ for detailed feature guides
  3. docs/visualization.md - Complete visualization API reference
  4. docs/backtesting.md - Backtesting configuration
  5. docs/performance/ - Performance optimization guides
  6. Run Tests: pytest tests/ to validate your setup
  7. Customize: Edit config/universes.yaml for your requirements
  8. Deploy: Use production workflow for live trading preparation

For questions and troubleshooting, see docs/troubleshooting.md.