← Dashboard

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.

How to use this course: New to finance? Start with Markets 101. Already know how markets work? Jump to Math Foundations or straight to a strategy. Every strategy page lists what you need to know first.

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:

The Three Jobs of a Market

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
Key insight: Price movements are not random noise — they reflect the constantly updating collective beliefs of millions of participants about future value. QuantForge strategies try to identify statistical patterns in how these beliefs evolve and correct.

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.

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.

What Drives a Stock's Price

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.

QuantForge's ETF Universe
TickerWhat It TracksWhy We Trade It
SPYS&P 500 — 500 largest US companiesBenchmark for US equities, extremely liquid
QQQNasdaq-100 — 100 largest non-financial Nasdaq stocksTech-heavy, higher volatility than SPY
GLDGold 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.

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.
Real Bid/Ask for SPY
SidePriceQuantityWho placed it
Ask (sell)$548.02200 sharesMarket maker offering to sell
Ask (sell)$548.01500 sharesBest ask — you pay this to buy now
── current spread: $0.01 ──
Bid (buy)$548.00800 sharesBest bid — you receive this if you sell now
Bid (buy)$547.99300 sharesNext 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:

  1. A large buyer places a market order for 50,000 SPY shares
  2. All 500 shares at $548.01 are consumed → order continues to $548.02
  3. All 200 shares at $548.02 consumed → order moves to $548.03
  4. 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.

For algorithms: The "last price" you see on a chart is the price of the most recent trade. But the relevant prices when placing an order are the current bid (to sell) and ask (to buy). QuantForge's paper execution engine accounts for this by using the current mid-price plus a simulated spread when computing paper fill prices.

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
QuantForge uses market orders for nearly all executions. In liquid markets (SPY, QQQ, AAPL, GLD), slippage is minimal. For crypto (BTC, ETH), where spreads can be wider, we accept some slippage in exchange for guaranteed fills.

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.

For your strategy: Tight spreads = healthy market maker presence = cheap to trade. In SPY, the spread is $0.01. In a thinly traded small-cap stock it might be $0.50. This difference in "transaction cost" can make or break a strategy.

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

Who is on the Other Side of Your Trade?

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

  1. Investopedia — Market Maker
  2. Investopedia — Hedge Fund
  3. Wikipedia — High-Frequency Trading
  4. 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):

Arithmetic Return$$r = \frac{P_1 - P_0}{P_0} = \frac{P_1}{P_0} - 1$$
Worked Example

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:

Log Return$$r_{\log} = \ln\!\left(\frac{P_1}{P_0}\right) = \ln P_1 - \ln P_0$$
Worked Example

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:

  1. Time-additive: Weekly log return = sum of daily log returns. Arithmetic returns require multiplication.
  2. Symmetric: A 10% gain and a 10% loss give the same log return magnitude (±0.0953).
  3. Normality: Log returns are closer to normally distributed than arithmetic returns, which makes statistical models more accurate.
Approximation for small returns: When returns are small (under ~5%), log return ≈ arithmetic return. ln(1.01) = 0.00995 ≈ 1.0%. They diverge significantly for large moves.

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:

Annualized Return$$R_{\text{annual}} = R_{\text{daily}} \times 252$$
Annualized Volatility$$\sigma_{\text{annual}} = \sigma_{\text{daily}} \times \sqrt{252}$$

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

  1. Campbell, Lo, MacKinlay — The Econometrics of Financial Markets (1997), Chapter 1 — foundational textbook
  2. Hull — Options, Futures, and Other Derivatives — continuous compounding and log returns
  3. 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:

SMA(N) at time t$$\text{SMA}_t = \frac{1}{N}\sum_{i=0}^{N-1} P_{t-i}$$
Worked Example — SMA(5) for SPY
DayCloseSMA(5)
1540.00
2542.00
3539.00
4545.00
5543.00(540+542+539+545+543)/5 = 541.80
6548.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:

EMA recursion$$\text{EMA}_t = \alpha\,P_t + (1-\alpha)\,\text{EMA}_{t-1}, \qquad \alpha = \frac{2}{N+1}$$
Worked Example — EMA(5) for SPY

