Skip to main content
Chapter 02May 16, 202610 min read

What is a trading strategy?

Factor to signal to portfolio to execution to metrics. The full pipeline plus the four numbers — Sharpe, max drawdown, hit rate, turnover — that decide whether it lives.

A trading strategy is a deterministic procedure that, given the information available up to a point in time, produces a target portfolio. Everything else — the data feed, the order router, the risk system, the performance reports — is plumbing around that procedure. The procedure itself is the strategy. If two different programmers cannot independently implement the same strategy from the description and get the same target portfolio, the description is incomplete.

This chapter walks the full pipeline from a factor (which chapter 1 introduced) all the way to a set of orders, and ends with the four numbers that decide whether the strategy is worth keeping.

The pipeline: factor, signal, portfolio, execution, metrics

Every systematic strategy has the same five stages. The names vary by shop but the structure does not.

Factor. A scalar per asset per date, as in chapter 1. A factor on its own is not a strategy because it does not yet say what to buy or how much.

Signal. A transformation of the factor that says "go long this list, go short that list, with these weights." Common transformations are: take the top-N and bottom-N (cross-sectional rank), take everything above a threshold (time-series), or pass the factor through a softmax to produce continuous weights. The signal also handles neutralisation — subtracting the factor's mean within an industry or sector so the strategy is not just a long-tech-short-energy bet — and risk scaling — sizing the position by some volatility estimate so volatile names do not dominate the portfolio.

Portfolio. The target weights aggregated across all signals (a real shop runs many at once), with constraints applied: maximum position per name, maximum gross exposure, maximum sector tilt, beta neutrality, dollar neutrality, leverage limits, borrow availability for shorts. The output is a vector of target weights summing to whatever leverage you allow.

Execution. The translation from "I want to hold 1.4% of NAV in AAPL" to a sequence of child orders sent to the broker, taking into account current holdings, available liquidity, expected transaction cost, and any constraints from compliance (no trading the close, no short on a hard-to-borrow list, no buying during a blackout window). Execution is its own multi-thousand-page literature — Almgren and Chriss (2000) is the standard reference for cost-aware execution — and we will spend exactly one paragraph on it here because for backtesting at daily frequency it usually collapses to "fill at the next open with some slippage assumption."

Metrics. The dashboard you stare at after the run finishes. Four numbers carry most of the signal.

Four numbers that decide a strategy's fate

Sharpe ratio. The annualised mean of strategy returns divided by their annualised standard deviation. Sharpe (1966) defined it as a way to compare strategies with different leverage and volatility on a common scale. Roughly: below 0.5 is noise, 0.5 to 1.0 is plausible but probably not worth running by itself, 1.0 to 2.0 is good, above 2.0 in production over five years is rare enough to be suspicious until you have stress-tested it. Important caveat: Sharpe is not robust to fat tails. A strategy that earns a small profit every day for two years then loses everything in a week (selling out-of-the-money options is the canonical example) reports a high Sharpe right up until the day it does not.

Maximum drawdown. The largest peak-to-trough percentage loss observed over the backtest. The number you would have stared at on your worst day. If max drawdown is 30%, you should ask yourself in advance whether you would have continued holding the strategy on the day it was down 30%. Most discretionary investors quit at 15-20%. Most institutional allocators redeem at 25-30%. A strategy with a high Sharpe and a 50% max drawdown is unrunnable in practice because no human investor will sit through it.

Hit rate. The fraction of trades (or fraction of days, depending on convention) that are profitable. Hit rate has a surprisingly weak relationship with profitability. A strategy with a 40% hit rate and average winners three times the size of average losers is highly profitable; a strategy with an 80% hit rate that loses everything in the 20% can be a catastrophe. Watch hit rate mostly as a sanity check: if it drifts dramatically from backtest to live trading, something has changed.

Turnover. The fraction of the portfolio that changes per period. Turnover translates directly into transaction costs. A strategy with a Sharpe of 2.0 before costs and 80% daily turnover can become a Sharpe of -1.0 after realistic costs are applied. Always look at turnover and always apply a transaction-cost model that you believe is conservative. Chapter 3 explores the cost models in depth.

Beyond these four, professional reports usually include Sortino ratio (Sharpe but using downside deviation only), Calmar ratio (annual return divided by max drawdown), tail ratio (right-tail divided by left-tail of the daily return distribution), and a per-trade P&L histogram. Useful, but you can ship a working strategy reasoning about the four primary numbers above.

A working strategy from end to end

The smallest example of a complete strategy in code is a moving-average crossover. It is not a good strategy on its own — chapter 5 will explain why these have decayed almost to zero alpha on liquid US equity — but it is small enough to read in one sitting and concrete enough to backtest.

import numpy as np
import pandas as pd

