Modern Portfolio Theory evolved for the fundamental investor.
A portfolio construction engine that fixes the "rear-view mirror" bias of traditional MPT by explicitly decoupling forward-looking return forecasting, risk modeling, and mathematical optimization.
Live Web Interface: https://bsachart.github.io/hybrid-quantamental-optimizer/
Use the web interface to:
- Upload your price history and asset metrics CSVs
- Configure optimization parameters interactively
- Visualize the Efficient Frontier and Capital Market Line
- Explore different risk/return allocations with real-time updates
No installation required - runs entirely in your browser using Stlite (Streamlit + WebAssembly).
Traditional MPT relies on historical returns to predict the future. This is fundamentally backward-looking and fails during regime changes.
Our Approach: Treat return forecasting, risk modeling, and optimization as three independent problems:
- Return Engine: Generate forward-looking expected returns (Fundamental CAGR).
- Risk Engine: Model covariance using forward-looking volatility (Implied Vol).
- Optimization Engine: A two-stage process:
- Stage 1: Solve for the Tangency Portfolio (Pure Equity).
- Stage 2: Construct the final portfolio along the Capital Market Line (Cash Mixing).
- Visit https://bsachart.github.io/hybrid-quantamental-optimizer/
- Upload your data files (see Data Specifications)
- Configure optimization parameters
- View results and explore allocations
Fetch historical prices and compute fundamental metrics using the provided utility:
python src/scripts/generate_universe.pyA single function call orchestrates data loading, alignment, risk modeling, and solving.
from src.engine.portfolio_engine import optimize_portfolio, target_portfolio, generate_cml
from src.engine.risk import RiskModel
# --- STAGE 1: Find the Tangency Portfolio ---
# This calculates the optimal mix of risky assets (Max Sharpe Ratio).
tangency_result = optimize_portfolio(
price_source="tmp/universe.csv",
metric_source="tmp/metrics.csv",
risk_model=RiskModel.FORWARD_LOOKING,
risk_free_rate=0.04
)
print(f"Max Sharpe: {tangency_result['sharpe_ratio']:.2f}")
print(f"Risky Volatility: {tangency_result['volatility']:.2%}")
# --- STAGE 2: Construct Final Portfolio (Target Risk) ---
# Scale the tangency portfolio to a specific volatility target (e.g., 10%)
# by mixing with Cash (Risk-Free Asset).
final_allocation = target_portfolio(
tangency_portfolio=tangency_result,
target_volatility=0.10,
risk_free_rate=0.04
)
print(f"Cash Weight: {final_allocation['cash_weight']:.2%}")
# --- UTILITY: Generate Capital Market Line ---
# Generate points for plotting the Efficient Frontier / CML
# Default: Steps of 1% volatility
cml_points = generate_cml(
tangency_portfolio=tangency_result,
risk_free_rate=0.04,
vol_step=0.01
)The engine requires two inputs (CSV files or Polars DataFrames).
Used to calculate correlation matrices (
- Format: Time-series.
- Columns:
date(YYYY-MM-DD), followed by one column per ticker.
date,AAPL,GOOG,TSLA
2023-01-31,150.23,105.44,250.67
2023-02-28,152.11,108.22,255.33Used for Expected Returns (
- Format: Cross-sectional.
- Units: Decimals (e.g., 0.12 = 12%).
ticker,expected_return,implied_volatility,min_weight,max_weight
AAPL,0.12,0.25,0.0,1.0
GOOG,0.15,0.28,0.0,1.0
TSLA,0.03,0.10,-0.5,0.5| Column | Description | Required For |
|---|---|---|
ticker |
Symbol matching price CSV | All |
expected_return |
Annualized expected return (Decimal) | All |
implied_volatility |
Forward-looking annual vol (Decimal) | RiskModel.FORWARD_LOOKING |
min_weight |
Minimum allocation (0.0 = long only) | All |
max_weight |
Maximum allocation (1.0 = no leverage) | All |
Combines the structure of the past with the magnitude of the future.
- Correlations: Derived from price history.
- Volatility: Derived from Implied Volatility (Options Market).
risk_model=RiskModel.FORWARD_LOOKING
# Requires 'implied_volatility' column in metrics.csvClassic MPT approach using sample covariance of historical returns.
risk_model=RiskModel.HISTORICAL, annualization_factor=252
# annualization_factor is required (e.g., 252 for daily data)The engine explicitly separates the mathematical solving from the portfolio construction.
Finds the Tangency Portfolio (Maximum Sharpe Ratio) considering only risky assets.
Subject to:
$\sum w_i = 1$ $w_{\min} \leq w_i \leq w_{\max}$
Allocates capital between the Tangency Portfolio and the Risk-Free Asset to achieve a precise target_volatility (
The weight allocated to the risky portfolio (
- If
$\sigma_{target} < \sigma_{tangency}$ : We hold Cash + Equity (Lending portfolio). - If
$\sigma_{target} \ge \sigma_{tangency}$ : We hold 100% Tangency Portfolio (Leverage is explicitly capped at 1.0).