N=5 → α = 2/(5+1) = 0.333

Seed: EMA₁ = P₁ = 540.00 (first EMA is just the first price)

DayCloseCalculationEMA(5)
1540.00seed540.00
2542.000.333×542 + 0.667×540540.67
3539.000.333×539 + 0.667×540.67540.11
4545.000.333×545 + 0.667×540.11541.74
5543.000.333×543 + 0.667×541.74542.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)
EMA Crossover — Illustrative Example
EMA200 EMA50 Golden Cross → Long signal high mid low Time →
Price
EMA50 (fast)
EMA200 (slow)
Crossover point
Lag warning: All moving averages are lagging indicators — they react to price moves after they happen. A Golden Cross often appears weeks into an uptrend. This is by design: the strategy waits for confirmation rather than trying to call the exact bottom.

Sources

  1. Murphy, John J. — Technical Analysis of the Financial Markets (1999), Chapter 9 — classic reference
  2. Appel, Gerald — Technical Analysis: Power Tools for Active Investors — EMA origins
  3. 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

Population Variance$$\sigma^2 = \frac{1}{N}\sum_{i=1}^{N}(x_i - \mu)^2$$
Standard Deviation$$\sigma = \sqrt{\sigma^2}$$
Worked Example — 5-day closing prices of SPY
DayClose (xᵢ)xᵢ - μ(xᵢ - μ)²
1538-3.612.96
2540-1.62.56
3544+2.45.76
4542+0.40.16
5544+2.45.76
μ = 541.6Sum: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:

Z-Score$$z = \frac{x - \mu}{\sigma}$$
Worked Example — Is SPY at $535 unusual?

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:

Standard Normal Distribution
μ = 0 -1σ +1σ -2σ +2σ 68.3% 13.6% 13.6%
  • |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

  1. Ang, Andrew — Asset Management (2014) — statistical moments in finance
  2. Investopedia — Z-Score · Standard Deviation
  3. 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):

Wilder's Smoothing$$\bar{G}_t = \frac{13\,\bar{G}_{t-1} + G_t}{14}, \qquad \bar{L}_t = \frac{13\,\bar{L}_{t-1} + L_t}{14}$$

Step 3: Relative Strength → RSI

RS and RSI$$\text{RS} = \frac{\bar{G}}{\bar{L}}, \qquad \text{RSI} = 100 - \frac{100}{1 + \text{RS}}$$
Worked Example — 14 bars of SPY
BarCloseChangeGainLoss
1540.00
2542.50+2.502.500
3541.00-1.5001.50
4545.00+4.004.000
5543.00-2.0002.00
6547.00+4.004.000
7548.00+1.001.000
8546.00-2.0002.00
9550.00+4.004.000
10548.00-2.0002.00
11551.00+3.003.000
12549.00-2.0002.00
13552.00+3.003.000
14550.00-2.0002.00
15553.00+3.003.000

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.

RSI Oscillator with Overbought/Oversold Zones
70 30 50 Long entry Overbought (RSI > 70) Oversold (RSI < 30) — Long signal

Sources

  1. Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — RSI original
  2. Investopedia — Relative Strength Index (RSI)
  3. 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$$\text{MACD} = \text{EMA}_{12} - \text{EMA}_{26}$$
Signal Line$$\text{Signal} = \text{EMA}_9(\text{MACD})$$
Histogram$$\text{Histogram} = \text{MACD} - \text{Signal}$$
  • 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.
Worked Example — SPY (simplified 8-day sequence)

Assume EMA(12) and EMA(26) have already been computed for the prior period. We're at the point where momentum is building:

DayEMA(12)EMA(26)MACDSignal (EMA9 of MACD)Histogram
1540.10541.50-1.40
2541.20541.60-0.40
3542.50541.80+0.70
4544.00542.10+1.90
5545.80542.50+3.30
6547.20543.00+4.20
7548.50543.60+4.90
8549.80544.20+5.60
9551.00544.90+6.10Seed: +6.100
10552.00545.50+6.506.10+0.2×(6.50-6.10)=6.18+0.32
11550.00545.80+4.206.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.

