Exercise · m05_almgren_chriss.py
Almgren-Chriss optimal execution, VWAP and TWAP
not run yet
Not saved: sign up to keep your code"""Exercise 5.3: Almgren-Chriss optimal execution, VWAP and TWAP (notes.md §5.6-5.7).
Sell X shares over [0, T] in N equal intervals of length tau = T / N.
Holdings x_0 = X, x_1, ..., x_N = 0; the k-th child order sells n_k = x_{k-1} - x_k.
Price dynamics (linear impact):
S_k = S_{k-1} + sigma sqrt(tau) xi_k - gamma n_k (permanent impact)
execution price of child k = S_{k-1} - epsilon sgn(n_k) - (eta / tau) n_k (temporary impact)
Implementation shortfall IS = X S_0 - sum n_k * (execution price) has
E[IS] = gamma X^2 / 2 + epsilon sum |n_k| + (eta_tilde / tau) sum n_k^2,
Var[IS] = sigma^2 tau sum_{k=1}^{N} x_k^2,
eta_tilde = eta - gamma tau / 2 (notes eq. (5.20)).
Proposition 5.5: minimising E + lam Var gives
x_j = X sinh(kappa (T - t_j)) / sinh(kappa T),
(2 / tau^2)(cosh(kappa tau) - 1) = lam sigma^2 / eta_tilde.
Check:
cd exercises && .venv/bin/python -m pytest modules/M05-intraday-execution/tests
"""
from __future__ import annotations
import numpy as np # noqa: F401
from scipy.optimize import minimize # noqa: F401
def eta_tilde(eta: float, gamma: float, tau: float) -> float:
"""eta - gamma * tau / 2."""
raise NotImplementedError
def ac_cost_mean_var(x: np.ndarray, T: float, sigma: float, eta: float, gamma: float, epsilon: float = 0.0) -> tuple[float, float]:
"""(E[IS], Var[IS]) for holdings x (length N + 1, x[0] = X, x[-1] = 0) using the formulas above."""
raise NotImplementedError
def ac_kappa(T: float, N: int, sigma: float, eta: float, gamma: float, lam: float) -> float:
"""Solve (2 / tau^2)(cosh(kappa tau) - 1) = lam sigma^2 / eta_tilde for kappa > 0 (use arccosh)."""
raise NotImplementedError
def ac_trajectory(X: float, T: float, N: int, sigma: float, eta: float, gamma: float, lam: float) -> np.ndarray:
"""Closed-form optimal holdings x_0..x_N (length N + 1).
x_j = X sinh(kappa (T - t_j)) / sinh(kappa T), t_j = j T / N.
For lam == 0 return the straight line X (1 - t_j / T).
"""
raise NotImplementedError
def ac_numerical(X: float, T: float, N: int, sigma: float, eta: float, gamma: float, lam: float) -> np.ndarray:
"""Minimise E + lam Var numerically over x_1..x_{N-1} with scipy.optimize.minimize.
Return the full holdings vector x_0..x_N. Hints: the gamma X^2/2 and
epsilon X terms do not depend on the schedule for a pure sell program;
rescale to w = x / X and divide the objective by eta_tilde X^2 / tau so the
problem is well conditioned; supply the analytic gradient; use BFGS with a
tight gtol (1e-12).
"""
raise NotImplementedError
def ac_continuous(X: float, T: float, t: np.ndarray, sigma: float, eta: float, lam: float) -> np.ndarray:
"""Continuous-time limit x(t) = X sinh(k (T - t)) / sinh(k T), k = sqrt(lam sigma^2 / eta)."""
raise NotImplementedError
def twap_schedule(X: float, N: int) -> np.ndarray:
"""N child orders of X / N each."""
raise NotImplementedError
def vwap(prices: np.ndarray, volumes: np.ndarray) -> float:
"""sum p_i v_i / sum v_i."""
raise NotImplementedError
def vwap_schedule(X: float, volume_profile: np.ndarray) -> np.ndarray:
"""Child orders proportional to an expected volume profile: X v_k / sum v."""
raise NotImplementedError
def implementation_shortfall_bps(side: str, decision_price: float, fill_prices: np.ndarray, fill_qty: np.ndarray) -> float:
"""Shortfall of the executed quantity versus the decision price, in bps; positive = cost.
buy: (avg_fill - decision) / decision * 1e4
sell: (decision - avg_fill) / decision * 1e4
with avg_fill the VWAP of the fills.
"""
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.