Skip to main content

Indicators & analytics

Why no TA-Lib

The original project's reference design pulled in TA-Lib, a C library that needs a compiler and system headers to install. That conflicts with the "easy to try" goal. Everything TA-Lib offered here is a few lines of pandas/numpy, so the project ships its own.

The payoff: uv sync is the entire install, the Dockerfile has no build toolchain, and the math is right there to read.

src/indicators/indicators.py

Pure, side-effect-free functions:

FunctionNotes
calculate_sma(series, period)simple moving average
calculate_ema(series, period)exponential moving average
calculate_rsi(series, period=14)RSI in [0, 100]
calculate_atr(df, period=14)average true range
calculate_volume_spike(volume, price, ...)boolean: volume > MA×threshold and price move > threshold
calculate_beta(symbol_close, benchmark_close)cov/var of returns vs a benchmark (used by BetaSizer)

Each takes Series/DataFrames and returns new ones, leaving inputs untouched.

src/analytics/

Performance accounting lives here, not in the strategy or engine.

metrics.py — primitives

returns_from_equity, sharpe_ratio, sortino_ratio, max_drawdown, win_rate, profit_factor, calmar_ratio. Every function is defensive: empty or degenerate inputs return 0.0 (or inf for a ratio with a zero denominator) rather than raising. These are shared by the backtester and the scanner.

performance.py — backtest metrics

compute_backtest_metrics(trades_df, equity_curve, initial_capital, final_capital, market_data) turns a trade log into a flat metrics dict (total/buy-hold return, Sharpe/Sortino/Calmar, drawdown, win rate, profit factor, average/largest win & loss). build_equity_curve accumulates daily P&L.

reporting.py — rendering

Formats a metrics dict into the aligned text block you see after a backtest. Kept separate from computation so the numbers can be consumed programmatically (the optimizer reads metrics, it doesn't parse text).