Key signal: A crossover from below (MACD crosses above Signal) is the classic buy signal. QuantForge also requires the histogram to be positive, filtering out weak crossovers.

Sources

  1. Appel, Gerald — Technical Analysis: Power Tools for Active Investors (2005) — MACD inventor
  2. Investopedia — MACD
  3. 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

Bollinger Bands (default: N=20, k=2)$$\begin{aligned}\text{Upper} &= \text{SMA}(20) + 2\,\sigma_{20} \\ \text{Middle} &= \text{SMA}(20) \\ \text{Lower} &= \text{SMA}(20) - 2\,\sigma_{20}\end{aligned}$$

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.

Worked Example — SPY (5-day simplified)

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.

Bollinger Bands — Price with Upper/Lower/Middle
Touch lower band → Long Upper (SMA+2σ) Middle (SMA) Lower (SMA-2σ)

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

  1. Bollinger, John — Bollinger on Bollinger Bands (2002) — the original book
  2. Investopedia — Bollinger Bands
  3. 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:

True Range$$\text{TR} = \max\!\bigl(H - L,\;\lvert H - C_{\text{prev}}\rvert,\;\lvert L - C_{\text{prev}}\rvert\bigr)$$
Worked Example — Gap day in BTC/USD
DayPrev CloseHighLowH-L|H-PC||L-PC|TR
Monday65,00066,20064,8001,4001,2002001,400
Tuesday66,20068,50067,0001,5002,3008002,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

ATR(14) — Wilder's smoothing$$\text{ATR}_t = \frac{13\,\text{ATR}_{t-1} + \text{TR}_t}{14}$$
Using ATR for Stop Loss

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

  1. Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — ATR original
  2. Investopedia — Average True Range (ATR)
  3. 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:

Key Distinction

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 β

OLS Regression$$P_A = \alpha + \beta\,P_B + \varepsilon$$

β 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).
Worked Example — SPY and QQQ over 252 days

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.

Cointegrated Pair — SPY and QQQ (illustrative)
SPY QQQ Time → spread
Asset A (SPY)
Asset B (QQQ)
Spread (mean-reverts)

Sources

  1. Engle & Granger (1987) — "Co-integration and Error Correction" (Econometrica) — Nobel Prize work
  2. Vidyamurthy, Ganapathy — Pairs Trading: Quantitative Methods and Analysis (2004)
  3. 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

OU Stochastic Differential Equation$$dX_t = \theta(\mu - X_t)\,dt + \sigma\,dW_t$$

$\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 θ:

Half-Life$$t_{1/2} = \frac{\ln 2}{\theta} \approx \frac{0.693}{\theta}$$

QuantForge estimates θ by fitting an AR(1) (autoregressive) model to the spread series:

Discrete OU → AR(1) regression$$\Delta X_t = a + b\,X_{t-1} + \varepsilon \qquad\Longrightarrow\qquad \theta \approx -\ln(1+b)$$
Worked Example — SPY/QQQ Spread

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:

Spread Z-Score$$z_t = \frac{(P_{A,t} - \beta P_{B,t}) - \mu_{\text{spread}}}{\sigma_{\text{spread}}}$$

Long: $z < -2.0$  ·  Short: $z > +2.0$  ·  Exit: $|z| < 0.5$

Sources

  1. Wikipedia — Ornstein-Uhlenbeck process
  2. Avellaneda & Lee (2010) — "Statistical Arbitrage in the US Equities Market"
  3. 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:

Example Transition Matrix$$A = \begin{pmatrix} 0.97 & 0.03 \\ 0.10 & 0.90 \end{pmatrix} \quad \begin{smallmatrix}\text{(Bull→Bull, Bull→Bear)} \\ \text{(Bear→Bull, Bear→Bear)}\end{smallmatrix}$$

3. Emission Distribution

Each state emits observations according to a Gaussian distribution. For log returns:

