QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

Analysis Deep Dive:
portfolio calculations, metric configs and crisis analysis

This is a sample split feature page for the Analysis module. It explains where the analysis layer lives, which classes and functions matter, how metric configs become report tables and how crisis analysis turns historical regimes into reviewable evidence.

backtester/portfolio/calc/

Pure portfolio analytics

Side-effect-free calculators for returns, drawdowns, ratios, exposures, attribution, rolling windows, liquidity, PnL and Monte Carlo.

backtester/metrics/configs/

Report metric contract

Configuration maps human-readable report rows to nested calculation outputs and formatter rules.

backtester/portfolio/crisis_analysis.py

Stress-period engine

Named crises and market regimes are loaded from JSON, matched to strategy dates and summarized for PDF and table output.

Analysis Flow

The analysis layer starts after the strategy produces returns, weights or order-derived positions. It keeps calculations inspectable: pandas objects go in, deterministic metric tables and report artifacts come out.

1. InputsPrices, returns, target weights, units, trades and benchmark returns.
2. Calc modulesPortfolio math in small functions across returns, risk, exposure and attribution modules.
3. Metric configNamed report rows point to result paths and formatter types.
4. Crisis windowsNamed historical periods are evaluated against strategy and benchmark returns.
5. Packet outputConsole tables, JSON metrics, plots and PDF tear sheets consume the same result contract.

Portfolio Calc Modules

The `calc` package is the reusable core for analysis. It is intentionally split by concern so a report, strategy page or notebook can reuse one piece without depending on a full backtest run.

backtester/portfolio/calc/returns.py

Return series and NAV construction

compute_periodic_returnscompute_total_returnscompute_annualized_returnsconvert_returns_to_nav
backtester/portfolio/calc/risk.py

Volatility, drawdowns, VaR/CVaR and risk-adjusted ratios

compute_volatilitycompute_drawdownscompute_max_drawdownsharpe_ratiosortino_ratioomega_ratio
backtester/portfolio/calc/metrics.py

Benchmark-relative and market-sensitivity summaries

excess_returnsactive_returninformation_coefficientmarket_sensitivity_summarytail_dependence
backtester/portfolio/calc/rolling_stats.py

Windowed statistics for report charts and monitoring

rolling_sharpe_ratiorolling_max_drawdownrolling_betarolling_alpharolling_correlation
backtester/portfolio/calc/exposures.py

Position exposure, turnover and participation checks

compute_exposurescompute_short_long_exposurecompute_turnovermarket_cap_participationvolume_participation
backtester/portfolio/calc/attribution.py

Factor exposure and performance attribution

compute_factor_exposures_olscompute_factor_alphacompute_factor_attributioncompute_performance_attribution
backtester/portfolio/calc/liquidity.py

Liquidity proxies for review and reporting

amihud_illiquidityaverage_daily_rangezero_return_days_ratiocompute_liquidity_summary
backtester/portfolio/calc/montecarlo.py

Bootstrap path simulation for risk questions

MonteCarloSimulation
backtester/portfolio/calc/scenario.py

Historical and synthetic stress scenarios

historical_scenario_analysisstress_teststress_test_vectorized
backtester/portfolio/calc/round_trips.py

FIFO trade grouping and round-trip analytics

RoundTripRoundTripAnalyzer
backtester/portfolio/calc/pnl_multi_asset.py

Contract-aware PnL, margin and notional exposure

compute_position_pnlcompute_portfolio_pnlcompute_margin_usagecompute_notional_exposure
analysis_calc_example.py Python
import pandas as pd

from backtester.portfolio.calc import exposures, returns, risk, rolling_stats

asset_returns = prices.pct_change().dropna()
portfolio_returns = asset_returns.mul(target_weights.shift(1)).sum(axis=1)

nav = returns.convert_returns_to_nav(portfolio_returns.to_frame("strategy"))
drawdowns = risk.compute_drawdowns(portfolio_returns.to_frame("strategy"))
rolling_sharpe = rolling_stats.rolling_sharpe_ratio(
    portfolio_returns.to_frame("strategy"),
    window=126,
)
gross_exposure = exposures.compute_exposures(prices, units).abs().sum(axis=1)

