MeridianAlgo is a Python library for quantitative finance and algorithmic trading. It covers the full stack a quant desk works with. That means portfolio optimization, risk management, derivatives pricing, backtesting, credit risk, volatility modeling, Monte Carlo simulation, portfolio insurance, machine learning for signals, execution algorithms, fixed income analytics, and market microstructure.
Every example in this file was run against a live install before it was published. If a snippet is here, it works.
pip install meridianalgo# Optional extras, install only what you need
pip install meridianalgo[ml] # scikit-learn, torch, statsmodels, hmmlearn
pip install meridianalgo[optimization] # cvxpy, cvxopt for convex optimization
pip install meridianalgo[volatility] # arch for the GARCH family
pip install meridianalgo[data] # lxml, beautifulsoup4, polygon-api-client
pip install meridianalgo[distributed] # ray, dask for parallel work
pip install meridianalgo[all] # everything aboveRequires Python 3.10 or newer. The core library runs on numpy, pandas, and scipy alone. The extras unlock the machine learning models, convex solvers, and maximum likelihood GARCH.
| Module | Key classes and functions |
|---|---|
portfolio |
MeanVariance, HierarchicalRiskParity, RiskParity, BlackLitterman, KellyCriterion |
portfolio.insurance |
CPPI, TimeInvariantCPPI |
risk |
RiskAnalyzer, VaRCalculator, CVaRCalculator, StressTesting, RiskBudgeting |
risk.scenario |
ScenarioAnalyzer, CorrelationScenario |
credit |
MertonModel, CreditDefaultSwap, CreditRiskAnalyzer, ZSpreadCalculator |
volatility |
GARCHModel, RealizedVolatility, VolatilityForecaster, VolatilityTermStructure, VolatilityRegimeDetector |
monte_carlo |
GeometricBrownianMotion, HestonModel, JumpDiffusionModel, CIRModel, MonteCarloEngine |
derivatives |
BlackScholes, GreeksCalculator, ImpliedVolatility, MonteCarloPricer, OptionChain |
fixed_income |
BondPricer, YieldCurve, CreditSpreadAnalyzer |
backtesting |
BacktestEngine, Strategy, Backtest |
ml |
LSTMPredictor, WalkForwardValidator, FeatureEngineer, ModelSelector |
execution |
VWAP, TWAP, POV, ImplementationShortfall |
analytics |
PerformanceAnalyzer, BenchmarkAnalytics, ActiveShare, BrinsonAttribution |
quant |
StatisticalArbitrage, RegimeDetector, MarketMicrostructure |
signals |
RSI, MACD, BollingerBands, and more than forty indicators |
liquidity |
OrderBook, SpreadAnalyzer, MarketImpact |
factors |
FamaFrenchModel, FactorExposure |
import meridianalgo as ma
# Top level convenience metrics on a return series
sharpe = ma.calculate_sharpe_ratio(returns)
sortino = ma.calculate_sortino_ratio(returns)
calmar = ma.calculate_calmar_ratio(returns)
max_dd = ma.calculate_max_drawdown(returns)
cvar_95 = ma.calculate_expected_shortfall(returns)
# One call summary of around 28 metrics plus a formatted text report
stats = ma.summary_stats(returns)
print(ma.tearsheet(returns))
# Compare several strategies in one table and track a rolling Sharpe ratio
table = ma.compare({"strategy": returns, "benchmark": returns * 0.8})
roll = ma.rolling_sharpe(returns, window=63)Each optimizer takes expected returns as a pandas Series and a covariance matrix as a pandas DataFrame. Annualize both before you pass them in.
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}")Each call returns an OptimizationResult with weights, expected_return, volatility, sharpe_ratio, and a success flag.
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) # long run growth rate
print(f"Kelly fraction {f:.2%}")
print(weights.sort_values(ascending=False))from meridianalgo import RiskAnalyzer, ScenarioAnalyzer, CorrelationScenario
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)
print(f"Historical VaR 95 percent {var_95:.2%}")
print(f"Cornish Fisher VaR 99 percent {var_99:.2%}")
print(f"CVaR 95 percent {cvar_95:.2%}")
# Correlated scenario generation, all inputs are pandas
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 MertonModel, CreditDefaultSwap, CreditRiskAnalyzer, ZSpreadCalculator
import pandas as pd
# Merton structural model, equity as a call option on firm assets
model = MertonModel(
equity_value=500e6, equity_volatility=0.35,
debt_face_value=800e6, time_to_maturity=1.0, risk_free_rate=0.05,
)
result = model.calibrate()
print(f"Distance to default {result['distance_to_default']:.4f}")
print(f"Default probability {result['default_probability']:.2%}")
# CDS fair spread and a bootstrapped hazard curve
cds = CreditDefaultSwap(hazard_rate=0.02, recovery_rate=0.40, maturity=5.0)
print(f"CDS fair spread {cds.price().fair_spread * 10000:.1f} bps")
curve = CreditDefaultSwap.bootstrap_hazard_curve(
maturities=[1, 3, 5, 7, 10], spreads=[0.0080, 0.0120, 0.0150, 0.0170, 0.0200],
)
# Portfolio expected loss
exposures = pd.DataFrame({
"pd": [0.010, 0.025, 0.050, 0.005],
"lgd": [0.45, 0.40, 0.60, 0.35],
"ead": [2e6, 1.5e6, 0.5e6, 3e6],
})
el = CreditRiskAnalyzer().portfolio_expected_loss(exposures)
print(f"Portfolio expected loss {el['total_el']:,.0f}")
# Z spread from a price
calc = ZSpreadCalculator(
cash_flows=[6, 6, 6, 6, 106], times=[1, 2, 3, 4, 5],
risk_free_rates=[0.035, 0.038, 0.040, 0.042, 0.044],
)
print(f"Z spread {calc.z_spread(market_price=97.5) * 10000:.1f} bps")RealizedVolatility accepts OHLCV columns in any capitalization, so open or Open or OPEN all work.
from meridianalgo import (
GARCHModel, RealizedVolatility, VolatilityForecaster,
VolatilityTermStructure, VolatilityRegimeDetector,
)
rv = RealizedVolatility(ohlcv)
est = rv.all_estimators(window=21)
print(est[["close_to_close_vol", "parkinson_vol", "yang_zhang_vol"]].iloc[-1])
garch = GARCHModel(daily_returns, model_type="garch", p=1, q=1)
fit = garch.fit()
print(f"Persistence {fit.persistence:.4f}, half life {fit.half_life:.1f} days")
vts = VolatilityTermStructure(daily_returns)
vts.build(horizons=[5, 10, 21, 63, 126, 252])
print(f"Term structure slope {vts.slope():.4f}")
regimes = VolatilityRegimeDetector(daily_returns).classify()
print(regimes.value_counts())from meridianalgo import (
GeometricBrownianMotion, HestonModel, JumpDiffusionModel, CIRModel, MonteCarloEngine,
)
gbm = GeometricBrownianMotion(mu=0.08, sigma=0.20)
res = gbm.simulate(S0=100, T=1.0, n_paths=100_000, n_steps=252, antithetic=True)
print(f"Mean {res.mean:.2f}, 5th pct {res.percentile_5:.2f}, 95th pct {res.percentile_95:.2f}")
call = gbm.call_price(S0=100, K=105, T=0.25, r=0.05, n_paths=200_000)
print(f"MC call {call['price']:.4f}, std error {call['std_error']:.4f}")
heston = HestonModel(mu=0.05, v0=0.04, kappa=2.0, theta=0.04, xi=0.30, rho=-0.70)
jdm = JumpDiffusionModel(mu=0.05, sigma=0.15, lam=0.10, mu_jump=-0.03, sigma_jump=0.06)
cir = CIRModel(r0=0.03, kappa=0.80, theta=0.04, sigma=0.06)
engine = MonteCarloEngine(model="heston")
engine.configure(mu=0.05, v0=0.04, kappa=2.0, theta=0.04, xi=0.30, rho=-0.7)
engine.simulate(S0=100, T=1.0, n_paths=100_000)
put = engine.price_option(K=95, r=0.05, T=1.0, option_type="put")
print(f"Heston put {put['price']:.4f}")from meridianalgo import CPPI, TimeInvariantCPPI
cppi = CPPI(multiplier=3.0, floor_pct=0.80, safe_rate=0.04, rebalance_frequency=1)
result = cppi.run(equity_returns, initial_value=1_000_000)
print(f"Total return {result.total_return:.2%}, max drawdown {result.max_drawdown:.2%}")
print(f"Floor breaches {result.floor_breaches}")
# Sweep multiplier and floor combinations
sensitivity = cppi.sensitivity_analysis(
equity_returns, multipliers=[1.0, 2.0, 3.0, 4.0, 5.0], floor_pcts=[0.70, 0.80, 0.90],
)
# TIPP, where the floor ratchets up with new portfolio peaks
tipp = TimeInvariantCPPI(multiplier=3.0, floor_pct=0.80)
tipp_result = tipp.run(equity_returns, initial_value=1_000_000)from meridianalgo import BlackScholes, ImpliedVolatility
call = BlackScholes(S=100, K=105, T=0.25, r=0.05, sigma=0.20, option_type="call")
put = BlackScholes(S=100, K=105, T=0.25, r=0.05, sigma=0.20, option_type="put")
print(f"Call {call['price']:.4f}, delta {call['delta']:.4f}, vega {call['vega']:.4f}")
print(f"Put {put['price']:.4f}, delta {put['delta']:.4f}")
iv = ImpliedVolatility(market_price=3.50, S=100, K=105, T=0.25, r=0.05, option_type="call")
print(f"Implied volatility {iv:.4f}")BondPricer().price_bond returns price, duration, and modified duration in one call.
from meridianalgo import BondPricer, YieldCurve
pricer = BondPricer()
bond = pricer.price_bond(
face_value=1000, coupon_rate=0.05, yield_to_maturity=0.06,
years_to_maturity=10, frequency=2,
)
print(f"Price {bond['price']:.4f}")
print(f"Duration {bond['duration']:.4f}")
print(f"Modified duration {bond['modified_duration']:.4f}")
curve = YieldCurve()
for maturity, rate in [(0.25, 0.04), (1, 0.045), (2, 0.048), (5, 0.052), (10, 0.055), (30, 0.058)]:
curve.add_point(maturity, rate)
curve.build_curve(method="nelson_siegel")
print(f"7 year rate interpolated {curve.get_yield(7):.4f}")
print(f"5y5y forward rate {curve.get_forward_rate(5, 10):.4f}")from meridianalgo import PerformanceAnalyzer, BenchmarkAnalytics, ActiveShare, BrinsonAttribution
analyzer = PerformanceAnalyzer(portfolio_returns, benchmark=spy_returns, risk_free_rate=0.05)
metrics = analyzer.calculate_all_metrics()
analytics = BenchmarkAnalytics(
portfolio_returns=portfolio_returns, benchmark_returns=spy_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)
print(f"Active share {active_share:.2%}, {ActiveShare.categorize(active_share)}")
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}")The schedulers are built with the order size and a time window, then driven slice by slice as the market moves.
from meridianalgo import VWAP, TWAP, POV
vwap = VWAP(total_quantity=10_000, start_time="09:30", end_time="16:00")
twap = TWAP(total_quantity=10_000, duration_minutes=60, slice_interval_minutes=5)
pov = POV(total_quantity=10_000, target_pov=0.10)
slice_order = vwap.execute_slice(
current_time=now, market_volume=500_000, market_price=100.0, max_participation=0.1,
)More than forty indicators ship as plain functions on numpy and pandas. They need no extras.
import meridianalgo as ma
rsi = ma.RSI(prices, period=14)
macd = ma.MACD(prices)
upper, mid, lower = ma.BollingerBands(prices, period=20)
atr = ma.ATR(high, low, close, period=14)import meridianalgo as ma
stat_arb = ma.StatisticalArbitrage(prices)
zscore = stat_arb.calculate_zscore(window=21) # rolling spread z score
result = stat_arb.calculate_cointegration(prices["KO"], prices["PEP"]) # needs the ml extraThe cointegration test relies on statsmodels, so install the ml extra to use it. The z score helper runs on the core install.
The backtesting engine is event driven. It processes market, signal, order, and fill events through a portfolio and order manager rather than a single run call.
from meridianalgo import BacktestEngine
engine = BacktestEngine(initial_capital=100_000, commission=0.001, slippage=0.0005)
# feed market data, submit orders, and read metrics through the event API
metrics = engine.get_performance_metrics()The machine learning models need the ml extra. With it installed you get LSTM and GRU predictors, walk forward cross validation, and feature engineering.
from meridianalgo.ml import FeatureEngineer, WalkForwardValidator
from sklearn.ensemble import RandomForestClassifier
features = FeatureEngineer().create_features(
prices, features=["returns", "rsi", "macd", "volume_ratio", "volatility", "momentum"],
)
labels = (returns.shift(-1) > 0).astype(int)
results = WalkForwardValidator().validate(
features, labels,
model=RandomForestClassifier(n_estimators=200, random_state=42),
train_window=252, test_window=21,
)
print(f"Average accuracy {results['accuracy'].mean():.2%}")meridianalgo version # show installed version
meridianalgo info # show module availability and loaded extras
meridianalgo demo # run a portfolio optimization demo
meridianalgo metrics AAPL --period 2y # compute metrics for a tickerIf the meridianalgo command is not found, your Python scripts directory is not on your PATH (common outside an activated virtualenv). Either add it to PATH, or use the equivalent module form, which always works:
python -m meridianalgo version
python -m meridianalgo info
python -m meridianalgo demo
python -m meridianalgo metrics AAPL --period 2yMeasured on the test stack described in the test results document, on a 6 asset, 1000 day synthetic set.
| Operation | Detail | Time |
|---|---|---|
| Mean variance max sharpe | 6 assets | 3.8 ms |
| Hierarchical risk parity | 6 assets | 12.3 ms |
| Black Scholes price and greeks | single option | 0.5 ms |
| Merton model calibration | single firm | 1.7 ms |
| GBM simulation | 100k paths, 252 steps | around 0.3 s |
| Heston simulation | 5k paths, 252 steps | 40.7 ms |
| GARCH fit | 1000 observations | 77.2 ms |
| Realized volatility, five estimators | 1000 days | 4.1 ms |
| Full performance metrics | 1000 days | 3.6 ms |
For the full methodology and the complete pass and fail breakdown see docs/TEST_RESULTS.md.
meridianalgo/
├── portfolio/ mean variance, HRP, Black Litterman, Kelly, CPPI
├── risk/ VaR, CVaR, stress testing, scenario analysis
├── credit/ Merton model, CDS, Z spread, expected loss
├── volatility/ GARCH, realized vol with five estimators, HAR RV, regimes
├── monte_carlo/ GBM, Heston, jump diffusion, CIR, variance reduction
├── derivatives/ Black Scholes, greeks, implied vol, exotics
├── fixed_income/ bond pricing, yield curves, credit spreads
├── analytics/ performance metrics, benchmark attribution
├── backtesting/ event driven engine, order management, slippage
├── ml/ LSTM and GRU, walk forward CV, feature engineering
├── execution/ VWAP, TWAP, POV, implementation shortfall
├── quant/ stat arb, pairs trading, regime detection, HFT
├── signals/ RSI, MACD, Bollinger Bands, forty plus indicators
├── liquidity/ order book, bid ask spread, market impact
├── factors/ Fama French, PCA, alpha generation
├── data/ yfinance, Polygon, streaming, storage
└── utils/ logging, validation, visualization
| Extra | Packages | Enables |
|---|---|---|
ml |
scikit-learn, torch, statsmodels, hmmlearn | LSTM models, walk forward CV, HMM regime, cointegration |
optimization |
cvxpy, cvxopt | convex portfolio optimization, CVaR minimization |
volatility |
arch | GARCH, EGARCH, GJR GARCH by maximum likelihood |
data |
lxml, beautifulsoup4, polygon-api-client | Polygon data, web scraping |
distributed |
ray, dask | parallel backtesting and optimization |
all |
everything above | the full feature set |
- API reference at meridianalgo.readthedocs.io
- Per module guide in docs/MODULES.md
- Quickstart in docs/quickstart.md
- Test results in docs/TEST_RESULTS.md
- Changelog in CHANGELOG.md
See CONTRIBUTING.md. Pull requests are welcome.
MIT License. See LICENSE.
This software is for research and educational use. Trading financial instruments carries a substantial risk of loss. Past performance does not guarantee future results. The authors accept no responsibility for financial losses that arise from use of this software.