What Quant Trading Actually Is
Quant trading is mathematics applied to markets, repeated often enough that a small edge becomes a business.
The casino, not the gambler
A casino never tries to predict whether one roulette spin will win. It runs the game millions of times with a small, known mathematical edge (2.7% on European roulette) and lets the law of large numbers turn that edge into revenue. Quant firms operate the same way:
- Find an edge. A statistical relationship, pricing inefficiency, or structural feature of the market that gives positive expected value.
- Size it correctly. Big enough to matter, small enough to survive the variance.
- Repeat it thousands of times. Across instruments, across time, across strategies, so the average shows through the noise.
Edges exist because markets are complex systems run by humans and machines that make systematic, repeatable mistakes: overreacting to news, being forced to sell, paying for immediacy, ignoring small-probability events, or simply being slower.
The only equation that matters (at first)
Every trade is a bet with an expected value. If you win \(W\) with probability \(p\) and lose \(L\) with probability \(1-p\):
A 52% win rate on even-money bets with 0.5% costs per round trip sounds like nothing. Do it 2,000 times a year with correct sizing and it compounds into a serious business. Do it once and it is a coin flip. One trial is noise. Two thousand trials, sized correctly, are a business.
The main families of strategies
| Family | Where the edge comes from | Typical horizon |
|---|---|---|
| Market making | Earning the bid-ask spread for providing liquidity, while managing inventory and avoiding informed traders. | µs – minutes |
| Statistical arbitrage | Mean reversion between related assets (pairs, baskets, factor residuals). | minutes – weeks |
| Momentum / trend | Under-reaction and herding: winners keep winning for a while. | days – months |
| Factor investing | Persistent risk premia: value, quality, low volatility, carry. | months – years |
| Volatility / options | Implied volatility tends to exceed realised volatility; mispriced skew and term structure. | days – months |
| Event-driven | Systematic reactions to earnings, index rebalances, macro releases, mergers. | minutes – weeks |
| Latency arbitrage / HFT | Being first to react to information across venues. | ns – ms |
| Prediction markets | Implied probabilities that deviate from what the data supports; cross-market arbitrage. | hours – months |
agents.surf catalogues 200 of these strategies, each with the math, a test plan, and a Rust signal layer. They are worked examples of the families above, from factor investing out to market making and latency arbitrage.
Discretionary vs. systematic
A discretionary trader decides each trade. A systematic (quant) trader decides the rules, tests them on history, and lets a machine execute. The quant's advantage is that every rule can be measured, falsified, and improved. The quant's danger is that a machine will execute a bad rule perfectly, thousands of times, at 3 AM. That is why most of this guide is about testing and risk, not about clever signals.
Once the rule exists, someone still has to turn it into a trading bot. The next lecture is that step: what has to be specified and tested before the code, including when the strategy is the fast kind.
From the Idea to a Trading Bot
You now know what an edge is, and you have 200 worked examples on this site. The remaining question is how an idea becomes a system: data, tests, a signal, and something that can run without you watching it.
Why a chat is not enough
A coding chat will write a trading script the moment you describe a strategy. It will also skip the tests, charge costs on the wrong bar, and forget the kill switch. That is a nuisance for a slow daily strategy. It is fatal for anything on the short end of the table you just read, where a market-making or HFT path lives or dies on latency, inventory, and whether the code did exactly what the spec said.
The fix is a harness: a written spec, tests that fail until the dangerous cases are handled, then the code, then a review of the code against the spec. You stay at the points where a decision is required. Nothing ships because a model was confident.
Where AgenKit fits
AgenKit is that harness, inside the coding tool you already use: Claude Code, Cursor, Codex, OpenCode, Antigravity, GitHub Copilot, and Kimi Code. You start it with /agenkit, or @agenkit in Copilot, and describe the strategy in plain language. You do not start by opening a Rust file.
The 200 strategies on agents.surf already use a Rust signal layer. AgenKit can build that layer from the spec, whether the strategy is a daily mean-reversion book or a low-latency execution path. You do not need to have been a Rust engineer first. You need to read what it claims and reject it when the claim is wrong.
- A spec. The signal, the data it is allowed to see, the costs, and the risk limits. You approve this before any code exists.
- Failing tests. Look-ahead, missing costs, and a broken risk limit fail on purpose. The build cannot go green by skipping them.
- The trading bot. Implementation follows the spec, including the Rust signal. A reviewer blocks code that compiles and still disagrees with the math.
- Paper trading. The first run is paper. Live capital, including anything that needs to be fast, is a later decision with the same code.
A plain prompt is enough to start: a daily mean-reversion strategy on liquid US stocks, costs on every fill, no look-ahead, and a stop if the drawdown hits the limit in the spec. The build walkthrough later in this guide shows the commands. The license key is on agenkit.xyz.
npx agenkit activate <license-key> npx agenkit install engineering-kit
Node 18 or newer. After it installs, /agenkit is how you start.
The Roles Inside a Quant Firm
"Quant" is four or five different jobs. Preparation differs a lot between them, so pick a direction early.
Quant researcher
Finds patterns in huge datasets, builds predictive models, designs strategies. PhD-level math, stats and ML, or an exceptional undergraduate record in a hard quantitative field.
Quant trader
Runs capital in real time using researchers' models. Fast probabilistic thinking, strong mental math, calm decisions under pressure. Highest pay variance in the industry.
Quant developer
Builds trading platforms, execution engines, real-time data pipelines and low-latency systems. Production C++, Rust and Python.
Risk quant
Model validation, value at risk, stress testing, regulatory work. Most stable path, most predictable pay, lower ceiling.
The fastest-growing hybrid is the AI / ML quant: deep learning for signal generation, alternative data at scale, models deployed straight into live trading. It sits between quant research and ML engineering, and it is where much of the 2025–2026 hiring growth has been.
What it pays
Figures below are publicly reported ranges from 2025 compensation data and press coverage. Treat them as orders of magnitude, not offers.
| Level | Top tier (Jane Street, Citadel, HRT) | Mid tier (Two Sigma, DE Shaw, etc.) |
|---|---|---|
| New grad | $300K – $500K+ (QR up to ~$650K) | $250K – $350K |
| Mid career (3–7 yrs) | $550K – $950K | $350K – $625K |
| Senior (8+ yrs) | $1M – $3M+ | $575K – $1.2M |
| Star trader / PM | $3M – $30M+ | Highly variable |
Jane Street's average compensation per employee was reported at roughly $1.4M for the first half of 2025. Interns at firms like IMC have earned the annualised equivalent of more than $240K. These numbers exist for one reason: the people who build and run edges generate far more than they cost.
Which role fits you
| If you are… | Aim for | Double down on |
|---|---|---|
| Strong in math / stats, enjoy open research | Quant researcher | Statistics, ML, research projects you can defend |
| Fast, competitive, love games and betting | Quant trader | Probability puzzles, mental math, market-making games |
| A software engineer who loves performance | Quant developer | C++/Rust, systems, networking, data engineering |
| Careful, rigorous, like regulation and models | Risk quant | Statistics, derivatives pricing, stress testing |
| Building for yourself, not an employer | Independent systematic trader | All of the above at a smaller scale, plus engineering discipline |
You do not need an Ivy League name or a finance degree. You need mathematical ability, programming skill, and proof you can turn both into something that works. The rest of this guide is how you build that proof.
What a Trading System Actually Is
Before the math, see the whole machine. Every quant system, from a weekend bot to a Citadel desk, has the same six stages.
The same six boxes are also how you should split the work when an AI agent writes the code. An engineering harness like AgenKit assigns each stage to a specialist (the green labels above), so data, testing and risk each get an owner instead of being an afterthought in one long prompt.
The six stages in one paragraph each
1. Data. Prices, order books, fundamentals, alternative data. Collected from exchanges or vendors, cleaned, aligned to a single clock, stored so the past can be replayed exactly. Bad data is the most common silent killer of strategies.
2. Signal. A rule or model that turns data into a forecast: "this spread will narrow", "this stock will outperform its sector next week", "this contract is underpriced by 4 cents". This is where probability, statistics and ML live.
3. Backtest. Replay history as if you were trading it, with realistic costs, delays and constraints. The purpose is not to find a great curve. It is to try hard to kill the idea and see whether it survives.
4. Risk and sizing. How much to bet on each signal, the maximum loss you will tolerate, and the hard limits that stop the machine when something breaks.
5. Execution. Turning target positions into orders and fills: which venue, which order type, how fast, how to avoid moving the price against yourself.
6. Monitoring. Live PnL, slippage vs. backtest, data gaps, model drift, latency, alerts. The loop closes when what you learn live becomes the next research question.
Beginners spend 90% of their time on stage 2. Professionals spend most of theirs on stages 1, 3, 4 and 6. A mediocre signal with excellent data, testing and risk will outlive a brilliant signal with sloppy engineering.
Why Probability Comes First
Everything in quantitative finance comes down to one question: what are the odds, and are they in my favour?
Options pricing is a probability problem. Market making is a probability problem. Position sizing is a probability problem. Prediction markets are probability problems with a price tag. If you skip this layer, nothing above it will hold.
Conditional thinking
Most people think in absolutes. Quants think in conditionals: given what I know right now, how likely is this?
Suppose a market moves sharply after 40% of economic announcements. That is the base rate, and on its own it is not tradable. But on days when options implied volatility is elevated beforehand, a sharp move happens 68% of the time. That conditional probability is a usable signal. The 40% mixes signal and noise in a way you cannot separate without conditioning.
Bayes' theorem: updating beliefs
A model says a stock is worth $50. Earnings come out 3% above estimate. The posterior shifts up. The traders who update fastest and most accurately when new information arrives are the ones who get paid. In practice you rarely compute the denominator by hand: you use conjugate priors (below) or Monte Carlo sampling.
Random variables and distributions you must know
| Distribution | Models | Where you will meet it |
|---|---|---|
| Bernoulli / Binomial | Win/lose, number of wins in n trials | Hit rates, prediction market outcomes |
| Geometric | Trials until first success | Interview puzzles, waiting times |
| Poisson | Counts of rare events per interval | Order arrivals, trade counts, jumps |
| Exponential | Time between Poisson events | Time to next fill, queue modelling |
| Normal | Sums of many small effects | Everything, as a first approximation |
| Log-normal | Products of many small effects | Asset prices under GBM / Black-Scholes |
| Student-t | Normal with fat tails | Real daily returns |
| Beta | A probability that is itself uncertain | Bayesian win-rate estimates, prediction markets |
Expected value and variance
If your strategy has positive expected value and you can survive the variance long enough, you will make money. If you size too large relative to the variance, you go broke before the expected value has time to show up. Chapter 12 makes this precise with the Kelly criterion.
Two theorems justify the whole business:
- Law of large numbers: the sample average converges to the expected value as \(n \to \infty\).
- Central limit theorem: the sample average is approximately normal with standard error \(\sigma/\sqrt{n}\). To halve your uncertainty you need four times the data.
Code: simulate, then update like a Bayesian
import numpy as np
from scipy import stats
rng = np.random.default_rng(42)
# 1) Law of large numbers: running average converges to the true p
flips = rng.random(10_000) < 0.55
running = np.cumsum(flips) / np.arange(1, flips.size + 1)
print(f"after 100: {running[99]:.3f} after 10,000: {running[-1]:.4f} (true 0.55)")
# 2) Bayesian updater for a strategy's win rate (Beta-Binomial, conjugate prior)
# Prior Beta(a, b): start sceptical, centred on 50% with the weight of 20 fake trades
a, b = 10, 10
trades = rng.random(60) < 0.57 # 60 live trades, true win rate 57%
wins, losses = trades.sum(), (~trades).sum()
post = stats.beta(a + wins, b + losses)
print(f"wins {wins}/{trades.size}")
print(f"posterior mean win rate: {post.mean():.3f}")
print(f"95% credible interval: {post.ppf(0.025):.3f} – {post.ppf(0.975):.3f}")
print(f"P(true win rate > 50%): {1 - post.cdf(0.5):.3f}")Homework (3–4 weeks at 2 hours a day)
- Read Blitzstein & Hwang, Introduction to Probability (free from Harvard). Work every problem in chapters 1–6. Watch the matching Stat 110 lectures.
- Code: simulate 10,000 coin flips and plot the running average. Then a Bayesian updater that takes a prior and returns a posterior.
- Puzzle: you flip a fair coin until you get two heads in a row. What is the expected number of flips? (Answer in chapter 19. Try it first.)
Why Most Signals Are Noise
Once you speak probability, you need to learn to listen to data. The first lesson: most of what looks like signal is noise.
Hypothesis testing
Your strategy backtests at 15% a year. Is it real? Set it up as a test:
- Null hypothesis \(H_0\): the strategy has zero true expected return.
- Compute a test statistic, e.g. the t-statistic of mean daily return: \(t = \bar r / (s/\sqrt{n})\).
- The p-value is the probability of seeing a result this strong if \(H_0\) were true.
A handy link: the t-statistic of daily returns is roughly the annualised Sharpe ratio times \(\sqrt{\text{years}}\). A Sharpe of 1 over 4 years gives \(t \approx 2\), which is only borderline significant.
The multiple comparisons trap
Test 1,000 random strategies and about 50 will show \(p < 0.05\) purely by chance. That is the single most common reason backtests look great and live results are terrible. Run the lab below: every strategy is pure noise, yet the best one looks like a hedge fund.
Fixes, from simplest to most rigorous:
- Bonferroni: divide your significance threshold by the number of tests, \(\alpha/m\). Simple and very conservative.
- Benjamini–Hochberg: controls the false discovery rate instead. Sort p-values and accept \(p_{(k)} \le \tfrac{k}{m}\alpha\).
- Raise the bar: Harvey, Liu and Zhu argue a new factor needs \(t > 3\), not 2, given how many have been tried.
- Deflated Sharpe ratio (chapter 11): adjusts the Sharpe for the number of trials, skew and fat tails.
- Write down every trial. If you do not know how many things you tried, you cannot correct for them.
Your first ten strategies will almost all be noise. Accept that now and it will save you a lot of money.
Regression: decomposing returns
Linear regression \(y = X\beta + \varepsilon\) is the workhorse. In finance you regress strategy returns on known risk factors:
If \(\alpha\) is zero after the factors, your "edge" is just disguised market, size or value exposure, which anyone can buy cheaply in an ETF. Financial returns are autocorrelated and heteroskedastic, so default OLS standard errors are wrong. Use Newey–West (HAC) standard errors.
Maximum likelihood estimation
MLE fits GARCH volatility models, jump-diffusions, and option-pricing models to market quotes. It is asymptotically efficient: for large samples no consistent estimator has lower variance (the Cramér–Rao bound).
Fat tails: why the normal distribution lies
Real daily returns have far more extreme days than a normal distribution predicts. A "5-sigma" day should happen once every ~14,000 years under normality; equity markets produce several per decade. Model tails with a Student-t, and stress-test with history, never with a bell curve alone.
import numpy as np, pandas as pd, yfinance as yf, statsmodels.api as sm
from scipy import stats
from statsmodels.stats.multitest import multipletests
import pandas_datareader.data as web
# 1) Fat tails: fit a Student-t by MLE to real SPY returns
r = yf.download("SPY", start="2010-01-01", auto_adjust=True, progress=False)["Close"].squeeze().pct_change().dropna()
df_, loc, scale = stats.t.fit(r)
print(f"normality test p = {stats.normaltest(r).pvalue:.1e} (reject: fat tails)")
print(f"Student-t degrees of freedom = {df_:.2f} (normal would be infinite)")
# 2) Fama-French 3-factor regression with Newey-West standard errors
ff = web.DataReader("F-F_Research_Data_Factors_daily", "famafrench", start="2010-01-01")[0] / 100
aapl = yf.download("AAPL", start="2010-01-01", auto_adjust=True, progress=False)["Close"].squeeze().pct_change()
data = pd.concat([aapl.rename("r"), ff], axis=1, join="inner").dropna()
y = data["r"] - data["RF"]
X = sm.add_constant(data[["Mkt-RF", "SMB", "HML"]])
fit = sm.OLS(y, X).fit(cov_type="HAC", cov_kwds={"maxlags": 5})
print(fit.summary().tables[1])
print(f"annualised alpha: {fit.params['const'] * 252:.2%}")
# 3) Multiple testing: 200 random 'strategies', how many survive?
rng = np.random.default_rng(0)
pvals = [stats.ttest_1samp(rng.normal(0, 0.01, 756), 0).pvalue for _ in range(200)]
print("naive p<0.05:", sum(p < 0.05 for p in pvals))
print("after Benjamini-Hochberg:", multipletests(pvals, alpha=0.05, method="fdr_bh")[0].sum())Homework (4–5 weeks)
- Read Wasserman, All of Statistics, chapters 1–13.
- Download real returns, test normality (it will fail), fit a t-distribution by MLE.
- Run a Fama–French regression on a portfolio. Is there any alpha left?
- Permutation test: shuffle your strategy's signal dates 10,000 times and see where the real result lands in the shuffled distribution.
Why Linear Algebra Runs Everything
Portfolio construction, PCA, factor models, covariance estimation, neural networks: all of it is matrices.
Thinking in matrices
A covariance matrix \(\Sigma\) captures how every asset moves with every other. For 500 stocks it is \(500 \times 500\) with 125,250 unique entries. Yet portfolio risk collapses to one expression:
Vocabulary to master: vectors and dot products, matrix multiplication, transpose and inverse, rank, positive (semi)definite matrices, projections, eigenvalues and eigenvectors, singular value decomposition (SVD), and Cholesky decomposition (used to simulate correlated returns).
Eigenvalues: what actually matters
Decompose the covariance of a 500-stock universe and the first eigenvector is roughly "the market". The first five typically explain most of the variance. The long tail is mostly noise. That is principal component analysis (PCA), and it is the basis of factor investing and statistical arbitrage.
import numpy as np, yfinance as yf, cvxpy as cp
tickers = ["AAPL","MSFT","NVDA","AMZN","GOOGL","META","JPM","XOM","JNJ","PG","KO","PEP","WMT","HD","CVX"]
px = yf.download(tickers, start="2019-01-01", auto_adjust=True, progress=False)["Close"].dropna()
R = px.pct_change().dropna()
mu = R.mean().values * 252
cov = R.cov().values * 252
# PCA via eigendecomposition of the covariance matrix
vals, vecs = np.linalg.eigh(cov)
vals, vecs = vals[::-1], vecs[:, ::-1]
print("variance explained by PC1..PC5:", np.round(vals[:5] / vals.sum(), 3))
# Markowitz: minimise variance subject to a return target and limits
w = cp.Variable(len(tickers))
target = np.percentile(mu, 60)
prob = cp.Problem(
cp.Minimize(cp.quad_form(w, cov)),
[mu @ w >= target, cp.sum(w) == 1, w >= -0.05, w <= 0.25],
)
prob.solve()
ret, vol = mu @ w.value, np.sqrt(w.value @ cov @ w.value)
print(f"return {ret:.2%} vol {vol:.2%} Sharpe(rf=4%) {(ret - 0.04) / vol:.2f}")
print(dict(zip(tickers, np.round(w.value, 3))))Unconstrained Markowitz with sample estimates is famously unstable: it piles into whatever had the luckiest past returns. Professionals shrink the covariance (Ledoit–Wolf), cap weights, or skip expected returns entirely and size by risk. The math is exact; your inputs are not.
Homework (4–6 weeks)
- Watch all of Gilbert Strang's MIT 18.06. Not optional.
- Work through Strang's Introduction to Linear Algebra. Optional and beautiful: 3Blue1Brown's Essence of Linear Algebra for intuition.
- Run PCA on S&P 500 returns. Plot the eigenvalue spectrum. Name the top three components.
- Implement Markowitz from scratch, then compare it with Ledoit–Wolf shrinkage (
sklearn.covariance.LedoitWolf).
Calculus and Optimisation
Nearly every problem in quant finance is "maximise something subject to constraints". Calculus tells you how things change; optimisation tells you what to do about it.
Derivatives, gradients and Taylor expansion
Derivatives show up in every option Greek and every neural network's backpropagation. The tool that connects them is the Taylor expansion:
Gradient descent
Convex optimisation
A convex problem (convex objective, convex constraints) has one global minimum, and solvers find it fast and reliably. Most portfolio construction, risk budgeting and execution problems can be written as convex programs. Recognising convexity, and knowing when a relaxation of a non-convex problem is tight, is a core skill. Learn Lagrange multipliers and the KKT conditions: they explain why the optimiser chose what it chose.
import numpy as np, cvxpy as cp
# 1) Gradient descent from scratch on the Rosenbrock function (minimum at 1, 1)
def grad(x, y):
return np.array([-2 * (1 - x) - 400 * x * (y - x**2), 200 * (y - x**2)])
p = np.array([-1.2, 1.0])
for _ in range(50_000):
p -= 1e-3 * grad(*p)
print("gradient descent found:", np.round(p, 4))
# 2) Rebalance with transaction costs: a convex problem
rng = np.random.default_rng(1)
n = 8
mu = rng.uniform(0.03, 0.12, n)
A = rng.normal(size=(n, n)) * 0.1
cov = A @ A.T + np.eye(n) * 0.01
w_old = np.full(n, 1 / n)
w = cp.Variable(n)
risk_aversion, cost = 4.0, 0.002 # 20 bps per unit traded
objective = cp.Maximize(mu @ w - risk_aversion * cp.quad_form(w, cov) - cost * cp.norm1(w - w_old))
cp.Problem(objective, [cp.sum(w) == 1, w >= 0, w <= 0.3]).solve()
print("new weights:", np.round(w.value, 3), " turnover:", round(np.abs(w.value - w_old).sum(), 3))Homework (4–5 weeks)
- Read Boyd & Vandenberghe, Convex Optimization (free), chapters 1–5. The Stanford EE364A lectures are excellent companions.
- Implement gradient descent and Newton's method from scratch. Compare their convergence on Rosenbrock.
- Solve a portfolio problem with transaction costs in
cvxpyand watch how turnover falls as cost rises.
Stochastic Calculus: Where You Become a Quant
This is the layer where a derivative price is derived, rather than taken from a formula sheet.
Brownian motion
A Brownian motion (Wiener process) \(W_t\) is a continuous-time random walk with four properties:
- \(W_0 = 0\).
- Increments are normal: \(W_t - W_s \sim \mathcal N(0,\, t-s)\) for \(t > s\).
- Non-overlapping increments are independent.
- Paths are continuous but nowhere differentiable.
The one fact everything else depends on: \(dW_t\) has size \(\sqrt{dt}\), so \((dW_t)^2 = dt\). In ordinary calculus a squared small change is negligible. Here it is first order, and you cannot drop it.
Geometric Brownian motion: the standard stock model
Itô's lemma: the chain rule for randomness
Deriving Black–Scholes in four steps
Follow along with pen and paper. Let \(V(S,t)\) be an option's price.
- Apply Itô to \(V\): you get a \(dt\) term and a \(dW_t\) term.
- Hedge. Build \(\Pi = V - \frac{\partial V}{\partial S} S\). The \(dW_t\) terms cancel exactly, so the portfolio is locally riskless.
- No arbitrage. A riskless portfolio must earn the risk-free rate: \(d\Pi = r\Pi\, dt\).
- Substitute and rearrange to get the PDE:
Solving it for a European call with strike \(K\) and expiry \(T\):
The Greeks
| Greek | Definition | What it tells a desk |
|---|---|---|
| Delta Δ | \(\partial V/\partial S\) | Hedge ratio: shares to hold per option |
| Gamma Γ | \(\partial^2 V/\partial S^2\) | How fast delta changes, so how often to re-hedge |
| Theta Θ | \(\partial V/\partial t\) | Time decay: the daily cost of holding long options |
| Vega ν | \(\partial V/\partial \sigma\) | Volatility exposure: the core of vol-trading desks |
| Rho ρ | \(\partial V/\partial r\) | Interest-rate sensitivity |
import numpy as np
from scipy.stats import norm
def black_scholes(S, K, T, r, sigma, kind="call"):
d1 = (np.log(S / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if kind == "call":
return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
def monte_carlo(S0, K, T, r, sigma, n=500_000, seed=0):
"""Risk-neutral simulation: the drift is r, not mu."""
Z = np.random.default_rng(seed).standard_normal(n)
ST = S0 * np.exp((r - sigma**2 / 2) * T + sigma * np.sqrt(T) * Z)
pay = np.exp(-r * T) * np.maximum(ST - K, 0)
return pay.mean(), pay.std() / np.sqrt(n)
def greeks(S, K, T, r, sigma):
d1 = (np.log(S / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
return {
"delta": norm.cdf(d1),
"gamma": norm.pdf(d1) / (S * sigma * np.sqrt(T)),
"theta": -S * norm.pdf(d1) * sigma / (2 * np.sqrt(T)) - r * K * np.exp(-r * T) * norm.cdf(d2),
"vega": S * np.sqrt(T) * norm.pdf(d1),
"rho": K * T * np.exp(-r * T) * norm.cdf(d2),
}
S, K, T, r, sigma = 100, 105, 1.0, 0.05, 0.2
mc, se = monte_carlo(S, K, T, r, sigma)
print(f"Black-Scholes {black_scholes(S, K, T, r, sigma):.4f} Monte Carlo {mc:.4f} ± {se:.4f}")
for k, v in greeks(S, K, T, r, sigma).items():
print(f"{k:>6}: {v: .5f}")Homework (6–8 weeks, the hardest level)
- Read Shreve, Stochastic Calculus for Finance I and II: the gold standard. A gentler alternative: Arguin, A First Course in Stochastic Calculus.
- Derive: apply Itô to \(\ln S\) under GBM and get the \(-\sigma^2/2\).
- Derive the full Black–Scholes PDE from the delta-hedging argument, without notes.
- Code Black–Scholes and Monte Carlo and confirm they converge. Then price an Asian option, which has no closed form, by Monte Carlo.
Time Series and Signals
Markets are sequences. The bridge from math to money is learning which sequences are predictable and which only look predictable.
Returns, not prices
Prices wander and have no stable mean, so statistics computed on them are meaningless. Work with returns:
Stationarity
A stationary series has a stable mean and variance, so its past says something about its future. Returns are roughly stationary; prices are not. The Augmented Dickey–Fuller (ADF) test checks for a unit root. Most features in a model should be stationary: z-scores, returns, spreads, ratios. López de Prado's fractional differentiation keeps as much memory as possible while still passing the test.
The core models
| Model | Equation | Used for |
|---|---|---|
| AR(1) | \(x_t = \phi x_{t-1} + \varepsilon_t\) | Short-term autocorrelation, mean reversion |
| GARCH(1,1) | \(\sigma_t^2 = \omega + \alpha\varepsilon_{t-1}^2 + \beta\sigma_{t-1}^2\) | Volatility clustering: calm follows calm, storms follow storms |
| Ornstein–Uhlenbeck | \(dX_t = \theta(\mu - X_t)dt + \sigma dW_t\) | Mean-reverting spreads; half-life \(= \ln 2/\theta\) |
| Cointegration | \(y_t - \beta x_t\) is stationary | Pairs and basket trading |
| Kalman filter | state-space update | Time-varying hedge ratios and noisy signals |
Two forces: momentum and mean reversion
Nearly every price-based signal is one of two bets. Momentum: what went up keeps going up for a while (trend following, cross-sectional momentum). Mean reversion: what stretched too far snaps back (pairs, statistical arbitrage, short-term reversal). They work at different horizons and often on the same asset. Knowing which one your signal bets on tells you when it will fail.
import numpy as np, pandas as pd, yfinance as yf, statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller, coint
px = yf.download(["KO", "PEP"], start="2015-01-01", auto_adjust=True, progress=False)["Close"].dropna()
y, x = np.log(px["KO"]), np.log(px["PEP"])
print("ADF p-value on KO price (should be high):", round(adfuller(y)[1], 3))
print("Engle-Granger cointegration p-value: ", round(coint(y, x)[1], 3))
# Rolling hedge ratio and spread z-score. Only past data enters each estimate.
win = 252
beta = y.rolling(win).cov(x) / x.rolling(win).var()
spread = y - beta * x
z = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()
# Half-life of mean reversion from an AR(1) fit on the spread
s = spread.dropna()
phi = sm.OLS(s.diff().dropna(), sm.add_constant(s.shift(1).dropna())).fit().params.iloc[1]
print(f"half-life ≈ {np.log(2) / -phi:.1f} days")
# Positions: -1 short spread, +1 long spread, 0 flat. Hysteresis avoids flip-flopping.
pos = pd.Series(0.0, index=z.index)
for t in range(1, len(z)):
prev, zt = pos.iloc[t - 1], z.iloc[t]
if np.isnan(zt): continue
if prev == 0 and zt > 2: pos.iloc[t] = -1
elif prev == 0 and zt < -2: pos.iloc[t] = 1
elif prev != 0 and (abs(zt) < 0.5 or abs(zt) > 4): pos.iloc[t] = 0
else: pos.iloc[t] = prev
print(pos.value_counts())Why Data Is the Silent Killer
More strategies fail on the data than on the idea. Gaps, wrong timestamps and survivorship bias do not announce themselves.
The types of data
| Type | What it is | Good for |
|---|---|---|
| OHLCV bars | Open, high, low, close, volume per interval (1m, 1h, 1d) | Learning, swing and daily strategies |
| Trades (tick) | Every print: time, price, size, aggressor side | Intraday signals, volume profiles, realistic fills |
| Quotes / L1 | Best bid and ask with sizes | Spread, slippage modelling |
| Order book L2 / L3 | Depth by price level (L2) or every individual order (L3) | Market making, order-flow imbalance, HFT |
| Fundamentals | Earnings, balance sheets, estimates | Factor and value strategies |
| Options chains | Prices, implied vols, Greeks across strikes and expiries | Volatility trading |
| Alternative data | News, filings, satellite, web traffic, card spend, on-chain | Edges others do not have yet |
Where to get it
| Tier | Sources | Notes |
|---|---|---|
| Free | yfinance, Finnhub, Alpha Vantage, FRED, Ken French library, exchange WebSockets (crypto) | Fine for learning. yfinance is unofficial and can break or rate-limit. |
| Mid-range | Polygon, Databento, Tiingo, Alpaca | Real-time feeds, tick history, cleaner corporate actions. Roughly $30–$300 a month. |
| Institutional | Bloomberg (~$32K/yr), Refinitiv/LSEG, FactSet, direct exchange feeds | The professional standard. Direct feeds need colocation. |
| Prediction / on-chain | Polymarket API, Kalshi API, Alchemy | Public order books and on-chain history |
The seven data sins
- Look-ahead bias. Using information that was not available at decision time: today's close to trade today's close, restated fundamentals, a revised economic number.
- Survivorship bias. Testing only on stocks that exist today ignores every company that went bankrupt or was delisted. Your backtest silently buys only winners.
- Corporate actions. Splits and dividends create fake jumps. Use adjusted prices for returns, raw prices for order sizing.
- Timezones and clocks. Store UTC. Record both the exchange timestamp and your receive timestamp. Daylight-saving bugs are real.
- Silent gaps. A WebSocket that drops for 40 seconds at 2 AM and reconnects without telling you. Detect sequence-number gaps and re-request.
- Bad ticks. Fat-finger prints and stale quotes. Filter with a robust rule such as rejecting returns beyond k × the median absolute deviation.
- Overwriting raw data. Clean into a new table. Never mutate the original. You will need to replay it.
import polars as pl, yfinance as yf
from pathlib import Path
tickers = ["SPY", "QQQ", "IWM", "TLT", "GLD", "KO", "PEP"]
raw = yf.download(tickers, start="2005-01-01", auto_adjust=True, progress=False, group_by="ticker")
frames = []
for t in tickers:
d = raw[t].dropna().reset_index()
frames.append(pl.from_pandas(d).with_columns(pl.lit(t).alias("ticker")))
bars = pl.concat(frames).rename(str.lower)
# Validation: no duplicate (ticker, date), positive prices, flag suspicious jumps
bars = bars.unique(["ticker", "date"]).sort(["ticker", "date"])
assert bars.filter(pl.col("close") <= 0).height == 0
bars = bars.with_columns(pl.col("close").pct_change().over("ticker").alias("ret"))
mad = bars.group_by("ticker").agg((pl.col("ret") - pl.col("ret").median()).abs().median().alias("mad"))
flagged = bars.join(mad, on="ticker").filter(pl.col("ret").abs() > 15 * pl.col("mad"))
print(f"{flagged.height} suspicious bars to inspect by hand")
Path("data").mkdir(exist_ok=True)
bars.write_parquet("data/daily_bars.parquet") # columnar, compressed, fast to scanimport asyncio, json, time, websockets # pip install websockets
URL = "wss://stream.binance.com:9443/ws/btcusdt@bookTicker" # public best bid/ask stream
async def stream():
backoff = 1
while True:
try:
async with websockets.connect(URL, ping_interval=20) as ws:
backoff = 1
last_id = None
async for msg in ws:
recv_ns = time.time_ns()
m = json.loads(msg)
if last_id is not None and m["u"] <= last_id:
continue # stale or out-of-order update
last_id = m["u"]
bid, ask = float(m["b"]), float(m["a"])
bq, aq = float(m["B"]), float(m["A"])
micro = (bid * aq + ask * bq) / (aq + bq)
print(f"{recv_ns} bid {bid:.2f} ask {ask:.2f} spread {ask - bid:.2f} micro {micro:.2f}")
except Exception as e:
print(f"disconnected: {e!r}; reconnecting in {backoff}s")
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 30) # exponential backoff, capped
asyncio.run(stream())This is exactly the kind of work worth handing to an agent harness rather than a single chat prompt. In AgenKit, the Data Engineer designs the schema, gap detection and causal timestamps before any code exists, so a 2 AM disconnect becomes a logged, re-requested gap instead of a silent hole in your history.
Storage that scales
- Parquet + Polars/DuckDB: columnar files partitioned by date and symbol. Fast and free. Start here.
- TimescaleDB / QuestDB / ClickHouse: time-series databases when you need live queries and many writers.
- kdb+/q: the industry standard at banks and HFT firms. Very fast, steep learning curve, expensive.
Why Most Backtests Lie
A backtest is not a tool for finding strategies. It is a tool for killing them. The ones that survive deserve paper money.
Vectorised vs. event-driven
| Vectorised | Event-driven | |
|---|---|---|
| How | Whole arrays at once in pandas/numpy | Replay events one by one through the same code that trades live |
| Speed | Very fast: thousands of parameter sets in seconds | Slower, but realistic |
| Realism | Easy to sneak in look-ahead; crude fills | Order queues, partial fills, latency, rejects |
| Use it for | Research and screening ideas | Validating finalists before going live |
| Tools | vectorbt, pandas, polars | NautilusTrader, backtrader, LEAN/QuantConnect |
Costs: the edge-killer
Every trade pays commission, half the bid-ask spread, slippage, and market impact that grows with size. A common impact model is the square-root law:
Rule of thumb: if your strategy's edge per trade is not at least 2–3× its estimated round-trip cost, it will not survive contact with the market.
Metrics that matter
| Metric | Formula | Rough bar |
|---|---|---|
| Sharpe ratio | \(\sqrt{252}\,\bar r / \sigma_r\) | > 1 decent, > 2 excellent (daily strategies, after costs) |
| Sortino ratio | Sharpe using only downside deviation | Higher than Sharpe if losses are rare |
| Max drawdown | \(\max_t (1 - E_t / \max_{s\le t} E_s)\) | Ask: could I sit through it? |
| Calmar | CAGR / max drawdown | > 0.5 is respectable |
| Hit rate & payoff | % winners, average win / average loss | Read them together, never alone |
| Turnover | Average fraction of the book traded per day | Drives cost sensitivity |
| Capacity | Capital before impact eats the edge | Know it before you scale |
import numpy as np, pandas as pd, yfinance as yf
px = yf.download("SPY", start="2000-01-01", auto_adjust=True, progress=False)["Close"].squeeze()
ret = px.pct_change().fillna(0)
def run(fast=50, slow=200, cost_bps=2.0):
signal = (px.rolling(fast).mean() > px.rolling(slow).mean()).astype(float)
pos = signal.shift(1).fillna(0) # decide on close t, hold from t+1
turnover = pos.diff().abs().fillna(0)
return pos * ret - turnover * cost_bps / 1e4
def report(r, name):
eq = (1 + r).cumprod()
yrs = len(r) / 252
cagr = eq.iloc[-1] ** (1 / yrs) - 1
vol = r.std() * np.sqrt(252)
sharpe = r.mean() / r.std() * np.sqrt(252)
downside = r[r < 0].std() * np.sqrt(252)
mdd = (eq / eq.cummax() - 1).min()
print(f"{name:<14} CAGR {cagr:6.2%} vol {vol:6.2%} Sharpe {sharpe:5.2f} "
f"Sortino {r.mean() * 252 / downside:5.2f} maxDD {mdd:7.2%} Calmar {cagr / -mdd:5.2f}")
report(ret, "buy & hold")
report(run(), "50/200 trend")
report(run(cost_bps=20), "trend @20bps")Walk-forward testing and purging
Never tune parameters on the same data you evaluate them on. Walk-forward analysis re-fits on a rolling training window and tests only on the period that follows. If labels look ahead (say, the next 5 days' return), purge training samples whose label window overlaps the test set, and leave an embargo gap after it. Otherwise information leaks across the boundary.
Deflate your Sharpe
If you tried \(N\) variations, the best one's Sharpe is inflated. Bailey and López de Prado's probabilistic Sharpe ratio asks how likely the true Sharpe exceeds a benchmark \(SR^*\), given track length \(T\), skew \(\gamma_3\) and kurtosis \(\gamma_4\):
The pre-live checklist
- Signal uses only data available at decision time (
shift(1), point-in-time fundamentals, survivorship-free universe). - Realistic costs and slippage, and the result survives doubling them.
- Out-of-sample and walk-forward results are in the same ballpark as in-sample.
- Works across nearby parameters (a plateau, not a spike) and across several assets or regimes.
- You know why it should work: who is on the other side, and why they keep losing.
- You logged how many variants you tried and deflated the Sharpe accordingly.
- Paper trading for weeks matches the backtest's fills and slippage.
If you build with an AI coding agent, turn this checklist into tests, not intentions. A harness like AgenKit writes a failing test for each item first (no look-ahead, costs on every fill) and its reviewer blocks code whose math disagrees with the spec, even when it compiles.
Why Sizing Decides Survival
The signal decides direction. Sizing decides whether you are still in business next year.
The Kelly criterion
Kelly gives the bet size that maximises the long-run growth rate of wealth. For a bet paying \(b\) to 1 with win probability \(p\):
Full Kelly is mathematically optimal and practically brutal: drawdowns of 50% are routine, and it assumes you know \(p\) exactly. You never do. Most professionals bet quarter to half Kelly, which keeps most of the growth with far less pain. Try it:
Push the slider past 2× and the typical bettor loses money while holding a winning edge. At 3× most of them are wiped out. Overbetting a good strategy is one of the most reliable ways to lose money.
Volatility targeting
Measuring tail risk
- Value at Risk (VaR 99%): the loss exceeded on only 1% of days. Easy to report, silent about how bad the worst 1% is.
- Expected Shortfall / CVaR: the average loss within that worst 1%. Better, and convex, so you can optimise it.
- Stress tests: replay 2008, March 2020, the 2010 flash crash, or a 25% overnight gap. Ask what breaks.
Hard limits: the kill switch
Before every order
- Max order size and notional
- Price collar vs. last trade
- Max position per symbol
- Max gross and net exposure
- Order rate limit
While running
- Daily loss limit
- Max drawdown from peak
- Stale data detector
- Position vs. broker reconciliation
When tripped
- Cancel all open orders
- Flatten or freeze positions
- Page a human
- Require manual restart
In 2012 Knight Capital lost about $440M in 45 minutes because of a deployment error that reactivated old code, with no effective kill switch. Risk checks are not paperwork. They are the brakes.
import numpy as np
class RiskGate:
"""Every order passes through check(). Any breach trips the kill switch."""
def __init__(self, max_pos, max_notional, daily_loss_limit, max_dd):
self.max_pos, self.max_notional = max_pos, max_notional
self.daily_loss_limit, self.max_dd = daily_loss_limit, max_dd
self.peak_equity, self.killed = None, False
def update_equity(self, equity, day_start_equity):
self.peak_equity = max(self.peak_equity or equity, equity)
dd = 1 - equity / self.peak_equity
if equity - day_start_equity < -self.daily_loss_limit or dd > self.max_dd:
self.killed = True # sticky until a human resets it
def check(self, symbol, qty, price, position):
if self.killed:
return False, "kill switch active"
if abs(position + qty) > self.max_pos:
return False, "position limit"
if abs(qty * price) > self.max_notional:
return False, "notional limit"
return True, "ok"
def kelly_fraction(returns, rf=0.0, fraction=0.5, cap=2.0):
mu, var = returns.mean() * 252 - rf, returns.var() * 252
return float(np.clip(fraction * mu / var, -cap, cap))
def vol_target_weight(returns, target=0.10, lam=0.94):
var = returns[:20].var()
for r in returns[20:]:
var = lam * var + (1 - lam) * r * r
return target / np.sqrt(var * 252)
def historical_var_cvar(returns, level=0.99):
q = np.quantile(returns, 1 - level)
return -q, -returns[returns <= q].mean()Write these limits into the spec before the first line of strategy code. When an agent harness such as AgenKit builds the bot, the kill switch and key handling are a security gate the build cannot pass without, and its debugger can trace any rejected order back to the rule that stopped it.
Machine Learning Without Fooling Yourself
Machine learning combines weak signals. Without a test designed to reject noise, it will fit the noise.
Why finance is hard for ML
- Low signal-to-noise. A model that predicts next-day direction 52% of the time can be world-class.
- Non-stationary. The data-generating process changes: regimes, regulations, competitors, your own impact.
- Small effective sample. 20 years of daily data is only about 5,000 points, heavily autocorrelated.
- Adversarial. Once an edge is found and traded, it decays.
The ML workflow for trading
- FeaturesStationary, causal inputs: returns over several horizons, volatility, z-scores, order-flow imbalance, carry, fundamentals ratios, text sentiment. Rank-transform across assets to remove scale.
- LabelsWhat you predict. Fixed-horizon returns are naive. The triple-barrier method labels each event by which comes first: take-profit, stop-loss, or time-out.
- ModelStart with gradient-boosted trees (LightGBM, XGBoost, CatBoost) on tabular data. They beat deep learning on most tabular finance problems and are easy to inspect.
- ValidationPurged, embargoed walk-forward cross-validation (chapter 11). Never shuffle time series.
- Meta-labellingA second model decides whether to act on a primary signal and how big to bet. Often worth more than a better primary model.
- Sizing and backtestMap probabilities to positions (for example, bet size ∝ 2p − 1), then run the full cost-aware backtest.
import numpy as np, pandas as pd, yfinance as yf, lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import roc_auc_score
px = yf.download("SPY", start="2005-01-01", auto_adjust=True, progress=False)["Close"].squeeze()
r = px.pct_change()
vol = r.ewm(span=50).std()
def triple_barrier(px, vol, horizon=10, k=1.5):
out = pd.Series(np.nan, index=px.index)
for i in range(len(px) - horizon):
p0, up, dn = px.iloc[i], k * vol.iloc[i], -k * vol.iloc[i]
path = px.iloc[i + 1 : i + 1 + horizon] / p0 - 1
hit_up = path[path >= up].index.min()
hit_dn = path[path <= dn].index.min()
if pd.isna(hit_up) and pd.isna(hit_dn): out.iloc[i] = 0
elif pd.isna(hit_dn) or (not pd.isna(hit_up) and hit_up < hit_dn): out.iloc[i] = 1
else: out.iloc[i] = -1
return out
X = pd.DataFrame({f"ret_{h}": px.pct_change(h) for h in (1, 5, 20, 60)})
X["vol_20"] = r.rolling(20).std()
X["z_20"] = (px - px.rolling(20).mean()) / px.rolling(20).std()
X["dist_200"] = px / px.rolling(200).mean() - 1
y = (triple_barrier(px, vol) == 1).astype(int)
data = pd.concat([X, y.rename("y")], axis=1).dropna().iloc[:-10]
aucs = []
for train, test in TimeSeriesSplit(n_splits=5, gap=10).split(data): # gap = label horizon (purge)
tr, te = data.iloc[train], data.iloc[test]
model = lgb.LGBMClassifier(n_estimators=300, learning_rate=0.02, num_leaves=15,
subsample=0.8, subsample_freq=1, colsample_bytree=0.8, verbose=-1)
model.fit(tr.drop(columns="y"), tr["y"])
aucs.append(roc_auc_score(te["y"], model.predict_proba(te.drop(columns="y"))[:, 1]))
print("out-of-sample AUC per fold:", np.round(aucs, 3)) # ~0.50–0.55 is normal. Be suspicious of 0.7.Deep learning, reinforcement learning, and LLMs
Sequences and order books
LSTMs, temporal CNNs and transformers on limit-order-book data (DeepLOB) and long return histories. Most useful with very large datasets: high-frequency data, many assets, or text.
Execution and market making
RL suits sequential decisions with clear rewards: optimal execution, inventory management. FinRL is a starting point. Simulators are the hard part.
Text as data
Sentiment and event extraction from news, filings and earnings calls, turned into numeric features. Watch for look-ahead: models trained on data after your backtest date already "know" the future.
Agents as your research team
The biggest 2026 shift is AI coding agents that write, test and review the research and trading code itself. Used with discipline (specs, tests, review), they compress months of engineering into weeks. That is what agent harnesses such as AgenKit exist for.
Also worth exploring: Microsoft Qlib, an AI-oriented quant research platform with data handling, models and backtesting in one place.
Inside the Order Book
Prices move because orders arrive, rest, cancel and match. Short-horizon research starts in the limit order book.
The limit order book
Buyers post bids, sellers post asks. The highest bid and lowest ask form the spread. A limit order rests in the book and provides liquidity. A market order crosses the spread and takes liquidity. Most exchanges match by price-time priority: best price first, then first in the queue.
Key concepts
- Adverse selection. When your resting order fills, it is often because someone knew more than you. Market makers win on the spread and lose to informed flow.
- Queue position. At the same price, being first in the queue is worth real money. This is a big reason firms race on latency.
- Market impact. Large orders move prices, temporarily and permanently (Kyle's lambda, the square-root law).
- Maker/taker fees and rebates. Exchanges pay some liquidity providers. Many market-making strategies depend on rebates.
- Tick size. The minimum price increment shapes queue dynamics: large-tick assets reward queue position, small-tick assets reward speed.
Market making: Avellaneda–Stoikov
The classic model of an optimal market maker quotes around a reservation price that is skewed against current inventory \(q\):
import numpy as np
def avellaneda_stoikov(mid, inventory, sigma, gamma=0.1, k=1.5, t_left=1.0, tick=0.01):
reservation = mid - inventory * gamma * sigma**2 * t_left
spread = gamma * sigma**2 * t_left + (2 / gamma) * np.log(1 + gamma / k)
bid = np.floor((reservation - spread / 2) / tick) * tick
ask = np.ceil((reservation + spread / 2) / tick) * tick
return round(bid, 2), round(ask, 2)
for inv in (-5, 0, 5):
print(f"inventory {inv:+d}: quotes", avellaneda_stoikov(mid=100.0, inventory=inv, sigma=2.0))Read further
- Cartea, Jaimungal & Penalva, Algorithmic and High-Frequency Trading: the modern textbook on optimal execution and market making.
- Bouchaud, Bonart, Donier & Gould, Trades, Quotes and Prices: the physics view of order books.
- Larry Harris, Trading and Exchanges: the best plain-English overview of who trades and why.
Execution and Low Latency
Execution is where a paper edge becomes real money, or leaks away one tick at a time.
Execution algorithms
| Algo | Idea | When |
|---|---|---|
| TWAP | Slice evenly across time | Simple, predictable, low urgency |
| VWAP | Slice in proportion to expected volume | Benchmark-driven institutional orders |
| POV | Trade a fixed percentage of market volume | Adapts to liquidity |
| Implementation shortfall | Trade off impact against the risk of waiting | Alpha-driven orders where delay is costly |
| Passive / pegged | Rest at the bid or ask, earn the spread | When you are not in a hurry |
The Almgren–Chriss model gives the optimal schedule for liquidating \(X\) shares over time \(T\) when impact and price risk compete:
What "low latency" means, and what it means for you
For an individual, "low latency" should mean robust and predictable: a few milliseconds, no garbage-collection pauses at the wrong time, no dropped messages, clean reconnects. Pick strategies where the edge lasts seconds to days, not microseconds: stat-arb, trend, volatility, prediction markets. The engineering below still matters, because it makes your system reliable.
Architecture of a fast trading engine
The low-latency engineering playbook
| Technique | Why |
|---|---|
| Pre-allocate everything | Heap allocation and garbage collection cause unpredictable pauses |
| Fixed-point integer prices | Floats are inexact and slower to compare; store prices as integer ticks |
| Lock-free SPSC ring buffers | Pass messages between threads without mutexes |
| Pin threads to isolated cores, busy-spin | No context switches, no scheduler jitter, warm caches |
| Cache-friendly data layout | Arrays of structs sized to cache lines; avoid pointer chasing and false sharing |
| Kernel bypass (DPDK, Solarflare/AMD Onload, ef_vi) | Skip the operating system's network stack |
| Binary protocols (SBE, ITCH/OUCH) | Decoding JSON is very slow by comparison |
| Colocation and FPGAs | Put the server in the exchange's data centre; move logic into hardware |
| Measure tail latency | Average latency hides the problems. Track p99 and p99.9 with hardware timestamps. |
Why Rust (and C++)
C++ has dominated HFT for decades: control over memory layout, no GC, and decades of tooling (QuantLib, Eigen, Boost). Rust gives the same performance with memory safety checked at compile time, removing whole classes of crashes. The pattern spreading through new trading infrastructure is a Rust core with a Python API: NautilusTrader works this way, and every strategy on agents.surf ships a Rust signal layer for the same reason.
// Cargo.toml: rtrb = "0.3", core_affinity = "0.8" use rtrb::RingBuffer; /// Prices as integer ticks: exact, fast to compare, no float surprises. #[derive(Clone, Copy, Debug)] struct Tick { ts_ns: u64, bid: i64, ask: i64, bid_sz: u32, ask_sz: u32 } #[derive(Clone, Copy, Debug)] enum Intent { Buy { px: i64, qty: u32 }, Sell { px: i64, qty: u32 } } fn main() { let (mut tick_tx, mut tick_rx) = RingBuffer::<Tick>::new(1 << 16); let (mut order_tx, mut order_rx) = RingBuffer::<Intent>::new(1 << 12); // Feed thread, pinned to core 2: decode packets, push ticks. Never allocates. std::thread::spawn(move || { core_affinity::set_for_current(core_affinity::CoreId { id: 2 }); loop { let t = read_next_tick(); while tick_tx.push(t).is_err() { std::hint::spin_loop(); } } }); // Gateway thread, pinned to core 4: risk-check and send. std::thread::spawn(move || { core_affinity::set_for_current(core_affinity::CoreId { id: 4 }); loop { match order_rx.pop() { Ok(intent) => send_if_within_limits(intent), Err(_) => std::hint::spin_loop(), } } }); // Strategy thread (this one), pinned to core 3: microprice imbalance signal. core_affinity::set_for_current(core_affinity::CoreId { id: 3 }); loop { let Ok(t) = tick_rx.pop() else { std::hint::spin_loop(); continue }; let (b, a) = (t.bid_sz as i64, t.ask_sz as i64); // imbalance in basis points of total top-of-book size, integer math only let imb_bps = (b - a) * 10_000 / (b + a).max(1); let intent = if imb_bps > 6_000 { Some(Intent::Buy { px: t.bid, qty: 1 }) } else if imb_bps < -6_000 { Some(Intent::Sell { px: t.ask, qty: 1 }) } else { None }; if let Some(i) = intent { let _ = order_tx.push(i); } } } fn read_next_tick() -> Tick { /* kernel-bypass socket + binary decoder */ unimplemented!() } fn send_if_within_limits(_i: Intent) { /* pre-trade risk, then encode + send */ }
import numpy as np
def almgren_chriss_schedule(X, n_slices, T=1.0, risk_aversion=1e-6, sigma=0.3, eta=2.5e-6):
"""Shares remaining after each slice. risk_aversion -> 0 gives TWAP."""
kappa = np.sqrt(risk_aversion * sigma**2 / eta)
t = np.linspace(0, T, n_slices + 1)
if kappa < 1e-9:
return X * (1 - t / T)
return X * np.sinh(kappa * (T - t)) / np.sinh(kappa * T)
for ra in (0.0, 1e-6, 1e-5):
remaining = almgren_chriss_schedule(100_000, 10, risk_aversion=ra)
print(f"risk aversion {ra:g}: trades per slice", np.round(-np.diff(remaining)).astype(int))Most of this work is unglamorous: latency budgets, p99 alerts, idempotent order IDs, reconnects, broker reconciliation. It is also exactly what an AI agent skips unless something forces it. In AgenKit the performance and infrastructure specialists own it, with paper and live as separate deploy targets and automated rollback.
Prediction Markets: Probability With Prices
Elections, rates, sports, geopolitics. A prediction market contract pays $1 if an event happens, so its price is an implied probability.
That makes prediction markets the cleanest application of everything in this guide. A systematic quant does not try to guess who wins an election. They look for markets where the implied probability differs measurably from what the data supports, bet on the gap with correct sizing, and repeat across hundreds of events. Institutional quants now run the same frameworks here as in equities: probability models, signal combination, Kelly sizing, risk limits.
The edge, in one line
How automated market makers price beliefs: LMSR
Robin Hanson's Logarithmic Market Scoring Rule powers many automated prediction markets. With \(q_i\) outstanding shares of outcome \(i\) and liquidity \(b\):
Polymarket itself runs a central limit order book, so chapter 14's microstructure applies directly. Kalshi is a CFTC-regulated exchange for US event contracts.
Where the edges are
- Mispriced probabilities: models built from polls, odds, base rates or live data that beat the crowd on specific market types.
- Structural arbitrage: mutually exclusive outcomes whose YES prices sum to less than $1 (or more), or the same event priced differently on two venues.
- Combinatorial arbitrage: logically linked markets ("Party wins" vs. the state-by-state markets) that disagree. Finding these at scale is an integer program, which is where solvers like Gurobi and OR-Tools come in.
- Market making: providing liquidity in thin books, with inventory risk managed as in Avellaneda–Stoikov.
- Speed on resolution-relevant news: reacting to official data releases faster than manual traders.
import numpy as np
def lmsr_cost(q, b):
return b * np.log(np.exp(np.asarray(q) / b).sum())
def lmsr_prices(q, b):
e = np.exp(np.asarray(q) / b - np.max(q) / b) # stable softmax
return e / e.sum()
b, q = 100.0, np.array([0.0, 0.0, 0.0])
print("start prices:", lmsr_prices(q, b))
buy = np.array([50.0, 0, 0])
print(f"cost of 50 YES on outcome 0: ${lmsr_cost(q + buy, b) - lmsr_cost(q, b):.2f}")
print("new prices:", np.round(lmsr_prices(q + buy, b), 3))
print(f"max market-maker loss: ${b * np.log(len(q)):.2f}")
# Structural arbitrage check on mutually exclusive outcomes (best asks, $ per share)
asks = {"A": 0.41, "B": 0.33, "C": 0.22}
total = sum(asks.values())
fee = 0.01 * len(asks)
if total + fee < 1:
print(f"buy every outcome for ${total:.2f}, guaranteed $1 payout, edge ${1 - total - fee:.2f}")Check legal eligibility in your jurisdiction, resolution criteria (the exact wording decides payouts), fees, and liquidity before trading any prediction market. Thin books make backtested fills unrealistic.
Building Your First Bot With an Agent Harness
You have the math, the data, the backtest, the risk rules and the architecture. The last step is engineering it the way a desk would.
A single AI chat will happily write you a trading script. It will also leave out the reconnect logic, apply costs on the wrong bar, skip the kill switch, and give you no tests. An agent harness fixes that by wrapping the model in process: a written spec, specialist roles, tests before code, review against the spec, and approval gates where nothing moves without you. That is how a professional desk ships research. We use AgenKit for the walkthrough below because it runs that process inside the coding tool you already use. The method is what matters; the commands are just how you run it.
Step 1 · Set up the harness
Get a license key from agenkit.xyz. You need Node 18 or newer. Run these in your project folder; the installer detects your harness automatically.
npx agenkit activate <license-key> npx agenkit install engineering-kit
Works in Claude Code, Cursor, Codex CLI, OpenCode, Antigravity, GitHub Copilot and Kimi Code. Use /agenkit in most tools, @agenkit in Copilot, and invoke the AgenKit skill by name in Codex.
Step 2 · Write the brief as a spec
The better your spec, the better the system. Notice how every line below maps to a chapter of this guide:
/agenkit build a production-grade trading bot: Python research layer, Rust execution core. Strategy (ch. 9): KO/PEP pairs mean reversion. Hedge ratio from 252-day rolling OLS on log prices. Enter when |z| > 2, exit when |z| < 0.5, stop out when |z| > 4. Daily decisions on the close. Data (ch. 10): yfinance daily bars for research, Alpaca WebSocket for live. Parquet storage, gap detection, UTC timestamps, exchange_ts and received_ts on every record. Backtest (ch. 11): event-driven, positions take effect on the next bar, 2 bps cost + 1 bp slippage, walk-forward 3y train / 6m test with a 10-day embargo. Report Sharpe, Sortino, max DD, turnover and deflated Sharpe. Fail the build if any test detects look-ahead. Risk (ch. 12): 10% annualised vol target, half-Kelly cap, max 20% gross per leg, daily loss limit 2%, 15% drawdown kill switch that cancels orders and requires manual reset. Execution (ch. 15): Alpaca paper account first. Idempotent client order IDs, reconnect with backoff, reconcile positions against the broker every minute. Monitoring: dashboard with positions, z-score, PnL vs. backtest, slippage; Telegram alerts.
Or start smaller and let the Conductor ask questions one at a time: /agenkit build a production-grade Polymarket bot with a Markov-chain signal.
Step 3 · Stay at the gates
- SpecThe Conductor asks about universe, lookback, thresholds and sizing. Answer in plain English, then read the written spec. Does the math match what you learned here? Approve it.
- ArchitectureThe Backend Architect lays out modules, interfaces, data model, retries and failure modes. Check that research and live share the same signal code. Approve.
- Plan and testsThe Test Engineer writes a failing test for each rule: no look-ahead, costs on every fill, kill switch trips at 15%. Then the minimum code to pass.
- ReviewThe Code Reviewer checks against the spec first, then code quality. It blocks if the z-score is computed with future data, even when everything compiles.
- Ship to paperInfra/DevOps deploys to the paper target with CI and rollback. Run for weeks. Compare live slippage and PnL with the backtest.
- Go live smallOnly when the paper book is clean. Then scale slowly, and keep the kill switch armed.
Who owns what on a trading build
| Role | What it does on your bot |
|---|---|
| Conductor | Turns your prompt into a written spec, one question at a time, and runs the whole pipeline |
| Backend architect | Modules, interfaces, retries, reconnects, idempotent orders. Failure design before any code. |
| Test engineer | A failing test for every rule in the spec, then the minimum code to pass it |
| Code reviewer | Blocks merges where the math does not match the spec |
| Data engineer | Price feeds, schemas, gap detection, causal timestamps, backfills |
| Debugger | Traces a bad fill or a broken z-score back to its root cause |
| Frontend engineer | The dashboard where you watch inventory, signals and PnL |
| Infra / DevOps | CI, deploys, rollback, with paper and live as separate targets |
| Perf & observability | Latency budgets, p99 alerts, metrics and logs while it runs |
| Security | API keys, position limits and the kill switch, in place before the first order |
Then iterate
Not sure what to build first? Every one of the 200 strategies on agents.surf comes with its math, test plan, a Rust signal layer and a ready-made brief you can paste into the same pipeline.
Every change after that is another prompt through the same pipeline: a tighter threshold, a new universe, a Telegram alert, a Kalman-filter hedge ratio. The tests grow with the book. Run npx agenkit update to pull the latest kit; your code stays yours.
The Complete Toolbox
The libraries are public. The work is deciding which ones a given system actually needs, and using them correctly.
Two kinds of programming
Research programming: clean Python to analyse data, build models and backtest. Used by researchers and analysts every day. Production systems programming: C++ or Rust that handles live data and orders at microsecond latency without missing a message. Used by quant developers and HFT engineers. Know which one your target role needs.
Python stack
| Job | Libraries |
|---|---|
| Data frames | pandas, polars (often 10–50× faster on big data), duckdb |
| Numerics | numpy, scipy, numba (JIT for hot loops) |
| Statistics | statsmodels, arch (GARCH), scikit-learn |
| ML, tabular | lightgbm, xgboost, catboost |
| Deep learning | pytorch |
| Optimisation | cvxpy, Gurobi (free academic licence), Google OR-Tools, PuLP/Pyomo |
| Derivatives | QuantLib (C++ core, Python bindings) |
| Backtesting | NautilusTrader (production), vectorbt and backtrader (learning), LEAN/QuantConnect |
| Research platforms | Microsoft Qlib, FinRL |
| Brokers / APIs | Alpaca, Interactive Brokers, ccxt (crypto exchanges), Polymarket and Kalshi APIs |
Systems stack
| Language | Libraries and tools |
|---|---|
| C++ | QuantLib, Eigen (linear algebra), Boost, Abseil, Google Benchmark, perf |
| Rust | RustQuant, NautilusTrader core, tokio, rtrb / crossbeam, criterion benchmarks |
| Infra | Linux tuning (isolcpus, hugepages), Docker, Grafana + Prometheus, kdb+/q or ClickHouse |
Decoding the Interview Gauntlet
Quant interviews are more structured and more demanding than most candidates expect, and very learnable.
What each stage tests
- Mental math: speed and accuracy under pressure. Practise on Zetamac until you score 50+ consistently.
- Probability and expected value: conditional probability, Markov chains, martingales, optimal stopping, dice and card games.
- Betting and market-making games: "make me a market on the sum of three dice". Width, skew and updating on information all matter.
- Coding: data structures and algorithms. LeetCode Blind 75, with dynamic programming as the most common final-round failure point.
- Systems (dev roles): C++/Rust internals, memory, concurrency, networking, design of a matching engine or feed handler.
- Research (QR roles): a hypothesis you formed, tested and can defend, including what failed and why.
- Behavioural: practise out loud. Every final round has a human judgement layer that weighs as much as the technical rounds.
Think out loud. A candidate who narrates their reasoning, checks edge cases and keeps going under uncertainty beats one who goes silent and then produces a correct answer.
Practice problems (try before opening)
1. You flip a fair coin until you get two heads in a row. What is the expected number of flips?
Show solution
Define states by progress. \(E_0\): expected flips from scratch. \(E_1\): expected flips when the last flip was heads.
The method matters more than the number: define states, write one equation per state, solve the linear system. The same approach handles any pattern, such as HTH (10) or HHH (14).
2. You roll a fair die and are paid its face value in dollars. If you do not like the roll, you may re-roll once and must take the second result. What is the game worth?
Show solution
A re-roll is worth 3.5, so keep 4, 5 or 6 and re-roll 1, 2 or 3. Value \(= \tfrac12 \cdot 5 + \tfrac12 \cdot 3.5 = 4.25\). This is optimal stopping: compare the value of stopping with the expected value of continuing.
3. A test for a condition is 99% accurate, and 1 in 1,000 people have it. You test positive. What is the probability you have it?
Show solution
Bayes: \(P = \dfrac{0.99 \times 0.001}{0.99 \times 0.001 + 0.01 \times 0.999} \approx 9\%\). Base rates dominate. The same logic says most backtests showing "99% significance" are false positives if you tested enough ideas.
The preparation stack
- Xinfeng Zhou, A Practical Guide to Quantitative Finance Interviews ("the Green Book")#1200+ real problems. Spend 15 honest minutes on each before looking at a hint.
- QuantGuidepractice"LeetCode for quants": firm-tagged probability and brainteaser problems.
- BrainstellarpuzzlesProbability puzzles at interview difficulty.
- Jane Street monthly puzzleshardHarder than interviews. Excellent for depth.
- Timothy Crack, Heard on the Street · Mark Joshi, Quant Job Interview Questions and AnswersbooksClassic complements to the Green Book.
- NeetCode / Blind 75codingLearn the pattern behind each problem rather than memorising solutions.
Competitions that work as recruiting pipelines
- WorldQuant BRAIN: submit alpha signals, get paid for good ones, and get noticed.
- Jane Street Kaggle competitions: real market-data forecasting with serious prize pools.
- Citadel / Correlation One datathons: strong performers are fast-tracked to interviews.
- IMC Prosperity and similar trading challenges: simulated markets, algorithmic and manual rounds.
The 18-Month Staircase
Run the math track and the coding track in parallel. Do not wait for the math to be perfect before you start building.
- Build the foundation in orderProbability → statistics → linear algebra → optimisation → stochastic calculus. Code every concept as you learn it.
- Ship one real project before applying anywhereA documented backtest on real data with every assumption written down. A model submitted to WorldQuant BRAIN or Kaggle. A paper-trading bot on Alpaca, built and tested through AgenKit. Projects prove you can turn math into something that works.
- Get your first institutional credentialCold-email PhD students and ask to help with their research. TA a quantitative course. Take a research-assistant role. The title matters much less than having real technical work to talk about.
- Use each credential to reach the nextResearch lab → startup → mid-tier firm → elite fund. Each step is evidence you can do the work of the next one.
- Apply before you feel ready, and track everythingKeep a spreadsheet of every application, assessment, interview, and question you could not answer cleanly. Study that exact thing before the next one.
- Compete in publicFirms watch competition leaderboards. Strong results have led to offers for people with no prior connection to the firm.
Name the lecture you are on, the one that comes next, and a dated task for the next seven days: a chapter of problems, a backtest, or a set of interview questions.
Three constraints that do not go away
Estimation error is the real enemy. Full Kelly, unconstrained Markowitz, and ML models with too many features all fail for the same reason: they fit noise in parameter estimates. The math is exact with true parameters, and you never have true parameters.
Libraries are not an edge. QuantLib, a market-data vendor and PyTorch are available to everyone. Edge is data, a model, or execution that someone else does not have.
The mathematics is the part you cannot skip. Code can be generated. You still have to know why Itô's lemma carries an extra term, and when a convex relaxation is tight. A result you cannot derive is a result you cannot trust.
Reading List and Resources
Read them in this order. Free material is marked.
Mathematics
- Blitzstein & Hwang, Introduction to Probability + Stat 110freeLayer 1. Chapters 1–6, every problem.
- Wasserman, All of StatisticsbookLayer 2. Chapters 1–13.
- Strang, Introduction to Linear Algebra + MIT 18.06free lecturesLayer 3. Watch all of them.
- Boyd & Vandenberghe, Convex OptimizationfreeLayer 4. Chapters 1–5.
- Shreve, Stochastic Calculus for Finance I & IIbookLayer 5. The gold standard. Gentler: Arguin, A First Course in Stochastic Calculus.
- Tsay, Analysis of Financial Time SeriesbookLayer 6. ARMA, GARCH, cointegration in a finance setting.
Quantitative finance and trading
- Hull, Options, Futures, and Other DerivativesderivativesThe standard reference.
- Natenberg, Option Volatility and Pricingvol tradingHow option traders actually think.
- López de Prado, Advances in Financial Machine LearningMLTriple barrier, meta-labelling, purged CV, deflated Sharpe.
- Ernest Chan, Quantitative Trading and Algorithmic TradingpracticalThe best start for independent systematic traders.
- Grinold & Kahn, Active Portfolio ManagementportfolioThe fundamental law of active management, and alpha in a risk-model world.
- Cartea, Jaimungal & Penalva, Algorithmic and High-Frequency TradingHFTOptimal execution and market making, rigorously.
- Larry Harris, Trading and ExchangesmicrostructureWho trades, why, and how markets are built.
- Zuckerman, The Man Who Solved the Market · Thorp, A Man for All MarketsstoryRenaissance and the original quant. Motivation for the hard chapters.
Papers worth reading
- Black & Scholes (1973), The Pricing of Options and Corporate LiabilitiesclassicWhere it all started.
- Kelly (1956), A New Interpretation of Information RatesizingThe original Kelly criterion.
- Almgren & Chriss (2000), Optimal Execution of Portfolio TransactionsexecutionThe impact-vs-risk trade-off.
- Avellaneda & Stoikov (2008), High-frequency trading in a limit order bookmarket makingReservation price and optimal spread.
- Bailey & López de Prado (2014), The Deflated Sharpe RatiobacktestingCorrecting for selection bias and non-normality.
- Harvey, Liu & Zhu (2016), …and the Cross-Section of Expected Returnsmultiple testingWhy the bar should be t > 3.
- Cont, Kukanov & Stoikov (2014), The Price Impact of Order Book EventsOFIOrder-flow imbalance explains short-term moves.
- Moskowitz, Ooi & Pedersen (2012), Time Series MomentumtrendTrend following across asset classes.
Platforms, data and code
- NautilusTraderopen sourceProduction-grade, Rust core, Python API. Backtest and live with the same code.
- Microsoft Qlib · FinRLopen sourceAI-oriented research platform; reinforcement learning for trading.
- vectorbt · backtraderlearningSimpler backtesters to learn the concepts.
- AlpacabrokerFree paper trading with a clean API. The default first live target.
- Polymarket docs · Kalshi docsprediction marketsOrder book and trading APIs.
- agents.surfthis site200 strategies with math, tests, Rust code and AgenKit prompts.
- AgenKitbuildTurn any strategy into a tested, reviewed, deployable bot with
/agenkit.
© agents.surf