Gaussian Emissions$$\begin{aligned}\text{Bull}: r_t &\sim \mathcal{N}(\mu_{\text{bull}},\,\sigma_{\text{bull}}^2) \quad \mu\approx+0.0008,\;\sigma\approx0.008 \\ \text{Bear}: r_t &\sim \mathcal{N}(\mu_{\text{bear}},\,\sigma_{\text{bear}}^2) \quad \mu\approx-0.0012,\;\sigma\approx0.015\end{aligned}$$

Baum-Welch Training

Given a sequence of SPY log-returns, Baum-Welch iteratively estimates the transition matrix and emission parameters using expectation-maximization (EM):

  1. E-step: Given current parameters, compute the probability of being in each state at each time step (forward-backward algorithm)
  2. M-step: Re-estimate the transition matrix and emission parameters to maximize the likelihood of the observations
  3. 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 signals
  • hmm_bull_prob < 0.35: High confidence in bear regime → suppress Long signals (set is_tradable = false)
  • Between 0.35–0.65: Uncertain — signals pass through unchanged
Minimum history: QuantForge requires at least 62 SPY daily closes to fit the HMM. With fewer bars, the filter is skipped.
Trend

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

  1. Collect the last 210+ four-hour bars for the asset (AAPL or GLD)
  2. Compute EMA(50) and EMA(200) on closing prices
  3. 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
  4. Set stop loss at: entry_price − 3 × ATR(14)
  5. Confidence = 1.0 on crossover, 0.0 otherwise
Real Numbers — GLD (Gold ETF), 4-hour bars
BarCloseEMA(50)EMA(200)Signal
T-2$182.40$181.20$181.90
T-1$183.10$181.55$181.92EMA50 < EMA200, no cross
T (now)$184.80$182.10$181.95Golden 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

Works well: Strong, sustained trends (bull markets, commodity super-cycles). The GLD trade above would be profitable if the trend continued for weeks or months.
Fails in: Sideways/choppy markets. The EMA crossover fires Long, price wobbles, then crosses back. This is called "whipsaw" — lots of small losses from failed crossovers. The HMM filter helps by suppressing signals when the market regime is bearish.

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
Volatility

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

  1. Compute ATR(14) from the last 14+ daily bars
  2. Compute SMA(14) of ATR values (the baseline volatility)
  3. Get the current candle's body: body = |close - open|
  4. Check breakout conditions:
    • ATR > 1.5 × SMA(ATR): volatility is expanding
    • Current body > 0.5 × ATR: candle is meaningful, not noise
  5. Direction: if close > open → Long; if close < open → Short
  6. Stop loss: 2× ATR below entry (Long) or above entry (Short)
  7. Confidence = ATR / (SMA_ATR × 2), capped at 1.0
Real Numbers — BTC/USD, Daily Bars
DayOpenCloseATR(14)SMA(ATR)RatioSignal
T-564,10064,3009809501.03None
T-464,40064,2509409480.99None
T-364,20064,5009209460.97None (quiet)
T (now)64,40067,2002,1009502.21Long! (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

Works well: Crypto markets which have frequent volatility explosions (halving events, regulatory news, macro shocks). The BTC example above — a $2,800 candle on a day when daily moves had been $900 — is exactly the type of setup this strategy targets.
Fails in: Markets that frequently gap and reverse. A large ATR candle that reverses the next day turns a Long into an immediate loss. Also generates false signals during low-information events (e.g., thin holiday markets with random large candles).

Sources

  1. Williams, Larry — Long-Term Secrets to Short-Term Trading (1999) — volatility breakout origins
  2. Donchian, Richard — channel breakout systems — historical predecessor
  3. Investopedia: Breakout Strategy
Mean Reversion

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

  1. Collect 25+ 15-minute bars
  2. Compute SMA(20) of closing prices
  3. Compute standard deviation σ over the last 20 closes
  4. Z-score = (current_close − SMA20) / σ
  5. 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)
  6. Stop loss: Long = 2×ATR below entry; Short = 2×ATR above entry
  7. Confidence = min(|z| / threshold, 1.0) — stronger z-score = more confidence
