Trading Course
A complete, step-by-step guide to every strategy, indicator, and risk control running inside QuantForge. No finance background assumed — each section builds on the last. Start with Math Foundations if you're new, or jump directly to a strategy.
Markets 101 — New to Finance? Start Here
Math Foundations
Strategies
Filters & Risk
Why Markets Exist
Before you can trade anything, you need to understand why financial markets exist at all — and why they work the way they do. This is the foundation everything else builds on.
The Problem: Trading Without Markets
Imagine you grow wheat and need shoes. Without a market, you'd need to find a shoemaker who both (a) wants wheat and (b) is willing to trade right now. Economists call this the double coincidence of wants — and it's nearly impossible to satisfy at scale.
Money solved this for physical goods. But financial markets solve a different, harder problem: coordinating who has capital, who needs it, and at what price.
What a Financial Market Actually Does
A financial market is a mechanism that lets people exchange claims on future value — a share of a company's profits, a loan that pays interest, exposure to gold prices. It serves three fundamental purposes:
1. Price discovery. Thousands of buyers and sellers, each with different information and opinions, continuously bid and ask. The resulting price is society's best real-time estimate of what something is worth. No central authority sets it — the crowd does.
2. Liquidity. You can turn your investment into cash quickly, and at a price close to what others just paid. Without liquidity, you'd be stuck holding an asset until you personally found a buyer — which could take months.
3. Risk transfer. A farmer can sell futures on the wheat they'll harvest in autumn, locking in a price now. A speculator on the other side bets prices will rise. Risk moves from someone who doesn't want it to someone willing to bear it — for a price.
Stock Markets: Funding Companies
When a company wants to grow — build a factory, hire engineers, expand to new countries — it needs capital. It has two options:
- Borrow it (issue bonds, take a bank loan) — must repay with interest
- Sell ownership (issue stock) — investors get a share of future profits; company never has to repay
The stock market is what makes the second option possible at scale. A company can raise millions from thousands of investors simultaneously, and those investors can later sell their shares to others — they don't need to wait for the company to buy them back.
Why Prices Move
At any moment, the market price of something represents the current equilibrium between buyers and sellers. It moves when that balance shifts:
- Good earnings report → more buyers appear → price rises until sellers are willing
- Interest rates rise → bonds become more attractive vs stocks → investors sell stocks → price falls
- Big fund needs to sell a large position quickly → they flood the market with sell orders → price drops temporarily
Why This Matters for Algorithmic Trading
QuantForge is a participant in these markets. Every time it sends a buy or sell order, it interacts with all these other participants — market makers, institutions, retail traders. The price you get depends on market conditions at the exact moment of execution. Understanding the market's mechanics is understanding the environment your algorithm lives in.
Sources & Further Reading
Stocks, Bonds & ETFs
When QuantForge trades SPY, QQQ, GLD, AAPL, BTC — what exactly is it trading? This page explains the instruments and what you actually own when you hold them.
Stocks (Equities)
A stock (also called a share or equity) is fractional ownership of a company. If Apple has 15 billion shares outstanding and you own 100, you own roughly 1/150,000,000th of Apple — including a claim on its profits, voting rights on major decisions, and a residual claim on assets if it ever liquidates.
In theory: Price = Present value of all future cash flows (earnings, dividends) discounted at the appropriate rate.
In practice: Price = What buyers and sellers currently agree it's worth, based on their expectations of those future cash flows.
When Apple reports higher-than-expected iPhone sales, the market upgrades its estimate of future earnings → buyers arrive → price rises. When interest rates rise, the discount rate goes up → future earnings are worth less today → prices fall even with unchanged fundamentals.
Dividends: Some companies pay a portion of profits directly to shareholders (quarterly cash payments per share). Growth companies (like early Amazon) typically pay no dividends and reinvest profits instead. Dividend payments reduce the stock price by approximately the dividend amount on the ex-dividend date.
ETFs — Exchange-Traded Funds
An ETF is a basket of assets packaged as a single tradeable instrument. It trades exactly like a stock — you can buy/sell it any time the market is open, and the price updates continuously.
| Ticker | What It Tracks | Why We Trade It |
|---|---|---|
| SPY | S&P 500 — 500 largest US companies | Benchmark for US equities, extremely liquid |
| QQQ | Nasdaq-100 — 100 largest non-financial Nasdaq stocks | Tech-heavy, higher volatility than SPY |
| GLD | Gold price (holds physical gold bars) | Safe-haven asset, low correlation to equities |
When QuantForge's Trend Following strategy "buys GLD," it's actually buying shares of the GLD ETF, which is backed by actual gold stored in HSBC vaults in London.
Bonds (Fixed Income)
A bond is a loan from the buyer to the issuer. The issuer promises to:
- Pay a fixed coupon (interest) at regular intervals (e.g., 4% per year)
- Return the principal (face value) at a set maturity date
QuantForge doesn't currently trade bonds, but bond yields matter: when government bond yields rise, they become more attractive alternatives to stocks, often causing equity prices to fall.
Crypto
Bitcoin (BTC) and Ethereum (ETH) are digital assets with no issuer, no earnings, and no dividends. Their price is determined entirely by supply/demand dynamics and speculative expectations. They trade 24/7 — unlike equities which close at 4pm ET — and have dramatically higher volatility. QuantForge's Volatility Breakout strategy was specifically designed for this characteristic.
Sources & Further Reading
Order Books & Price Discovery
The price you see on a screen isn't a fixed fact — it's the current frontier between buyers and sellers, continuously renegotiated. Here's the machinery behind it.
Bid and Ask
At any moment, there are two prices for every asset:
- Bid: The highest price any buyer is currently willing to pay. If you want to sell immediately, you sell at the bid.
- Ask (also called offer): The lowest price any seller currently wants. If you want to buy immediately, you pay the ask.
- Spread: Ask − Bid. This is the market maker's profit margin and a measure of market quality. Tight spreads = high liquidity = cheap to trade.
| Side | Price | Quantity | Who placed it |
|---|---|---|---|
| Ask (sell) | $548.02 | 200 shares | Market maker offering to sell |
| Ask (sell) | $548.01 | 500 shares | Best ask — you pay this to buy now |
| ── current spread: $0.01 ── | |||
| Bid (buy) | $548.00 | 800 shares | Best bid — you receive this if you sell now |
| Bid (buy) | $547.99 | 300 shares | Next best bid |
SPY's spread is typically $0.01 — one cent. BTC/USD spread can be $1–5. Thinly traded stocks can have spreads of $0.10 or more. Every trade you make costs you the spread.
The Order Book
The order book is the full sorted list of all pending buy and sell orders at every price level. The exchange maintains it and matches orders automatically:
- New buy order at $548.01 → matches the best ask at $548.01 → trade executes, both orders (or portions) are removed from the book
- New buy order at $547.95 → no seller willing to go that low → order sits in the book waiting
Market depth refers to how many shares are available at each price level. A market with deep books can absorb large orders without the price moving much. Shallow books move violently on large orders.
Why Prices Move
A price moves because orders consumed all the quantity at the best bid or ask:
- A large buyer places a market order for 50,000 SPY shares
- All 500 shares at $548.01 are consumed → order continues to $548.02
- All 200 shares at $548.02 consumed → order moves to $548.03
- Price has now risen by $0.02 because of this single order
This is called market impact or price impact — your own orders move the price against you. QuantForge uses small position sizes relative to typical daily volume to minimize this.
Sources & Further Reading
Types of Orders
When QuantForge wants to buy or sell, it sends an order to the exchange. The type of order determines how and when it gets filled. Understanding this is essential for knowing how your strategy actually executes.
Market Order
A market order says: "Buy/sell immediately at whatever price is available."
- Guaranteed: You will get filled (unless the market is closed or the asset is halted)
- Not guaranteed: The exact price — in a fast-moving market, the price can "slip" from what you saw
- Best for: Highly liquid assets (SPY, QQQ) where the spread is tiny and you want certainty of execution
Limit Order
A limit order says: "Buy at this price or lower / Sell at this price or higher — but only if possible."
- Guaranteed: You will never pay more than your limit (buy) or receive less (sell)
- Not guaranteed: Execution — if the price never reaches your limit, you don't get filled
- Best for: When precise entry price matters more than speed of execution
Stop Order (Stop-Loss)
A stop order sits dormant until price crosses a trigger level, then converts to a market order:
- You buy SPY at $548. You place a stop order to sell at $541.
- If SPY falls to $541, the stop triggers → market order to sell fires → you exit around $541
- In fast markets, price can "gap through" your stop → you fill at $539 or worse
QuantForge computes stop prices (e.g., entry − 3×ATR) and stores them as logical stop levels. The worker process monitors prices and sends exit orders when the stop is breached.
Stop-Limit Order
Like a stop order, but instead of converting to a market order at the trigger, it converts to a limit order. Protects against gap-fill slippage but risks not being filled at all if the price blows through your limit.
Time-in-Force
Every order has a time-in-force that controls how long it stays active:
- Day: Cancels at market close if unfilled
- GTC (Good Till Cancelled): Stays active across sessions until filled or you cancel
- IOC (Immediate or Cancel): Fill what you can right now, cancel the rest
- FOK (Fill or Kill): Fill the entire order immediately or cancel everything
QuantForge uses Day orders by default — if a signal isn't filled during the current session, it's re-evaluated next bar.
Who Trades & Why
You are not alone in the market. Every order you send interacts with orders from a vast ecosystem of participants with very different goals, time horizons, and information. Understanding who they are shapes how you design your strategies.
Retail Investors
Individual people trading their own money. Goals vary from long-term wealth building (buy S&P 500 index funds and hold for decades) to active day trading. Retail has historically been at an information and speed disadvantage vs professionals — but modern index ETFs have made passive investing extremely efficient for retail. QuantForge is a retail-grade algorithmic system.
Institutional Investors
Pension funds, endowments, insurance companies, sovereign wealth funds. Manage billions, often trillions. Their primary constraint is liquidity: they can't just buy 10 million shares of a small stock without moving the price dramatically. They trade slowly, accumulate over weeks or months, and care deeply about transaction costs. Their large flows create the multi-week trends that Trend Following strategies capture.
Mutual Funds & ETFs
Pooled investment vehicles. Passive ETFs (like SPY) just hold every stock in an index — they must buy/sell as index compositions change (rebalancing). Active mutual funds try to beat the index by picking stocks. Their predictable rebalancing behavior creates small exploitable patterns (e.g., index additions tend to outperform in the days before they're added).
Market Makers
Firms (like Citadel Securities, Virtu, Jane Street) that continuously post both bid and ask prices, profiting from the spread. They provide liquidity — without market makers, you'd struggle to find a buyer for your shares immediately. They hedge their inventory risk using derivatives and statistical models.
Hedge Funds
Private investment funds using sophisticated strategies: long/short equity, global macro, statistical arbitrage, event-driven. They can short sell (profit from falling prices), use leverage, and hold complex derivatives. QuantForge's strategies overlap most with quant hedge funds — particularly pairs stat arb and trend following funds.
High-Frequency Traders (HFT)
Firms that hold positions for microseconds to milliseconds, executing millions of trades per day. They profit from tiny price discrepancies between exchanges, market microstructure patterns, and being faster than anyone else. They provide liquidity but also create short-term price noise. QuantForge operates on 15-minute to daily timeframes — far away from HFT territory.
Why This Ecosystem Matters for QuantForge
When QuantForge sends a buy order on SPY, it gets matched with whoever is selling. That seller might be:
- A market maker automatically hedging their inventory
- A pension fund reducing its SPY exposure during rebalancing
- A retail trader panic-selling during a correction (exactly when Mean Reversion says to buy)
- An ETF mechanically selling because it tracks a different index
The market is a constant negotiation between all these parties. Strategies that understand why other participants behave the way they do — not just that prices move — are more robust. Mean Reversion works because retail panic-selling creates temporary mispricings that institutions later correct.
Sources & Further Reading
- Investopedia — Market Maker
- Investopedia — Hedge Fund
- Wikipedia — High-Frequency Trading
- Lewis, Michael — Flash Boys (2014) — accessible book on HFT and market structure
Returns & Log Returns
Before you can measure whether a strategy is working, you need to measure how much prices moved. There are two ways to do this — arithmetic returns and logarithmic returns — and understanding the difference matters.
Arithmetic Returns
The simplest way to measure a price change is the arithmetic return (also called the simple return or percentage return):
SPY closes at $540 on Monday and $548 on Tuesday.
r = (548 - 540) / 540 = 8 / 540 = +1.48%
If SPY then falls back to $540 on Wednesday:
r = (540 - 548) / 548 = -8 / 548 = -1.46%
Problem: +1.48% then −1.46% should return you to zero, but 540 × 1.0148 × (1 − 0.0146) ≈ 539.98 — not exactly 540. Arithmetic returns don't add up across time correctly.
Logarithmic Returns
Logarithmic returns (log returns) solve the compounding problem. They are defined as the natural log of the price ratio:
SPY: $540 → $548 → $540
Day 1 log return: ln(548/540) = ln(1.01481) = +0.01470
Day 2 log return: ln(540/548) = ln(0.98540) = -0.01470
Sum = 0 — perfectly cancels out. Log returns are time-additive.
Why QuantForge uses log returns
Log returns are used in the HMM Regime Filter (applied to SPY daily closes) for three reasons:
- Time-additive: Weekly log return = sum of daily log returns. Arithmetic returns require multiplication.
- Symmetric: A 10% gain and a 10% loss give the same log return magnitude (±0.0953).
- Normality: Log returns are closer to normally distributed than arithmetic returns, which makes statistical models more accurate.
Annualizing Returns
Returns over different time periods need to be normalized to compare them. The standard is to annualize to 252 trading days per year:
Note: returns scale linearly with time; volatility (standard deviation) scales with the square root of time. A strategy with daily σ = 0.8% has annual σ = 0.8% × √252 ≈ 12.7%.
Sources
- Campbell, Lo, MacKinlay — The Econometrics of Financial Markets (1997), Chapter 1 — foundational textbook
- Hull — Options, Futures, and Other Derivatives — continuous compounding and log returns
- Investopedia — Logarithmic Return
Moving Averages: SMA and EMA
A moving average smooths out noise in a price series to reveal the underlying trend direction. There are two main types used in QuantForge: the Simple Moving Average (SMA) and the Exponential Moving Average (EMA).
Simple Moving Average (SMA)
The SMA is the arithmetic mean of the last N closing prices:
| Day | Close | SMA(5) |
|---|---|---|
| 1 | 540.00 | — |
| 2 | 542.00 | — |
| 3 | 539.00 | — |
| 4 | 545.00 | — |
| 5 | 543.00 | (540+542+539+545+543)/5 = 541.80 |
| 6 | 548.00 | (542+539+545+543+548)/5 = 543.40 |
Each day, the oldest price drops off and the newest one is added.
The SMA's weakness: it weighs all N days equally. A price spike 20 days ago has the same influence as yesterday's price. This makes SMA slow to react.
Exponential Moving Average (EMA)
The EMA gives more weight to recent prices using an exponential decay. It only needs the previous EMA value — not the entire history — to update:
N=5 → α = 2/(5+1) = 0.333
Seed: EMA₁ = P₁ = 540.00 (first EMA is just the first price)
| Day | Close | Calculation | EMA(5) |
|---|---|---|---|
| 1 | 540.00 | seed | 540.00 |
| 2 | 542.00 | 0.333×542 + 0.667×540 | 540.67 |
| 3 | 539.00 | 0.333×539 + 0.667×540.67 | 540.11 |
| 4 | 545.00 | 0.333×545 + 0.667×540.11 | 541.74 |
| 5 | 543.00 | 0.333×543 + 0.667×541.74 | 542.16 |
Compare: SMA(5) = 541.80, EMA(5) = 542.16. EMA is higher because it weighted the recent day (543) more heavily.
EMA(50) vs EMA(200): The Golden Cross
The Trend Following strategy in QuantForge uses EMA(50) and EMA(200). The key relationships:
- EMA(50) responds to recent price changes faster than EMA(200)
- When EMA(50) crosses above EMA(200) → Golden Cross → bullish signal (Long)
- When EMA(50) crosses below EMA(200) → Death Cross → bearish signal (Exit)
Sources
- Murphy, John J. — Technical Analysis of the Financial Markets (1999), Chapter 9 — classic reference
- Appel, Gerald — Technical Analysis: Power Tools for Active Investors — EMA origins
- Investopedia — Moving Average · Exponential Moving Average
Standard Deviation & Z-Score
Standard deviation measures how spread out prices are. Z-score tells you how far the current price is from the mean, measured in standard deviations. These are the mathematical core of every mean-reversion strategy.
Variance and Standard Deviation
| Day | Close (xᵢ) | xᵢ - μ | (xᵢ - μ)² |
|---|---|---|---|
| 1 | 538 | -3.6 | 12.96 |
| 2 | 540 | -1.6 | 2.56 |
| 3 | 544 | +2.4 | 5.76 |
| 4 | 542 | +0.4 | 0.16 |
| 5 | 544 | +2.4 | 5.76 |
| μ = 541.6 | Sum: | 27.20 | |
σ² = 27.20 / 5 = 5.44
σ = √5.44 = 2.33
Interpretation: on average, SPY's daily close was $2.33 away from the 5-day mean of $541.60.
Z-Score
The Z-score expresses a price's deviation from the mean in units of standard deviations:
Using the same 5-day window: μ = 541.6, σ = 2.33
z = (535 - 541.6) / 2.33 = -6.6 / 2.33 = -2.83
A Z-score of -2.83 means the price is 2.83 standard deviations below the recent mean. This is statistically unusual (only ~0.2% of normally distributed observations fall below -3σ).
The Mean Reversion strategy enters Long when z < -1.5 and exits when |z| < 0.3.
The 68-95-99.7 Rule
Under a normal distribution, the probability of falling within each range:
- |z| < 1: normal territory — occurs 68.3% of the time
- |z| > 2: unusual — occurs only 4.6% of the time
- |z| > 3: very rare — occurs only 0.3% of the time
Mean reversion strategies exploit the statistical tendency for prices to return toward the mean after extreme z-scores. A Z-score of -2.0 doesn't guarantee a bounce — but historically, prices have tended to revert.
Sources
- Ang, Andrew — Asset Management (2014) — statistical moments in finance
- Investopedia — Z-Score · Standard Deviation
- Aronson, David — Evidence-Based Technical Analysis (2006)
Relative Strength Index (RSI)
The RSI is a momentum oscillator that measures the speed and magnitude of price changes. It always falls between 0 and 100. Values above 70 signal overbought; values below 30 signal oversold. Below 30, QuantForge's RSI Reversion strategy looks for Long entries.
Step-by-Step Calculation
Standard RSI uses a 14-period lookback. The calculation has three steps:
Step 1: Calculate daily gains and losses
For each bar, compare close to previous close:
- If today's close > yesterday's: Gain = close - prev_close, Loss = 0
- If today's close < yesterday's: Gain = 0, Loss = prev_close - close (positive number)
Step 2: Calculate Average Gain and Average Loss
First period: simple average of the first 14 gains and 14 losses.
Subsequent periods: exponential smoothing (Wilder's smoothing = EMA with α = 1/N):
Step 3: Relative Strength → RSI
| Bar | Close | Change | Gain | Loss |
|---|---|---|---|---|
| 1 | 540.00 | — | — | — |
| 2 | 542.50 | +2.50 | 2.50 | 0 |
| 3 | 541.00 | -1.50 | 0 | 1.50 |
| 4 | 545.00 | +4.00 | 4.00 | 0 |
| 5 | 543.00 | -2.00 | 0 | 2.00 |
| 6 | 547.00 | +4.00 | 4.00 | 0 |
| 7 | 548.00 | +1.00 | 1.00 | 0 |
| 8 | 546.00 | -2.00 | 0 | 2.00 |
| 9 | 550.00 | +4.00 | 4.00 | 0 |
| 10 | 548.00 | -2.00 | 0 | 2.00 |
| 11 | 551.00 | +3.00 | 3.00 | 0 |
| 12 | 549.00 | -2.00 | 0 | 2.00 |
| 13 | 552.00 | +3.00 | 3.00 | 0 |
| 14 | 550.00 | -2.00 | 0 | 2.00 |
| 15 | 553.00 | +3.00 | 3.00 | 0 |
First Avg_Gain (bars 2-15): Sum of gains (2.5+4+4+1+4+3+3+3) / 14 = 24.5 / 14 = 1.75
First Avg_Loss (bars 2-15): Sum of losses (1.5+2+2+2+2+2) / 14 = 11.5 / 14 = 0.821
RS = 1.75 / 0.821 = 2.132
RSI = 100 - (100 / (1 + 2.132)) = 100 - 31.9 = 68.1
RSI of 68 is approaching overbought (70). The RSI Reversion strategy would not signal here — it waits for RSI < 30 (oversold) to go Long.
Sources
- Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — RSI original
- Investopedia — Relative Strength Index (RSI)
- Wikipedia — RSI
MACD — Moving Average Convergence Divergence
MACD is a trend-following momentum indicator. It measures the difference between two EMAs. When the MACD line crosses above the signal line and the histogram is positive, the MACD Momentum strategy goes Long.
Three Components
- MACD Line: faster EMA minus slower EMA. Positive = short-term momentum is above long-term.
- Signal Line: smoothed MACD. Acts as a trigger for buy/sell signals.
- Histogram: visualizes the gap between MACD and Signal. Widening = increasing momentum. Narrowing = momentum fading.
Assume EMA(12) and EMA(26) have already been computed for the prior period. We're at the point where momentum is building:
| Day | EMA(12) | EMA(26) | MACD | Signal (EMA9 of MACD) | Histogram |
|---|---|---|---|---|---|
| 1 | 540.10 | 541.50 | -1.40 | — | — |
| 2 | 541.20 | 541.60 | -0.40 | — | — |
| 3 | 542.50 | 541.80 | +0.70 | — | — |
| 4 | 544.00 | 542.10 | +1.90 | — | — |
| 5 | 545.80 | 542.50 | +3.30 | — | — |
| 6 | 547.20 | 543.00 | +4.20 | — | — |
| 7 | 548.50 | 543.60 | +4.90 | — | — |
| 8 | 549.80 | 544.20 | +5.60 | — | — |
| 9 | 551.00 | 544.90 | +6.10 | Seed: +6.10 | 0 |
| 10 | 552.00 | 545.50 | +6.50 | 6.10+0.2×(6.50-6.10)=6.18 | +0.32 |
| 11 | 550.00 | 545.80 | +4.20 | 6.18+0.2×(4.20-6.18)=5.78 | -1.58 |
Day 10: MACD (+6.50) > Signal (+6.18), Histogram positive → momentum is building → QuantForge's strategy looks for a Long entry.
Day 11: Histogram turns negative → momentum fading → potential Exit signal.
Sources
- Appel, Gerald — Technical Analysis: Power Tools for Active Investors (2005) — MACD inventor
- Investopedia — MACD
- Wikipedia — MACD
Bollinger Bands
Bollinger Bands create a dynamic price envelope around a simple moving average. Prices touching the lower band suggest oversold conditions; touching the upper band suggests overbought. QuantForge's Bollinger Reversion strategy trades these extremes.
Formula
Where σ(20) is the standard deviation of the last 20 closes. Because σ grows when prices are volatile and shrinks when they're calm, the bands automatically widen in volatile markets and narrow in calm ones.
Closes: [538, 540, 544, 542, 544]. Mean = 541.6, σ = 2.33 (computed earlier)
Middle Band = 541.60
Upper Band = 541.60 + 2 × 2.33 = 546.26
Lower Band = 541.60 - 2 × 2.33 = 536.94
If SPY closes at 536 — below the lower band — that's a Long signal in the Bollinger Reversion strategy. The exit is when price returns to the middle band.
Band Squeeze
When the bands narrow (σ shrinks), volatility is compressed — a major move is often imminent. Traders watch for band squeezes as setups for breakout strategies (including QuantForge's Volatility Breakout strategy).
Sources
- Bollinger, John — Bollinger on Bollinger Bands (2002) — the original book
- Investopedia — Bollinger Bands
- Wikipedia — Bollinger Bands
Average True Range (ATR)
ATR measures how much an asset moves on a typical day, accounting for overnight gaps. It's the gold standard for setting stop losses and measuring volatility. Both the Volatility Breakout strategy and the stop-loss sizing in QuantForge use ATR.
True Range
Simple high-minus-low misses overnight gaps. True Range (TR) fixes this by also considering the previous close:
| Day | Prev Close | High | Low | H-L | |H-PC| | |L-PC| | TR |
|---|---|---|---|---|---|---|---|
| Monday | 65,000 | 66,200 | 64,800 | 1,400 | 1,200 | 200 | 1,400 |
| Tuesday | 66,200 | 68,500 | 67,000 | 1,500 | 2,300 | 800 | 2,300 |
| Tuesday had a gap up overnight (prev close 66,200 → opened near 67,000). The True Range is 68,500 - 66,200 = 2,300, not just 1,500 (H-L). | |||||||
ATR: Smoothed True Range
SPY price: $548. ATR(14) = $4.20.
The Trend Following strategy uses a stop loss at: Price - 3 × ATR = 548 - 3×4.20 = $535.40
This ensures the stop is outside normal daily noise. A stop at $547 would be stopped out constantly by routine $4 daily moves.
ATR as a Breakout Signal
The Volatility Breakout strategy compares the current ATR to its recent average:
- When ATR > 1.5 × SMA(ATR, 14) — volatility has expanded significantly
- This signals a potential breakout — the strategy enters in the direction of the current candle
Sources
- Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — ATR original
- Investopedia — Average True Range (ATR)
- Kaufman, Perry — Trading Systems and Methods — volatility-based position sizing
Cointegration
Two price series are cointegrated if they tend to move together in the long run, even though they wander apart in the short run. The spread between them is stationary — it mean-reverts. This is the foundation of Pairs Statistical Arbitrage.
Correlation vs. Cointegration
These are different concepts that are often confused:
Correlation measures whether two series move in the same direction at the same time. High correlation does not mean the spread between them is stable.
Cointegration means the spread (Price_A - β × Price_B) is stationary — it has a fixed mean it keeps returning to, no matter how far apart the prices drift.
Example: SPY and QQQ are highly correlated AND cointegrated. Both track the US economy. Their spread fluctuates but doesn't grow indefinitely.
Example: Two random walks can be temporarily correlated but are NOT cointegrated — their spread grows over time without bound.
Engle-Granger Test (Two-Step Method)
QuantForge uses the Engle-Granger method to test cointegration:
Step 1: Run OLS regression to find the hedge ratio β
β is the hedge ratio — how many units of B to hold for each unit of A to make the spread stationary.
Step 2: Test if the residuals ε (the spread) are stationary
Run the Augmented Dickey-Fuller (ADF) test on the residuals. The ADF test checks whether a time series has a unit root (i.e., whether it's a random walk or a stationary, mean-reverting process).
- ADF p-value < 0.05: Reject the null hypothesis of a unit root → the spread IS stationary → the pair IS cointegrated.
- ADF p-value > 0.05: Cannot reject the null → not cointegrated (at the 5% level).
Step 1: Regress SPY on QQQ. Result: β = 1.23 (for each $1 of QQQ, hold $1.23 of SPY to hedge).
Spread: SPY - 1.23 × QQQ
Step 2: Run ADF on the spread.
Result: ADF statistic = -3.8, p-value = 0.003 < 0.05 → Cointegrated!
The strategy can now trade the spread, knowing it will mean-revert.
Sources
- Engle & Granger (1987) — "Co-integration and Error Correction" (Econometrica) — Nobel Prize work
- Vidyamurthy, Ganapathy — Pairs Trading: Quantitative Methods and Analysis (2004)
- Wikipedia — Cointegration
Ornstein-Uhlenbeck (OU) Process
The OU process is a stochastic differential equation that describes mean-reverting behavior. The cointegration spread between two assets often follows an OU process. QuantForge uses it to compute the half-life of mean reversion — which tells you whether a trade will resolve fast enough to be profitable.
The OU Equation
$\theta$ = mean-reversion speed · $\mu$ = long-run mean · $\sigma$ = noise · $dW$ = Wiener process
The key term is θ(μ - X_t): when X is above μ (the mean), the drift is negative — the process is pulled downward. When X is below μ, the drift is positive — pulled upward. This is mean reversion by construction.
Half-Life of Mean Reversion
The half-life tells you: "how long does it typically take for a deviation from the mean to reduce by 50%?" It's derived from θ:
QuantForge estimates θ by fitting an AR(1) (autoregressive) model to the spread series:
Suppose the spread (SPY − 1.23 × QQQ) over 252 days gives an AR(1) coefficient b = −0.045.
θ = −ln(1 + (−0.045)) = −ln(0.955) = 0.0461
half_life = ln(2) / 0.0461 = 15.0 days
Interpretation: a deviation from the mean halves in roughly 15 trading days (~3 weeks). This is a tradeable half-life — the position resolves fast enough to earn a return before the spread wanders further.
QuantForge's gate: if half_life > 60 days, the strategy holds off. A 60-day half-life means it takes months to resolve — too slow, holding costs exceed potential profit.
Z-Score of the Spread
Once the pair is confirmed cointegrated with an acceptable half-life, QuantForge computes a rolling z-score of the spread to trigger entries and exits:
Long: $z < -2.0$ · Short: $z > +2.0$ · Exit: $|z| < 0.5$
Sources
- Wikipedia — Ornstein-Uhlenbeck process
- Avellaneda & Lee (2010) — "Statistical Arbitrage in the US Equities Market"
- Vidyamurthy, Ganapathy — Pairs Trading: Quantitative Methods and Analysis (2004)
Hidden Markov Models (HMM)
An HMM assumes the world exists in one of several hidden states (e.g., "bull market" or "bear market"). You can't observe the state directly — you only see noisy emissions (e.g., daily returns). The HMM learns which state is most likely given the observed data. QuantForge uses a 2-state HMM to classify the market regime from SPY daily log-returns.
The Three Problems of HMMs
- Evaluation: Given a sequence of observations and a trained model, what is the probability of the observation sequence? (Forward algorithm)
- Decoding: Given observations and a trained model, what is the most likely sequence of hidden states? (Viterbi algorithm)
- Learning: Given observations, find the model parameters (transition matrix, emission parameters) that best explain the data. (Baum-Welch algorithm — expectation-maximization)
Key Components
1. Hidden States
For QuantForge's 2-state HMM:
- State 0: One regime (bull or bear — determined after fitting by which has the higher emission mean)
- State 1: The other regime
The model doesn't know which is bull/bear until after fitting. After Baum-Welch converges, the state with the higher emission mean (higher average daily return) is called the bull state.
2. Transition Matrix
A 2×2 matrix A where A[i][j] = probability of transitioning from state i to state j:
3. Emission Distribution
Each state emits observations according to a Gaussian distribution. For log returns:
Baum-Welch Training
Given a sequence of SPY log-returns, Baum-Welch iteratively estimates the transition matrix and emission parameters using expectation-maximization (EM):
- E-step: Given current parameters, compute the probability of being in each state at each time step (forward-backward algorithm)
- M-step: Re-estimate the transition matrix and emission parameters to maximize the likelihood of the observations
- Repeat until convergence (change in log-likelihood < threshold)
Using the HMM: Predict Bull Probability
After training, QuantForge runs the HMM's forward algorithm on the full return sequence to compute the probability of being in the bull state at the latest time step. This is hmm_bull_prob:
hmm_bull_prob > 0.65:High confidence in bull regime → allow Long signalshmm_bull_prob < 0.35:High confidence in bear regime → suppress Long signals (setis_tradable = false)- Between 0.35–0.65: Uncertain — signals pass through unchanged
Trend Following Strategy
The simplest and oldest systematic strategy: identify a trend and ride it. QuantForge's implementation uses an EMA(50)/EMA(200) crossover on GLD and AAPL with a 4-hour timeframe. Enter Long when the fast EMA crosses above the slow EMA; exit when it crosses back below.
Prerequisites
Intuition
Imagine price is a ball rolling down a hill. The EMA(200) is the slope of the hill — it captures the long-term direction. The EMA(50) is where the ball is right now. When the ball rolls to the top of the hill (EMA50 > EMA200), you're in an uptrend. When it rolls back down (EMA50 < EMA200), the trend has reversed.
The crossover is the confirmation signal. It doesn't catch the exact bottom or top — it catches the confirmed direction after momentum has shifted.
Step-by-Step Logic
- Collect the last 210+ four-hour bars for the asset (AAPL or GLD)
- Compute EMA(50) and EMA(200) on closing prices
- Compare current vs previous bar's EMA relationship:
- Was EMA50 ≤ EMA200 last bar, and EMA50 > EMA200 now? → Golden Cross → Long signal
- Was EMA50 ≥ EMA200 last bar, and EMA50 < EMA200 now? → Death Cross → Exit signal
- Otherwise → Hold
- Set stop loss at: entry_price − 3 × ATR(14)
- Confidence = 1.0 on crossover, 0.0 otherwise
| Bar | Close | EMA(50) | EMA(200) | Signal |
|---|---|---|---|---|
| T-2 | $182.40 | $181.20 | $181.90 | — |
| T-1 | $183.10 | $181.55 | $181.92 | EMA50 < EMA200, no cross |
| T (now) | $184.80 | $182.10 | $181.95 | Golden Cross → Long! |
ATR(14) = $1.85. Stop loss = $184.80 − 3×$1.85 = $179.25
If GLD rallies to $188 and then EMA(50) crosses back below EMA(200), the Exit signal fires. Profit ≈ $188 − $184.80 = $3.20/share.
When It Works / When It Fails
QuantForge Parameters
- Assets: GLD, AAPL
- Timeframe: 4-hour bars
- Fast EMA: 50 periods
- Slow EMA: 200 periods
- Stop loss multiplier: 3× ATR(14)
- Min confidence to trade: 0.55 (but this strategy always outputs 0.0 or 1.0)
- Required history: 210 bars
Sources
- Faber (2007) — "A Quantitative Approach to Tactical Asset Allocation"
- Hurst, Ooi, Pedersen (2017) — "A Century of Evidence on Trend-Following Investing" (AQR)
- Covel, Michael — Trend Following (2004)
Volatility Breakout Strategy
When volatility suddenly spikes — measured by ATR expanding above its recent average — it signals that a breakout from a range is underway. QuantForge trades in the direction of the current candle when this happens. Applied to BTC/USD and ETH/USD, which are known for sudden, large moves.
Prerequisites
Intuition
Markets alternate between quiet (low volatility, tight range) and explosive (high volatility, large candles). ATR measures this breathing rhythm. When ATR suddenly jumps to 1.5× its 14-period average, the market is waking up from sleep. The first big move in a new direction often continues — this is the breakout.
Think of a compressed spring. As prices chop sideways for days, energy builds. When the spring releases, price moves fast and far. This strategy tries to be early in that move.
Step-by-Step Logic
- Compute ATR(14) from the last 14+ daily bars
- Compute SMA(14) of ATR values (the baseline volatility)
- Get the current candle's body:
body = |close - open| - Check breakout conditions:
- ATR > 1.5 × SMA(ATR): volatility is expanding
- Current body > 0.5 × ATR: candle is meaningful, not noise
- Direction: if close > open → Long; if close < open → Short
- Stop loss: 2× ATR below entry (Long) or above entry (Short)
- Confidence = ATR / (SMA_ATR × 2), capped at 1.0
| Day | Open | Close | ATR(14) | SMA(ATR) | Ratio | Signal |
|---|---|---|---|---|---|---|
| T-5 | 64,100 | 64,300 | 980 | 950 | 1.03 | None |
| T-4 | 64,400 | 64,250 | 940 | 948 | 0.99 | None |
| T-3 | 64,200 | 64,500 | 920 | 946 | 0.97 | None (quiet) |
| T (now) | 64,400 | 67,200 | 2,100 | 950 | 2.21 | Long! (breakout) |
ATR(14) = 2,100, SMA(ATR) = 950. Ratio = 2.21 > 1.5 ✓
Body = |67,200 - 64,400| = 2,800. 0.5 × ATR = 1,050. Body > 1,050 ✓
Close (67,200) > Open (64,400) → Long signal
Stop loss = 67,200 − 2 × 2,100 = 63,000
Confidence = 2,100 / (950 × 2) = 1.105 → capped at 1.0
When It Works / When It Fails
Sources
- Williams, Larry — Long-Term Secrets to Short-Term Trading (1999) — volatility breakout origins
- Donchian, Richard — channel breakout systems — historical predecessor
- Investopedia: Breakout Strategy
Mean Reversion Strategy
Prices tend to oscillate around their historical mean. When a price deviates too far — measured by Z-score — it statistically tends to revert. QuantForge uses a Z-score of the SMA(20) on a 15-minute timeframe for SPY, QQQ, and AAPL.
Prerequisites
Intuition
Imagine a rubber band. The SMA is the center point. Every time a price moves away from the SMA, the rubber band stretches. At some point the tension pulls it back. Mean reversion bets that prices which have stretched too far (high z-score magnitude) will snap back.
Unlike trend following, mean reversion is a counter-trend strategy. You're buying when the crowd is selling (price below mean) and selling when the crowd is buying (price above mean).
Step-by-Step Logic
- Collect 25+ 15-minute bars
- Compute SMA(20) of closing prices
- Compute standard deviation σ over the last 20 closes
- Z-score = (current_close − SMA20) / σ
- Entry rules:
- z < −1.5 → Long (price too low, expect bounce)
- z > +1.5 → Short (price too high, expect drop)
- |z| < 0.3 → Exit (back to mean)
- Stop loss: Long = 2×ATR below entry; Short = 2×ATR above entry
- Confidence = min(|z| / threshold, 1.0) — stronger z-score = more confidence
Last 20 closes (15-min bars, ~5 hours of trading): mean = $541.60, σ = $2.33
Current close: $535.50
Z-score = (535.50 − 541.60) / 2.33 = −6.10 / 2.33 = −2.62
−2.62 < −1.5 → Long signal
Confidence = 2.62 / 1.5 = 1.0 (capped). Maximum confidence.
ATR(14) = $1.10. Stop loss = $535.50 − 2×$1.10 = $533.30
Exit fires when z returns to |z| < 0.3, i.e., price between ~$540.90 and ~$542.30.
Sources
- Chan, Ernest P. — Quantitative Trading (2009) — mean reversion strategies
- Chan, Ernest P. — Algorithmic Trading (2013) — Chapter 2
- Aronson, David — Evidence-Based Technical Analysis (2006)
RSI Reversion Strategy
RSI below 30 is oversold — the asset has lost momentum and historically tends to bounce. RSI above 70 is overbought — tends to pull back. QuantForge runs this strategy on SPY and QQQ on the daily timeframe.
Prerequisites
Intuition
RSI measures the ratio of recent gains to recent losses. If RSI = 20, it means the asset has had far more losses than gains over the last 14 bars. This extreme reading suggests selling pressure has been exhausted — there are few sellers left. A bounce is more likely than a continued fall. This is the contrarian bet RSI Reversion makes.
Step-by-Step Logic
- Collect 20+ daily bars
- Compute RSI(14)
- Entry rules:
- RSI < 30 → Long (oversold)
- RSI > 70 → Short (overbought)
- RSI between 45–55 → Exit (neutral territory, close position)
- Confidence:
- Long: (30 − RSI) / 30 — deeper below 30 = higher confidence
- Short: (RSI − 70) / 30
- Stop loss: 2× ATR(14) from entry
After a market selloff, QQQ RSI(14) = 22.4
22.4 < 30 → Long signal
Confidence = (30 − 22.4) / 30 = 7.6 / 30 = 0.253
Hmm — 0.253 < 0.55 (min_confidence). This trade would be blocked by the risk engine!
To get approved: RSI would need to be < 13.5 (confidence > 0.55). That's an extremely deep oversold reading, which is rare but real during crisis events.
Lesson: Deep RSI readings provide the strongest signals, but they're also rare. This strategy generates fewer but higher-conviction trades than Mean Reversion.
Sources
- Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — RSI original
- Connors, Larry & Alvarez, Cesar — Short Term Trading Strategies That Work (2008) — RSI(2) high-frequency version
MACD Momentum Strategy
When the MACD line crosses above its signal line AND the histogram is positive, momentum is building to the upside. QuantForge runs MACD Momentum on SPY and QQQ on the hourly timeframe.
Prerequisites
Step-by-Step Logic
- Collect 50+ hourly bars (EMA26 needs 26+, signal line needs 9 more)
- Compute MACD = EMA(12) − EMA(26)
- Compute Signal = EMA(9) of MACD
- Compute Histogram = MACD − Signal
- Entry rules:
- MACD > Signal AND histogram > 0 AND MACD recently crossed above Signal → Long
- MACD < Signal AND histogram < 0 AND MACD recently crossed below Signal → Short
- Histogram reverses sign → Exit
- Confidence = min(|histogram| / ATR, 1.0) — larger histogram vs. ATR = stronger signal
Previous bar: MACD = −0.18, Signal = −0.05. MACD < Signal.
Current bar: MACD = +0.12, Signal = −0.02. MACD > Signal (crossover!)
Histogram = +0.12 − (−0.02) = +0.14
ATR(14) = $0.55
Confidence = 0.14 / 0.55 = 0.255 — too low (below 0.55 minimum). No trade.
Later that session: MACD = +0.35, Signal = +0.08. Histogram = +0.27.
Confidence = 0.27 / 0.55 = 0.49 — still below threshold.
After a stronger move: MACD = +0.48, histogram = +0.38. Confidence = 0.38/0.55 = 0.69 → Long approved!
Sources
- Appel, Gerald — Technical Analysis: Power Tools for Active Investors (2005)
- Investopedia: MACD
- Murphy, John J. — Technical Analysis of the Financial Markets, Chapter 10
Bollinger Bands Reversion Strategy
When price touches or crosses outside a Bollinger Band, it's statistically unusual — about 95% of closes fall inside the 2σ bands. QuantForge uses this on QQQ and AAPL on the daily timeframe to trade the reversion back to the middle band.
Prerequisites
Step-by-Step Logic
- Collect 25+ daily bars
- Compute Middle Band = SMA(20), Upper = Middle + 2σ, Lower = Middle − 2σ
- Compute %B = (close − Lower) / (Upper − Lower). %B = 0.0 means on lower band, 1.0 on upper band.
- Entry rules:
- %B < 0 (close below lower band) → Long
- %B > 1 (close above upper band) → Short
- %B between 0.4–0.6 (back near middle) → Exit
- Confidence:
- Long: |%B| (how far below the lower band)
- Short: %B − 1.0
- Stop loss: Long = 2×ATR below entry; Short = 2×ATR above entry
SMA(20) = $438.50, σ = $4.20
Upper Band = $438.50 + 2×$4.20 = $446.90
Lower Band = $438.50 − 2×$4.20 = $430.10
Today's close: $428.60 (below lower band)
%B = (428.60 − 430.10) / (446.90 − 430.10) = −1.50 / 16.80 = −0.089
%B < 0 → Long signal
Confidence = |−0.089| = 0.089 — very low, well below 0.55. Not approved by risk engine.
For a stronger signal: close at $425.00. %B = (425−430.10)/16.80 = −0.304. Still 0.304 < 0.55.
For approval: close needs to be at $421.67 (%B = −0.5) or below. That's a 3.9% close below the lower band — a significant event (2.5+ σ move).
This filters Bollinger signals to only the most extreme readings, which have the strongest statistical reversion tendency.
Sources
- Bollinger, John — Bollinger on Bollinger Bands (2002)
- Kaufman, Perry — Trading Systems and Methods — %B computation
Momentum Breakout Strategy
When an asset breaks above its recent N-day high on strong rate-of-change momentum, it often continues higher. This is the classic Donchian channel breakout, combined with a rate-of-change filter. QuantForge runs this on SPY and QQQ on the daily timeframe.
Prerequisites
Intuition
When a price breaks above its 20-day high, every trader who held the asset over that period is now profitable. There's no overhead supply (people who want to sell at break-even). Prices can move freely upward. Combined with strong rate-of-change (positive ROC), this confirms real buying pressure, not a random spike.
Step-by-Step Logic
- Collect 25+ daily bars
- Find highest_high = max(high prices) over last 20 bars
- Compute ROC(10) = (close_today / close_10_days_ago) − 1
- Entry rules:
- close > highest_high AND ROC > 0.02 (+2%) → Long
- close < lowest_low AND ROC < −0.02 → Short
- close retraces > 50% of the breakout move → Exit
- Confidence = min(ROC / 0.05, 1.0) — stronger momentum = higher confidence
20-day highest high = $548.00. Today's close = $549.50. Breakout!
ROC(10) = (549.50 / 534.80) − 1 = 1.0275 − 1 = +2.75%
2.75% > 2.0% → momentum confirmed ✓
Confidence = 0.0275 / 0.05 = 0.55 — exactly at the minimum threshold. Approved (barely).
ATR(14) = $3.20. Stop loss = $549.50 − 2×$3.20 = $543.10
If SPY rallies to $565 and then closes back below $548 (the old high), Exit fires.
Sources
- Donchian, Richard — Donchian Channel systems (1960s) — historical origin
- Jegadeesh & Titman — "Returns to Buying Winners and Selling Losers" (1993, Journal of Finance) — academic evidence for momentum
- Asness, Moskowitz, Pedersen — "Value and Momentum Everywhere" (2013, Journal of Finance)
Pairs Statistical Arbitrage
The most mathematically sophisticated strategy in QuantForge. Two cointegrated assets share a long-run equilibrium. When their spread deviates significantly (z < −2 or z > +2), trade the convergence. Applied to SPY/QQQ (equities) and BTC/ETH (crypto).
Prerequisites
Intuition
SPY and QQQ both track the US equity market. They're driven by the same macroeconomic forces. Normally their spread (SPY − β×QQQ) is stable. But sometimes, one has a large intraday move that the other doesn't follow immediately — a sector rotation, index rebalancing, or liquidity event. The spread widens temporarily. Since the fundamentals haven't changed, the spread is expected to close.
This is market-neutral: you go Long the underperforming asset and Short the overperforming one. If the overall market falls, both sides fall together — and you're hedged. You only make money (or lose money) on the convergence of the spread.
Complete Step-by-Step Logic
- Gather data: 252+ daily bars for both the primary (SPY) and companion (QQQ)
- Cointegration test: Run Engle-Granger on the full 252-day window
- OLS regression: SPY = α + β×QQQ → get β (hedge ratio)
- ADF test on residuals: must give p-value < 0.05
- If NOT cointegrated: output Hold, don't trade
- OU half-life check: Fit AR(1) to the spread residuals, compute half_life
- If half_life > 60 days: spread reverts too slowly, output Hold
- Compute rolling spread: spread_t = SPY_t − β × QQQ_t
- Z-score: z = (spread_t − mean(spread, 60)) / std(spread, 60)
- Trade:
- z < −2.0 → Long (SPY too cheap vs QQQ — buy SPY, short QQQ)
- z > +2.0 → Short (SPY too expensive vs QQQ — sell SPY, buy QQQ)
- |z| < 0.5 → Exit (spread converged)
- Confidence: min(|z| / 4.0, 1.0)
Day 252 ADF test result: p-value = 0.013 < 0.05 → Cointegrated ✓
Hedge ratio β = 1.23 (hold $1.23 of QQQ for each $1 of SPY)
OU half-life: AR(1) coefficient b = −0.052 → half_life = 0.693/0.054 = 12.8 days ✓ (under 60)
Today's spread: SPY = $548.20, QQQ = $453.10 → spread = 548.20 − 1.23×453.10 = 548.20 − 557.31 = −9.11
60-day spread stats: mean = −2.40, std = 3.80
Z-score: (−9.11 − (−2.40)) / 3.80 = −6.71 / 3.80 = −1.77
−1.77 > −2.0: Not yet at entry threshold. Hold.
Next day: SPY gaps down slightly, spread = −10.20. Z = (−10.20 − (−2.40))/3.80 = −2.05 → Long entry!
Position: Long SPY, Short QQQ (with β = 1.23 as ratio).
Exit when |z| < 0.5, i.e., spread returns near −2.40 (the mean).
Sources
- Engle & Granger — "Co-integration and Error Correction" (1987, Econometrica)
- Vidyamurthy, Ganapathy — Pairs Trading: Quantitative Methods and Analysis (2004) — the practitioner's bible
- Avellaneda & Lee — "Statistical Arbitrage in the US Equities Market" (2010)
- Do & Faff — "Does Simple Pairs Trading Still Work?" (2010, Financial Analysts Journal)
Kalman Filter Pairs Trading
An upgrade to OLS Pairs Stat Arb: instead of a fixed hedge ratio β computed once, a 1-D Kalman filter tracks a time-varying β that adapts to structural shifts in the relationship. Applied to SPY/QQQ and BTC/ETH on daily bars.
Prerequisites
Why Kalman Instead of OLS?
OLS regression computes a single β over the entire look-back window and holds it fixed until the next recompute. This works until the relationship between two assets shifts — a common event called a regime change. Examples:
- QQQ becomes more tech-heavy after a large rebalancing → SPY/QQQ ratio drifts
- BTC and ETH decouple temporarily during an altcoin rally
- A corporate event (merger, index addition) shifts one asset's loading on the other
The Kalman filter treats β as a random walk with noise. Each new observation updates our estimate of β, weighted by how informative that observation is relative to how noisy the process is. The result: β adapts continuously, so the spread reflects the current relationship rather than the average over the past year.
The 1-D Kalman Filter Model
The model has two equations:
State equation (how β evolves):
β_t = β_{t−1} + w_t, w_t ~ N(0, Q)
Observation equation (what we see):
y_t = β_t × x_t + e_t, e_t ~ N(0, R_t)
Here y_t is the primary price, x_t is the companion price, and e_t is the spread innovation (our trading signal). Q is the process noise (how fast β can change) and R_t is the observation noise.
Rλ Parameterisation
Instead of directly choosing Q, QuantForge uses a delta parameter (default: 1e-4) that controls the signal-to-noise ratio:
Q = delta / (1 − delta)
Small delta (0.0001) → β changes slowly, appropriate for stable pairs. Large delta (0.01) → β changes quickly, good for volatile relationships. The observation noise scales with the companion price squared: R_t = obs_noise × (1 + x_t²), keeping the filter well-conditioned across price levels.
Complete Step-by-Step Logic
- Normalise prices: x_scale = mean(x over all bars); xi = x_i / x_scale; yi = y_i / x_scale
- Kalman pass over all bars (warmup = 30 bars):
- Predict: p_pred = p + Q
- Compute innovation: e = yi − β × xi
- Compute Kalman gain: K = p_pred × xi / (p_pred × xi² + R_t)
- Update: β = β + K × e; p = (1 − K × xi) × p_pred
- Store innovation e
- Rolling z-score: over last 30 innovations — z = (current_e − mean) / std
- Trade:
- z < −1.5 → Long (primary cheap relative to companion)
- z > +1.5 → Short (primary expensive)
- |z| < 0.3 and |prev_z| ≥ 0.3 → Exit
Kalman vs. OLS: The Key Difference
| Property | OLS Pairs Arb | Kalman Pairs |
|---|---|---|
| Hedge ratio | Fixed (recomputed daily) | Time-varying (updates each bar) |
| Regime changes | Stale β until next window | β adapts within days |
| Cointegration test | Required (Engle-Granger + ADF) | Built into state noise (no separate test) |
| Computation | Batch OLS once per window | O(n) sequential pass |
QuantForge Parameters
| Parameter | Default | Meaning |
|---|---|---|
| delta | 1e-4 | Process noise — how fast β can drift per bar |
| obs_noise | 1e-3 | Base observation noise multiplier |
| zscore_window | 30 | Rolling window for innovation z-score |
| entry_z | 1.5 | Entry threshold in standard deviations |
| exit_z | 0.3 | Exit when |z| falls below this |
| warmup | 30 | Bars before β is considered reliable |
Sources
- Chan, Ernest P. — Algorithmic Trading: Winning Strategies and Their Rationale (2013) ch. 4 — the Rλ parameterisation used here
- Avellaneda & Lee — "Statistical Arbitrage in the US Equities Market" (2010)
- Kalman, R.E. — "A New Approach to Linear Filtering and Prediction Problems" (1960, ASME Journal)
Carry Strategy
In traditional finance, "carry" is the return from holding an asset regardless of price changes — e.g., bond yield, dividend yield, FX interest differential. For spot assets without explicit carry, QuantForge proxies carry via rolling risk-adjusted return differentials. Applied to BTC/USD, ETH/USD, GLD, and USO on daily bars.
Prerequisites
What Is "Carry" in Practice?
The academic carry trade (Koijen et al. 2018) captures the return differential across assets from holding them — forward premium for currencies, dividend yield for equities, roll yield for futures. For spot crypto and commodities (where there's no explicit yield), QuantForge defines carry as a Sharpe-like ratio:
carry(window) = rolling_return(window) / rolling_vol(window)
This is a risk-adjusted measure of how well the asset has rewarded holders over a look-back period — exactly a Sharpe ratio without annualisation.
The strategy then computes the differential between short-term and long-term carry:
carry_signal = carry(30 days) − carry(90 days)
Signal Interpretation
The differential tells you whether the asset's short-term risk-adjusted performance is accelerating or decelerating relative to its medium-term trend:
Short-term Sharpe is much better than medium-term → carry is accelerating → asset is rewarding holders more richly right now → go long.
Short-term Sharpe is much worse than medium-term → carry is deteriorating → the asset that was rewarding holders is now punishing them → go short.
Complete Step-by-Step Logic
- Load closes: 95+ daily bars for the symbol
- Short carry (30 days):
- short_return = (close[today] − close[today−30]) / close[today−30]
- short_vol = std(daily returns over last 30 days)
- short_carry = short_return / short_vol (or 0 if vol ≈ 0)
- Long carry (90 days): same formula with 90-bar look-back
- Signal: carry_signal = short_carry − long_carry
- Trade:
- signal > +0.5 → Long
- signal < −0.5 → Short
- |signal| < 0.1 and |prev_signal| ≥ 0.1 → Exit
- Stop: 2 × short_vol × last_close below entry
When Carry Works / When It Fails
- Asset has clear trending behaviour (BTC bull runs, GLD safe-haven demand)
- Momentum and carry are aligned (carry accelerates → price trend continues)
- Asset mean-reverts aggressively (large short-term moves always reverse)
- Carry differential is noisy — many false crossings around the threshold
- Macro regime shifts: 2022 BTC crash had persistently negative carry; short is correct but the strategy may exit early
Sources
- Koijen, Moskowitz, Pedersen & Vrugt — "Carry" (2018, Journal of Financial Economics) — the defining carry paper
- Asness, Moskowitz & Pedersen — "Value and Momentum Everywhere" (2013, Journal of Finance)
Multi-Factor Momentum
Combines three orthogonal momentum horizons — 20-day, 63-day (skip 5), and 252-day (skip 20) — plus a vol-adjusted factor into a single composite score. Applied to SPY, QQQ, BTC/USD, and ETH/USD on daily bars. Requires 277 bars of history.
Prerequisites
Why Three Horizons?
Academic research (Jegadeesh-Titman 1993, Moskowitz-Ooi-Pedersen 2012) shows momentum exists simultaneously at multiple time scales:
- Short (20 days ≈ 1 month): Captures the most recent trend — most responsive but noisy
- Medium (63 days ≈ 3 months, skip last 5): Classic "3-month momentum" with the short-term reversal effect removed by skipping the last week
- Long (252 days ≈ 12 months, skip last 20): Jegadeesh-Titman momentum — the 12-month return excluding the last month avoids "momentum reversal"
The "skip period" is a well-documented trick: the most recent 1-5 days of a long-horizon return exhibit short-term reversal, contaminating the momentum signal. Skipping them isolates the true trend from microstructure noise.
Vol-Adjusted Factor
A fourth factor normalises the medium-term return by rolling volatility — a Sharpe-ratio-style measure:
vol_adj_mom = med_return / realized_vol(21 days)
This up-weights momentum signals that occur in calm environments (high vol → discount the signal; low vol → trust the signal).
tanh Normalisation
Combining factors from different time scales requires normalising them to a common range. QuantForge uses a sign-preserving tanh normalisation:
scale = ann_vol × sqrt(window / 252) # expected magnitude of a random-walk return output = tanh(ret / scale) # maps to (−1, +1), sign-preserving
The scale is the expected absolute return of a random walk over the horizon — so a return that's "normal" for a random walk maps to tanh(1) ≈ 0.76, while an extreme return maps to ±1. Critically: if all factors are positive, the composite is positive, correctly signalling Long.
Composite Score
composite = (
tanh_norm(short_20d, ann_vol, 20)
+ tanh_norm(med_63d, ann_vol, 63)
+ tanh_norm(long_252d, ann_vol, 252)
+ tanh(vol_adj_mom / sqrt(63))
) / 4.0
The composite lives in (−1, +1). Entry threshold: 0.25 in either direction.
QuantForge Parameters
| Parameter | Default | Note |
|---|---|---|
| short_window | 20 | 1-month momentum |
| med_window / med_skip | 63 / 5 | 3-month, skip 1 week |
| long_window / long_skip | 252 / 20 | 12-month, skip 1 month (Jegadeesh-Titman) |
| entry_threshold | 0.25 | Composite score for entry (in −1 to +1 scale) |
| required_history | 277 bars | 252 + 20 (long_skip) + 5 padding |
Sources
- Jegadeesh & Titman — "Returns to Buying Winners and Selling Losers" (1993, Journal of Finance) — the foundational momentum paper
- Moskowitz, Ooi & Pedersen — "Time Series Momentum" (2012, Journal of Financial Economics)
- Barroso & Santa-Clara — "Momentum Has Its Moments" (2015) — vol-scaling of momentum signals
VWAP Reversion
Fades intraday price deviations from the rolling Volume-Weighted Average Price (VWAP). When price moves more than ±2 standard deviations from the rolling VWAP, expect it to snap back. Runs on 15-minute bars for SPY, QQQ, BTC/USD, and ETH/USD.
Prerequisites
What Is VWAP?
VWAP is the average price of every trade in a session, weighted by how many shares (or units) traded at that price. A trade of 10,000 shares at $450 has more influence on VWAP than 100 shares at $460.
typical_price = (high + low + close) / 3 VWAP = Σ(typical_price × volume) / Σ(volume) over the last N bars
The typical price includes high and low to capture intraday range, not just closing price. QuantForge uses a rolling 26-bar window (≈ 6.5 trading hours at 15 min/bar).
Why VWAP Is Mean-Reverting Intraday
Institutional investors measure their execution quality against VWAP. If they need to buy 1 million shares in a day, they'll try to complete it near the VWAP. This creates a gravitational pull: when price wanders far from VWAP, large institutional orders arrive to bring it back.
The effect is strongest intraday and weakens across sessions (overnight gaps can shift VWAP dramatically). This is why QuantForge runs VWAP reversion only on 15-minute bars — a frequency where institutional VWAP execution is active.
The Band
The entry threshold isn't a fixed dollar amount — it's a number of standard deviations of recent price-minus-VWAP deviations:
band_σ = std(close − VWAP) over last 26 bars deviation = (close − VWAP) / band_σ # signed z-score Long if deviation < −2.0 (price is 2σ below VWAP) Short if deviation > +2.0 (price is 2σ above VWAP) Exit if |deviation| < 0.4 and |prev_deviation| ≥ 0.4
Complete Step-by-Step Logic
- Load bars: 28+ 15-minute bars (window=26, need 2 extra for exit detection)
- Rolling VWAP over last 26 bars:
- typical_price[i] = (high[i] + low[i] + close[i]) / 3
- VWAP = Σ(typical_price × volume) / Σ(volume)
- Band: deviations = close[i] − VWAP; band_σ = std(deviations)
- Current deviation z-score: dev = (close[last] − VWAP) / band_σ
- Previous bar z-score: compute VWAP and band_σ from the 26-bar window ending at the previous bar, then prev_dev = |close[last−1] − prev_vwap| / prev_σ
- Trade:
- dev < −2.0 → Long
- dev > +2.0 → Short
- |dev| < 0.4 and prev_dev ≥ 0.4 → Exit
- Stop: 3σ (band_mult + 1.0) beyond entry to avoid catching a falling knife
Why Use Previous Bar's σ for Exit Detection?
A spike in volatility (e.g., a news event) can inflate today's band_σ, making the current deviation look smaller than it is. If we used today's σ for the exit check, a vol spike would hide the fact that the previous bar was already inside the band — creating a false exit signal.
By computing the previous bar's deviation using the previous bar's own σ, the comparison is like-for-like: "was it outside the band yesterday by yesterday's standard, and is it inside today by today's standard?"
When VWAP Reversion Fails
Sources
- Harris, Lawrence — Trading and Exchanges: Market Microstructure for Practitioners (2003) ch. 16 — VWAP and institutional execution
- Berkowitz, Logue & Noser — "The Total Cost of Transactions on the NYSE" (1988, Journal of Finance)
- Kissell & Glantz — Optimal Trading Strategies (2003) — VWAP execution benchmarks
Regime Detection (SimpleRegimeDetector)
Different market regimes favor different strategies. Trend-following thrives in trending markets; mean reversion thrives in ranging ones. QuantForge classifies the current market regime from SPY price data every cycle and adjusts position sizes accordingly.
The 8 Regimes
| Regime | Meaning | Position Scale |
|---|---|---|
| Crisis | Extreme drawdown, panic selling | 10% of normal |
| High Volatility | Realized vol > 30% annualized | 50% |
| Risk Off | Negative SPY trend, elevated vol | 50% |
| Uncertain | Mixed signals, no clear regime | 75% |
| Range | Low vol, no trend, price oscillating | 75% |
| Trend | Clear directional price trend | 100% |
| Risk On | Strong positive trend, low vol | 100% |
| Low Volatility | Very calm, low vol environment | 100% |
How It's Computed
- Compute realized volatility from the last 20 SPY returns (annualized)
- Compute SPY 20-day return (trend direction)
- Compute portfolio drawdown from peak
- Rules (in order of priority):
- Drawdown > 15% → Crisis
- Realized vol > 30% AND drawdown > 5% → High Volatility
- SPY 20d return < −5% → Risk Off
- Realized vol < 10% AND SPY return > 2% → Low Volatility
- SPY return > 5% → Risk On
- SPY return > 2% → Trend
- Realized vol < 15% → Range
- Otherwise → Uncertain
The regime affects position sizing via the scale factor in RiskConfig. A Crisis regime reduces all new trades to 10% of their normal size — protecting capital during extreme market dislocations.
HMM Regime Filter
A 2-state Hidden Markov Model trained on SPY daily log-returns. The bull-state probability from this model gates all Long signals across every strategy. When the HMM is confident the market is in a bear state, all Long entries are blocked — regardless of what individual strategy indicators say.
Prerequisites
How It Works in QuantForge
- Gather the last 90 SPY daily closes (from the
barstable) - Compute 89 daily log-returns: r_t = ln(P_t / P_{t-1})
- Fit a 2-state Gaussian HMM using Baum-Welch on these 89 returns
- Identify the "bull state": whichever state has the higher emission mean (higher average daily return)
- Run the forward algorithm to get the probability of being in the bull state right now →
hmm_bull_prob - Apply the gate in the worker:
hmm_bull_prob < 0.35AND direction = Long →is_tradable = false- Invalidation reason: "HMM regime filter: bull_prob below threshold"
After 3 weeks of falling SPY prices, the HMM detects a shift:
Bull state emission: μ = +0.0008, Bear state emission: μ = −0.0019
Forward algorithm on recent returns: P(bull) = 0.18
0.18 < 0.35 → HMM is confident we're in bear state.
Effect: All Long signals from all 8 strategies are blocked today, even if their individual indicators say Long.
Short signals: NOT blocked. Exit signals: NOT blocked (exits are always allowed).
This protects the portfolio from buying into a falling market just because an individual indicator briefly flashed Long.
Risk Engine
Every signal generated by a strategy passes through a 9-gate risk filter before becoming an order. The risk engine protects the portfolio from excessive losses, concentration risk, and low-quality signals. It runs in the worker process synchronously after signal generation.
The 9 Gates (in order)
Gate 1: Tradability
If is_tradable = false (set by the HMM filter or the strategy itself), the signal is rejected immediately. No further checks.
Gate 2: Exit Signals Always Pass
Exit signals (close an existing position) always pass the risk engine — regardless of portfolio state. Even during a drawdown crisis, existing positions must be closeable. The risk engine returns an approved evaluation with quantity = 0 (the execution layer handles the actual close).
Gate 3: Hold Signals
Hold signals do nothing. Rejected as "no action required."
Gate 4: Confidence Filter
Gate 5: Circuit Breaker — 15% Drawdown
If the portfolio's drawdown from peak exceeds 15%, all new entries are blocked. Exits still pass (Gate 2). This is the hard stop that prevents the system from digging deeper into a loss hole.
Gate 6: Open Position Cap
Maximum 10 simultaneous open positions. If the portfolio already has 10+ open positions, new entries are rejected. This prevents over-diversification from becoming over-concentration.
Gate 7: Concentration Cap
No single position can exceed 10% of total portfolio equity. The existing exposure for this symbol is checked. If adding a new position would push the symbol above 10%, the signal is rejected or sized down to fit within the limit.
Gate 8: Position Sizing
Compute the trade quantity. This is the most complex gate. See the Position Sizing page for the full calculation. If the computed quantity is 0 (not enough room), the signal is rejected.
Gate 9: Remaining Room Cap
After computing the quantity, check whether adding this position would exceed the remaining portfolio capacity. If yes, reduce quantity to fit within the remaining room, or reject if no room exists.
Gate Summary
| Gate | Condition | Result |
|---|---|---|
| 1. Tradability | is_tradable = false | Reject |
| 2. Exit Pass | direction = Exit | Approve always |
| 3. Hold Reject | direction = Hold | No action |
| 4. Confidence | confidence < 0.55 | Reject |
| 5. Circuit Breaker | drawdown > 15% | Reject |
| 6. Position Cap | open_positions ≥ 10 | Reject |
| 7. Concentration | symbol_exposure > 10% | Size down or reject |
| 8. Position Sizing | quantity = 0 | Reject |
| 9. Room Cap | no portfolio room left | Reduce or reject |
Position Sizing
Position sizing answers: given a valid signal, how many shares should we buy? Too few and you miss the opportunity. Too many and a single bad trade wipes out the portfolio. QuantForge uses stop-loss-based sizing as the primary method with a fractional Kelly fallback, further scaled by the current market regime.
Method 1: Stop-Loss-Based Sizing (Primary)
Risk exactly 1% of portfolio equity per trade. The stop-loss distance tells you how far the trade can move against you before you exit. From these two numbers, you compute the maximum quantity:
Portfolio equity = $100,000. entry_price = $548.00, stop_price = $541.00
risk_dollars = $100,000 × 0.01 = $1,000
stop_distance_pct = (548 − 541) / 548 = 7 / 548 = 0.01277 (1.277%)
quantity = $1,000 / ($548 × 0.01277) = $1,000 / $7.00 = 142.8 → 142 shares
Trade value = 142 × $548 = $77,816 (77.8% of portfolio) — but maximum position is capped at 10% by Gate 7:
max_value = $100,000 × 0.10 = $10,000 → max_quantity = $10,000 / $548 = 18 shares
After concentration cap: 18 shares. Trade value $9,864.
Note: Stop-based sizing can produce very large quantities when the stop is tight. The concentration cap (Gate 7, 10% max) is the binding constraint here.
Method 2: Fractional Kelly (Fallback)
Used when no stop price is available. Kelly Criterion gives the theoretically optimal bet fraction given the win probability and payoff ratio. QuantForge uses a conservative 25% of full Kelly (quarter-Kelly) to account for model uncertainty:
Historical backtests for a strategy: win_rate = 55%, avg_win = $320, avg_loss = $180
Payoff ratio = 320/180 = 1.778
f* = (0.55 × 1.778 − 0.45) / 1.778 = (0.978 − 0.45) / 1.778 = 0.528 / 1.778 = 0.297
Quarter-Kelly: f = 0.25 × 0.297 = 0.074 (7.4% of equity)
quantity = ($100,000 × 0.074) / $548 = $7,400 / $548 = 13 shares
Regime Scaling
After computing the base quantity, it's multiplied by the regime scale factor:
Crisis: $s=0.10$ · Risk-Off / High-Vol: $s=0.50$ · Uncertain: $s=0.75$ · Trending / Risk-On: $s=1.00$
Base quantity = 18 shares (from the SPY example above)
Regime = Risk Off (market in downtrend) → scale = 0.50
final_quantity = floor(18 × 0.50) = 9 shares
The system still trades, but with half-sized positions — risk is reduced without stopping trading entirely.
Regime = Crisis → scale = 0.10 → final_quantity = floor(18 × 0.10) = 1 share
In crisis, the position is nearly eliminated, preserving capital.
Sources
- Wikipedia — Kelly Criterion (includes the original 1956 derivation)
- Thorp (1997) — "The Kelly Criterion in Blackjack, Sports Betting and the Stock Market"
- Poundstone, William — Fortune's Formula (2005) — accessible history