Portfolio optimization, risk analysis, and performance analytics.
MeanVariance, HierarchicalRiskParity, RiskParity, and BlackLitterman share an optimize interface. Each takes expected returns as a pandas Series and a covariance matrix as a pandas DataFrame. Annualize both before you pass them in. Each call returns an OptimizationResult with weights, expected_return, volatility, sharpe_ratio, and a success flag.
from meridianalgo import MeanVariance, HierarchicalRiskParity, RiskParity, BlackLitterman
expected_returns = returns.mean() * 252
covariance = returns.cov() * 252
max_sharpe = MeanVariance().optimize(expected_returns, covariance, objective="max_sharpe")
min_vol = MeanVariance().optimize(expected_returns, covariance, objective="min_volatility")
hrp = HierarchicalRiskParity().optimize(expected_returns, covariance, returns_data=returns)
rp = RiskParity().optimize(expected_returns, covariance)
bl = BlackLitterman().optimize(expected_returns, covariance)
print(max_sharpe.weights.sort_values(ascending=False))
print(f"Max sharpe ratio {max_sharpe.sharpe_ratio:.4f}")
print(f"Min variance vol {min_vol.volatility:.4f}")MeanVariance supports objective="max_sharpe" and objective="min_volatility". HierarchicalRiskParity accepts the raw return data through returns_data for its clustering step.
from meridianalgo import KellyCriterion
kc = KellyCriterion(fraction=0.5) # half kelly
f = kc.single_asset(win_prob=0.55, win_loss_ratio=1.0) # discrete binary bet
weights = kc.optimize(returns) # continuous multi asset
f_moments = kc.from_moments(expected_return=0.12, volatility=0.18)
g = kc.growth_rate(expected_return=0.12, volatility=0.18)RiskAnalyzer, VaRCalculator, and CVaRCalculator share the same interface. Value at risk supports "historical", "parametric", "cornish_fisher", and "monte_carlo".
from meridianalgo import RiskAnalyzer
portfolio_returns = returns.mean(axis=1)
risk = RiskAnalyzer(portfolio_returns)
var_95 = risk.value_at_risk(confidence=0.95, method="historical")
var_99 = risk.value_at_risk(confidence=0.99, method="cornish_fisher")
cvar_95 = risk.conditional_var(confidence=0.95)import numpy as np
from meridianalgo import StressTesting
stress = StressTesting()
weights = np.repeat(1 / returns.shape[1], returns.shape[1])
# Smallest shock that produces a 10 percent loss
result = stress.reverse_stress_test(weights, returns, target_loss=-0.10)For correlated, scenario based stress testing see ScenarioAnalyzer and CorrelationScenario.
from meridianalgo import CorrelationScenario
gen = CorrelationScenario(mean_returns, correlation_matrix, volatilities, weights)
scenarios = gen.generate(n_scenarios=100_000, stress_correlation=True, stress_factor=0.5)
print(f"Stressed 99 percent VaR {scenarios['var_99']:.2%}")from meridianalgo import PerformanceAnalyzer
analyzer = PerformanceAnalyzer(portfolio_returns, risk_free_rate=0.05)
metrics = analyzer.calculate_all_metrics()
# Or the one call helper
import meridianalgo as ma
print(ma.tearsheet(portfolio_returns))from meridianalgo import BenchmarkAnalytics, ActiveShare, BrinsonAttribution
analytics = BenchmarkAnalytics(
portfolio_returns=portfolio_returns,
benchmark_returns=benchmark_returns,
risk_free_rate=0.05,
)
m = analytics.active_metrics()
print(f"Active return {m.active_return:.2%}, tracking error {m.tracking_error:.2%}")
print(f"Information ratio {m.information_ratio:.3f}, beta {m.beta:.3f}")
active_share = ActiveShare.compute(portfolio_weights, benchmark_weights)
attribution = BrinsonAttribution(
portfolio_weights=sector_w_port, benchmark_weights=sector_w_bench,
portfolio_returns=sector_r_port, benchmark_returns=sector_r_bench,
).compute()
print(f"Total active return {attribution.total_active_return:.4f}")See the project README for end to end examples.