Real Numbers — SPY on a 15-minute chart

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.

Critical risk: In a trending market, mean reversion is dangerous. A stock in a strong downtrend has consistently low z-scores — but the price keeps falling. The HMM regime filter partly mitigates this by suppressing Long signals when the overall market is bearish.

Sources

  1. Chan, Ernest P. — Quantitative Trading (2009) — mean reversion strategies
  2. Chan, Ernest P. — Algorithmic Trading (2013) — Chapter 2
  3. Aronson, David — Evidence-Based Technical Analysis (2006)
Mean Reversion

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

  1. Collect 20+ daily bars
  2. Compute RSI(14)
  3. Entry rules:
    • RSI < 30 → Long (oversold)
    • RSI > 70 → Short (overbought)
    • RSI between 45–55 → Exit (neutral territory, close position)
  4. Confidence:
    • Long: (30 − RSI) / 30 — deeper below 30 = higher confidence
    • Short: (RSI − 70) / 30
  5. Stop loss: 2× ATR(14) from entry
Real Numbers — QQQ, Daily

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.

RSI divergence: An advanced use of RSI (not in QuantForge but worth knowing): if price makes a new low but RSI makes a higher low, that's bullish divergence — the downtrend is losing momentum even though price is still falling.

Sources

  1. Wilder, J. Welles — New Concepts in Technical Trading Systems (1978) — RSI original
  2. Connors, Larry & Alvarez, Cesar — Short Term Trading Strategies That Work (2008) — RSI(2) high-frequency version
Momentum

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

  1. Collect 50+ hourly bars (EMA26 needs 26+, signal line needs 9 more)
  2. Compute MACD = EMA(12) − EMA(26)
  3. Compute Signal = EMA(9) of MACD
  4. Compute Histogram = MACD − Signal
  5. 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
  6. Confidence = min(|histogram| / ATR, 1.0) — larger histogram vs. ATR = stronger signal
Real Numbers — SPY, Hourly Bars

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.69Long approved!

Key insight: The MACD Momentum strategy requires a strong, fresh crossover with a large histogram. It filters out weak, indecisive crossovers. Combined with the confidence gate (0.55 minimum), only significant momentum bursts trigger trades.

Sources

  1. Appel, Gerald — Technical Analysis: Power Tools for Active Investors (2005)
  2. Investopedia: MACD
  3. Murphy, John J. — Technical Analysis of the Financial Markets, Chapter 10
Mean Reversion

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

  1. Collect 25+ daily bars
  2. Compute Middle Band = SMA(20), Upper = Middle + 2σ, Lower = Middle − 2σ
  3. Compute %B = (close − Lower) / (Upper − Lower). %B = 0.0 means on lower band, 1.0 on upper band.
  4. 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
  5. Confidence:
    • Long: |%B| (how far below the lower band)
    • Short: %B − 1.0
  6. Stop loss: Long = 2×ATR below entry; Short = 2×ATR above entry
Real Numbers — QQQ, Daily

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

  1. Bollinger, John — Bollinger on Bollinger Bands (2002)
  2. Kaufman, Perry — Trading Systems and Methods — %B computation
Momentum

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

  1. Collect 25+ daily bars
  2. Find highest_high = max(high prices) over last 20 bars
  3. Compute ROC(10) = (close_today / close_10_days_ago) − 1
  4. 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
  5. Confidence = min(ROC / 0.05, 1.0) — stronger momentum = higher confidence
Real Numbers — SPY, Daily

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.

False breakout risk: Prices sometimes spike above a resistance level only to immediately reverse. These "fakeouts" are common. The ROC filter helps by requiring momentum confirmation beyond just the price level. The 2×ATR stop loss contains the damage when a breakout fails.

Sources

  1. Donchian, Richard — Donchian Channel systems (1960s) — historical origin
  2. Jegadeesh & Titman — "Returns to Buying Winners and Selling Losers" (1993, Journal of Finance) — academic evidence for momentum
  3. Asness, Moskowitz, Pedersen — "Value and Momentum Everywhere" (2013, Journal of Finance)