def sma_crossover_signal(prices: pd.DataFrame, fast: int = 20, slow: int = 100) -> pd.DataFrame:
    """
    Long when the fast SMA crosses above the slow SMA, flat otherwise.

    Single-name, long-only, daily rebalance. Returns target weights in
    [0.0, 1.0], one column per instrument.
    """
    sma_fast = prices.rolling(fast).mean()
    sma_slow = prices.rolling(slow).mean()
    raw = (sma_fast > sma_slow).astype(float)
    # Shift by one bar so we trade on tomorrow's open, not today's close.
    # This is the most common source of look-ahead bias in toy backtests.
    return raw.shift(1).fillna(0.0)


def backtest(prices: pd.DataFrame, weights: pd.DataFrame, cost_bps: float = 5.0) -> pd.Series:
    """
    Daily-rebalance backtest with a flat transaction-cost model.

    cost_bps : float
        Round-trip cost in basis points (1 bp = 0.01%). 5 bps is a
        plausible all-in cost for liquid US equity at moderate size; it
        is wildly optimistic for small-cap or emerging markets.
    """
    returns = prices.pct_change().fillna(0.0)
    # Strategy return = weight (in yesterday's market) * today's return.
    strat_return = (weights.shift(1) * returns).sum(axis=1)
    turnover = weights.diff().abs().sum(axis=1)
    costs = turnover * (cost_bps / 10_000)
    return strat_return - costs


def report(equity_curve: pd.Series) -> dict:
    """Compute the four numbers."""
    daily_ret = equity_curve
    ann_factor = 252
    sharpe = (daily_ret.mean() / daily_ret.std()) * np.sqrt(ann_factor)
    cum = (1 + daily_ret).cumprod()
    drawdown = (cum / cum.cummax() - 1).min()
    hit_rate = (daily_ret > 0).mean()
    return {
        "sharpe": float(sharpe),
        "max_drawdown": float(drawdown),
        "hit_rate": float(hit_rate),
        "ann_return": float(((1 + daily_ret.mean()) ** ann_factor) - 1),
    }

That is a complete strategy. It is also wrong in at least four ways that we will fix across the next three chapters: it has no walk-forward validation, the transaction cost is flat instead of a function of size and volatility, it ignores survivorship bias if prices came from a "current S&P 500" universe, and it has no risk management beyond the implicit fully-invested-when-on assumption. But it runs, the four numbers come out, and you can swap sma_crossover_signal for momentum_12_1 from chapter 1 to get a momentum strategy with the same scaffold.

Why we shift by one bar

The .shift(1) on the raw signal is the most important line. The signal at the close of day t uses information available at the close of day t — so the earliest you can act on it is the open of day t+1. If you accidentally apply the signal to the same day's return, you are buying at the close after seeing the close, which is impossible in real life and inflates Sharpe by orders of magnitude. We will spend chapter 3 hunting for subtler versions of this bug.

Reading a backtest report

When you run the strategy above (or any other) inside AlphaHub, the backtest report shows the equity curve, an underwater plot (drawdown over time), the four metrics, a trade log, and a comparison to a benchmark — usually the asset's buy-and-hold return. A few habits that catch the most common mistakes:

  • Read the equity curve before the metrics. A high Sharpe with a curve that is flat for years and then steep at the end is a fitted strategy, not a real one.
  • Look at the worst three months. If they are clustered (all in one drawdown), the strategy has a regime exposure you have not accounted for. If they are spread out, the strategy probably has fat-tailed daily returns and Sharpe is overstating its quality.
  • Compare against buy-and-hold of the underlying. If a long-only single-name strategy on AAPL underperforms buy-and-hold AAPL after costs, the strategy is destroying value relative to a passive benchmark, and "Sharpe is higher" is not a sufficient defense unless the user is leveraged and capacity-constrained.
  • Check turnover and apply realistic costs. Most published backtests assume zero costs. A strategy that looks beautiful at zero costs and ugly at 5 bps is most likely fitting noise.

Chapter 3 dedicates itself to the discipline behind every one of those checks: how to set them up automatically, how to spot the bugs they catch, and how to convince yourself that the backtest you are looking at corresponds to something that could have actually been traded.

Try it in AlphaHub

Run the strategy above on real data.

Backtest an SMA crossover strategy (fast=20, slow=100) on SPY from 2014-01-01 to 2024-12-31 with 5 bps round-trip costs. Show me the equity curve, drawdown, and the four metrics.
Open workspace

References

  • Almgren, R. and Chriss, N. (2000). Optimal execution of portfolio transactions. Journal of Risk, 3(2), 5–39.
  • Grinold, R. and Kahn, R. (2000). Active Portfolio Management, 2nd ed. McGraw-Hill. Chapters 1 and 5 for the information ratio framing used here.
  • Lopez de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. Chapter 14 on the dangers of selection bias under multiple testing.
  • Sharpe, W. (1966). Mutual fund performance. Journal of Business, 39(1), 119–138.