Strategy Expansion 03 - Method-specific strategy variants

Tutorial Navigation

Track Tutorial notebook
Roadmap Tutorial 00 - Roadmap
Strategy Lab 01 Trend-Following Lab
Tutorial Sequence 01 Real Market Data and Feature Factory
Tutorial Sequence 02 Decomposition-aware MA and MACD
Strategy Lab 02 Oscillation-Reversion Lab
Strategy Expansion 03 Method-Specific Variants
Tutorial Sequence 03 Residual Mean Reversion
Strategy Expansion 04 Component Pair Trading
Tutorial Sequence 04 Donchian Breakout
Tutorial Sequence 05 Pair-Spread Stat-Arb
Tutorial Sequence 06 Cross-Sectional Rotation
Native SSA Replay 07 Native SSA High-Return / Low-Drawdown

Executed Notebook

This notebook shows the core tutorial point: different decomposition methods and windows create different strategies because they produce different trend, cycle, and residual components. The trading rule is held mostly fixed; the decomposition front-end changes the signal.

Strategy map

The notebook runs five decomposition-based strategy families:

  • trend following from decomposed trend;
  • oscillation reversion from residual z-score;
  • residual Bollinger bands around trend + cycle fair value;
  • MACD computed on the decomposed trend;
  • dual-EMA crossover computed on the decomposed trend.

Each method/period/window combination is treated as one strategy variant.

In [1]
import matplotlib.pyplot as plt

from quant_trading.data import load_sample_goog_ohlcv
from quant_trading.strategy_lab import execution_price_panel
from quant_trading.strategy_method_variants import (
    DecompositionVariantSpec,
    collect_orders_and_trades,
    run_method_variant_grid,
)
In [2]
ohlcv = load_sample_goog_ohlcv(trim_start='2014-01-01')
symbol = 'GOOG'
close = ohlcv['Close'].rename(symbol).to_frame()
volume = ohlcv['Volume'].rename(symbol).to_frame()
execution_prices = execution_price_panel(ohlcv, field='Open', next_bar=True)
execution_prices.columns = [symbol]

close.tail()

Build a compact method grid

The grid below is intentionally small for a notebook. A full research run can add periods 63/126/252, shorter steps, and optional Wavelet/EMD/VMD variants. The default grid avoids very short periods so the extracted components read as market structure rather than one-month noise.

In [3]
specs = [
    DecompositionVariantSpec('STL', period=126, train_window=504, step=21, z_window=63, role='fixed_period_half_year_cycle'),
    DecompositionVariantSpec('SSA', period=126, train_window=504, step=21, z_window=63, role='subspace_half_year_cycle'),
    DecompositionVariantSpec('STD', period=126, train_window=504, step=21, z_window=63, role='dispersion_half_year_cycle'),
]

stats, results, spec_table, coverage, failed = run_method_variant_grid(
    close,
    volume,
    specs=specs,
    execution_prices=execution_prices,
    allow_short_trend=False,
    allow_short_reversion=True,
)

stats.head(20)
In [4]
plot_stats = stats.sort_values("sharpe", ascending=True).tail(10).copy()
labels = plot_stats["strategy"].astype(str).str.replace("detime_", "", regex=False)
fig, ax = plt.subplots(figsize=(10, 4.5))
ax.barh(labels, plot_stats["sharpe"])
ax.axvline(0, color="black", linewidth=0.8)
ax.set_xlabel("Sharpe ratio")
ax.set_title("Method-variant Sharpe comparison on real GOOG sample")
ax.grid(True, axis="x", alpha=0.25)
plt.tight_layout()
plt.show()
In [5]
orders, trades = collect_orders_and_trades(results)
print('orders:', len(orders))
print('round-trip trades:', len(trades))
trades.head()
In [6]
coverage.groupby(['method', 'variant', 'feature'])['coverage'].max().reset_index().head(20)

Parameter interpretation

  • period is the assumed cycle length. It plays a similar role to an indicator window.
  • train_window controls how much recent history the decomposition sees. Short windows adapt quickly; long windows produce smoother components.
  • step controls how often the decomposition is recomputed in walk-forward mode. Smaller steps are more responsive and more expensive.
  • z_window controls residual normalization and residual-band sensitivity.