Statistical Arbitrage

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

  1. Gather data: 252+ daily bars for both the primary (SPY) and companion (QQQ)
  2. 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
  3. 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
  4. Compute rolling spread: spread_t = SPY_t − β × QQQ_t
  5. Z-score: z = (spread_t − mean(spread, 60)) / std(spread, 60)
  6. 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)
  7. Confidence: min(|z| / 4.0, 1.0)
Spread Z-Score — Entry and Exit Zones
Short (z=+2) Long (z=-2) Exit (|z|<0.5) +2σ 0 -2σ
Real Numbers — SPY/QQQ Daily

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.05Long 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).

Regime risk: During market crises, cointegration can break down temporarily. If SPY crashes -10% in a day and QQQ only -8%, the spread widens dramatically — but it's not a mean-reversion opportunity; it's a structural shift. The cointegration test re-runs each cycle to catch this, and the ADF gate prevents trading a broken relationship.

Sources

  1. Engle & Granger — "Co-integration and Error Correction" (1987, Econometrica)
  2. Vidyamurthy, Ganapathy — Pairs Trading: Quantitative Methods and Analysis (2004) — the practitioner's bible
  3. Avellaneda & Lee — "Statistical Arbitrage in the US Equities Market" (2010)
  4. Do & Faff — "Does Simple Pairs Trading Still Work?" (2010, Financial Analysts Journal)
Statistical Arbitrage Machine Learning

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-Space Model

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.

Price-scale independence: Before running the filter, both y_t and x_t are divided by the mean of x (x_scale). This makes the innovations dimensionless — the same filter parameters work whether x is a $400 ETF or a $50,000 BTC.

Complete Step-by-Step Logic

  1. Normalise prices: x_scale = mean(x over all bars); xi = x_i / x_scale; yi = y_i / x_scale
  2. Kalman pass over all bars (warmup = 30 bars):
    1. Predict: p_pred = p + Q
    2. Compute innovation: e = yi − β × xi
    3. Compute Kalman gain: K = p_pred × xi / (p_pred × xi² + R_t)
    4. Update: β = β + K × e; p = (1 − K × xi) × p_pred
    5. Store innovation e
  3. Rolling z-score: over last 30 innovations — z = (current_e − mean) / std
  4. 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 Filter: β Tracking and Innovation Z-Score
β high β true β low regime shift Kalman β̂ (adapts) true β

Kalman vs. OLS: The Key Difference

PropertyOLS Pairs ArbKalman Pairs
Hedge ratioFixed (recomputed daily)Time-varying (updates each bar)
Regime changesStale β until next windowβ adapts within days
Cointegration testRequired (Engle-Granger + ADF)Built into state noise (no separate test)
ComputationBatch OLS once per windowO(n) sequential pass

QuantForge Parameters

ParameterDefaultMeaning
delta1e-4Process noise — how fast β can drift per bar
obs_noise1e-3Base observation noise multiplier
zscore_window30Rolling window for innovation z-score
entry_z1.5Entry threshold in standard deviations
exit_z0.3Exit when |z| falls below this
warmup30Bars before β is considered reliable

Sources

  1. Chan, Ernest P. — Algorithmic Trading: Winning Strategies and Their Rationale (2013) ch. 4 — the Rλ parameterisation used here
  2. Avellaneda & Lee — "Statistical Arbitrage in the US Equities Market" (2010)
  3. Kalman, R.E. — "A New Approach to Linear Filtering and Prediction Problems" (1960, ASME Journal)
Factor

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:

Proxy Carry Definition
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:

carry_signal > +0.5 → Long

Short-term Sharpe is much better than medium-term → carry is accelerating → asset is rewarding holders more richly right now → go long.

carry_signal < −0.5 → Short

Short-term Sharpe is much worse than medium-term → carry is deteriorating → the asset that was rewarding holders is now punishing them → go short.

Carry Signal: Short-Term vs Long-Term Sharpe
+0.5 Long −0.5 Short Long entry Short entry 0