Main Classes And Entry Points

For a public feature page, these are the names worth explaining first. They are the objects a quant developer will search for when moving from marketing copy into source code.

Name What it does
CrisisPeriod Dataclass for one named period: name, start, end and category.
CrisisPeriodResult Dataclass with return, volatility, drawdown, Sharpe, worst day, best day and benchmark-relative fields.
load_crisis_periods() Loads bundled or custom JSON definitions; optionally includes broader market regimes.
compute_crisis_analysis() Main crisis entry point. Produces summary rows, details, overlap counts, hit rate and best/worst periods.
PORTFOLIO_PERF_METRICS Report contract mapping visible metric names to result paths and formatter types.
MonteCarloSimulation Bootstrap simulation object used when the report needs distribution and drawdown probability questions.
RoundTripAnalyzer FIFO round-trip layer for trade-level review after order-aware runs.

Metric Configs

`PORTFOLIO_PERF_METRICS` is the bridge between computed analytics and the visible report. Each row has a display name, a nested result path and a formatter type. That keeps the calculation layer separate from the reporting layout.

Why configs matter

Without a config layer, report code hard-codes metric names and formatting. Here the report can ask for sections like Executive Summary, Risk Metrics or Interesting Times and resolve each row from the same result object.

Executive SummaryPerformance BreakdownRisk-Adjusted MetricsBenchmark ComparisonRisk MetricsMarket DynamicsTrading AnalyticsAdvanced RiskOperational MetricsInteresting TimesReproducibility

What gets configured

Paths such as `compute_crisis_analysis.worst_crisis` or `compute_advanced_sharpe_ratio.smart_sharpe` tell the report where to find data. Formatter keys such as `percentage`, `ratio`, `currency0`, `count` and `text` control presentation.

backtester/metrics/configs/portfolio_perf.py Python
PORTFOLIO_PERF_METRICS = {
    "Executive Summary": {
        "CAGR": ("compute_annualized_return", "percentage"),
        "Sharpe Ratio": ("compute_advanced_sharpe_ratio.smart_sharpe", "ratio"),
        "Max Drawdown": ("compute_max_drawdown", "percentage"),
    },
    "Interesting Times": {
        "Crises Evaluated": ("compute_crisis_analysis.overlapping_count", "count"),
        "Worst Crisis": ("compute_crisis_analysis.worst_crisis", "text"),
        "Crisis Hit Rate": ("compute_crisis_analysis.crisis_hit_rate", "percentage_raw"),
    },
}

Crisis Analysis

Crisis analysis is a separate module because it answers a different question from summary performance: how did this strategy behave during named stress windows and market regimes?

Output Meaning
summary Rows ready for a report table: period, dates, days, return, volatility, max drawdown, Sharpe, worst day and benchmark fields.
details Ordered per-period dictionaries for plots or deeper inspection.
overlapping_count Number of configured periods that overlap the strategy date range.
total_defined Total number of configured periods after optional regime inclusion.
avg_crisis_return Mean strategy return across overlapping crisis windows.
worst_crisis / best_crisis Names of the lowest and highest return stress windows.
crisis_hit_rate Percent of overlapping crisis windows with positive strategy return.
crisis_analysis_usage.py Python
from backtester.portfolio.crisis_analysis import compute_crisis_analysis

analysis = compute_crisis_analysis(
    returns=strategy_returns,
    benchmark_returns=benchmark_returns,
    include_regimes=True,
)

for row in analysis["summary"]:
    print(row["Period"], row["Return"], row["Max DD"], row["Sharpe"])
Config layer

`crisis_periods.json` ships named periods such as GFC / Lehman, Flash Crash, COVID Crash, 2022 Rate Shock, SVB / Banking Crisis, Japan Carry Unwind and DeepSeek Shock. The same file also contains broader regimes, and a custom JSON path can be passed at runtime.

Suggested Template For The Other Feature Pages

If this direction works, each feature page should follow the same pattern: what the module does, where it lives, main classes and functions, one short code example, output contract and a link to the strategy packet that uses it.

Analysis should link naturally into SMA Crossover, Momentum Rotation, Optuna Optimization and Walk-Forward Case Study. Those strategy pages show the same analytics layer from the user workflow side.