Exercise · m04a_trailing.py
ATR and trailing stops
not run yet
Not saved: sign up to keep your code"""M04A exercise 3: ATR and trailing stops (notes §4A.7-4A.8).
ATR. True range TR_t = max(H_t, C_{t-1}) - min(L_t, C_{t-1}); Wilder's ATR with
period n is W_n = mean(TR_1..TR_n), then W_t = ((n-1) W_{t-1} + TR_t)/n.
For a continuously traded log price with daily volatility sigma and no gaps,
E[H-L] ~ 2 sqrt(2/pi) sigma S (M09 Proposition 9.1), so sigma ~ ATR/(2 sqrt(2/pi) S).
Trailing stop. A long position in X_t = x0 + mu t + sigma W_t exits at the first
time the drawdown from the running maximum M_t reaches d: tau = inf{t: M_t - X_t >= d}.
Proposition 4A.11 (Taylor 1975; Lehoczky 1977): M_tau - x0 is exponential with mean
m(d) = (exp(g d) - 1)/g, g = 2 mu / sigma^2 (m = d when mu = 0),
so E[P&L] = m - d, P(loss) = 1 - exp(-d/m), E[tau] = (m - d)/mu (= d^2/sigma^2 if mu = 0),
and the give-back from the peak, M_tau - X_tau, equals d under continuous monitoring.
"""
from __future__ import annotations
import math
import numpy as np
import pandas as pd
RANGE_MEAN = 2.0 * math.sqrt(2.0 / math.pi) # E[max W - min W] on [0,1], about 1.5958
BETA = 0.5825971579390106
def true_range(ohlc: pd.DataFrame) -> pd.Series:
"""TR_t from columns open/high/low/close (same index). The first bar has no
previous close: use H - L."""
raise NotImplementedError
def atr_wilder(ohlc: pd.DataFrame, n: int = 14) -> pd.Series:
"""Wilder's ATR: NaN for positions 0..n-2, mean(TR[0..n-1]) at position n-1,
then ATR_t = ((n-1) ATR_{t-1} + TR_t)/n."""
raise NotImplementedError
def atr_to_sigma(atr: float, price: float) -> float:
"""Daily log volatility implied by an ATR under the no-gap Brownian model: atr/(RANGE_MEAN*price)."""
raise NotImplementedError
def trailing_stop_exit(path: np.ndarray, d: float) -> tuple[int, float, float]:
"""Exit of a long trailing stop of distance d on a monitored price path.
path[0] is the entry price. Exit at the first index n >= 1 with
max(path[:n+1]) - path[n] >= d, at price path[n]. If never triggered, exit at
the last index. Returns (exit_index, exit_price, running_max_at_exit).
"""
raise NotImplementedError
def trailing_analytic(mu: float, sigma: float, d: float) -> dict:
"""Closed forms of Proposition 4A.11 for continuous monitoring.
Returns dict(mean_peak, mean_pnl, p_loss, mean_time) where mean_peak = E[M_tau - x0].
Handle mu -> 0 continuously (use math.expm1).
"""
raise NotImplementedError
def simulate_trailing(
mu: float, sigma: float, d: float, dt: float, n_paths: int, seed: int, max_steps: int = 200_000
) -> dict:
"""Monte Carlo of a trailing stop monitored every dt on X = mu t + sigma W, x0 = 0.
Use np.random.default_rng(seed). The running max starts at 0 and is updated on
the grid; exit at the first grid time with max - X >= d.
Returns dict(mean_peak, mean_pnl, p_loss, mean_time, mean_giveback, se_pnl), where
peak is the observed running max at exit, giveback = peak - exit value, p_loss =
fraction of exits below 0, se_pnl = std(pnl, ddof=1)/sqrt(n_paths).
"""
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.