Complete Step-by-Step Logic

  1. Load closes: 95+ daily bars for the symbol
  2. 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)
  3. Long carry (90 days): same formula with 90-bar look-back
  4. Signal: carry_signal = short_carry − long_carry
  5. Trade:
    • signal > +0.5 → Long
    • signal < −0.5 → Short
    • |signal| < 0.1 and |prev_signal| ≥ 0.1 → Exit
  6. Stop: 2 × short_vol × last_close below entry

When Carry Works / When It Fails

Works well when:
  • Asset has clear trending behaviour (BTC bull runs, GLD safe-haven demand)
  • Momentum and carry are aligned (carry accelerates → price trend continues)
Fails when:
  • 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

  1. Koijen, Moskowitz, Pedersen & Vrugt — "Carry" (2018, Journal of Financial Economics) — the defining carry paper
  2. Asness, Moskowitz & Pedersen — "Value and Momentum Everywhere" (2013, Journal of Finance)
Momentum

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:

tanh_norm(ret, ann_vol, window)
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.

Factor Contributions to Composite Score (uptrend example)
Short 20d +0.72 Med 63d +0.81 Long 252d +0.87 Vol-Adj +0.65 Composite +0.76 → Long +0.25 +1 0

QuantForge Parameters

ParameterDefaultNote
short_window201-month momentum
med_window / med_skip63 / 53-month, skip 1 week
long_window / long_skip252 / 2012-month, skip 1 month (Jegadeesh-Titman)
entry_threshold0.25Composite score for entry (in −1 to +1 scale)
required_history277 bars252 + 20 (long_skip) + 5 padding
Why 277 bars? The 252-day long-window return must be measured from 252+20 = 272 bars ago (the skip period pushes the start back). Add 5 bars of padding to handle edge cases → 277 bars minimum. QuantForge will Hold until it has this much history.

Sources

  1. Jegadeesh & Titman — "Returns to Buying Winners and Selling Losers" (1993, Journal of Finance) — the foundational momentum paper
  2. Moskowitz, Ooi & Pedersen — "Time Series Momentum" (2012, Journal of Financial Economics)
  3. Barroso & Santa-Clara — "Momentum Has Its Moments" (2015) — vol-scaling of momentum signals
Mean Reversion Intraday

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.

VWAP Formula (rolling window)
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
VWAP Band — Entry and Exit Zones (15-min bars)
Short entry (2σ above) Long entry (2σ below) +2σ VWAP −2σ Price

Complete Step-by-Step Logic

  1. Load bars: 28+ 15-minute bars (window=26, need 2 extra for exit detection)
  2. Rolling VWAP over last 26 bars:
    • typical_price[i] = (high[i] + low[i] + close[i]) / 3
    • VWAP = Σ(typical_price × volume) / Σ(volume)
  3. Band: deviations = close[i] − VWAP; band_σ = std(deviations)
  4. Current deviation z-score: dev = (close[last] − VWAP) / band_σ
  5. 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_σ
  6. Trade:
    • dev < −2.0 → Long
    • dev > +2.0 → Short
    • |dev| < 0.4 and prev_dev ≥ 0.4 → Exit
  7. 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

Trend days: Some days have a strong directional trend (e.g., market opens and keeps rising all day). On these days, price never returns to VWAP — the VWAP itself drifts higher as price moves. VWAP reversion on trend days leads to painful losses on Short signals that keep going against you. The regime filter reduces position sizes on strongly trending days but does not block trades entirely.

Sources

  1. Harris, Lawrence — Trading and Exchanges: Market Microstructure for Practitioners (2003) ch. 16 — VWAP and institutional execution
  2. Berkowitz, Logue & Noser — "The Total Cost of Transactions on the NYSE" (1988, Journal of Finance)
  3. 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

RegimeMeaningPosition Scale
CrisisExtreme drawdown, panic selling10% of normal
High VolatilityRealized vol > 30% annualized50%
Risk OffNegative SPY trend, elevated vol50%
UncertainMixed signals, no clear regime75%
RangeLow vol, no trend, price oscillating75%
TrendClear directional price trend100%
Risk OnStrong positive trend, low vol100%
Low VolatilityVery calm, low vol environment100%

