Notes on quantitative trading in the Rust programming language

A First Sharpe Ratio in Rust

2nd August 2026 · Rust · 6 min

Chapter three of Chan's Quantitative Trading is where the book stops being career advice and starts being arithmetic. His claim is simple: before a strategy deserves capital, it deserves a backtest, and the backtest reduces to two numbers — the Sharpe ratio and the maximum drawdown. This post computes the first of them, in Rust, against a boring pair of gold ETFs.

What Chan Actually Claims

Chan is careful to sell the Sharpe ratio as a filter, not a promise. A backtested Sharpe below 1 rarely survives transaction costs; near 2, a strategy starts to earn its keep on a monthly basis. The number is a summary of the past, and the past is the only thing a backtest can see — survivorship bias, data-snooping, and dead regimes all inflate it. The working posture is suspicion.

There is nothing like losing all you have in the world for teaching you what not to do.

— Edwin Lefèvre, Reminiscences of a Stock Operator, 1923

Lefèvre's tuition plan works, but it is expensive. The backtest is the cheaper school.

The Arithmetic

From Daily to Annual

The Sharpe ratio is the mean excess return over its own volatility. Computed on daily returns it must be annualized, and with 252 trading days in a year the constant is √252:

S=252·R¯rfσ

where R is the mean daily return, rf the daily risk-free rate, and σ the standard deviation of the daily series. Everything in the formula is an average of things that already happened; nothing in it knows what happens next.

The Risk-Free Leg

The risk-free rate arrives quoted as an annual figure — say the 4% APR on three-month bills — and must be brought down to daily terms before subtraction:

rf = Rf252

A division this simple stays in plain HTML; the radical above earned MathML. That is the whole rule.

The Rust

The lab is deliberately small — one binary crate, no framework, no async:

Cargo.toml
[package]
name = "lsb-lab"
version = "0.1.0"
edition = "2021"

Prices come in as a CSV of daily closes and become a Vec<f64> of simple returns. The ratio itself is a dozen lines:

src/bin/sharpe.rs
/// Annualized Sharpe ratio of a daily return series.
/// After Chan, “Quantitative Trading”, ch. 3.
fn sharpe(daily: &[f64], rf_annual: f64) -> f64 {
    let rf = rf_annual / 252.0;
    let n = daily.len() as f64;
    let mean = daily.iter().sum::<f64>() / n;
    let var = daily.iter()
        .map(|r| (r - mean).powi(2))
        .sum::<f64>() / (n - 1.0);
    (mean - rf) / var.sqrt() * 252.0_f64.sqrt()
}

fn main() {
    let rets = returns("data/gld.csv");
    println!("Sharpe: {:.2}", sharpe(&rets, 0.04));
}

I ran it against both legs , with numbers that should be read as scaffolding, not signal:

Illustrative output; not a recommendation.
StrategyAPRSharpeMax drawdown
Buy & hold GLD9.8%0.61−28.3%
GLD–GDX spread13.1%1.42−9.7%

The spread clears Chan's Sharpe bar; buy-and-hold does not. By his own argument that is exactly when to reach for the suspicion: a 1.42 measured on seven years of one regime is a hypothesis, not an income.

What Breaks Next

The function above is honest but naive: returns() trusts its file, the variance pass allocates nothing but also checks nothing, and 0.04 is a hard-coded lie of convenience.


Next post: the drawdown half of Chan's pair, where the arithmetic is easier and the feelings are worse.