Markets: A Rigorous Course

Exercise · m04a_levels.py

Algorithmic support/resistance, gaps and anchored VWAP

Input bars are a DataFrame with columns open, high, low, close, volume and a DatetimeIndex (one row per bar), e.g. toolkit.data.synthetic_ohlcv().

Definitions used (Definitions 4A.3-4A.6 in the notes):

  • Swing high at bar u with half-width k: H_u > H_{u-j} and H_u > H_{u+j} for j = 1..k. It is only KNOWN at bar u + k (its confirmation bar). Swing lows mirror this with L.
  • A level is a cluster of confirmed pivot prices: sort the prices and split wherever two neighbours differ by more than tol (single linkage). The level is the cluster mean; its strength is the number of pivots in it.
  • Gap on bar t: g_t = ln(O_t / C_{t-1}). An up-gap (g > 0) is filled on bar t if L_t <= C_{t-1}; any other gap (g <= 0) is filled if H_t >= C_{t-1}.
  • Under driftless intraday Brownian motion with daily volatility s (in log terms) started at the open, P(fill) = 2 Phi(-|g|/s) (reflection principle).
  • Anchored VWAP from bar a: sum_{a<=u<=t} P_u V_u / sum V_u, with P_u = (H+L+C)/3.

Tests (7)

  • test_swing_pivots_hand_fixture
  • test_cluster_levels
  • test_levels_use_confirmed_pivots_only
  • test_levels_have_no_lookahead
  • test_gap_table_and_fill
  • test_gap_fill_bm_formula_and_simulation
  • test_anchored_vwap

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

not run yet

Not saved: sign up to keep your code
"""M04A exercise 2: algorithmic support/resistance, gaps and anchored VWAP (notes §4A.2-4A.4).

Input bars are a DataFrame with columns open, high, low, close, volume and a
DatetimeIndex (one row per bar), e.g. toolkit.data.synthetic_ohlcv().

Definitions used (Definitions 4A.3-4A.6 in the notes):
  * Swing high at bar u with half-width k: H_u > H_{u-j} and H_u > H_{u+j} for j = 1..k.
    It is only KNOWN at bar u + k (its confirmation bar). Swing lows mirror this with L.
  * A level is a cluster of confirmed pivot prices: sort the prices and split
    wherever two neighbours differ by more than tol (single linkage). The level is
    the cluster mean; its strength is the number of pivots in it.
  * Gap on bar t: g_t = ln(O_t / C_{t-1}). An up-gap (g > 0) is filled on bar t if
    L_t <= C_{t-1}; any other gap (g <= 0) is filled if H_t >= C_{t-1}.
  * Under driftless intraday Brownian motion with daily volatility s (in log terms)
    started at the open, P(fill) = 2 Phi(-|g|/s) (reflection principle).
  * Anchored VWAP from bar a: sum_{a<=u<=t} P_u V_u / sum V_u, with P_u = (H+L+C)/3.
"""

from __future__ import annotations

import numpy as np
import pandas as pd
from scipy.stats import norm


def swing_pivots(ohlc: pd.DataFrame, k: int) -> pd.DataFrame:
    """All swing highs and lows with half-width k (strict inequalities).

    Returns a DataFrame with columns kind ('high' or 'low'), price, pivot_pos (int
    position of the pivot bar), confirm_pos (= pivot_pos + k), sorted by
    confirm_pos then kind (stable). Bars within k of either end cannot be pivots.
    """
    raise NotImplementedError


def cluster_levels(prices, tol: float) -> pd.DataFrame:
    """Single-linkage clusters of prices whose sorted neighbours differ by <= tol.

    Returns DataFrame(level, strength, low, high) sorted by level, one row per cluster.
    """
    raise NotImplementedError


def levels_asof(ohlc: pd.DataFrame, t: int, k: int, tol: float, lookback: int = 250) -> pd.DataFrame:
    """Support/resistance levels known at the close of bar position t.

    Use only bars in positions max(0, t - lookback + 1) .. t, and only pivots
    whose confirmation bar is <= t. Return cluster_levels of their prices.
    The result must not change if any bar after t is altered.
    """
    raise NotImplementedError


def gap_table(ohlc: pd.DataFrame) -> pd.DataFrame:
    """Per-bar gap statistics, from the second bar on (same index as ohlc[1:]).

    Columns: gap (log), up (bool, gap > 0), filled (bool), oc (log open-to-close return).
    """
    raise NotImplementedError


def gap_fill_prob_bm(gap, sigma_intraday):
    """2 Phi(-|gap| / sigma_intraday); vectorised over numpy arrays."""
    raise NotImplementedError


def anchored_vwap(ohlc: pd.DataFrame, anchor_pos: int) -> pd.Series:
    """Anchored VWAP using typical price (H+L+C)/3; NaN before the anchor bar.
    Return a Series on ohlc.index."""
    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.