How It's Computed

  1. Compute realized volatility from the last 20 SPY returns (annualized)
  2. Compute SPY 20-day return (trend direction)
  3. Compute portfolio drawdown from peak
  4. 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

  1. Gather the last 90 SPY daily closes (from the bars table)
  2. Compute 89 daily log-returns: r_t = ln(P_t / P_{t-1})
  3. Fit a 2-state Gaussian HMM using Baum-Welch on these 89 returns
  4. Identify the "bull state": whichever state has the higher emission mean (higher average daily return)
  5. Run the forward algorithm to get the probability of being in the bull state right nowhmm_bull_prob
  6. Apply the gate in the worker:
    • hmm_bull_prob < 0.35 AND direction = Long → is_tradable = false
    • Invalidation reason: "HMM regime filter: bull_prob below threshold"
Scenario: S&P 500 selloff over 3 weeks

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.

Why not just use the simple regime detector? The SimpleRegimeDetector uses hard thresholds on SPY return and volatility. The HMM is probabilistic — it gives a continuous confidence score and is trained on the actual return distribution. It's particularly good at detecting regime shifts before the simple rules do, because it accounts for the full joint probability of the return sequence, not just recent levels.

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

Minimum Confidencemin_confidence = 0.55 If signal.confidence < 0.55 → REJECT

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.

Drawdown Circuit Breakerdrawdown = (peak_equity − current_equity) / peak_equity If drawdown > 0.15 → REJECT all entries

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

GateConditionResult
1. Tradabilityis_tradable = falseReject
2. Exit Passdirection = ExitApprove always
3. Hold Rejectdirection = HoldNo action
4. Confidenceconfidence < 0.55Reject
5. Circuit Breakerdrawdown > 15%Reject
6. Position Capopen_positions ≥ 10Reject
7. Concentrationsymbol_exposure > 10%Size down or reject
8. Position Sizingquantity = 0Reject
9. Room Capno portfolio room leftReduce 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:

Stop-Based Sizing$$Q = \left\lfloor\frac{E \times r_{\max}}{P_{\text{entry}} \times \dfrac{P_{\text{entry}} - P_{\text{stop}}}{P_{\text{entry}}}}\right\rfloor = \left\lfloor\frac{E \times r_{\max}}{P_{\text{entry}} - P_{\text{stop}}}\right\rfloor, \quad r_{\max} = 1\%$$
Example: SPY Long with Stop

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:

Kelly Criterion$$f^* = \frac{p\,b - (1-p)}{b}, \quad b = \frac{\overline{W}}{\overline{L}}, \qquad f = \tfrac{1}{4}\,f^*, \qquad Q = \left\lfloor\frac{E \cdot f}{P_{\text{entry}}}\right\rfloor$$
Example: Quarter-Kelly Sizing

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:

Regime Scale$$Q_{\text{final}} = \lfloor Q_{\text{base}} \times s_{\text{regime}} \rfloor$$

Crisis: $s=0.10$  ·  Risk-Off / High-Vol: $s=0.50$  ·  Uncertain: $s=0.75$  ·  Trending / Risk-On: $s=1.00$

Regime Scaling in Practice

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.

Why Quarter-Kelly, not Full Kelly? Full Kelly is mathematically optimal for a fixed edge over infinite bets with perfect knowledge. In practice, our win rates and payoff ratios are estimates — they'll be wrong sometimes. Quarter-Kelly cuts the bet size by 75%, dramatically reducing drawdown risk in exchange for a small reduction in expected return. Nassim Taleb calls full Kelly "ruinous" in the presence of model uncertainty.

Sources

  1. Wikipedia — Kelly Criterion (includes the original 1956 derivation)
  2. Thorp (1997) — "The Kelly Criterion in Blackjack, Sports Betting and the Stock Market"
  3. Poundstone, William — Fortune's Formula (2005) — accessible history