Markets: A Rigorous Course

Exercise · m03_sizing.py

Position sizing, R-multiples, expectancy and SQN

Implement every function, then press Run tests.

numpy and math only. Provided: toolkit.stats.max_drawdown(equity), the most negative E_t / running max - 1 (a fraction <= 0, the M02 convention); it takes one curve per row.

Run locally

From the repository root, with the exercises environment installed:

cd exercises && .venv/bin/python -m pytest modules/M03-betting-kelly-ruin/tests/test_m03_sizing.py

Tests (7)

  • test_position_size_cash_equity
  • test_position_size_lots
  • test_r_multiples_and_expectancy
  • test_sqn
  • test_equity_curve_and_drawdown
  • test_kelly_from_R_matches_binary_formula
  • test_bootstrap_drawdown_grows_with_risk

Theory: M03 notes. Run with Ctrl/⌘+Enter.

not run yet

Not saved: sign up to keep your code
"""Exercise: position sizing, R-multiples, expectancy and SQN (notes §3.1, §3.9).

Implement every function, then run from the repo root:

    cd exercises && .venv/bin/python -m pytest modules/M03-betting-kelly-ruin/tests/test_m03_sizing.py

numpy and math only. Provided: toolkit.stats.max_drawdown(equity), the most negative
E_t / running max - 1 (a fraction <= 0, the M02 convention); it takes one curve per row.
"""

from __future__ import annotations

import math  # noqa: F401

import numpy as np

from toolkit.stats import max_drawdown  # noqa: F401  (provided)


def position_size(equity: float, risk_frac: float, entry: float, stop: float, lot_size: int = 1) -> int:
    """Fixed-fractional ("percent risk") sizing (notes eq. 3.23):
        units = floor(equity * risk_frac / |entry - stop|),
    then rounded DOWN to a whole number of lots (a multiple of lot_size).
    Works for longs (stop < entry) and shorts (stop > entry).
    Raise ValueError if entry == stop. Guard the floor against float error
    (e.g. add 1e-9 before flooring).
    """
    raise NotImplementedError


def r_multiples(pnl, initial_risk) -> np.ndarray:
    """R_i = pnl_i / risk_i where risk_i = |entry - stop| * quantity (1R) for trade i.
    initial_risk may be a scalar or an array. Raise ValueError if any risk <= 0.
    """
    raise NotImplementedError


def expectancy(R) -> float:
    """Expectancy in R units: the mean R-multiple (notes eq. 3.1)."""
    raise NotImplementedError


def expectancy_from_stats(win_rate: float, avg_win_R: float, avg_loss_R: float) -> float:
    """E = p W - (1 - p) L, with W and L the average win and average loss in R (both > 0)."""
    raise NotImplementedError


def sqn(R, cap: int | None = 100) -> float:
    """Tharp's System Quality Number: sqrt(N) * mean(R) / sd(R), sd with ddof=1,
    N = min(len(R), cap) (no cap if cap is None). It is a t-statistic for the
    mean R with the sample size capped (notes §3.1)."""
    raise NotImplementedError


def equity_curve_fixed_fraction(R, risk_frac: float, equity0: float = 1.0) -> np.ndarray:
    """Equity when each trade risks risk_frac of current equity:
    E_0 = equity0, E_i = E_{i-1} (1 + risk_frac R_i). Return length len(R) + 1."""
    raise NotImplementedError


def bootstrap_mdd(R, risk_frac: float, n_trades: int, n_paths: int, seed: int) -> np.ndarray:
    """Distribution of max drawdown by resampling trades with replacement:
    rng = np.random.default_rng(seed); draws = R[rng.integers(0, len(R), (n_paths, n_trades))];
    build each equity curve (starting at 1) and return the n_paths max drawdowns in the
    toolkit convention (<= 0; -0.11 is an 11% drawdown). toolkit.stats.max_drawdown(curves)
    takes one curve per row."""
    raise NotImplementedError


def kelly_from_R(R) -> float:
    """Empirical Kelly risk fraction: the f maximising mean(ln(1 + f R_i)).
    The derivative mean(R_i / (1 + f R_i)) is decreasing in f; bisect for its root
    on [0, 1/|min R|). Return 0.0 if mean(R) <= 0. For a two-outcome R list
    (+W w.p. p, -1 w.p. q) this equals p - q/W."""
    raise NotImplementedError

No runs yet

The stub's functions raise NotImplementedError until you write them. Fill in one, press Run tests, and watch its tests turn green.

Reference solution

Available after your first run, with an account approved for code runs.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.