{ "cells": [ { "cell_type": "markdown", "id": "1c0daa6c", "metadata": {}, "source": [ "## **Crypto Statistical Arbitrage via Cointegration and Regime Filtering**" ] }, { "cell_type": "markdown", "id": "5c2f853a", "metadata": {}, "source": [ "### Research Framework\n", "\n", "#### **Objective**\n", "Discover cointegrated crypto baskets that exhibit stable, regime-aware mean-reverting relationships, and combine them into a portfolio that generates strong risk-adjusted returns, evaluated via out-of-sample testing.\n", "\n", "---\n", "\n", "#### **Step 1 — Walk-Forward Backtest**\n", "To avoid overfitting, the entire history was split into rolling **365-day train windows** and **60-day test windows**, stepped forward sequentially. \n", "Within each train window:\n", "\n", "- **Basket Discovery** — Johansen cointegration was applied across all candidate baskets (2–4 assets). Only baskets with exactly one cointegrating relationship (**rank = 1**) were retained. \n", "\n", "- **Quality Filtering** — Surviving baskets were passed through a sequential pipeline:\n", " - ADF stationarity \n", " - Variance ratio (mean-reversion check) \n", " - Half-life between 2–15 days (estimated via OLS) \n", " - Rolling half-life stability \n", " - Spread predictability \n", "\n", "- **Parameter Calibration** — Since mean-reversion speed varies across time, key parameters were calibrated within each train window. \n", " In particular, the **half-life lookback window** used in signal construction was scaled based on the **median half-life of the filtered baskets**. \n", " This ensures that the signal adapts to the prevailing market regime, rather than relying on fixed global parameters.\n", "\n", "- **Dynamic Cointegrating Weights** — Rolling cointegration weights were computed to allow hedge ratios to adapt to slow structural drift, with weights forward-filled to ensure no look-ahead bias. \n", "\n", "- **Alpha & Regime Signal** — An Ornstein-Uhlenbeck mean-reversion signal was computed on the dynamic spread, with clipping and deadbanding to suppress noise. \n", " A two-tier regime filter was applied:\n", " - **Hard filters**: volatility spikes and ADF breakdown (signal = 0) \n", " - **Soft score**: continuous weighting based on mean-reversion strength, stationarity quality, and volatility stability \n", "\n", "---\n", "\n", "#### **Step 2 — Basket Scoring & Selection**\n", "After all walk-forward folds, each basket was evaluated using **per-basket PnL attribution** to ensure accurate contribution measurement.\n", "\n", "- Baskets were scored using a composite metric combining:\n", " - Mean fold Sharpe \n", " - Consistency across folds \n", " - Number of fold appearances \n", "\n", "- Baskets were filtered to those that:\n", " - Appeared in at least **2 folds** \n", " - Had both **minimum and mean Sharpe > 0** across folds \n", "\n", "- A greedy diversification step was then applied, capping each asset to at most **2 baskets** to ensure diversification. \n", "\n", "---\n", "\n", "#### **Step 3 — Out-of-Sample Testing**\n", "The selected baskets were traded on a fully held-out **20% test set**.\n", "\n", "- **Execution** — Positions were sized using a **bucketed framework** (e.g., flat / partial / full exposure), where trades occur only when alpha crosses bucket boundaries, significantly reducing turnover. \n", "\n", "- **Portfolio Construction** — Capital was allocated using **inverse-volatility risk parity**, based on trailing 30-day basket returns. \n", "\n", "- **Transaction Costs** — ~20 bps transaction costs were applied **only on signal-driven position changes**." ] }, { "cell_type": "markdown", "id": "3189df63", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "a719bb9e", "metadata": {}, "source": [ "### 1. Setup & Imports" ] }, { "cell_type": "code", "execution_count": 1, "id": "9426c67e", "metadata": {}, "outputs": [], "source": [ "from binance.client import Client\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from itertools import combinations\n", "import statsmodels.api as sm\n", "from statsmodels.tsa.vector_ar.vecm import coint_johansen\n", "from scipy.optimize import minimize\n", "from statsmodels.tsa.stattools import adfuller\n", "import statsmodels.api as sm\n", "from tqdm import tqdm\n", "import warnings\n", "warnings.filterwarnings('ignore')" ] }, { "cell_type": "markdown", "id": "4bfe5998", "metadata": {}, "source": [ "### 2. Constants & Hyperparameters" ] }, { "cell_type": "code", "execution_count": 2, "id": "b046413b", "metadata": {}, "outputs": [], "source": [ "# ── Data & Spread ──────────────────────────────────────────────────────────────\n", "MIN_HALF_LIFE = 2 # minimum acceptable half-life (days)\n", "MAX_HALF_LIFE = 15 # maximum acceptable half-life (days)\n", "HL_WINDOW = 120 # lookback (bars) for rolling OU parameter estimation\n", "REGIME_WINDOW = 30 # lookback (bars) for regime vol and ADF checks\n", "\n", "# ── OU Signal ─────────────────────────────────────────────────────────────────\n", "ALPHA_CLIP = 3.0 # winsorise raw alpha to ±3σ\n", "ALPHA_DEADBAND = 0.10 # zero out alpha below this\n", "\n", "# ── Regime Filter ─────────────────────────────────────────────────────────────\n", "# Tier 1 — hard kill: either condition triggers immediate alpha = 0\n", "HARD_VOL_RATIO = 4.0 # short-term vol / long-term vol > this → structural break\n", "HARD_ADF_PVAL = 0.40 # rolling ADF p-value > this → spread non-stationary\n", "\n", "# Tier 2 — soft score weights (must sum to 1.0)\n", "REGIME_W_KAPPA = 0.35 # mean reversion speed\n", "REGIME_W_ADF = 0.35 # stationarity quality\n", "REGIME_W_VOL = 0.30 # volatility sanity\n", "\n", "# ── Position Sizing ───────────────────────────────────────────────────────────\n", "ENTRY_THRESHOLD = 0.08 # minimum alpha to open a position\n", "EXIT_THRESHOLD = 0.02 # alpha below this → exit\n", "MAX_POS_SIZE = 1.0 # maximum position size\n", "MIN_POS_SIZE = 0.10 # minimum position size\n", "\n", "# ── Risk Parity ───────────────────────────────────────────────────────────────\n", "VOL_WINDOW = 30 # trailing bars for per-basket volatility estimation\n", "VOL_FLOOR = 1e-6 # prevents division by zero for near-constant spreads\n", "\n", "# ── Basket Selection ──────────────────────────────────────────────────────────\n", "MIN_FOLDS = 2 # basket must appear in at least this many WF folds\n", "MIN_MEAN_SR = 0.0 # mean fold Sharpe must be positive\n", "MIN_FLOOR_SR = 0.0 # worst single fold Sharpe must be non-negative\n", "N_SELECT = 8 # max baskets after greedy diversification\n", "\n", "# ── OOS Deployment ────────────────────────────────────────────────────────────\n", "BUCKET_THRESHOLDS = [0.08, 0.10] # alpha boundaries for position buckets\n", "BUCKET_SIZES = [0.0, 0.5, 1.0] # position sizes: flat / half / full\n", "TCOST_BPS = 20.0 # round-trip transaction cost" ] }, { "cell_type": "markdown", "id": "804f76e3", "metadata": {}, "source": [ "### 3. Data Acquisition" ] }, { "cell_type": "markdown", "id": "b04037da", "metadata": {}, "source": [ "#### 3.1 Download Prices" ] }, { "cell_type": "code", "execution_count": 3, "id": "35bada5f", "metadata": {}, "outputs": [], "source": [ "class BinanceDataLoader:\n", " def __init__(self):\n", " self.client = Client(tld='US')\n", " \n", " def get_klines(self, symbol, interval, start, end):\n", " klines = self.client.get_historical_klines(symbol, interval, start, end)\n", " df = pd.DataFrame(klines, columns=[\n", " \"open_time\", \"open\", \"high\", \"low\", \"close\", \"volume\",\n", " \"close_time\", \"quote_asset_volume\", \"number_of_trades\",\n", " \"taker_buy_base\", \"taker_buy_quote\", \"ignore\"\n", " ])\n", " df[\"open_time\"] = pd.to_datetime(df[\"open_time\"], unit=\"ms\")\n", " df = df.set_index(\"open_time\")\n", " df = df.astype(float)\n", " return df\n", " \n", " def download_prices(self, symbols, interval=\"1d\",\n", " start=\"1 Jan 2021\", end=\"1 Mar 2026\"):\n", " prices = {}\n", " for symbol in tqdm(symbols):\n", " try:\n", " df = self.get_klines(symbol, interval, start, end)\n", " prices[symbol] = df[\"close\"]\n", " except Exception as e:\n", " print(f\"Failed: {symbol} — {e}\")\n", " prices = pd.DataFrame(prices).sort_index()\n", " return prices" ] }, { "cell_type": "code", "execution_count": 4, "id": "efe37a65", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 23/23 [00:07<00:00, 2.90it/s]\n" ] } ], "source": [ "symbols = [\n", " \"BTCUSDT\",\"ETHUSDT\",\"SOLUSDT\",\"BNBUSDT\",\"XRPUSDT\",\"ADAUSDT\",\"AVAXUSDT\",\"DOTUSDT\",\"ATOMUSDT\",\"LINKUSDT\",\"LDOUSDT\",\n", " \"ARBUSDT\",\"OPUSDT\",\"FILUSDT\",\"LTCUSDT\",\"DOGEUSDT\",\"FETUSDT\",\"MATICUSDT\",\"NEARUSDT\",\"APTUSDT\",\"SUIUSDT\",\"MKRUSDT\",\n", " \"CRVUSDT\"\n", "]\n", "\n", "loader = BinanceDataLoader()\n", "prices = loader.download_prices(\n", " symbols,\n", " interval=\"1d\",\n", " start=\"1 Mar 2023\",\n", " end= '1 Mar 2026'\n", ")" ] }, { "cell_type": "markdown", "id": "ac30e63d", "metadata": {}, "source": [ "#### 3.2 Clean & Filter" ] }, { "cell_type": "code", "execution_count": 5, "id": "729e7366", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
BTCUSDTETHUSDTSOLUSDTBNBUSDTADAUSDTAVAXUSDTDOTUSDTATOMUSDTLINKUSDTLDOUSDTOPUSDTFILUSDTLTCUSDTDOGEUSDTFETUSDTNEARUSDTAPTUSDTCRVUSDT
open_time
2023-03-0123629.081664.9222.5147302.56470.3604217.756.52012.7817.5403.0402.7707.28097.680.0819620.48382.32413.48001.0370
2023-03-0223468.021647.2821.9753299.85830.3493317.466.33012.4507.2303.0192.7236.92095.180.0804860.46632.23912.84280.9900
2023-03-0322358.061569.1121.3900290.40000.3429016.666.03612.0116.9522.8132.5406.22890.430.0768200.43912.09411.74750.9360
2023-03-0422346.681566.8420.9800289.60000.3363016.135.91011.8386.8502.5492.3635.90489.330.0746500.42922.04211.27630.9060
2023-03-0522430.681564.6320.9900288.60000.3366016.185.97512.0306.9142.6022.4276.11089.940.0746600.43202.02611.09060.9240
.........................................................
2026-02-2567957.932057.3588.0300625.47000.295809.501.6391.9309.2000.2940.1231.09156.590.0999400.16561.1731.00200.2516
2026-02-2667481.402026.7185.9300625.93000.286909.301.6431.9019.1100.3020.1171.03355.430.0970300.16251.1300.97800.2484
2026-02-2765882.981929.8281.9800614.20000.277108.941.6031.8768.6800.2920.1200.97554.550.0933800.15591.0980.95700.2421
2026-02-2866954.131964.6584.3000617.24000.281509.181.6751.8288.8300.3100.1381.01254.490.0938600.15451.1770.94900.2505
2026-03-0165721.171939.1283.5700620.71000.273509.041.5381.8008.6800.3100.1210.99053.230.0918200.15161.1600.90900.2430
\n", "

1097 rows × 18 columns

\n", "
" ], "text/plain": [ " BTCUSDT ETHUSDT SOLUSDT BNBUSDT ADAUSDT AVAXUSDT DOTUSDT \\\n", "open_time \n", "2023-03-01 23629.08 1664.92 22.5147 302.5647 0.36042 17.75 6.520 \n", "2023-03-02 23468.02 1647.28 21.9753 299.8583 0.34933 17.46 6.330 \n", "2023-03-03 22358.06 1569.11 21.3900 290.4000 0.34290 16.66 6.036 \n", "2023-03-04 22346.68 1566.84 20.9800 289.6000 0.33630 16.13 5.910 \n", "2023-03-05 22430.68 1564.63 20.9900 288.6000 0.33660 16.18 5.975 \n", "... ... ... ... ... ... ... ... \n", "2026-02-25 67957.93 2057.35 88.0300 625.4700 0.29580 9.50 1.639 \n", "2026-02-26 67481.40 2026.71 85.9300 625.9300 0.28690 9.30 1.643 \n", "2026-02-27 65882.98 1929.82 81.9800 614.2000 0.27710 8.94 1.603 \n", "2026-02-28 66954.13 1964.65 84.3000 617.2400 0.28150 9.18 1.675 \n", "2026-03-01 65721.17 1939.12 83.5700 620.7100 0.27350 9.04 1.538 \n", "\n", " ATOMUSDT LINKUSDT LDOUSDT OPUSDT FILUSDT LTCUSDT DOGEUSDT \\\n", "open_time \n", "2023-03-01 12.781 7.540 3.040 2.770 7.280 97.68 0.081962 \n", "2023-03-02 12.450 7.230 3.019 2.723 6.920 95.18 0.080486 \n", "2023-03-03 12.011 6.952 2.813 2.540 6.228 90.43 0.076820 \n", "2023-03-04 11.838 6.850 2.549 2.363 5.904 89.33 0.074650 \n", "2023-03-05 12.030 6.914 2.602 2.427 6.110 89.94 0.074660 \n", "... ... ... ... ... ... ... ... \n", "2026-02-25 1.930 9.200 0.294 0.123 1.091 56.59 0.099940 \n", "2026-02-26 1.901 9.110 0.302 0.117 1.033 55.43 0.097030 \n", "2026-02-27 1.876 8.680 0.292 0.120 0.975 54.55 0.093380 \n", "2026-02-28 1.828 8.830 0.310 0.138 1.012 54.49 0.093860 \n", "2026-03-01 1.800 8.680 0.310 0.121 0.990 53.23 0.091820 \n", "\n", " FETUSDT NEARUSDT APTUSDT CRVUSDT \n", "open_time \n", "2023-03-01 0.4838 2.324 13.4800 1.0370 \n", "2023-03-02 0.4663 2.239 12.8428 0.9900 \n", "2023-03-03 0.4391 2.094 11.7475 0.9360 \n", "2023-03-04 0.4292 2.042 11.2763 0.9060 \n", "2023-03-05 0.4320 2.026 11.0906 0.9240 \n", "... ... ... ... ... \n", "2026-02-25 0.1656 1.173 1.0020 0.2516 \n", "2026-02-26 0.1625 1.130 0.9780 0.2484 \n", "2026-02-27 0.1559 1.098 0.9570 0.2421 \n", "2026-02-28 0.1545 1.177 0.9490 0.2505 \n", "2026-03-01 0.1516 1.160 0.9090 0.2430 \n", "\n", "[1097 rows x 18 columns]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "threshold = int(0.05 * len(prices))\n", "prices = prices.loc[:, prices.isna().sum() < threshold]\n", "prices" ] }, { "cell_type": "markdown", "id": "557b491d", "metadata": {}, "source": [ "#### 3.3 Train / Test Split" ] }, { "cell_type": "code", "execution_count": 6, "id": "74cdd591", "metadata": {}, "outputs": [], "source": [ "split_idx = int(len(prices) * 0.8)\n", "prices_train = prices.iloc[:split_idx]\n", "prices_test = prices.iloc[split_idx:]" ] }, { "cell_type": "code", "execution_count": 7, "id": "86132e9b", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
BTCUSDTETHUSDTSOLUSDTBNBUSDTADAUSDTAVAXUSDTDOTUSDTATOMUSDTLINKUSDTLDOUSDTOPUSDTFILUSDTLTCUSDTDOGEUSDTFETUSDTNEARUSDTAPTUSDTCRVUSDT
open_time
2023-03-0123629.081664.9222.5147302.56470.3604217.756.52012.7817.5403.0402.7707.28097.680.0819620.48382.32413.48001.0370
2023-03-0223468.021647.2821.9753299.85830.3493317.466.33012.4507.2303.0192.7236.92095.180.0804860.46632.23912.84280.9900
2023-03-0322358.061569.1121.3900290.40000.3429016.666.03612.0116.9522.8132.5406.22890.430.0768200.43912.09411.74750.9360
2023-03-0422346.681566.8420.9800289.60000.3363016.135.91011.8386.8502.5492.3635.90489.330.0746500.42922.04211.27630.9060
2023-03-0522430.681564.6320.9900288.60000.3366016.185.97512.0306.9142.6022.4276.11089.940.0746600.43202.02611.09060.9240
.........................................................
2025-07-20117232.913760.19181.4600757.49000.8571025.124.4455.19619.2901.2410.7932.859116.880.2737100.80002.9865.22000.9558
2025-07-21117480.913767.17195.7300766.39000.8897025.564.4735.20319.5301.1900.8072.897115.520.2712200.84203.0385.44000.9779
2025-07-22119999.613749.89205.8500783.71000.9021025.874.5195.12019.6601.2200.8002.932119.480.2704300.82403.0285.46800.9796
2025-07-23118620.893624.05189.1600776.00000.8148023.964.1004.68718.1501.0960.6902.640112.240.2404000.74602.7664.84900.9383
2025-07-24118322.033705.54182.7900769.71000.8066023.623.9894.63917.8801.0520.6932.617111.940.2311300.72402.7084.63300.9495
\n", "

877 rows × 18 columns

\n", "
" ], "text/plain": [ " BTCUSDT ETHUSDT SOLUSDT BNBUSDT ADAUSDT AVAXUSDT \\\n", "open_time \n", "2023-03-01 23629.08 1664.92 22.5147 302.5647 0.36042 17.75 \n", "2023-03-02 23468.02 1647.28 21.9753 299.8583 0.34933 17.46 \n", "2023-03-03 22358.06 1569.11 21.3900 290.4000 0.34290 16.66 \n", "2023-03-04 22346.68 1566.84 20.9800 289.6000 0.33630 16.13 \n", "2023-03-05 22430.68 1564.63 20.9900 288.6000 0.33660 16.18 \n", "... ... ... ... ... ... ... \n", "2025-07-20 117232.91 3760.19 181.4600 757.4900 0.85710 25.12 \n", "2025-07-21 117480.91 3767.17 195.7300 766.3900 0.88970 25.56 \n", "2025-07-22 119999.61 3749.89 205.8500 783.7100 0.90210 25.87 \n", "2025-07-23 118620.89 3624.05 189.1600 776.0000 0.81480 23.96 \n", "2025-07-24 118322.03 3705.54 182.7900 769.7100 0.80660 23.62 \n", "\n", " DOTUSDT ATOMUSDT LINKUSDT LDOUSDT OPUSDT FILUSDT LTCUSDT \\\n", "open_time \n", "2023-03-01 6.520 12.781 7.540 3.040 2.770 7.280 97.68 \n", "2023-03-02 6.330 12.450 7.230 3.019 2.723 6.920 95.18 \n", "2023-03-03 6.036 12.011 6.952 2.813 2.540 6.228 90.43 \n", "2023-03-04 5.910 11.838 6.850 2.549 2.363 5.904 89.33 \n", "2023-03-05 5.975 12.030 6.914 2.602 2.427 6.110 89.94 \n", "... ... ... ... ... ... ... ... \n", "2025-07-20 4.445 5.196 19.290 1.241 0.793 2.859 116.88 \n", "2025-07-21 4.473 5.203 19.530 1.190 0.807 2.897 115.52 \n", "2025-07-22 4.519 5.120 19.660 1.220 0.800 2.932 119.48 \n", "2025-07-23 4.100 4.687 18.150 1.096 0.690 2.640 112.24 \n", "2025-07-24 3.989 4.639 17.880 1.052 0.693 2.617 111.94 \n", "\n", " DOGEUSDT FETUSDT NEARUSDT APTUSDT CRVUSDT \n", "open_time \n", "2023-03-01 0.081962 0.4838 2.324 13.4800 1.0370 \n", "2023-03-02 0.080486 0.4663 2.239 12.8428 0.9900 \n", "2023-03-03 0.076820 0.4391 2.094 11.7475 0.9360 \n", "2023-03-04 0.074650 0.4292 2.042 11.2763 0.9060 \n", "2023-03-05 0.074660 0.4320 2.026 11.0906 0.9240 \n", "... ... ... ... ... ... \n", "2025-07-20 0.273710 0.8000 2.986 5.2200 0.9558 \n", "2025-07-21 0.271220 0.8420 3.038 5.4400 0.9779 \n", "2025-07-22 0.270430 0.8240 3.028 5.4680 0.9796 \n", "2025-07-23 0.240400 0.7460 2.766 4.8490 0.9383 \n", "2025-07-24 0.231130 0.7240 2.708 4.6330 0.9495 \n", "\n", "[877 rows x 18 columns]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prices_train" ] }, { "cell_type": "code", "execution_count": 8, "id": "211f131c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
BTCUSDTETHUSDTSOLUSDTBNBUSDTADAUSDTAVAXUSDTDOTUSDTATOMUSDTLINKUSDTLDOUSDTOPUSDTFILUSDTLTCUSDTDOGEUSDTFETUSDTNEARUSDTAPTUSDTCRVUSDT
open_time
2025-07-25117577.403720.94186.58784.570.816724.024.0754.71218.231.0490.7182.636113.530.236170.74002.8574.7651.0229
2025-07-26117850.003740.65184.88792.910.820224.914.1244.77018.411.0980.7252.658113.960.235300.73102.8954.8491.1037
2025-07-27119395.673873.34188.56842.390.830426.024.2264.85719.211.1470.7422.761115.030.240560.75502.9724.9141.0437
2025-07-28118080.543794.79182.39823.260.789825.193.9414.60918.051.0680.7552.592108.930.225990.71802.7744.6921.0233
2025-07-29117875.983794.85181.61804.750.783424.393.8854.52217.791.0640.7262.547108.240.223000.70702.7274.5750.9713
.........................................................
2026-02-2567957.932057.3588.03625.470.29589.501.6391.9309.200.2940.1231.09156.590.099940.16561.1731.0020.2516
2026-02-2667481.402026.7185.93625.930.28699.301.6431.9019.110.3020.1171.03355.430.097030.16251.1300.9780.2484
2026-02-2765882.981929.8281.98614.200.27718.941.6031.8768.680.2920.1200.97554.550.093380.15591.0980.9570.2421
2026-02-2866954.131964.6584.30617.240.28159.181.6751.8288.830.3100.1381.01254.490.093860.15451.1770.9490.2505
2026-03-0165721.171939.1283.57620.710.27359.041.5381.8008.680.3100.1210.99053.230.091820.15161.1600.9090.2430
\n", "

220 rows × 18 columns

\n", "
" ], "text/plain": [ " BTCUSDT ETHUSDT SOLUSDT BNBUSDT ADAUSDT AVAXUSDT DOTUSDT \\\n", "open_time \n", "2025-07-25 117577.40 3720.94 186.58 784.57 0.8167 24.02 4.075 \n", "2025-07-26 117850.00 3740.65 184.88 792.91 0.8202 24.91 4.124 \n", "2025-07-27 119395.67 3873.34 188.56 842.39 0.8304 26.02 4.226 \n", "2025-07-28 118080.54 3794.79 182.39 823.26 0.7898 25.19 3.941 \n", "2025-07-29 117875.98 3794.85 181.61 804.75 0.7834 24.39 3.885 \n", "... ... ... ... ... ... ... ... \n", "2026-02-25 67957.93 2057.35 88.03 625.47 0.2958 9.50 1.639 \n", "2026-02-26 67481.40 2026.71 85.93 625.93 0.2869 9.30 1.643 \n", "2026-02-27 65882.98 1929.82 81.98 614.20 0.2771 8.94 1.603 \n", "2026-02-28 66954.13 1964.65 84.30 617.24 0.2815 9.18 1.675 \n", "2026-03-01 65721.17 1939.12 83.57 620.71 0.2735 9.04 1.538 \n", "\n", " ATOMUSDT LINKUSDT LDOUSDT OPUSDT FILUSDT LTCUSDT DOGEUSDT \\\n", "open_time \n", "2025-07-25 4.712 18.23 1.049 0.718 2.636 113.53 0.23617 \n", "2025-07-26 4.770 18.41 1.098 0.725 2.658 113.96 0.23530 \n", "2025-07-27 4.857 19.21 1.147 0.742 2.761 115.03 0.24056 \n", "2025-07-28 4.609 18.05 1.068 0.755 2.592 108.93 0.22599 \n", "2025-07-29 4.522 17.79 1.064 0.726 2.547 108.24 0.22300 \n", "... ... ... ... ... ... ... ... \n", "2026-02-25 1.930 9.20 0.294 0.123 1.091 56.59 0.09994 \n", "2026-02-26 1.901 9.11 0.302 0.117 1.033 55.43 0.09703 \n", "2026-02-27 1.876 8.68 0.292 0.120 0.975 54.55 0.09338 \n", "2026-02-28 1.828 8.83 0.310 0.138 1.012 54.49 0.09386 \n", "2026-03-01 1.800 8.68 0.310 0.121 0.990 53.23 0.09182 \n", "\n", " FETUSDT NEARUSDT APTUSDT CRVUSDT \n", "open_time \n", "2025-07-25 0.7400 2.857 4.765 1.0229 \n", "2025-07-26 0.7310 2.895 4.849 1.1037 \n", "2025-07-27 0.7550 2.972 4.914 1.0437 \n", "2025-07-28 0.7180 2.774 4.692 1.0233 \n", "2025-07-29 0.7070 2.727 4.575 0.9713 \n", "... ... ... ... ... \n", "2026-02-25 0.1656 1.173 1.002 0.2516 \n", "2026-02-26 0.1625 1.130 0.978 0.2484 \n", "2026-02-27 0.1559 1.098 0.957 0.2421 \n", "2026-02-28 0.1545 1.177 0.949 0.2505 \n", "2026-03-01 0.1516 1.160 0.909 0.2430 \n", "\n", "[220 rows x 18 columns]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prices_test" ] }, { "cell_type": "markdown", "id": "8c99ad29", "metadata": {}, "source": [ "### 4. Basket Discovery" ] }, { "cell_type": "markdown", "id": "d2b17217", "metadata": {}, "source": [ "#### 4.1 Johansen Cointegration" ] }, { "cell_type": "code", "execution_count": 9, "id": "6543623e", "metadata": {}, "outputs": [], "source": [ "def find_cointegrated_baskets(prices, symbols, min_size=2, max_size=3):\n", " \"\"\"\n", " Enumerate all asset combinations of size [min_size, max_size] and return\n", " only those with exactly one cointegrating relationship (Johansen rank == 1).\n", "\n", " Parameters\n", " ----------\n", " prices : pd.DataFrame — daily close prices, columns = asset symbols\n", " symbols : list[str] — universe of assets to enumerate over\n", " min_size : int — minimum basket size (default 2)\n", " max_size : int — maximum basket size (default 3)\n", "\n", " Returns\n", " -------\n", " list[dict] — each dict contains:\n", " assets : tuple[str] — asset names in the basket\n", " weights : np.ndarray — normalised cointegrating vector\n", " trace : float — Johansen trace statistic (first eigenvalue)\n", " spread : pd.Series — log_prices @ weights (static, full-sample)\n", "\n", " \"\"\"\n", " baskets = []\n", " for size in range(min_size, max_size + 1):\n", " for combo in combinations(symbols, size):\n", " subset = prices[list(combo)].dropna()\n", " if len(subset) < 200:\n", " continue\n", " log_prices = np.log(subset).replace([np.inf, -np.inf], np.nan).dropna()\n", " if len(log_prices) < 200:\n", " continue\n", " try:\n", " result = coint_johansen(log_prices, det_order=0, k_ar_diff=1)\n", " except np.linalg.LinAlgError:\n", " continue\n", " \n", " trace_stats = result.lr1\n", " crit_vals = result.cvt[:, 1]\n", " rank = sum(trace_stats > crit_vals)\n", " if rank != 1:\n", " continue\n", " \n", " weights = result.evec[:, 0]\n", " weights = weights / np.sum(np.abs(weights))\n", " if weights[0] < 0:\n", " weights = -weights\n", " \n", " spread = log_prices.dot(weights)\n", " baskets.append({\n", " \"assets\" : combo,\n", " \"weights\" : weights,\n", " \"trace\" : trace_stats[0],\n", " \"spread\" : spread,\n", " })\n", " return baskets" ] }, { "cell_type": "markdown", "id": "a1147c30", "metadata": {}, "source": [ "#### 4.2 Spread Quality Filters" ] }, { "cell_type": "markdown", "id": "0b4ec585", "metadata": {}, "source": [ "##### 4.2.1 ADF Stationarity" ] }, { "cell_type": "code", "execution_count": 10, "id": "016ccd70", "metadata": {}, "outputs": [], "source": [ "def check_spread_stationarity(spread: pd.Series) -> dict:\n", " \"\"\"\n", " Test whether a spread is stationary using the Augmented Dickey-Fuller test.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series — spread series (log price linear combination)\n", "\n", " Returns\n", " -------\n", " dict with keys:\n", " adf_pval : float — ADF p-value rounded to 4dp\n", " adf_pass : bool — True if p-value < 0.05\n", " is_stationary : bool — alias of adf_pass\n", " \"\"\"\n", " s = spread.dropna()\n", " adf_pval = adfuller(s, maxlag=1, autolag=None)[1]\n", " adf_pass = adf_pval < 0.05\n", " return {\n", " \"adf_pval\" : round(adf_pval, 4),\n", " \"adf_pass\" : adf_pass,\n", " \"is_stationary\": adf_pass,\n", " }" ] }, { "cell_type": "markdown", "id": "d980f72e", "metadata": {}, "source": [ "##### 4.2.2 Variance Ratio" ] }, { "cell_type": "code", "execution_count": 11, "id": "8e05df2e", "metadata": {}, "outputs": [], "source": [ "def variance_ratio(spread, k=5):\n", " \"\"\"\n", " Compute the variance ratio statistic to test for mean reversion.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series — spread series\n", " k : int — horizon for multi-period variance (default 5 days)\n", "\n", " Returns\n", " -------\n", " float — variance ratio (< 1.0 = mean-reverting, > 1.0 = momentum)\n", " \"\"\"\n", " s = spread.dropna()\n", " var1 = np.var(s.diff().dropna())\n", " vark = np.var(s.diff(k).dropna())\n", " return vark / (k * var1)" ] }, { "cell_type": "markdown", "id": "13a768b2", "metadata": {}, "source": [ "##### 4.2.3 Half-Life & Stability" ] }, { "cell_type": "code", "execution_count": 12, "id": "8432e098", "metadata": {}, "outputs": [], "source": [ "def estimate_half_life(spread: pd.Series) -> dict:\n", " \"\"\"\n", " Estimate the mean-reversion half-life of a spread via OLS.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series — spread series\n", "\n", " Returns\n", " -------\n", " dict with keys:\n", " half_life : float — mean-reversion half-life in days (np.inf if κ ≥ 0)\n", " kappa : float — OLS mean-reversion speed coefficient\n", " r_squared : float — regression R² (signal quality indicator)\n", " \"\"\"\n", " s = spread.dropna()\n", " s_lag = s.shift(1).dropna()\n", " s_dif = s.diff().dropna()\n", " idx = s_dif.index\n", " \n", " model = sm.OLS(s_dif.loc[idx], sm.add_constant(s_lag.loc[idx])).fit()\n", " kappa = model.params.iloc[1]\n", " \n", " if kappa >= 0:\n", " return {\"half_life\": np.inf, \"kappa\": kappa, \"r_squared\": model.rsquared}\n", " \n", " half_life = -np.log(2) / kappa\n", " return {\n", " \"half_life\" : round(half_life, 2),\n", " \"kappa\" : round(kappa, 6),\n", " \"r_squared\" : round(model.rsquared, 4),\n", " }\n", "\n", "def halflife_stability(spread, window=200, step=30):\n", " \"\"\"\n", " Measure how stable the half-life estimate is over rolling sub-windows.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series — spread series\n", " window : int — rolling estimation window in bars (default 200)\n", " step : int — step size between estimates in bars (default 30)\n", "\n", " Returns\n", " -------\n", " float — coefficient of variation of rolling half-life estimates.\n", " Returns np.inf if fewer than 2 valid estimates exist, which\n", " correctly triggers the > 0.5 filter and rejects the basket.\n", " \"\"\"\n", " rolling_hl = []\n", " for i in range(window, len(spread), step):\n", " hl = estimate_half_life(spread.iloc[i - window : i])[\"half_life\"]\n", " if hl != np.inf:\n", " rolling_hl.append(hl)\n", "\n", " if len(rolling_hl) < 2: \n", " return np.inf\n", " return np.std(rolling_hl) / np.mean(rolling_hl)" ] }, { "cell_type": "markdown", "id": "d260d842", "metadata": {}, "source": [ "##### 4.2.4 Spread Predictability" ] }, { "cell_type": "code", "execution_count": 13, "id": "d07e4a95", "metadata": {}, "outputs": [], "source": [ "def spread_predictability(spread, half_life):\n", " \"\"\"\n", " Test whether the current z-score predicts future spread returns.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series — spread series\n", " half_life : float — estimated half-life, used as forecast horizon\n", "\n", " Returns\n", " -------\n", " dict with keys:\n", " beta : float — regression coefficient of z-score on future returns\n", " tstat : float — t-statistic of beta\n", " horizon : int — forecast horizon used (= int(half_life))\n", " r_squared : float — regression R²\n", " \"\"\"\n", " s = spread.dropna()\n", " z = (s - s.mean()) / s.std()\n", " horizon = max(1, int(half_life))\n", " future_ret = s.diff(horizon).shift(-horizon)\n", " df = pd.DataFrame({\"z\": z, \"future_ret\": future_ret}).dropna()\n", " model = sm.OLS(df[\"future_ret\"], sm.add_constant(df[\"z\"])).fit()\n", " return {\n", " \"beta\" : model.params[\"z\"],\n", " \"tstat\" : model.tvalues[\"z\"],\n", " \"horizon\" : horizon,\n", " \"r_squared\": model.rsquared,\n", " }" ] }, { "cell_type": "markdown", "id": "9e5e35f2", "metadata": {}, "source": [ "#### 4.3 Dynamic Spread Reconstruction" ] }, { "cell_type": "code", "execution_count": 14, "id": "f1ea5673", "metadata": {}, "outputs": [], "source": [ "def rolling_coint_weights(prices, assets, window=180, step=30):\n", " \"\"\"\n", " Re-estimate the Johansen cointegrating vector on a rolling walk-forward\n", " basis, returning one weight vector per re-estimation date.\n", "\n", " Parameters\n", " ----------\n", " prices : pd.DataFrame — daily close prices\n", " assets : tuple[str] — basket assets (ordered, matches weight indices)\n", " window : int — estimation window in bars (default 180 ≈ 6 months)\n", " step : int — re-estimation frequency in bars (default 30 ≈ 1 month)\n", "\n", " Returns\n", " -------\n", " pd.DataFrame — index = re-estimation dates, columns = asset names,\n", " values = normalised cointegrating weights.\n", " Empty DataFrame if no valid estimates were produced.\n", " \"\"\"\n", " log_prices = np.log(prices[list(assets)]).dropna()\n", " records = []\n", " \n", " for i in range(window, len(log_prices), step):\n", " window_data = log_prices.iloc[i - window : i]\n", " try:\n", " result = coint_johansen(window_data, det_order=0, k_ar_diff=1)\n", " rank = sum(result.lr1 > result.cvt[:, 1])\n", " if rank < 1:\n", " continue\n", " w = result.evec[:, 0]\n", " w = w / np.sum(np.abs(w))\n", " if w[0] < 0:\n", " w = -w\n", " records.append({\n", " \"date\": log_prices.index[i],\n", " **{assets[j]: w[j] for j in range(len(assets))}\n", " })\n", " except Exception:\n", " continue\n", " \n", " if not records:\n", " return pd.DataFrame()\n", " return pd.DataFrame(records).set_index(\"date\")" ] }, { "cell_type": "code", "execution_count": 15, "id": "f358a1fb", "metadata": {}, "outputs": [], "source": [ "def build_dynamic_spread(prices, assets, window=180, step=30):\n", " \"\"\"\n", " Reconstruct a daily spread series using rolling cointegrating weights,\n", " ensuring no look-ahead at any point in the series.\n", "\n", " Parameters\n", " ----------\n", " prices : pd.DataFrame — daily close prices\n", " assets : tuple[str] — basket assets\n", " window : int — rolling estimation window in bars (default 180)\n", " step : int — re-estimation frequency in bars (default 30)\n", "\n", " Returns\n", " -------\n", " spread_dynamic : pd.Series — daily dynamic spread aligned to price index,\n", " or None if insufficient valid estimates exist\n", " weights_latest : np.ndarray — most recent cointegrating vector for OOS\n", " initialisation, or None on failure\n", " \"\"\"\n", " log_prices = np.log(prices[list(assets)]).dropna()\n", " rolling_w = rolling_coint_weights(prices, assets, window=window, step=step)\n", " \n", " if rolling_w.empty or len(rolling_w) < 2:\n", " return None, None\n", " \n", " weights_daily = rolling_w.reindex(log_prices.index).ffill().dropna()\n", " common_idx = log_prices.index.intersection(weights_daily.index)\n", " \n", " if len(common_idx) < 200:\n", " return None, None\n", " \n", " lp_aligned = log_prices.loc[common_idx]\n", " w_aligned = weights_daily.loc[common_idx]\n", " spread_dynamic = (lp_aligned * w_aligned).sum(axis=1)\n", " weights_latest = rolling_w.iloc[-1].values\n", " \n", " return spread_dynamic, weights_latest" ] }, { "cell_type": "markdown", "id": "13b83ea6", "metadata": {}, "source": [ "### 5. Signal Generation" ] }, { "cell_type": "markdown", "id": "e6bccddd", "metadata": {}, "source": [ "#### 5.1 OU Alpha (Point & Series)" ] }, { "cell_type": "code", "execution_count": 16, "id": "90aeda63", "metadata": {}, "outputs": [], "source": [ "def compute_ou_alpha_point(\n", " spread_history: pd.Series,\n", " hl_window : int = HL_WINDOW,\n", ") -> dict:\n", " \"\"\"\n", " Compute the Ornstein-Uhlenbeck alpha signal at a single point in time.\n", "\n", " Filters applied before returning:\n", " - Returns alpha=0 if fewer than hl_window+20 bars available (insufficient history)\n", " - Returns alpha=0 if half-life is infinite or outside [MIN_HALF_LIFE, MAX_HALF_LIFE]\n", " (spread not mean-reverting, or reverting too slowly/quickly to trade)\n", " - Returns alpha=0 if sigma < 1e-8 (degenerate/constant spread)\n", " - Winsorises raw alpha to [-ALPHA_CLIP, +ALPHA_CLIP] (default ±3.0)\n", " - Applies deadband: alpha set to 0 if |alpha| < ALPHA_DEADBAND (default 0.10)\n", " to avoid entering on negligible displacements\n", "\n", " Parameters\n", " ----------\n", " spread_history : pd.Series\n", " Full history of the spread up to and including the current bar.\n", " No future data — caller must slice to current index.\n", " hl_window : int\n", " Lookback (in bars) for half-life and kappa estimation.\n", " Defaults to HL_WINDOW (120). Calibrated per basket in OOS deployment.\n", "\n", " Returns\n", " -------\n", " dict with keys:\n", " alpha : float — processed signal value, 0.0 if any filter triggered\n", " half_life : float — estimated half-life in days, np.nan if estimation failed\n", " kappa : float — mean-reversion speed (negative = reverting), np.nan if failed\n", " \"\"\"\n", " s = spread_history.dropna()\n", " if len(s) < hl_window + 20:\n", " return {\"alpha\": 0.0, \"half_life\": np.nan, \"kappa\": np.nan}\n", " \n", " hl_est = estimate_half_life(s.iloc[-hl_window:])\n", " hl = hl_est[\"half_life\"]\n", " kappa = hl_est[\"kappa\"]\n", " \n", " if hl == np.inf or not (MIN_HALF_LIFE <= hl <= MAX_HALF_LIFE):\n", " return {\"alpha\": 0.0, \"half_life\": hl, \"kappa\": kappa}\n", " \n", " bb_window = max(20, int(3 * hl))\n", " s_window = s.iloc[-bb_window:]\n", " mu = s_window.mean()\n", " sigma = s_window.std()\n", " \n", " if sigma < 1e-8:\n", " return {\"alpha\": 0.0, \"half_life\": hl, \"kappa\": kappa}\n", " \n", " raw_alpha = abs(kappa) * (mu - s.iloc[-1]) / sigma\n", " alpha = float(np.clip(raw_alpha, -ALPHA_CLIP, ALPHA_CLIP))\n", " if abs(alpha) < ALPHA_DEADBAND:\n", " alpha = 0.0\n", " \n", " return {\"alpha\": alpha, \"half_life\": hl, \"kappa\": kappa}" ] }, { "cell_type": "code", "execution_count": 17, "id": "bbb2f9ed", "metadata": {}, "outputs": [], "source": [ "def compute_ou_alpha_series(\n", " spread : pd.Series,\n", " hl_window : int = HL_WINDOW,\n", ") -> pd.Series:\n", " \"\"\"\n", " Compute the OU alpha signal for every bar in the spread history,\n", " strictly without look-ahead.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series\n", " Full spread series (train + test concatenated). \n", " hl_window : int\n", " Lookback for half-life and kappa estimation at each bar.\n", " Defaults to HL_WINDOW (120).\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Alpha values aligned to spread.index. Zero where:\n", " - bar is within the warmup period\n", " - any filter inside compute_ou_alpha_point triggered (HL out of\n", " range, sigma degenerate, deadband, winsorisation to zero)\n", " - spread had NaN at that bar\n", " \"\"\"\n", " s = spread.dropna()\n", " alphas = pd.Series(0.0, index=s.index)\n", " for i in range(hl_window + 20, len(s)):\n", " result = compute_ou_alpha_point(s.iloc[: i + 1], hl_window)\n", " alphas.iloc[i] = result[\"alpha\"]\n", " return alphas.reindex(spread.index).fillna(0.0)" ] }, { "cell_type": "markdown", "id": "6093f742", "metadata": {}, "source": [ "#### 5.2 Two-Tier Regime Filter" ] }, { "cell_type": "code", "execution_count": 18, "id": "ee7ef6f8", "metadata": {}, "outputs": [], "source": [ "def compute_regime_score_point(\n", " spread_history: pd.Series,\n", " hl : float,\n", " window : int = REGIME_WINDOW,\n", ") -> float:\n", " \"\"\"\n", " Compute the two-tier regime quality score for a spread at a single bar.\n", "\n", " Tier 1 — Hard kill (returns 0.0 immediately, no soft scoring attempted):\n", " vol_ratio > HARD_VOL_RATIO (default 4.0):\n", " adf_pval > HARD_ADF_PVAL (default 0.40):\n", "\n", " Tier 2 — Soft score (continuous, returned if both hard kills cleared):\n", " Three sub-scores, each clipped to [0, 1], combined as a weighted sum:\n", "\n", " kappa_score (weight REGIME_W_KAPPA, default 0.35):\n", " Measures local mean-reversion speed relative to the slowest\n", " acceptable rate\n", "\n", " adf_score (weight REGIME_W_ADF, default 0.35):\n", " Stationarity quality.\n", "\n", " vol_score (weight REGIME_W_VOL, default 0.30):\n", " Volatility sanity. score = clip(1 - vol_ratio / 2.5, 0, 1).\n", "\n", " Adaptive BB window:\n", " The local kappa estimation window is set to max(20, 3 × hl) when a\n", " valid half-life is available, otherwise falls back to window.\n", "\n", " Parameters\n", " ----------\n", " spread_history : pd.Series\n", " Spread history up to and including the current bar. No future data.\n", " hl : float\n", " Current half-life estimate passed in from compute_regime_score_series.\n", " Used to set the adaptive BB window. If np.nan or np.inf, falls back\n", " to window for the BB window size.\n", " window : int\n", " Base lookback for vol ratio computation (short-term window).\n", " Long-term window is 3 × this value. Defaults to REGIME_WINDOW (30).\n", "\n", " Returns\n", " -------\n", " float\n", " Score in [0, 1]. 0.0 if hard kill triggered or insufficient history\n", " (min bars = max(window * 4, 30)). Higher scores indicate a healthier\n", " mean-reversion regime.\n", " \"\"\"\n", " s = spread_history.dropna()\n", " \n", " min_len = max(window * 4, 30)\n", " if len(s) < min_len:\n", " return 0.0\n", " \n", " s_dif = s.diff()\n", " \n", " # ── adaptive BB window ─────────────────────────────────\n", " bb_w = (max(20, int(3 * hl))\n", " if (hl not in (np.nan, np.inf) and hl > 0)\n", " else window)\n", " \n", " # ── Condition A: volatility ratio ─────────────────────\n", " sv = s_dif.iloc[-window:].std()\n", " lv = s_dif.iloc[-(window * 3):].std()\n", " vol_ratio = sv / lv if lv > 1e-8 else np.inf\n", " \n", " # Tier 1 hard kill: vol spike\n", " if vol_ratio > HARD_VOL_RATIO:\n", " return 0.0\n", " \n", " # ── Condition B: rolling ADF p-value ──────────────────\n", " adf_window = min(len(s), 90)\n", " try:\n", " adf_pval = adfuller(\n", " s.iloc[-adf_window:], maxlag=1, autolag=None\n", " )[1]\n", " except Exception:\n", " adf_pval = 1.0 \n", " \n", " if adf_pval > HARD_ADF_PVAL:\n", " return 0.0\n", " \n", " # ── Condition C: local kappa (mean reversion alive) ───\n", " s_kw = s.iloc[-bb_w:]\n", " sl_k = s_kw.shift(1).dropna()\n", " sd_k = s_kw.diff().dropna()\n", " cm_k = sl_k.index.intersection(sd_k.index)\n", " \n", " kappa_local = np.nan\n", " if len(cm_k) >= 20:\n", " try:\n", " mk = sm.OLS(sd_k.loc[cm_k],\n", " sm.add_constant(sl_k.loc[cm_k]))\n", " kappa_local = mk.fit().params.iloc[1]\n", " except Exception:\n", " pass\n", " \n", " # ── Tier 2: Soft score ────────────────────────────────\n", " # kappa score: normalise by slowest acceptable speed\n", " kappa_ref = np.log(2) / MAX_HALF_LIFE \n", " kappa_score = 0.0\n", " if not np.isnan(kappa_local) and kappa_local < 0:\n", " kappa_score = float(np.clip(abs(kappa_local) / kappa_ref, 0.0, 1.0))\n", " \n", " # adf score: low p-value → high score\n", " adf_score = float(np.clip(1.0 - adf_pval / 0.15, 0.0, 1.0))\n", " \n", " # vol score: low vol_ratio → high score (cap ratio at 2.5)\n", " vol_score = float(np.clip(1.0 - vol_ratio / 2.5, 0.0, 1.0))\n", " \n", " score = (\n", " REGIME_W_KAPPA * kappa_score\n", " + REGIME_W_ADF * adf_score\n", " + REGIME_W_VOL * vol_score\n", " )\n", " return float(np.clip(score, 0.0, 1.0))" ] }, { "cell_type": "code", "execution_count": 19, "id": "6abf961b", "metadata": {}, "outputs": [], "source": [ "def compute_regime_score_series(\n", " spread : pd.Series,\n", " hl_window : int = HL_WINDOW,\n", " window : int = REGIME_WINDOW,\n", ") -> pd.Series:\n", " \"\"\"\n", " Compute the two-tier regime score for every bar in the spread history,\n", " strictly without look-ahead.\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series\n", " Full spread series (train + test concatenated). NaNs are dropped\n", " internally; output is reindexed to the original index with 0.0 fill.\n", " hl_window : int\n", " Lookback for half-life re-estimation at each bar. Defaults to\n", " HL_WINDOW (120). Should match the hl_window used in\n", " compute_ou_alpha_series for the same basket — mismatching them\n", " means alpha and regime score are estimated on different timescales.\n", " window : int\n", " Base lookback for vol ratio and ADF inside compute_regime_score_point.\n", " Defaults to REGIME_WINDOW (30).\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Regime scores in [0, 1] aligned to spread.index. Zero where:\n", " - bar is within the warmup period\n", " - hard kill triggered (vol spike or ADF breakdown)\n", " - spread had NaN at that bar\n", " Fractional values between 0 and 1 reflect partial regime health —\n", " used to attenuate rather than fully kill the OU alpha signal.\n", " \"\"\"\n", " s = spread.dropna()\n", " scores = pd.Series(0.0, index=s.index)\n", " warmup = max(window * 4, hl_window + 20, 60)\n", " \n", " for i in range(warmup, len(s)):\n", " hl_est = estimate_half_life(s.iloc[max(0, i - hl_window) : i])\n", " hl = hl_est[\"half_life\"]\n", " if hl == np.inf:\n", " hl = MAX_HALF_LIFE\n", " \n", " scores.iloc[i] = compute_regime_score_point(\n", " s.iloc[: i + 1], hl=hl, window=window\n", " )\n", " return scores.reindex(spread.index).fillna(0.0)" ] }, { "cell_type": "code", "execution_count": 20, "id": "72244445", "metadata": {}, "outputs": [], "source": [ "def compute_regimed_alpha_series(\n", " spread : pd.Series,\n", " hl_window : int = HL_WINDOW,\n", " ewm_span : int = 5,\n", ") -> pd.Series:\n", "\n", " \"\"\"\n", " Produce the final tradeable alpha series by combining the raw OU signal\n", " with regime filtering, smoothing, and an adaptive noise floor. This is the top-level signal function — \n", " its output feeds directly into the position sizing logic. \n", " \n", " Three sequential transformations are applied:\n", "\n", " Step 1 — Regime gating:\n", " raw_alpha (from compute_ou_alpha_series) is multiplied bar-by-bar\n", " by the regime score (from compute_regime_score_series).\n", "\n", " Step 2 — EWM smoothing:\n", " The gated alpha is smoothed with an exponentially weighted mean\n", " (span=5, min_periods=5). \n", "\n", " Step 3 — Adaptive noise floor:\n", " Bars where smoothed |alpha| falls below the 25th percentile of the\n", " last 100 bars of |alpha| are zeroed out.\n", "\n", "\n", " Parameters\n", " ----------\n", " spread : pd.Series\n", " Full spread series (train + test concatenated). Same series passed\n", " to both compute_ou_alpha_series and compute_regime_score_series\n", " internally — no slicing needed by the caller.\n", " hl_window : int\n", " Lookback for OU alpha and regime score estimation. Passed through\n", " to both sub-functions. Should be the per-basket calibrated value\n", " (basket_hl_windows[key]) in OOS deployment, not the global default.\n", " ewm_span : int\n", " Span for the exponential weighted smoothing of the gated alpha.\n", " Default 5. Larger values produce smoother but more lagged signals.\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Final alpha series aligned to spread.index, in approximately the\n", " same scale as raw OU alpha but attenuated by regime quality and\n", " smoothed.\n", " \"\"\"\n", "\n", " raw_alpha = compute_ou_alpha_series(spread, hl_window=hl_window)\n", "\n", " regime_score = compute_regime_score_series(\n", " spread, hl_window=hl_window, window=REGIME_WINDOW\n", " )\n", " gated = raw_alpha * regime_score.reindex(raw_alpha.index).fillna(0.0)\n", " smoothed = (\n", " gated\n", " .ewm(span=ewm_span, min_periods=ewm_span)\n", " .mean()\n", " .fillna(0.0)\n", " )\n", "\n", " abs_vals = smoothed.abs()\n", " adaptive_floor = abs_vals.rolling(100, min_periods=20).quantile(0.25)\n", " fallback = abs_vals.expanding().mean() * 0.5\n", " adaptive_floor = adaptive_floor.fillna(fallback)\n", "\n", " smoothed = smoothed.copy()\n", " smoothed[abs_vals < adaptive_floor] = 0.0\n", "\n", " return smoothed" ] }, { "cell_type": "markdown", "id": "4e006b8d", "metadata": {}, "source": [ "### 6. Portfolio Construction" ] }, { "cell_type": "markdown", "id": "4ae71f2d", "metadata": {}, "source": [ "#### 6.1 Hysteresis Position Sizing" ] }, { "cell_type": "code", "execution_count": 21, "id": "d14cda34", "metadata": {}, "outputs": [], "source": [ "def compute_hysteresis_positions(\n", " alpha_series : pd.Series,\n", " entry_thresh : float = ENTRY_THRESHOLD,\n", " exit_thresh : float = EXIT_THRESHOLD,\n", " max_pos : float = MAX_POS_SIZE,\n", " min_pos : float = MIN_POS_SIZE,\n", ") -> pd.Series:\n", " \"\"\"\n", " Convert a smoothed OU alpha series into positions via a hysteresis\n", " state machine — bar by bar, no look-ahead.\n", "\n", " Used in the walk-forward loop for basket scoring. OOS deployment uses\n", " compute_bucketed_positions instead — same logic, discrete sizing.\n", "\n", " State machine:\n", " Flat → in-position : |alpha| > entry_thresh, size ∝ |alpha|\n", " In-position → flat : |alpha| < exit_thresh (signal decayed)\n", " : alpha flips sign (spread crossed mean)\n", " In-position → resize: neither exit triggered, size tracks |alpha|\n", "\n", " The entry/exit gap (hysteresis band) prevents rapid entries and exits\n", " when alpha oscillates near a single threshold, reducing turnover.\n", " Proportional sizing naturally scales down as spread approaches mean.\n", "\n", " Parameters\n", " ----------\n", " alpha_series : pd.Series\n", " Output of compute_regimed_alpha_series. NaNs filled with 0.0.\n", " entry_thresh : float\n", " Minimum |alpha| to open a position. Default ENTRY_THRESHOLD (0.08).\n", " exit_thresh : float\n", " |alpha| below which position is closed. Default EXIT_THRESHOLD (0.02).\n", " max_pos : float\n", " Maximum position size, fraction of risk budget. Default 1.0.\n", " min_pos : float\n", " Minimum non-zero position size. Default 0.10.\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Positions in [-max_pos, max_pos] aligned to alpha_series.index.\n", " Zero = flat. Positive = long spread. Negative = short spread.\n", " \"\"\"\n", " s = alpha_series.fillna(0.0)\n", " pos = pd.Series(0.0, index=s.index)\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " for i in range(len(s)):\n", " alpha = s.iloc[i]\n", " \n", " if cur_pos == 0.0:\n", " # ── Flat: look for entry ──────────────────────\n", " if abs(alpha) > entry_thresh:\n", " size = float(np.clip(\n", " abs(alpha) / entry_thresh,\n", " min_pos, max_pos\n", " ))\n", " cur_sign = int(np.sign(alpha))\n", " cur_pos = size * cur_sign\n", " else:\n", " # ── In position: check exit conditions ────────\n", " alpha_sign = int(np.sign(alpha)) if abs(alpha) > 1e-8 else 0\n", " \n", " # Exit 1: alpha dropped below exit threshold\n", " if abs(alpha) < exit_thresh:\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " # Exit 2: alpha flipped sign (spread crossed mean)\n", " elif alpha_sign != 0 and alpha_sign != cur_sign:\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " # Hold: update size proportionally\n", " else:\n", " size = float(np.clip(\n", " abs(alpha) / entry_thresh,\n", " min_pos, max_pos\n", " ))\n", " cur_pos = size * cur_sign\n", " \n", " pos.iloc[i] = cur_pos\n", " \n", " return pos\n" ] }, { "cell_type": "markdown", "id": "4897b3ac", "metadata": {}, "source": [ "#### 6.2 Risk Parity Weighting" ] }, { "cell_type": "code", "execution_count": 22, "id": "536c4f1d", "metadata": {}, "outputs": [], "source": [ "def compute_risk_parity_weights(\n", " positions : dict, \n", " basket_ret_all : dict, \n", " t : pd.Timestamp,\n", " vol_window : int = VOL_WINDOW,\n", " vol_floor : float = VOL_FLOOR,\n", ") -> pd.Series:\n", " \"\"\"\n", " Compute risk parity weights across active baskets at time t.\n", "\n", " weight_i = (1 / vol_i) / sum(1 / vol_j)\n", "\n", " Only baskets with non-zero position at t are included. Vol is estimated\n", " from the trailing vol_window bars of raw basket returns up to and\n", " including t — no look-ahead.\n", "\n", " Preferred over Σ⁻¹α: no covariance matrix needed, weights are stable,\n", " and allocation naturally shrinks when a basket is volatile — which\n", " typically coincides with regime degradation.\n", "\n", " Parameters\n", " ----------\n", " positions : dict\n", " {key: pd.Series} of positions from compute_hysteresis_positions\n", " or compute_bucketed_positions.\n", " basket_ret_all : dict\n", " {key: pd.Series} of raw daily basket returns (pct_change weighted\n", " by cointegrating vector). Used for vol estimation.\n", " t : pd.Timestamp\n", " Current bar. Weights are computed as of this timestamp.\n", " vol_window : int\n", " Trailing bars for vol estimation. Default VOL_WINDOW (30).\n", " vol_floor : float\n", " Minimum vol to avoid division by zero. Default VOL_FLOOR (1e-6).\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Weights indexed by basket key, summing to 1.0 across active baskets.\n", " Empty Series if no baskets are active at t.\n", " \"\"\"\n", " active_keys = [\n", " k for k, pos in positions.items()\n", " if abs(pos.get(t, 0.0)) > 1e-6\n", " ]\n", " if not active_keys:\n", " return pd.Series(dtype=float)\n", " \n", " vols = {}\n", " for key in active_keys:\n", " ret_hist = basket_ret_all[key].loc[:t].iloc[-vol_window:]\n", " vol = ret_hist.std()\n", " vols[key] = max(vol, vol_floor)\n", " \n", " inv_vol = pd.Series({k: 1.0 / v for k, v in vols.items()})\n", " weights = inv_vol / inv_vol.sum()\n", " return weights" ] }, { "cell_type": "markdown", "id": "765b622e", "metadata": {}, "source": [ "#### 6.3 Portfolio PnL" ] }, { "cell_type": "code", "execution_count": 23, "id": "33c33e08", "metadata": {}, "outputs": [], "source": [ "def run_hysteresis_portfolio(\n", " alpha_series : dict, \n", " basket_ret_all : dict, \n", " test_idx : pd.Index,\n", " entry_thresh : float = ENTRY_THRESHOLD,\n", " exit_thresh : float = EXIT_THRESHOLD,\n", " vol_window : int = VOL_WINDOW,\n", ") -> tuple:\n", " \"\"\"\n", " Build the full portfolio for a single WF fold using hysteresis\n", " positions and risk parity weights, evaluated over test_idx.\n", "\n", " Two-step construction:\n", " 1. Hysteresis positions computed on the full alpha series\n", " (train+test) for each basket — no look-ahead because\n", " alpha_series itself was built rolling.\n", " 2. Day-by-day loop over test_idx: risk parity weights computed\n", " at t, PnL = prev position × prev weight × today's return.\n", "\n", " Used in the walk-forward loop only. OOS deployment uses\n", " run_bucketed_filtered_oos with bucketed positions instead.\n", "\n", " Parameters\n", " ----------\n", " alpha_series : dict\n", " {key: pd.Series} — regimed alpha from compute_regimed_alpha_series,\n", " covering train+test period for each basket.\n", " basket_ret_all : dict\n", " {key: pd.Series} — raw daily basket returns (pct_change weighted\n", " by cointegrating vector), covering train+test period.\n", " test_idx : pd.Index\n", " Dates over which PnL is computed. Positions and weights outside\n", " this window are ignored in the output.\n", " entry_thresh : float\n", " Passed through to compute_hysteresis_positions. Default 0.08.\n", " exit_thresh : float\n", " Passed through to compute_hysteresis_positions. Default 0.02.\n", " vol_window : int\n", " Passed through to compute_risk_parity_weights. Default VOL_WINDOW.\n", "\n", " Returns\n", " -------\n", " port_pnl : pd.Series\n", " Daily portfolio PnL over test_idx.\n", " pos_df : pd.DataFrame\n", " Positions per basket over test_idx, columns = basket keys.\n", " weights_df : pd.DataFrame\n", " Risk parity weights per basket over test_idx, columns = basket keys.\n", " positions : dict\n", " Raw {key: pd.Series} position dict reused for per-basket PnL\n", " decomposition in the walk-forward loop without recomputation.\n", " \"\"\"\n", " keys = list(alpha_series.keys())\n", " \n", " # ── 1. Hysteresis positions per basket ───────────────────\n", " positions = {}\n", " for key in keys:\n", " positions[key] = compute_hysteresis_positions(\n", " alpha_series[key],\n", " entry_thresh = entry_thresh,\n", " exit_thresh = exit_thresh,\n", " )\n", " \n", " # ── 2. Day-by-day risk parity weights + PnL ──────────────\n", " weights_list = []\n", " pnl_list = []\n", " \n", " for t_pos, t in enumerate(test_idx):\n", " w_t = compute_risk_parity_weights(\n", " positions = positions,\n", " basket_ret_all = basket_ret_all,\n", " t = t,\n", " vol_window = vol_window,\n", " )\n", " \n", " weights_list.append(w_t if len(w_t) > 0 else pd.Series(dtype=float))\n", " \n", " if t_pos > 0:\n", " prev_t = test_idx[t_pos - 1]\n", " daily_pnl = 0.0\n", " for key in keys:\n", " pos_prev = positions[key].get(prev_t, 0.0)\n", " w_prev = (weights_list[t_pos - 1].get(key, 0.0)\n", " if len(weights_list[t_pos - 1]) > 0 else 0.0)\n", " ret_t = basket_ret_all[key].get(t, 0.0)\n", " daily_pnl += pos_prev * w_prev * ret_t\n", " pnl_list.append(daily_pnl)\n", " else:\n", " pnl_list.append(0.0)\n", " \n", " port_pnl = pd.Series(pnl_list, index=test_idx)\n", " pos_df = pd.DataFrame(\n", " {k: positions[k].reindex(test_idx).fillna(0.0) for k in keys}\n", " )\n", " weights_df = pd.DataFrame(\n", " weights_list, index=test_idx\n", " ).reindex(columns=keys).fillna(0.0)\n", " \n", " return port_pnl, pos_df, weights_df, positions" ] }, { "cell_type": "markdown", "id": "1b5eb239", "metadata": {}, "source": [ "### 7. Walk-Forward Backtest" ] }, { "cell_type": "markdown", "id": "323a7de5", "metadata": {}, "source": [ "#### 7.1 Parameter Calibration" ] }, { "cell_type": "code", "execution_count": 24, "id": "d31937ae", "metadata": {}, "outputs": [], "source": [ "def calibrate_params_on_train(\n", " train_prices: pd.DataFrame,\n", " symbols : list,\n", " min_size : int = 2,\n", " max_size : int = 4,\n", ") -> dict:\n", "\n", " \"\"\"\n", " Calibrate hl_window from the training data for a single WF fold.\n", "\n", " Discovers cointegrated baskets on train_prices, filters to stationary\n", " spreads with valid half-lives, and sets hl_window = clip(10 × median_hl,\n", " 40, 180). This ensures the OU estimation lookback is proportional to the\n", " typical reversion speed of baskets in that fold's market regime.\n", "\n", " Falls back to HL_WINDOW (120) if cointegration fails, no stationary\n", " spreads are found, or fewer than 3 valid half-lives are estimated.\n", "\n", " Parameters\n", " ----------\n", " train_prices : pd.DataFrame\n", " Price data for the current fold's training window.\n", " symbols : list\n", " Asset symbols to search for cointegrated baskets.\n", " min_size : int\n", " Minimum basket size for cointegration search. Default 2.\n", " max_size : int\n", " Maximum basket size for cointegration search. Default 4.\n", "\n", " Returns\n", " -------\n", " dict with keys:\n", " hl_window : int — calibrated lookback, clipped to [40, 180]\n", " median_hl : float — median half-life across valid baskets, None if fallback\n", " n_hls : int — number of valid half-lives used, absent if fallback\n", " calibrated : bool — False if fallback was used\n", " \"\"\"\n", " \n", " if max_size <= 2:\n", " sampled = symbols\n", " else:\n", " sampled = symbols\n", " fallback = {\"hl_window\": HL_WINDOW, \"median_hl\": None,\n", " \"calibrated\": False}\n", " try:\n", " baskets = find_cointegrated_baskets(\n", " train_prices, sampled,\n", " min_size=min_size, max_size=max_size\n", " )\n", " except Exception:\n", " return fallback\n", " \n", " stationary = [\n", " b for b in baskets\n", " if check_spread_stationarity(b[\"spread\"])[\"is_stationary\"]\n", " ]\n", " if not stationary:\n", " return fallback\n", " \n", " hls = []\n", " for b in stationary:\n", " hl = estimate_half_life(b[\"spread\"])[\"half_life\"]\n", " if hl != np.inf and MIN_HALF_LIFE <= hl <= MAX_HALF_LIFE:\n", " hls.append(hl)\n", " if len(hls) < 3:\n", " return fallback\n", " \n", " median_hl = np.median(hls)\n", " return {\n", " \"hl_window\" : int(np.clip(10 * median_hl, 40, 180)),\n", " \"median_hl\" : round(median_hl, 2),\n", " \"n_hls\" : len(hls),\n", " \"calibrated\": True,\n", " }" ] }, { "cell_type": "markdown", "id": "11ab2abd", "metadata": {}, "source": [ "#### 7.2 Walk-Forward Loop" ] }, { "cell_type": "code", "execution_count": 25, "id": "70dd32dd", "metadata": {}, "outputs": [], "source": [ "def walk_forward_ou_mv_hyst(\n", " prices : pd.DataFrame,\n", " train_size : int = 365,\n", " test_size : int = 60,\n", " min_basket_size : int = 2,\n", " max_basket_size : int = 3,\n", " calibrate : bool = True,\n", " debug_filters : bool = True,\n", " entry_thresh : float = ENTRY_THRESHOLD,\n", " exit_thresh : float = EXIT_THRESHOLD,\n", " vol_window : int = VOL_WINDOW,\n", "):\n", "\n", " \"\"\"\n", " Run the full walk-forward backtest over prices_train.\n", "\n", " Advances a sliding window across the dataset, running one fold per step.\n", " Each fold: calibrates hl_window → discovers baskets → filters spreads →\n", " builds dynamic spreads → computes regimed alpha → runs hysteresis portfolio\n", " → decomposes per-basket PnL for downstream scoring.\n", "\n", " Fold structure:\n", " Train window : train_size bars (default 365d)\n", " Test window : test_size bars (default 60d)\n", " Step : test_size bars (non-overlapping test periods)\n", " Total folds : floor((n - train_size) / test_size)\n", "\n", " Filter pipeline per fold (static spread, in order):\n", " Johansen cointegration (rank=1) → ADF stationarity → variance ratio\n", " < 0.9 → half-life in [MIN, MAX] + stability CV < 0.5 →\n", " predictability (β<0, |t|>1.5)\n", " Survivors get their spread replaced with a dynamic rolling-weight\n", " version (180d window, 30d step) before signal computation.\n", "\n", " Per-basket PnL decomposition:\n", " portfolio PnL is decomposed into individual basket contributions\n", " using positions and weights from run_hysteresis_portfolio. Stored\n", " in fold_records[\"basket_pnl\"] so score_baskets_across_folds can\n", " rank baskets by their own performance, not the portfolio aggregate.\n", "\n", " Parameters\n", " ----------\n", " prices : pd.DataFrame\n", " Training price data (prices_train). Test set is held out entirely.\n", " train_size : int\n", " Bars per training window. Default 365.\n", " test_size : int\n", " Bars per test window and step size. Default 60.\n", " min_basket_size : int\n", " Minimum assets per basket in cointegration search. Default 2.\n", " max_basket_size : int\n", " Maximum assets per basket in cointegration search. Default 3.\n", " calibrate : bool\n", " If True, calibrates hl_window per fold via calibrate_params_on_train.\n", " If False, uses global HL_WINDOW for all folds. Default True.\n", " debug_filters : bool\n", " If True, prints basket counts after each filter stage. Default True.\n", " entry_thresh : float\n", " Passed to run_hysteresis_portfolio. Default ENTRY_THRESHOLD (0.08).\n", " exit_thresh : float\n", " Passed to run_hysteresis_portfolio. Default EXIT_THRESHOLD (0.02).\n", " vol_window : int\n", " Passed to run_hysteresis_portfolio. Default VOL_WINDOW (30).\n", "\n", " Returns\n", " -------\n", " fold_records : list of dict\n", " One record per fold containing: fold index, train/test dates,\n", " n_baskets, fold_hl_window, median_hl, basket_lookup, basket_pnl.\n", " Consumed by score_baskets_across_folds and run_bucketed_filtered_oos.\n", " wf_pnl : pd.Series\n", " Concatenated daily portfolio PnL across all fold test periods.\n", " Empty Series if no folds produced trades.\n", " \"\"\"\n", " n = len(prices)\n", " symbols = list(prices.columns)\n", " fold_records = []\n", " all_pnls = []\n", " start = 0\n", " fold_idx = 0\n", "\n", " while start + train_size + test_size <= n:\n", " fold_idx += 1\n", " train_prices = prices.iloc[start : start + train_size]\n", " test_prices = prices.iloc[start + train_size : start + train_size + test_size]\n", "\n", " print(\n", " f\"\\nFold {fold_idx} | \"\n", " f\"train {train_prices.index[0].date()} → \"\n", " f\"{train_prices.index[-1].date()} | \"\n", " f\"test {test_prices.index[0].date()} → \"\n", " f\"{test_prices.index[-1].date()}\"\n", " )\n", "\n", " # ── 0. Calibrate ─────────────────────────────────────────────────────\n", " if calibrate:\n", " fold_params = calibrate_params_on_train(\n", " train_prices, symbols,\n", " min_size=min_basket_size, max_size=max_basket_size,\n", " )\n", " fold_hl_window = fold_params[\"hl_window\"]\n", " tag = (\n", " f\"median_HL={fold_params['median_hl']}d → \"\n", " f\"hl_window={fold_hl_window}\"\n", " if fold_params[\"calibrated\"]\n", " else f\"fallback → hl_window={fold_hl_window}\"\n", " )\n", " print(f\" Calibrated | {tag}\")\n", " else:\n", " fold_hl_window = HL_WINDOW\n", " fold_params = {\"median_hl\": None, \"calibrated\": False}\n", "\n", " # ── 1. Discover ───────────────────────────────────────────────────────\n", " baskets = find_cointegrated_baskets(\n", " train_prices, symbols,\n", " min_size=min_basket_size, max_size=max_basket_size,\n", " )\n", " if debug_filters:\n", " print(f\" after cointegration : {len(baskets)}\")\n", " if not baskets:\n", " start += test_size\n", " continue\n", "\n", " # ── 2. Filter pipeline ────────────────────────────────────────────────\n", " baskets = [\n", " b for b in baskets\n", " if check_spread_stationarity(b[\"spread\"])[\"is_stationary\"]\n", " ]\n", " if debug_filters:\n", " print(f\" after ADF : {len(baskets)}\")\n", " if not baskets:\n", " start += test_size\n", " continue\n", "\n", " passed = []\n", " for b in baskets:\n", " vr = variance_ratio(b[\"spread\"])\n", " if vr < 0.9:\n", " b[\"variance_ratio\"] = vr\n", " passed.append(b)\n", " baskets = passed\n", " if debug_filters:\n", " print(f\" after VR : {len(baskets)}\")\n", " if not baskets:\n", " start += test_size\n", " continue\n", "\n", " passed = []\n", " for b in baskets:\n", " hl = estimate_half_life(b[\"spread\"])\n", " if hl[\"half_life\"] == np.inf:\n", " continue\n", " if not (MIN_HALF_LIFE <= hl[\"half_life\"] <= MAX_HALF_LIFE):\n", " continue\n", " stab = halflife_stability(b[\"spread\"])\n", " if stab > 0.5:\n", " continue\n", " b.update({\n", " \"half_life\" : hl[\"half_life\"],\n", " \"kappa\" : hl[\"kappa\"],\n", " \"r_squared\" : hl[\"r_squared\"],\n", " \"hl_stability\": stab,\n", " })\n", " passed.append(b)\n", " baskets = passed\n", " if debug_filters:\n", " print(f\" after HL+stability : {len(baskets)}\")\n", " if not baskets:\n", " start += test_size\n", " continue\n", "\n", " passed = []\n", " for b in baskets:\n", " pred = spread_predictability(b[\"spread\"], b[\"half_life\"])\n", " if pred[\"beta\"] < 0 and abs(pred[\"tstat\"]) > 1.5:\n", " b[\"pred_tstat\"] = pred[\"tstat\"]\n", " passed.append(b)\n", " baskets = passed\n", " if debug_filters:\n", " print(f\" after predictability : {len(baskets)}\")\n", " if not baskets:\n", " start += test_size\n", " continue\n", "\n", " # ── 3. Dynamic spreads ────────────────────────────────────────────────\n", " for b in tqdm(baskets, desc=f\" Fold {fold_idx} dyn-spreads\", leave=False):\n", " spread_dyn, w_latest = build_dynamic_spread(\n", " train_prices, b[\"assets\"], window=180, step=30\n", " )\n", " if spread_dyn is not None:\n", " b[\"spread\"] = spread_dyn\n", " b[\"weights_latest\"] = w_latest\n", "\n", " print(\n", " f\" {len(baskets)} baskets survived → \"\n", " f\"computing OU alpha + regime scores...\"\n", " )\n", "\n", " # ── 4. OU alpha series ────────────────────────────────────────────────\n", " combined_prices = pd.concat([train_prices, test_prices])\n", " basket_lookup = {\"_\".join(b[\"assets\"]): b for b in baskets}\n", " alpha_series = {}\n", " basket_ret_all = {}\n", "\n", " for key, b in basket_lookup.items():\n", " beta = b.get(\"weights_latest\", b[\"weights\"])\n", " assets = list(b[\"assets\"])\n", "\n", " lp_all = np.log(combined_prices[assets]).dropna()\n", " spread_all = pd.Series(lp_all.values @ beta, index=lp_all.index)\n", "\n", " alpha_series[key] = compute_regimed_alpha_series(\n", " spread_all, hl_window=fold_hl_window\n", " )\n", " ret_all = combined_prices[assets].pct_change()\n", " basket_ret_all[key] = (ret_all * beta).sum(axis=1)\n", "\n", " # ── Hysteresis + Risk Parity ──────────────────────────────────\n", " test_idx = test_prices.index\n", "\n", " port_pnl, pos_df, weights_df, positions = run_hysteresis_portfolio(\n", " alpha_series = alpha_series,\n", " basket_ret_all = basket_ret_all,\n", " test_idx = test_idx,\n", " entry_thresh = entry_thresh,\n", " exit_thresh = exit_thresh,\n", " vol_window = vol_window,\n", " )\n", "\n", " # ── Turnover diagnostic ───────────────────────────────────────────────\n", " keys = list(basket_lookup.keys())\n", " n_entries = (\n", " (pos_df != 0) & (pos_df.shift(1).fillna(0) == 0)\n", " ).sum().sum()\n", " ann_trades = n_entries * (365 / len(test_idx))\n", " print(f\" entries this fold: {n_entries} (~{ann_trades:.0f} annualised)\")\n", "\n", " # ── CHANGE: compute per-basket PnL for scoring ────────────────────────\n", " #\n", " # Previously, score_baskets_across_folds assigned the entire portfolio\n", " # Sharpe to every basket in a fold — meaning a basket that hurt returns\n", " # looked identical to one that drove them.\n", " #\n", " # Now we decompose port_pnl into individual basket contributions so\n", " # scoring reflects each basket's actual performance.\n", "\n", " basket_pnl_fold = {}\n", " for key in keys:\n", " pnl_k = []\n", " for t_pos, t in enumerate(test_idx):\n", " if t_pos == 0:\n", " pnl_k.append(0.0)\n", " continue\n", " prev_t = test_idx[t_pos - 1]\n", " pos_prev = positions[key].get(prev_t, 0.0)\n", " w_prev = (\n", " weights_df.loc[prev_t, key]\n", " if (prev_t in weights_df.index and key in weights_df.columns)\n", " else 0.0\n", " )\n", " ret_t = basket_ret_all[key].get(t, 0.0)\n", " pnl_k.append(pos_prev * w_prev * ret_t)\n", " basket_pnl_fold[key] = pd.Series(pnl_k, index=test_idx)\n", " \n", "\n", " all_pnls.append(port_pnl)\n", " fold_records.append({\n", " \"fold\" : fold_idx,\n", " \"train_end\" : train_prices.index[-1],\n", " \"test_start\" : test_prices.index[0],\n", " \"test_end\" : test_prices.index[-1],\n", " \"n_baskets\" : len(baskets),\n", " \"fold_hl_window\" : fold_hl_window,\n", " \"median_hl\" : fold_params.get(\"median_hl\"),\n", " \"selected_baskets\": baskets,\n", " \"basket_lookup\" : basket_lookup,\n", " \"basket_pnl\" : basket_pnl_fold,\n", " })\n", "\n", " start += test_size\n", "\n", " # ── Final summary ─────────────────────────────────────────────────────────\n", " if not all_pnls:\n", " print(\"No folds produced any PnL.\")\n", " return fold_records, pd.Series(dtype=float)\n", "\n", " wf_pnl = pd.concat(all_pnls)\n", " ann = np.sqrt(365)\n", " sharpe = (wf_pnl.mean() / wf_pnl.std() * ann\n", " if wf_pnl.std() > 1e-8 else np.nan)\n", " cum = (1 + wf_pnl).cumprod()\n", " max_dd = (cum / cum.cummax() - 1).min()\n", " win_r = (wf_pnl[wf_pnl != 0] > 0).mean()\n", "\n", " print(f\"\\n{'='*55}\")\n", " print(f\"WF Sharpe (OU + two-tier + hysteresis) : {sharpe:.3f}\")\n", " print(f\"Cumulative return : {cum.iloc[-1]-1:.2%}\")\n", " print(f\"Max drawdown : {max_dd:.2%}\")\n", " print(f\"Win rate (active days) : {win_r:.1%}\")\n", " print(f\"Total folds with trades : {len(all_pnls)} / {fold_idx}\")\n", "\n", " print(f\"\\nFold breakdown:\")\n", " print(f\" {'Fold':<6} {'Period':<28} {'Sharpe':>7} \"\n", " f\"{'Baskets':>8} {'HL_win':>7} {'Med_HL':>8}\")\n", " print(f\" {'-'*65}\")\n", " for fr in fold_records:\n", " fp = wf_pnl.loc[fr[\"test_start\"] : fr[\"test_end\"]]\n", " fs = (fp.mean() / fp.std() * ann if fp.std() > 1e-8 else np.nan)\n", " period = f\"{fr['test_start'].date()} → {fr['test_end'].date()}\"\n", " print(\n", " f\" {fr['fold']:<6} {period:<28} \"\n", " f\"{fs:>7.3f} {fr['n_baskets']:>8} \"\n", " f\"{fr['fold_hl_window']:>7} {str(fr['median_hl']):>8}\"\n", " )\n", " print(f\"{'='*55}\")\n", "\n", " return fold_records, wf_pnl" ] }, { "cell_type": "code", "execution_count": 26, "id": "ffeef566", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Fold 1 | train 2023-03-01 → 2024-02-28 | test 2024-02-29 → 2024-04-28\n", " Calibrated | median_HL=7.61d → hl_window=76\n", " after cointegration : 142\n", " after ADF : 125\n", " after VR : 95\n", " after HL+stability : 94\n", " after predictability : 94\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " \r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " 94 baskets survived → computing OU alpha + regime scores...\n", " entries this fold: 43 (~262 annualised)\n", "\n", "Fold 2 | train 2023-04-30 → 2024-04-28 | test 2024-04-29 → 2024-06-27\n", " Calibrated | median_HL=9.55d → hl_window=95\n", " after cointegration : 164\n", " after ADF : 83\n", " after VR : 44\n", " after HL+stability : 43\n", " after predictability : 43\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " " ] }, { "name": "stdout", "output_type": "stream", "text": [ " 43 baskets survived → computing OU alpha + regime scores...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " entries this fold: 39 (~237 annualised)\n", "\n", "Fold 3 | train 2023-06-29 → 2024-06-27 | test 2024-06-28 → 2024-08-26\n", " Calibrated | median_HL=9.92d → hl_window=99\n", " after cointegration : 143\n", " after ADF : 82\n", " after VR : 55\n", " after HL+stability : 44\n", " after predictability : 44\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " " ] }, { "name": "stdout", "output_type": "stream", "text": [ " 44 baskets survived → computing OU alpha + regime scores...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " entries this fold: 70 (~426 annualised)\n", "\n", "Fold 4 | train 2023-08-28 → 2024-08-26 | test 2024-08-27 → 2024-10-25\n", " Calibrated | median_HL=7.18d → hl_window=71\n", " after cointegration : 214\n", " after ADF : 76\n", " after VR : 44\n", " after HL+stability : 44\n", " after predictability : 44\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " " ] }, { "name": "stdout", "output_type": "stream", "text": [ " 44 baskets survived → computing OU alpha + regime scores...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " entries this fold: 28 (~170 annualised)\n", "\n", "Fold 5 | train 2023-10-27 → 2024-10-25 | test 2024-10-26 → 2024-12-24\n", " Calibrated | median_HL=12.31d → hl_window=123\n", " after cointegration : 265\n", " after ADF : 189\n", " after VR : 48\n", " after HL+stability : 36\n", " after predictability : 36\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " " ] }, { "name": "stdout", "output_type": "stream", "text": [ " 36 baskets survived → computing OU alpha + regime scores...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " entries this fold: 20 (~122 annualised)\n", "\n", "Fold 6 | train 2023-12-26 → 2024-12-24 | test 2024-12-25 → 2025-02-22\n", " Calibrated | median_HL=8.89d → hl_window=88\n", " after cointegration : 77\n", " after ADF : 39\n", " after VR : 28\n", " after HL+stability : 25\n", " after predictability : 25\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " \r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " 25 baskets survived → computing OU alpha + regime scores...\n", " entries this fold: 18 (~110 annualised)\n", "\n", "Fold 7 | train 2024-02-24 → 2025-02-22 | test 2025-02-23 → 2025-04-23\n", " Calibrated | median_HL=10.04d → hl_window=100\n", " after cointegration : 285\n", " after ADF : 254\n", " after VR : 104\n", " after HL+stability : 89\n", " after predictability : 89\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " \r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " 89 baskets survived → computing OU alpha + regime scores...\n", " entries this fold: 71 (~432 annualised)\n", "\n", "Fold 8 | train 2024-04-24 → 2025-04-23 | test 2025-04-24 → 2025-06-22\n", " Calibrated | median_HL=6.39d → hl_window=63\n", " after cointegration : 122\n", " after ADF : 69\n", " after VR : 47\n", " after HL+stability : 45\n", " after predictability : 45\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " " ] }, { "name": "stdout", "output_type": "stream", "text": [ " 45 baskets survived → computing OU alpha + regime scores...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\r" ] }, { "name": "stdout", "output_type": "stream", "text": [ " entries this fold: 44 (~268 annualised)\n", "\n", "=======================================================\n", "WF Sharpe (OU + two-tier + hysteresis) : 1.440\n", "Cumulative return : 40.08%\n", "Max drawdown : -16.61%\n", "Win rate (active days) : 53.6%\n", "Total folds with trades : 8 / 8\n", "\n", "Fold breakdown:\n", " Fold Period Sharpe Baskets HL_win Med_HL\n", " -----------------------------------------------------------------\n", " 1 2024-02-29 → 2024-04-28 -2.672 94 76 7.61\n", " 2 2024-04-29 → 2024-06-27 4.761 43 95 9.55\n", " 3 2024-06-28 → 2024-08-26 4.371 44 99 9.92\n", " 4 2024-08-27 → 2024-10-25 7.471 44 71 7.18\n", " 5 2024-10-26 → 2024-12-24 1.497 36 123 12.31\n", " 6 2024-12-25 → 2025-02-22 -0.772 25 88 8.89\n", " 7 2025-02-23 → 2025-04-23 -1.399 89 100 10.04\n", " 8 2025-04-24 → 2025-06-22 0.773 45 63 6.39\n", "=======================================================\n" ] } ], "source": [ "fold_records_hyst, wf_pnl_hyst = walk_forward_ou_mv_hyst(\n", " prices = prices_train,\n", " train_size = 365,\n", " test_size = 60,\n", " min_basket_size = 2,\n", " max_basket_size = 3,\n", " calibrate = True,\n", " debug_filters = True,\n", " entry_thresh = ENTRY_THRESHOLD, \n", " exit_thresh = EXIT_THRESHOLD, \n", " vol_window = VOL_WINDOW, \n", ")" ] }, { "cell_type": "markdown", "id": "481acfa4", "metadata": {}, "source": [ "### 8. Basket Scoring & Selection" ] }, { "cell_type": "markdown", "id": "ad04e736", "metadata": {}, "source": [ "#### 8.1 Score Baskets Across Folds" ] }, { "cell_type": "code", "execution_count": 27, "id": "0efa6896", "metadata": {}, "outputs": [], "source": [ "def score_baskets_across_folds(\n", " fold_records : list,\n", " wf_pnl : pd.Series,\n", " ann : float = np.sqrt(365),\n", ") -> pd.DataFrame:\n", "\n", " \"\"\"\n", " Score every basket across all walk-forward folds and return a ranked table.\n", "\n", " For each basket, collects its per-fold Sharpe ratios and computes a\n", " composite score that rewards mean performance, consistency, and appearance\n", " across multiple folds:\n", "\n", " composite = mean_SR × (1 / (1 + std_SR)) × log1p(n_folds)\n", "\n", " where:\n", " mean_SR — average fold Sharpe, primary quality signal\n", " 1 / (1 + std_SR) — consistency penalty, shrinks score for volatile baskets\n", " log1p(n_folds) — fold count reward, logarithmic so 1→2 folds matters\n", " more than 8→9\n", "\n", " PnL source priority:\n", " Uses per-basket PnL from fold_records[\"basket_pnl\"] if available.\n", " Falls back to slicing the portfolio-level wf_pnl for the fold period\n", " if basket_pnl is missing — this is the old behaviour and assigns the\n", " same Sharpe to every basket in a fold regardless of contribution.\n", "\n", " Parameters\n", " ----------\n", " fold_records : list of dict\n", " Output of walk_forward_ou_mv_hyst. Each record must contain\n", " basket_lookup and ideally basket_pnl for accurate per-basket scoring.\n", " wf_pnl : pd.Series\n", " Portfolio-level PnL used as fallback if basket_pnl is absent.\n", " ann : float\n", " Annualisation factor. Default sqrt(365) for daily crypto data.\n", "\n", " Returns\n", " -------\n", " pd.DataFrame\n", " One row per basket, sorted descending by composite score. Columns:\n", " basket, n_folds, mean_sharpe, min_sharpe, std_sharpe,\n", " consistency, composite.\n", " \"\"\"\n", "\n", " basket_fold_sharpes = {}\n", "\n", " for fr in fold_records:\n", " basket_lookup = fr.get(\"basket_lookup\", {})\n", " basket_pnl = fr.get(\"basket_pnl\", {})\n", " if not basket_lookup:\n", " continue\n", "\n", " for key in basket_lookup:\n", " if key not in basket_fold_sharpes:\n", " basket_fold_sharpes[key] = []\n", " if key in basket_pnl:\n", " pnl_k = basket_pnl[key].dropna()\n", " else:\n", " # fallback: slice portfolio PnL for this fold (old behaviour)\n", " pnl_k = wf_pnl.loc[fr[\"test_start\"] : fr[\"test_end\"]].dropna()\n", "\n", " if len(pnl_k) < 5:\n", " continue\n", "\n", " sr_k = (pnl_k.mean() / pnl_k.std() * ann\n", " if pnl_k.std() > 1e-8 else np.nan)\n", "\n", " if not np.isnan(sr_k):\n", " basket_fold_sharpes[key].append(sr_k)\n", "\n", " rows = []\n", " for key, sharpes in basket_fold_sharpes.items():\n", " if not sharpes:\n", " continue\n", " n = len(sharpes)\n", " mean = float(np.mean(sharpes))\n", " mn = float(np.min(sharpes))\n", " std = float(np.std(sharpes)) if n > 1 else 0.0\n", " cons = 1.0 / (1.0 + std)\n", " comp = mean * cons * np.log1p(n)\n", " rows.append({\n", " \"basket\" : key,\n", " \"n_folds\" : n,\n", " \"mean_sharpe\" : round(mean, 3),\n", " \"min_sharpe\" : round(mn, 3),\n", " \"std_sharpe\" : round(std, 3),\n", " \"consistency\" : round(cons, 3),\n", " \"composite\" : round(comp, 3),\n", " })\n", "\n", " return (pd.DataFrame(rows)\n", " .sort_values(\"composite\", ascending=False)\n", " .reset_index(drop=True))\n" ] }, { "cell_type": "markdown", "id": "b18df842", "metadata": {}, "source": [ "#### 8.2 Threshold Filter" ] }, { "cell_type": "code", "execution_count": 28, "id": "83cace5e", "metadata": {}, "outputs": [], "source": [ "def filter_baskets(\n", " scores_df : pd.DataFrame,\n", " min_folds : int = MIN_FOLDS,\n", " min_mean_sr : float = MIN_MEAN_SR,\n", " min_floor_sr : float = MIN_FLOOR_SR,\n", ") -> pd.DataFrame:\n", " \"\"\"\n", " Filter the scored basket table to consistently performing baskets.\n", "\n", " Applies three independent threshold cuts in sequence:\n", " n_folds >= min_folds — must have appeared in enough folds to be\n", " statistically meaningful, not a one-fold fluke\n", " mean_sharpe >= min_mean_sr — average performance must be positive\n", " min_sharpe >= min_floor_sr — worst single fold must be acceptable,\n", " filters out baskets with blow-up folds\n", "\n", " Parameters\n", " ----------\n", " scores_df : pd.DataFrame\n", " Output of score_baskets_across_folds.\n", " min_folds : int\n", " Minimum number of folds a basket must appear in. Default MIN_FOLDS (2).\n", " min_mean_sr : float\n", " Minimum mean fold Sharpe. Default MIN_MEAN_SR (0.0).\n", " min_floor_sr : float\n", " Minimum single-fold Sharpe. Default MIN_FLOOR_SR (0.0).\n", "\n", " Returns\n", " -------\n", " pd.DataFrame\n", " Filtered and reset-indexed subset of scores_df. Prints a summary\n", " of how many baskets passed each threshold combination.\n", " \"\"\"\n", " df = scores_df.copy()\n", " df = df[df[\"n_folds\"] >= min_folds ]\n", " df = df[df[\"mean_sharpe\"] >= min_mean_sr ]\n", " df = df[df[\"min_sharpe\"] >= min_floor_sr]\n", " print(f\"\\n[FILTER] {len(scores_df)} total → {len(df)} passed thresholds \"\n", " f\"(n_folds≥{min_folds}, mean_sr≥{min_mean_sr}, min_sr≥{min_floor_sr})\")\n", " return df.reset_index(drop=True)" ] }, { "cell_type": "markdown", "id": "9d445146", "metadata": {}, "source": [ "#### 8.3 Greedy Diversification" ] }, { "cell_type": "code", "execution_count": 29, "id": "cbc60059", "metadata": {}, "outputs": [], "source": [ "def diversify_baskets(\n", " filtered_df : pd.DataFrame,\n", " max_per_asset : int = 2, \n", " n_select : int = N_SELECT,\n", ") -> pd.DataFrame:\n", " \"\"\"\n", " Greedily select a diversified basket subset from the filtered table.\n", "\n", " Iterates through baskets in descending composite score order, accepting\n", " each basket only if none of its constituent assets already appear in\n", " max_per_asset accepted baskets. Stops when n_select baskets are chosen\n", " or the filtered table is exhausted.\n", "\n", " Parameters\n", " ----------\n", " filtered_df : pd.DataFrame\n", " Output of filter_baskets, sorted descending by composite score.\n", " max_per_asset : int\n", " Maximum number of selected baskets that may share any single asset.\n", " Default 2.\n", " n_select : int\n", " Maximum baskets to select. Default N_SELECT (8).\n", "\n", " Returns\n", " -------\n", " pd.DataFrame\n", " Selected baskets, reset-indexed, sorted by composite score.\n", " Empty DataFrame if no baskets survive the asset cap constraints.\n", " Prints selected count and per-asset exposure on completion.\n", " \"\"\"\n", " asset_counts = {}\n", " selected = []\n", "\n", " for _, row in filtered_df.iterrows():\n", " assets = row[\"basket\"].split(\"_\")\n", " # check if adding this basket would breach the cap\n", " if any(asset_counts.get(a, 0) >= max_per_asset for a in assets):\n", " continue\n", " # accept\n", " selected.append(row)\n", " for a in assets:\n", " asset_counts[a] = asset_counts.get(a, 0) + 1\n", " if n_select and len(selected) >= n_select:\n", " break\n", "\n", " if not selected:\n", " print(\"[DIVERSIFY] No baskets passed — try relaxing max_per_asset\")\n", " return filtered_df.head(0)\n", "\n", " result = pd.DataFrame(selected).reset_index(drop=True)\n", " print(f\"\\n[DIVERSIFY] {len(filtered_df)} → {len(result)} baskets selected\")\n", " print(f\" Asset exposure: {asset_counts}\")\n", " return result" ] }, { "cell_type": "code", "execution_count": 30, "id": "cd6a35b4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Top 20 baskets by composite score:\n", " basket n_folds mean_sharpe min_sharpe std_sharpe consistency composite\n", " FILUSDT_APTUSDT_CRVUSDT 1 5.902 5.902 0.000 1.000 4.091\n", " OPUSDT_FILUSDT_APTUSDT 2 4.031 3.941 0.090 0.917 4.063\n", " BNBUSDT_LTCUSDT_DOGEUSDT 1 4.788 4.788 0.000 1.000 3.319\n", " AVAXUSDT_ATOMUSDT_CRVUSDT 1 4.658 4.658 0.000 1.000 3.228\n", " SOLUSDT_ADAUSDT_DOGEUSDT 1 4.488 4.488 0.000 1.000 3.111\n", " DOGEUSDT_APTUSDT_CRVUSDT 1 4.438 4.438 0.000 1.000 3.076\n", " FILUSDT_NEARUSDT_APTUSDT 2 4.326 3.747 0.579 0.633 3.010\n", "AVAXUSDT_LINKUSDT_NEARUSDT 1 4.336 4.336 0.000 1.000 3.006\n", " ADAUSDT_AVAXUSDT_DOTUSDT 1 4.071 4.071 0.000 1.000 2.822\n", " ETHUSDT_AVAXUSDT_APTUSDT 1 3.893 3.893 0.000 1.000 2.698\n", " LDOUSDT_FILUSDT_APTUSDT 1 3.760 3.760 0.000 1.000 2.606\n", " FILUSDT_APTUSDT 1 3.700 3.700 0.000 1.000 2.565\n", " BNBUSDT_FILUSDT_APTUSDT 1 3.692 3.692 0.000 1.000 2.559\n", " OPUSDT_LTCUSDT_APTUSDT 2 2.629 2.466 0.162 0.860 2.485\n", " SOLUSDT_FILUSDT_CRVUSDT 1 3.557 3.557 0.000 1.000 2.465\n", " ADAUSDT_AVAXUSDT_FILUSDT 1 3.503 3.503 0.000 1.000 2.428\n", " ADAUSDT_ATOMUSDT_CRVUSDT 2 2.230 2.130 0.100 0.909 2.227\n", " BNBUSDT_DOTUSDT_NEARUSDT 1 3.056 3.056 0.000 1.000 2.118\n", " SOLUSDT_FILUSDT_APTUSDT 1 3.050 3.050 0.000 1.000 2.114\n", " FILUSDT_LTCUSDT_APTUSDT 1 3.026 3.026 0.000 1.000 2.097\n" ] } ], "source": [ "scores_df = score_baskets_across_folds(fold_records_hyst, wf_pnl_hyst)\n", "print(\"Top 20 baskets by composite score:\")\n", "print(scores_df.head(20).to_string(index=False))" ] }, { "cell_type": "code", "execution_count": 31, "id": "1ec6ebb4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[FILTER] 155 total → 10 passed thresholds (n_folds≥2, mean_sr≥0.0, min_sr≥0.0)\n" ] } ], "source": [ "filtered_df = filter_baskets(\n", " scores_df,\n", " min_folds = MIN_FOLDS,\n", " min_mean_sr = MIN_MEAN_SR,\n", " min_floor_sr = MIN_FLOOR_SR,\n", ")" ] }, { "cell_type": "code", "execution_count": 32, "id": "6683ae72", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[DIVERSIFY] 10 → 4 baskets selected\n", " Asset exposure: {'OPUSDT': 2, 'FILUSDT': 2, 'APTUSDT': 2, 'NEARUSDT': 1, 'ADAUSDT': 1, 'ATOMUSDT': 1, 'CRVUSDT': 1, 'DOTUSDT': 1, 'LTCUSDT': 1}\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
basketn_foldsmean_sharpemin_sharpestd_sharpeconsistencycomposite
0OPUSDT_FILUSDT_APTUSDT24.0313.9410.0900.9174.063
1FILUSDT_NEARUSDT_APTUSDT24.3263.7470.5790.6333.010
2ADAUSDT_ATOMUSDT_CRVUSDT22.2302.1300.1000.9092.227
3DOTUSDT_OPUSDT_LTCUSDT21.8590.8261.0330.4921.005
\n", "
" ], "text/plain": [ " basket n_folds mean_sharpe min_sharpe std_sharpe \\\n", "0 OPUSDT_FILUSDT_APTUSDT 2 4.031 3.941 0.090 \n", "1 FILUSDT_NEARUSDT_APTUSDT 2 4.326 3.747 0.579 \n", "2 ADAUSDT_ATOMUSDT_CRVUSDT 2 2.230 2.130 0.100 \n", "3 DOTUSDT_OPUSDT_LTCUSDT 2 1.859 0.826 1.033 \n", "\n", " consistency composite \n", "0 0.917 4.063 \n", "1 0.633 3.010 \n", "2 0.909 2.227 \n", "3 0.492 1.005 " ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diversified_df = diversify_baskets(filtered_df, max_per_asset=2, n_select=8)\n", "diversified_df" ] }, { "cell_type": "markdown", "id": "d6c179bc", "metadata": {}, "source": [ "### 9. Out of Sample Testing" ] }, { "cell_type": "markdown", "id": "ef529341", "metadata": {}, "source": [ "### 9.1 Bucketed Position Sizing" ] }, { "cell_type": "code", "execution_count": 33, "id": "b334b633", "metadata": {}, "outputs": [], "source": [ "def alpha_to_bucket(alpha: float) -> float:\n", " \"\"\"\n", " Map a signed alpha value to a discrete bucketed position size.\n", "\n", " Iterates through BUCKET_THRESHOLDS in order, returning the corresponding\n", " BUCKET_SIZES entry for the first threshold that alpha's magnitude falls\n", " below. If magnitude exceeds all thresholds, returns the last (largest)\n", " bucket size. Sign of alpha is preserved throughout.\n", "\n", " Current mapping (BUCKET_THRESHOLDS=[0.08, 0.10], BUCKET_SIZES=[0.0, 0.5, 1.0]):\n", " |alpha| < 0.08 → 0.0 (flat)\n", " |alpha| < 0.10 → 0.5 (half size)\n", " |alpha| >= 0.10 → 1.0 (full size)\n", "\n", " Parameters\n", " ----------\n", " alpha : float\n", " Current alpha value from compute_regimed_alpha_series.\n", "\n", " Returns\n", " -------\n", " float\n", " Signed bucketed position size. Zero means flat. Sign matches alpha.\n", " \"\"\"\n", " mag = abs(alpha)\n", " for i, threshold in enumerate(BUCKET_THRESHOLDS):\n", " if mag < threshold:\n", " return BUCKET_SIZES[i] * (1.0 if alpha >= 0 else -1.0)\n", " return BUCKET_SIZES[-1] * (1.0 if alpha >= 0 else -1.0)" ] }, { "cell_type": "code", "execution_count": 34, "id": "88435229", "metadata": {}, "outputs": [], "source": [ "def compute_bucketed_positions(\n", " alpha_series : pd.Series,\n", " exit_thresh : float = EXIT_THRESHOLD,\n", ") -> pd.Series:\n", " \"\"\"\n", " Convert a regimed alpha series into positions using bucketed sticky sizing.\n", "\n", " State machine:\n", " Flat → in-position : alpha maps to non-zero bucket via alpha_to_bucket\n", " In-position → flat : |alpha| < exit_thresh (signal decayed)\n", " : alpha flips sign (spread crossed mean)\n", " In-position → resize: bucket changed (target_pos != cur_pos)\n", " In-position → hold : same bucket, no trade regardless of alpha move\n", "\n", " The hold condition is the critical property. Alpha oscillating between\n", " 0.11 and 0.14 stays in the full-size bucket throughout — no trades, no\n", " cost. Only crossing from full (≥0.10) to half (0.08–0.10) or to flat\n", " triggers a resize, which is exactly one trade.\n", "\n", " Parameters\n", " ----------\n", " alpha_series : pd.Series\n", " Output of compute_regimed_alpha_series. NaNs filled with 0.0.\n", " exit_thresh : float\n", " |alpha| below which an open position is closed. Default EXIT_THRESHOLD (0.02).\n", "\n", " Returns\n", " -------\n", " pd.Series\n", " Positions aligned to alpha_series.index. Values are discrete:\n", " {-1.0, -0.5, 0.0, 0.5, 1.0} based on current BUCKET_SIZES.\n", " Zero means flat. Sign matches alpha direction.\n", " \"\"\"\n", " s = alpha_series.fillna(0.0)\n", " pos = pd.Series(0.0, index=s.index)\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " for i in range(len(s)):\n", " alpha = s.iloc[i]\n", " target_pos = alpha_to_bucket(alpha)\n", " target_sign = int(np.sign(target_pos)) if abs(target_pos) > 1e-8 else 0\n", " \n", " if cur_pos == 0.0:\n", " # ── Flat: enter if alpha lands in non-zero bucket ──────────────\n", " if abs(target_pos) > 1e-8:\n", " cur_pos = target_pos\n", " cur_sign = target_sign\n", " \n", " else:\n", " # ── In position ────────────────────────────────────────────────\n", " # Exit 1: alpha fell below exit threshold\n", " if abs(alpha) < exit_thresh:\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " # Exit 2: alpha flipped sign (spread crossed mean)\n", " elif target_sign != 0 and target_sign != cur_sign:\n", " cur_pos = 0.0\n", " cur_sign = 0\n", " \n", " # Resize: ONLY if bucket changed (not every bar)\n", " elif target_pos != cur_pos:\n", " cur_pos = target_pos\n", " cur_sign = target_sign\n", " # else: same bucket → hold, no trade\n", " \n", " pos.iloc[i] = cur_pos\n", " \n", " return pos" ] }, { "cell_type": "markdown", "id": "75d370ae", "metadata": {}, "source": [ "#### 9.3 OOS Run & Metrics" ] }, { "cell_type": "code", "execution_count": 35, "id": "fce59447", "metadata": {}, "outputs": [], "source": [ "def run_bucketed_filtered_oos(\n", " prices_test : pd.DataFrame,\n", " prices_train : pd.DataFrame,\n", " fold_records : list,\n", " selected_keys: list,\n", " exit_thresh : float = EXIT_THRESHOLD,\n", " tcost_bps : float = TCOST_BPS,\n", " vol_window : int = VOL_WINDOW\n", ") -> tuple:\n", " \"\"\"\n", " Run the full OOS evaluation on prices_test using selected baskets.\n", "\n", " Sources each basket's weights from its most recent WF fold, builds\n", " spreads and regimed alpha on the combined train+test period, computes\n", " bucketed positions, applies risk parity weights day-by-day, and\n", " deducts transaction costs on signal changes only.\n", "\n", " Key design decisions:\n", " Per-basket hl_window: each basket uses the hl_window from its\n", " originating fold, not the last fold globally. Prevents\n", " underestimating alpha for baskets calibrated on longer windows.\n", " Bucketed positions: discrete sizing via compute_bucketed_positions\n", " rather than proportional — position only changes on bucket\n", " boundary crossings, minimising turnover.\n", " Transaction costs: charged on pos_df.diff() (pure signal changes)\n", " weighted by deployed capital. Position changes below\n", " REBAL_THRESHOLD (0.01) are ignored to avoid micro-cost noise.\n", " Alpha diagnostic: prints per-basket OOS alpha statistics before\n", " position computation to surface signal strength issues early.\n", "\n", " Parameters\n", " ----------\n", " prices_test : pd.DataFrame\n", " Held-out test prices (prices_test). Never seen during WF.\n", " prices_train : pd.DataFrame\n", " Training prices concatenated with prices_test for spread and\n", " alpha computation — provides sufficient history for warmup.\n", " fold_records : list of dict\n", " Output of walk_forward_ou_mv_hyst. Used to source basket weights,\n", " assets, and per-basket hl_windows.\n", " selected_keys : list of str\n", " Basket keys from diversify_baskets to deploy. Format: \"A_B_C\".\n", " exit_thresh : float\n", " Passed to compute_bucketed_positions. Default EXIT_THRESHOLD (0.02).\n", " tcost_bps : float\n", " One-way transaction cost in basis points. Default TCOST_BPS (20).\n", " vol_window : int\n", " Passed to compute_risk_parity_weights. Default VOL_WINDOW (30).\n", "\n", " Returns\n", " -------\n", " weights_df : pd.DataFrame\n", " Risk parity weights per basket over test_idx.\n", " pos_df : pd.DataFrame\n", " Bucketed positions per basket over test_idx.\n", " gross_pnl : pd.Series\n", " Daily gross PnL before transaction costs.\n", " net_pnl : pd.Series\n", " Daily net PnL after transaction costs.\n", " \"\"\"\n", " if not selected_keys:\n", " print(\"No baskets selected.\")\n", " return pd.DataFrame(), pd.DataFrame(), pd.Series(dtype=float), pd.Series(dtype=float)\n", "\n", " # ── Source each basket from most recent fold that contained it ────────────\n", " basket_lookup_filtered = {}\n", " for key in selected_keys:\n", " for fr in reversed(fold_records):\n", " bl = fr.get(\"basket_lookup\", {})\n", " if key in bl:\n", " basket_lookup_filtered[key] = bl[key]\n", " break\n", "\n", " if not basket_lookup_filtered:\n", " print(\"No baskets found in fold records.\")\n", " return pd.DataFrame(), pd.DataFrame(), pd.Series(dtype=float), pd.Series(dtype=float)\n", "\n", " last_fold = fold_records[-1]\n", " fold_hl_window = last_fold[\"fold_hl_window\"]\n", " keys_all = list(basket_lookup_filtered.keys())\n", " combined = pd.concat([prices_train, prices_test])\n", "\n", " basket_hl_windows = {}\n", " for key in selected_keys:\n", " for fr in reversed(fold_records):\n", " if key in fr.get(\"basket_lookup\", {}):\n", " basket_hl_windows[key] = fr[\"fold_hl_window\"]\n", " break\n", " else:\n", " basket_hl_windows[key] = fold_hl_window # fallback\n", "\n", " # ── Per-basket leg counts (used in cost model) ────────────────────────────\n", " leg_counts = {\n", " key: len(basket_lookup_filtered[key][\"assets\"])\n", " for key in keys_all\n", " }\n", " leg_series = pd.Series(leg_counts) \n", "\n", " print(f\"\\n[BUCKETED + RISK-PARITY OOS]\")\n", " print(f\" Baskets : {len(keys_all)}\")\n", " print(f\" hl_windows : {basket_hl_windows}\") \n", " print(f\" Buckets : {list(zip(BUCKET_THRESHOLDS, BUCKET_SIZES[1:]))}\")\n", " print(f\" exit_thresh : {exit_thresh}\")\n", " print(f\" tcost_bps : {tcost_bps}\")\n", " print(f\" Leg counts : {leg_counts}\")\n", "\n", " # ── Build spreads, alpha, basket returns ──────────────────────────────────\n", " basket_ret_all = {}\n", " alpha_oos = {}\n", "\n", " for key, b in basket_lookup_filtered.items():\n", " beta = b.get(\"weights_latest\", b[\"weights\"])\n", " assets = list(b[\"assets\"])\n", "\n", " ret_all = combined[assets].pct_change()\n", " basket_ret_all[key] = (ret_all * beta).sum(axis=1)\n", "\n", " lp_all = np.log(combined[assets]).dropna()\n", " spread_all = pd.Series(lp_all.values @ beta, index=lp_all.index)\n", "\n", " alpha_oos[key] = compute_regimed_alpha_series(\n", " spread_all, hl_window=basket_hl_windows[key] # per-basket hl_window\n", " )\n", "\n", " test_idx = prices_test.index \n", "\n", " positions_full = {\n", " key: compute_bucketed_positions(alpha_oos[key], exit_thresh=exit_thresh)\n", " for key in keys_all\n", " }\n", "\n", " # ── Day-by-day risk parity weights + PnL ─────────────────────────────────\n", " weights_list = []\n", " pnl_list = []\n", "\n", " for t_pos, t in enumerate(test_idx):\n", " w_t = compute_risk_parity_weights(\n", " positions = {k: positions_full[k] for k in keys_all},\n", " basket_ret_all= basket_ret_all,\n", " t = t,\n", " vol_window = vol_window,\n", " )\n", " weights_list.append(w_t if len(w_t) > 0 else pd.Series(dtype=float))\n", "\n", " if t_pos > 0:\n", " prev_t = test_idx[t_pos - 1]\n", " daily_pnl = 0.0\n", " for key in keys_all:\n", " pos_prev = positions_full[key].get(prev_t, 0.0)\n", " w_prev = (\n", " weights_list[t_pos - 1].get(key, 0.0)\n", " if len(weights_list[t_pos - 1]) > 0 else 0.0\n", " )\n", " ret_t = basket_ret_all[key].get(t, 0.0)\n", " daily_pnl += pos_prev * w_prev * ret_t\n", " pnl_list.append(daily_pnl)\n", " else:\n", " pnl_list.append(0.0)\n", "\n", " # ── Assemble ──────────────────────────────────────────────────────────────\n", " gross_pnl = pd.Series(pnl_list, index=test_idx)\n", " pos_df = pd.DataFrame(\n", " {k: positions_full[k].reindex(test_idx).fillna(0.0) for k in keys_all}\n", " )\n", " weights_df = pd.DataFrame(\n", " weights_list, index=test_idx\n", " ).reindex(columns=keys_all).fillna(0.0)\n", "\n", " # ── Transaction costs — signal changes only, per-basket leg count ─────────\n", " tcost = tcost_bps * 1e-4\n", "\n", " REBAL_THRESHOLD = 0.01 # ignore position changes smaller than this\n", "\n", " signal_changes = pos_df.diff().fillna(0.0)\n", " signal_changes = signal_changes.where(\n", " signal_changes.abs() > REBAL_THRESHOLD, 0.0\n", " )\n", " weighted_turnover = (\n", " signal_changes.abs() * weights_df.shift(1).fillna(0.0)\n", " ).sum(axis=1)\n", " daily_cost = weighted_turnover * tcost\n", "\n", " net_pnl = gross_pnl - daily_cost\n", "\n", " # ── Metrics ───────────────────────────────────────────────────────────────\n", " ann = np.sqrt(365)\n", " n_oos = len(gross_pnl)\n", "\n", " def _sr(s):\n", " return s.mean() / s.std() * ann if s.std() > 1e-8 else np.nan\n", "\n", " gross_sr = _sr(gross_pnl)\n", " net_sr = _sr(net_pnl)\n", " cum_gross = (1 + gross_pnl).cumprod()\n", " cum_net = (1 + net_pnl).cumprod()\n", " max_dd = (cum_net / cum_net.cummax() - 1).min()\n", " win_r = (\n", " (net_pnl[net_pnl != 0] > 0).mean()\n", " if (net_pnl != 0).any() else np.nan\n", " )\n", "\n", " n_entries = ((pos_df != 0) & (pos_df.shift(1).fillna(0) == 0)).sum().sum()\n", " ann_trades = n_entries * (365 / n_oos)\n", " ann_turnover = float(weighted_turnover.mean() * 365)\n", "\n", " n_resizes = (\n", " (pos_df != 0) &\n", " (pos_df.shift(1).fillna(0) != 0) &\n", " (pos_df != pos_df.shift(1).fillna(0))\n", " ).sum().sum()\n", "\n", " print(f\"\\n{'='*55}\")\n", " print(f\"BUCKETED + RISK-PARITY OOS — {len(keys_all)} baskets\")\n", " print(f\"{'='*55}\")\n", " print(f\"Gross Sharpe : {gross_sr:.3f}\")\n", " print(f\"Net Sharpe : {net_sr:.3f}\")\n", " print(f\" Cost drag : {gross_sr - net_sr:.3f}\")\n", " print(f\"Gross cumulative : {cum_gross.iloc[-1]-1:.2%}\")\n", " print(f\"Net cumulative : {cum_net.iloc[-1]-1:.2%}\")\n", " print(f\"Max drawdown(net) : {max_dd:.2%}\")\n", " print(f\"Win rate (net) : {win_r:.1%}\")\n", " print(f\"Entry events : {n_entries} (~{ann_trades:.0f} annualised)\")\n", " print(f\"Bucket resizes : {n_resizes}\")\n", " print(f\"Annual turnover : {ann_turnover:.2f}x\")\n", " print(f\"{'='*55}\")\n", "\n", " # ── Plot ──────────────────────────────────────────────────────────────────\n", " fig, axes = plt.subplots(3, 1, figsize=(14, 11))\n", "\n", " for pnl, label, color in [\n", " (gross_pnl, \"Gross\", \"steelblue\"),\n", " (net_pnl, \"Net\", \"darkorange\"),\n", " ]:\n", " cum_p = (1 + pnl).cumprod()\n", " dd_p = cum_p / cum_p.cummax() - 1\n", " sr_p = _sr(pnl)\n", " axes[0].plot(cum_p, color=color, linewidth=1.5,\n", " label=f\"{label} SR={sr_p:.2f}\")\n", " axes[1].fill_between(dd_p.index, dd_p, 0,\n", " color=color, alpha=0.3, label=label)\n", "\n", " axes[0].axhline(1, color=\"gray\", linestyle=\"--\", alpha=0.4)\n", " axes[0].set_title( \n", " f\"Bucketed ({len(BUCKET_SIZES)} zones) + Risk-Parity OOS — {len(keys_all)} Baskets \"\n", " f\"| Thresholds={BUCKET_THRESHOLDS} | tcost={tcost_bps}bps\"\n", " )\n", " axes[0].legend()\n", " axes[0].grid(True, alpha=0.3)\n", "\n", " axes[1].set_title(\"Drawdown\")\n", " axes[1].legend()\n", " axes[1].grid(True, alpha=0.3)\n", "\n", " if len(pos_df.columns) > 0:\n", " im = axes[2].imshow(\n", " pos_df.T.values,\n", " aspect=\"auto\", cmap=\"RdBu_r\",\n", " vmin=-1.0, vmax=1.0\n", " )\n", " axes[2].set_yticks(range(len(pos_df.columns)))\n", " axes[2].set_yticklabels(\n", " [c.replace(\"USDT\", \"\") for c in pos_df.columns],\n", " fontsize=8\n", " )\n", " axes[2].set_title(\n", " \"Positions — white=flat, blue=long, red=short \"\n", " \"(changes only on bucket crossings)\"\n", " )\n", " plt.colorbar(im, ax=axes[2], fraction=0.02)\n", "\n", " plt.tight_layout()\n", " plt.show()\n", "\n", " return weights_df, pos_df, gross_pnl, net_pnl" ] }, { "cell_type": "code", "execution_count": 36, "id": "5fa95c75", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[BUCKETED + RISK-PARITY OOS]\n", " Baskets : 4\n", " hl_windows : {'OPUSDT_FILUSDT_APTUSDT': 71, 'FILUSDT_NEARUSDT_APTUSDT': 71, 'ADAUSDT_ATOMUSDT_CRVUSDT': 99, 'DOTUSDT_OPUSDT_LTCUSDT': 99}\n", " Buckets : [(0.08, 0.5), (0.1, 1.0)]\n", " exit_thresh : 0.02\n", " tcost_bps : 20.0\n", " Leg counts : {'OPUSDT_FILUSDT_APTUSDT': 3, 'FILUSDT_NEARUSDT_APTUSDT': 3, 'ADAUSDT_ATOMUSDT_CRVUSDT': 3, 'DOTUSDT_OPUSDT_LTCUSDT': 3}\n", "\n", "=======================================================\n", "BUCKETED + RISK-PARITY OOS — 4 baskets\n", "=======================================================\n", "Gross Sharpe : 1.953\n", "Net Sharpe : 1.764\n", " Cost drag : 0.190\n", "Gross cumulative : 6.50%\n", "Net cumulative : 5.61%\n", "Max drawdown(net) : -1.38%\n", "Win rate (net) : 72.2%\n", "Entry events : 9 (~15 annualised)\n", "Bucket resizes : 0\n", "Annual turnover : 7.05x\n", "=======================================================\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAARCCAYAAADYGtDkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3QecFPX9//HP7vUDjnr0IooiFlCsqFFREjTE2JIYY6KxxNhi4WeM5p9ojElQI7ZYo0Y0sZtojI2gRtHEhoINURGkl6Nd4/rO//H+7s2yd9xegd3bcq8nj2VvZ2d3Z2e/892Zz37m8w14nucZAAAAAAAAACAlBJO9AAAAAAAAAACALQjaAgAAAAAAAEAKIWgLAAAAAAAAACmEoC0AAAAAAAAApBCCtgAAAAAAAACQQgjaAgAAAAAAAEAKIWgLAAAAAAAAACmEoC0AAAAAAAAApBCCtgAAAAAAAACQQgjaAkAG+M1vfmOBQMDWrVtnqWzGjBluOb/66qt2zX/eeefZ17/+9YQvV7qoq6uzYcOG2R133GGprqOftWhePeaGG26wVKfl1HYHNPfqq6+69vHkk0+m7Mo5/PDD7cc//nHGvudELI//PZtq/YNey7+kQ9+Zai6++OLI+uvevXu721ZHvtuQeKnWBwFAvBC0BYA4B6miL/3797eJEyfaCy+8kDbrefPmze5gUzvAybR48WK799577Ze//GVkWlVVlZ155pm2xx57WM+ePd0B1rhx4+yWW25xAc1Ml5OTY1OnTrXf//73Vl1d3emv37x9FxUV2WGHHWbPPfecpeI2mJ+fb7vssotdcMEFtmbNmoS+9v/+9z+33WzatCnuz+15nv31r3+1Qw891Hr16mWFhYW255572m9/+1urrKyMy2NCoZA9+OCDdsABB1ifPn2sR48ebt2deuqp9tZbb1kqeOONNyKfbXt+oMqUPlk/0ui9dLbm6y7WJdnfFTA7/vjj3fY+ZcqUrbbr66+/3kaOHOn6w7Fjx9ojjzzS7lWm/uzss8+24uJi69atm9t23n///a3m0/fRtGnTbLfddnN9zZAhQ+y73/2uffLJJ9vdrx5yyCHuOQcOHGgXXnihVVRUtOuxd955p1uG4cOHu3Ya60eKH/3oR27dfe1rX7N4+8Mf/mBPP/20JVu8v5/Wr19vf/zjH933i9qGvmMOPPBAe+yxx1qcv6amxn7xi1/Y4MGDraCgwH3PzJo1q92vt2LFCvve977nXkf7Hccee6wtWrQoLu8FAFJddrIXAAAyjYIiOkBS0ESBIh1sf/Ob37R//etf9q1vfcvSIWh79dVXR7KxkkWBWK1HHSRGB211EKj1ucMOO1gwGHQHI5dccom9/fbb9vDDD1umO/300+3yyy937/WMM87o9NdX5rMCeWrfS5YscQfGxxxzjAuCTZ48ucmB8Pe//33Ly8tL2jaoQIICfVrG559/3j7++GN38B8PaovZ2Vt2o9QOtd0oMKADy3hpaGiwH/zgB/b444+7oIIOvPUeXn/9dfd6TzzxhL300ks2YMCA7XqMgiG33367Oxg+5ZRT3Hv77LPP3Oe64447ugPyZFLw6Wc/+5kLHMUKVGdqn6ygbb9+/eKaGdseCmRFU1BfgZbm08eMGWOffvpppy4bmlIw9oc//OFWq+X//b//Z9dee6395Cc/sf3228/++c9/ur5BQUz1z21tcwoCf/DBB/bzn//ctUG1Re0XvPfee7bzzjtH5lWf8cwzz7jXGT9+vK1cudL1JxMmTLCPPvrIRowY0eGPbN68eXbkkUe69nXjjTfa8uXLXSbxF1980a4fXa677jorLy+3/fff31atWhVzvn322cdd1Ce2FJDe3qDtd77zHTvuuOMsmeL9/fTmm2+6tqV+9Fe/+pX7vvj73//u2tT8+fMj+5A+va4yYJXVrHbj98H/+c9/XFC+NQrSaz+wtLTU/YivH69vuukm94Ox2kjfvn23+/0AQErzAABxcf/993vqVt99990m0zds2ODl5OR4P/jBDxK2pq+66ir32iUlJdv9XHoOPZeeM1HraPHixa3OV1tb6/Xr18/71a9+1a7nveCCC9zzrlq1yusKvvWtb3lf+9rXtumx+lxHjBixTY/VOj7//PObTJs/f76bfvTRR3vbS+1Cz/XHP/4xrtvg1KlT3fSHH354u5avoaHBq6qqavE+LXN72nZH/eEPf3DPe+mll2513zPPPOMFg0HvqKOO2q7HrF692gsEAt5PfvKTreYPhULemjVrvGS78847vb59+3oXXXRRu/u6ZPXJ//nPf9zrPvHEE3F5vt1339077LDDvHjS85122mkdeoy2/ViHDtv7nisrK71U/gyiv2fbI1HfoR15reXLl7t2Ht1na3vWd8fQoUO9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qN6xvtO0/hUYu+SSS2I+VoFiza/XVlBWQVD9yKGsvmjq67Rs+jyVHa7gur7bFIhSwCxRNW31mtp29Z2mAJPKmOhHCGXPKvAbnYWv7yf1CdHlSrS8+mFH60Y/6mh703vVZxKdEax+TiVG9LnobCE9RutAAxvqh1B9j/r0Hah9mJayNptTNqPWqT4XfU+qn9L60rai7dSnwKbKnVx11VVNvqMVFPX3SfT+tR37Z6yolENn1H7WtqnPWn2LgvR67wpUKjNcPxz57019sgKL2vbVT+tsCfVP6oPV1yk4qm3c/4x0ZoCf/az1oT5b24vOHtKPDVr32j78LNTWvp+2paat1rm+l7UPou+M5j8k6HPz27Ler/Zr1Ma0raoP0/tQW9DAptH0nMr6jf6BV/tt2lfQ+9G+kpZd61PvNbqGsk/rSf2A2q22Zf2IotfUPms0fR7a9rUc2ufSPq/6Yu2r+mMNtGfdAkDCeQCAuLj//vu1l9nkkp+f7+21117enXfe6YVCoSbzl5SUeCeeeKJXWFjo9e7d2/vpT3/qffzxx+5xeq5omn788cd7vXr1cs85evRo79e//nXk/quuuso9Ts/p27x5s3fYYYd53bt399566y03rby83Lviiiu8UaNGebm5uV6/fv28gw46yLvhhhu82trayGP/97//efvss4+bR8+r5/d9+eWX3qmnnuoNHDjQy8nJ8YYMGeJ961vf8p588skmy/zhhx+619fyap5rrrnGu++++9zzLV68uM31eeGFF7rljPbuu+963/3ud73hw4d7eXl5Xrdu3bzx48d7N954o1dXV9fq8+k1m38+0ZfoZXrppZe8gw8+2CsoKPCKioq8Y445xps/f36T52tpnUe3g+bv8e9//7t3yCGHuGXWZdddd/XOP/9877PPPnP3L1q0yDvjjDO8nXbaya2zPn36eBMnTnTLEm3Tpk3uc7n33nu9baHlHjFixDY9Vu9Ly9yS3/zmN+7+//znPy2uh/fff987+eSTI59d//79XbuZM2fOVp/RH//4xybPfccdd7jpl156aavL57+m2klr1A533nlntxz6HPQ4//Ns7/ttvl2I2rjaejAYbLENXH/99W76H/7wB68jGhoa3DKqTao9qn3svvvu3tVXX+1VVFRs92PKysq8W265xZs8ebI3dOhQt1336NHDmzBhgnfPPfds1XclU6ztLh59cnvaxcsvv+wde+yx3uDBg912qGu1688//zwyj7YBPeaJJ55o8vyXXXaZm37bbbdFpv35z392fa36Gq3zPffc0823cuXKyDyrV6/2pkyZ4u7X49Wvyu9+9ztv//33d98LeryW+fe//32TvjwWPcdpp53mdYS2hViHDrHes79NR3+n6bXVFluiZb/uuuvc/foc9N2o9aN2W1paut2fQUvLI4899pi39957u9dU33vKKad4y5cvbzJPS31EVVWV+67q27ev69f1XbFs2bIm/UNNTY3385//3Bs3bpz7DDWf/la/Fg8t9UXR/YD6G/X5Wldar3/729+2mk9toaU+a8OGDd6ZZ57p3p/2VfTZtdS/ar5LLrnE22WXXdw61L7F97//ffe9Fu2jjz5yr3P55Ze36729/vrrbh9F221xcbFrg+qvovmfdfN14L+nli7NP39/fn02bfFfrz37MQsWLPAOPfRQt33qMdHb3JIlS9y+lN6X1tmOO+7o3p/aS/T+1ne+853Ivp+292effbbJa9x9993uNfQZ6Xm0D6H25m8v7f1+6oiW+tbW1q+2E31/a79Ry7jffvt5L7744lbPq/bVUh+jbUrrQd9l2qfVvsMXX3zR4ufyyCOPuP1c7WNovavv1Lpu/jraFrT/oe85rVttI9F9c0fWLQAkUkD/JT40DABAxyxatMjVtlWWnDI5EKasEQ3QpTqK6TyCdlek7D1l7imzp/mo9kBnUoauMirbynZE6tPZOjqTRme4KHMylb8XlKmr5dT3V/Oa8smiM450Rs3PfvYzl53b1mBwr776qsvsbW0QRaR+/6ezr9o7XgQAJBM1bQEAKUmn1um0So1IDIuc4qnTAjXoVyofmGNr+o1cp4LqdFgCtgDiSSUQVItTJT1Smcpe6FT/VAnY+iUDtO6a1+QFACAVUNMWAJCyokdbR7j+YvMB6ZDalMWlWtIKVqh+dGuDtQFAR6nGuc8fFCxVqWZrqlHNVNX69wfWBAAglfDNBAAAkCAlJSVuYCsNLKMBqDQADgDEy6RJk1iZ20GB7lQPdgMAui5q2gIAAAAAAABACqGmLQAAAAAAAACkEIK2AAAAAAAAAJBCqGmbZkKhkK1cudJ69OhhgUAg2YsDAAAAAAAAJJXneVZeXm6DBw+2YDAzclQJ2qYZBWyHDRuW7MUAAAAAAAAAUsqyZcts6NChlgkI2qYZZdj6jbCoqCjZi5PxWc0a9bu4uDhjfqVB6qPdgTaGTETfBtoYMhF9G2h7SCeZ3meVlZW5JEc/bpYJCNqmGb8kggK2BG0T36FVV1e79ZyJHRpSE+0OtDFkIvo20MaQiejbQNtDOukqfVYgg0qJZu6nBAAAAAAAAABpiKAtAAAAAAAAAKQQgrYAAAAAAAAAkEKoaZuhGhoarK6uLtmLkfb1XrQOVfMlk+u9dEROTo5lZWUlezEAAAAAAAAyGkHbDON5nq1evdo2bdqU7EXJiHWpwG15eXlGFbLeXr169bKBAweyTgAAAAAAABKEoG2G8QO2/fv3t8LCQgJr2xm0ra+vt+zsbNZj4/rYvHmzrV271q2fQYMGxanVAgAAAAAAIBpB2wwrieAHbPv27ZvsxUl7BG23VlBQ4K4VuFU7o1QCAAAAAABA/FGoM4P4NWyVYQskit++qJkMAAAAAACQGARtMxD1V0H7AgAAAAAASF+URwAAAAAAAOiAkOdZbV1DUtaZBsyuqWuw6tp6CwbJxUPH2k1DKES7SRMEbQEAAAAAANpJQa8L7v2vLVpTxjpD2vn5sWNt0thhyV4MtAM/ySAlrF692i666CIbNWqU5efn24ABA+zggw+2O++80zZv3myp5J577rFx48ZZ9+7drVevXrb33nvbtGnTIvf/5je/cSUqdNFAXcOGDbOzzz7bNmzYsM2vWV1dbT/+8Y9tzz33tOzsbDvuuOPa9bj333/fvv71r7vl1OB0Wo6Kioom8/jLGn159NFHt3lZAQAAACCTrS2tJmALIOHItEXSLVq0yAVoFVj8wx/+4AKTeXl59tFHH9mf//xnGzJkiH37299u8bEaDCsnJ6fTlvUvf/mLXXzxxXbrrbfaYYcdZjU1Nfbhhx/axx9/3GS+3Xff3V566SVraGiwTz/91M444wwrLS21xx57bJteV89TUFBgF154of39739v12NWrlxpkyZNspNOOsluu+02Kysrc8uu4O+TTz7ZZN7777/fjjrqqMhtfRYAAAAAgK2Vbq511/2K8u2+8w5PymnuJSUlVlxczGnu6HC7GTywP2stTRC0RdKdd955Lnt0zpw51q1bt8j0HXfc0Y499ljzPC8yTVmgd9xxh73wwgv28ssv289//nOX2aqM3BtuuMGWLVtmI0eOtF/96lf2ox/9yD1Gj7/66qtdwHXNmjUu4/Q73/mOC7yKnu+mm25yj+3Zs6d97Wtf2yqo6XvmmWfse9/7np155plNArTN6f0MHDjQ/a2g83e/+10XGN1WWi96j/Lf//7XNm3a1OZjnn32WRfQvv322yNf5HfddZeNHTvWFi5c6LKao4O0/vICAAAAAGIrawza9irMtfycrE5fVaFQwPKyg+61qWmLjrabLOogpw2CthlMwUoVmU6GvJwsF2Bty/r16+3f//63y7CNDthGa/48CtJee+21dvPNN7vg6FNPPeVKK+i2MksVrDz99NNt6NChNnHiRJeZqqCsTvlXgFWlGD744AP3XAoUK3v1r3/9qx100EGuhMHrr78ec3kV2HzttddsyZIlNmLEiHati6+++spmzpxpubm5TaYfffTRrb6Wnv+TTz6xbaUsYL1m9Je4snXljTfeaBK0Pf/88+2ss85ygfJzzjnHrb/2fH4AAAAA0FUzbXsWNj3GA4B4ImibwRSwPfa6mUl57X/+YrLl57bdvJTxqeDy6NGjm0zv16+fq+PqBxSvu+66yH0/+MEPXFDRd/LJJ7tT/pWxK1OnTrW33nrLZd4qaLt06VIXbFVAV5mnw4cPt/3339/Nq/sULP7Wt75lPXr0cIFS1aiN5aqrrrITTjjBdthhB9tll11swoQJ9s1vftNl7kYHR1XaQTVvVdbAfx833nhjk+e69957raqqKuZrbW/ZhyOOOMKtiz/+8Y8uqF1ZWWmXX365u2/VqlWR+X7729+6eQsLC10AXetRdW8VzAYAAAAANEXQFkBnYCAypKR33nnH5s2b5zJjlTEabd99921yWzVjVRM3mm5ruqg0gYKjyiL9yU9+4jJz6+vr3X0apEuBWt2ncgoPPfRQqwOfDRo0yN58800XlFUgVM9z2mmnuXqwqg/jUxBay//uu+/aL37xC5s8ebL97Gc/a/JcKpugbNdYl/Zm8saidffAAw/Y9OnTXUBWgWuVjtAgb9EB5l//+tdufSlYrWW97LLLXKAXAAAAABC7PEIRmbYAEohM2wymEgXKeE3Wa7eHgpM6Df+zzz5rMl1B1OjT+aPFKqMQy7Bhw9zza2CwWbNmuUxSBSVV5kDZte+//769+uqrLsv0yiuvdOUXFGxVfdtY9thjD3fRc6mcgOrg6vmU2SsqS+CXH1AphylTpri6utdcc02nlUfws5J1US1frTeta2X8+uu3JQcccIBbTgXLNSAcAAAAAGALMm0BdAaCthlMAbr2lChIJg0KpmzX2267zWWidjQgK2PGjHGDcynj1afbu+22W+S2gr/HHHOMu6jcwq677uqyZcePH+/q4qp0gi4qf6BBuV555RU7/vjj2/X6/uuo/EAsGhhNJQjOPfdcGzx4cKeUR4im7FrRYGz5+flunceiDOHevXsTsAUAAACAFhC0BdAZUjuihy7hjjvucKfnq+yBslzHjh3rTt9XtuuCBQtsn332afXxP//5z+173/ueO71fgdd//etf9o9//MNl1sqMGTNcbVllkKpMwN/+9jcXxFUmqwYtW7RokR166KEuUPn888+7MgfNa+z6/KCrArAa6Ey1YX/3u99ZcXGxq28bi+7T+9KAawpQ++UROmL+/PlWW1vrBksrLy93wVXZa6+9IiUlTj31VHv55Zcjz63X0gBrqq+rLGOtK2X+KjAtWlfKwj3wwANdMFfzaBkvvfTSDi0bAAAAAHQVZVWURwCQeARtkXQ77bSTzZ071wULr7jiClu+fLnL8lQGq4KH/gBjsRx33HF2yy23uIHHVGdWdVvvv/9+O/zww939ClAqUKlBuRS83XPPPV2wUlm+uk8BXgWLNWDYzjvvbI888oirB6sB0ppTUFjZqnfeeaetX7/eDZimgKwCpXq+1lxyySVuwDTVjVXJho7SgGdLliyJ3PYHTPOXU7V4VQairq4uMo8Cucoe1sBiyi6+++67Xe3e6Gze22+/3S2bnkclHVQ+QbV/AQAAAABbK60MB217UdMWQAIFvJYiU0hZZWVlrtZqaWmpFRUVNblPQcfFixe7oKWyJrF9tGlooDGVT1CpCdDOOoMyvdeuXWv9+/dvMmAcQBtDOqNvA20MmYi+rev6zg3/tvKqOvvzOYfaiOIenf76tD3QbjoWL0tXRAQAAAAAAADaoSEUcgFb6UmmLYAEImgLAAAAAADQDn7AVudi9iiI3+DRANAcQVsAAAAAAIB2KN0crmfbvSDHsihnBiCBCNoCAAAAAAB0IGjbsyCX9QUgoQjaxjB79mw75phjbPDgwW4QqqeffrrNlfnqq6/a+PHjLS8vz0aNGmUzZszYap4VK1bYD3/4Q+vbt68VFBTYnnvuaXPmzNn+TxIAAAAAAHRO0LYbQVsAiUXQNobKykobN26c3X777e1akYsXL7YpU6bYxIkTbd68eXbxxRfbWWedZTNnzozMs3HjRjv44IMtJyfHXnjhBZs/f75Nnz7devfuHZ9PEwAAAAAAJExZY9C2iExbAAmWnegXSFdHH320u7TXXXfdZSNHjnRBWBkzZoy98cYbdtNNN9nkyZPdtOuuu86GDRtm999/f+RxegwAAAAAAEijTNtCMm0BJBaZtnHy5ptv2qRJk5pMU7BW033PPPOM7bvvvvbd737X+vfvb3vvvbfdc8898VoEAAAAAACQQARtAXQWMm3jZPXq1TZgwIAm03S7rKzMqqqqXP3aRYsW2Z133mlTp061X/7yl/buu+/ahRdeaLm5uXbaaae1+Lw1NTXu4tPzSSgUcpdouu15XuSC7eevR9Zn03WiS0ttENvP345Zt0gU2hiSgXYH2hgyEX1b1w7a9ijISdo+O20PtJutZeIxNEHbTm5AyrT9wx/+4G4r0/bjjz92pRViBW2nTZtmV1999VbTS0pKrLq6usm0uro69xr19fXugu2jwFlDQ4P7W4PRIUxtS+1s/fr1rj4z4kvrtrS01LW/YJCTIRB/tDEkA+0OtDFkIvq2rqlkY4W7DtRX29q1a5OyDLQ90G62Vl5ebpmGoG2cDBw40NasWdNkmm4XFRW5LFsZNGiQ7bbbbk3mUe3bv//97zGf94orrnCZudGZtqqLW1xc7J47moK4aqTZ2dnuki5OP/10e+CBB1ww+/LLL49Mf/rpp+2EE07o0K8lqhF80UUXuYHgWvPBBx/YlVdeaW+99ZZbp/r8DjjgALv11ltd6YqvvvrKdtxxx8j8Gixuzz33tGuuuca+9rWvbeM7Nfvzn/9sjzzyiL3//vvus9qwYYP16tWrzfe0ZMmSraafe+65TQbKUymOX/3qV/b2229bVlaW7bXXXvbiiy9G2l+8qG0pmNi3b1/Lz8+P63MjvAOmHwm0jRO0RSLQxpAMtDvQxpCJ6Nu6puqGL9z10IF93bFjMtD2QLvZWibGJ9InspfiJkyYYM8//3yTabNmzXLTfQcffLB99tlnTeb5/PPPbcSIETGfNy8vz12aUzCneUBHtxXs8S/ptnFdf/31ds4557gAqfjvoaPvpa33ryxl1R/+1re+ZTNnznRBUwVpVXN48+bNTR6voOfYsWNdVunvf/97O+aYY9xn1rwURnupVMZRRx3lLgrIt+ezUhkNP+NXlJ399a9/3b73ve9FHquArQbO03P+6U9/coFVBaYVvI13W/CXuaU2iPitY9YvEok2hmSg3YE2hkxE39b1lFXVuete3fKTejxE2wPtpqlMjE9k3juKk4qKCps3b567yOLFi93fS5cudbcVHDv11FMj8yvYqJq1l112mS1YsMDuuOMOe/zxx+2SSy6JzKO/ldmpjNKFCxfaww8/7DIvzz//fOvqFERVtqvKQbTmjTfecJmuyh5VxrFqAldWVrr7Dj/8cJeRqvXcWjD0v//9rzv9/N5773UlKpTJOnHiRLvpppvc39H69OnjlmuPPfZwdYiVlatM1m2lDGBlEx944IHtfowyLrUM/uXZZ5+1nXbayQ477LDIPHrPWhd67t13391Gjx7tgrotBfwBAAAAANumtDI85kyvwlxWIYCEImgbw5w5c1xATxdRiQL9rVPqZdWqVZEArijY99xzz7ns2nHjxtn06dNdUHDy5MmRefbbbz976qmn3OnxCgLqVPubb77ZTjnllMR8uhpEq64yOZcODoSmjFAFs5Ulunz58hbn+fLLL12G6oknnmgffvihPfbYYy6Ie8EFF7j7//GPf9jQoUPtt7/9rft8dGmJAp+qy6rPor0DjClD9sEHH3R/a+A430MPPWTdu3dv9fL6669bvNTW1trf/vY3O+OMMyJBadVRUiBZp+YcdNBBLgtYAV2tGwAAAABAfFTXNVhNfbh8XxFBWwAJRnmEGJS12VpAb8aMGS0+Zu7cua2ucJ2Sr0unqN9sdmt3S4oLK8xyunXoIccff7yrw3rVVVfZfffdt9X9ysJVgNuvV7vzzju7GrQKUN55550uK1bB3x49erjAbCzKclXW7A9+8AOXIb3//vvbEUcc4TKnm5c90HMrxV5lE9Qe9tlnHzvyyCMj93/72992tXBbM2TIEIsX1fndtGmT/fjHP45MU4a3/OY3v7EbbrjBrUMFmLWcKqWg9QQAAAAAiE+WbU5W0Apys1idABKKTFuklOuuu84NSvbpp59udZ9qtCpYHp3FqkxmFWFX+YqOUH3a1atX21133eXKCeh61113tY8++qjJfMqk1aBhGixu1KhR7vVzcnIi9ytArOmtXeI5EJiC2apdO3jw4Mg0f6C2n/70p25QN2WEq9SDSiT85S9/idtrAwAAAEBX5tez7VmY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8Q/7xje+4V5b88+bN2+redpaPqSeTGh3f/7zn+3www9336eaZ9OmTdu9XhA/6d7GNmzYYD/72c9s9OjR7rWHDx9uF154oZWWlsZl/SC9253U1NTY//t//89GjBjhRmbfYYcd7C9/+Uuby3j77be7efPz8+2AAw6wd955p8n99G3pJ93bHf1d8qR725Gf/vSnttNOO7nXLi4udvGOBQsWbNd6AUFbpKHXXnvNdVZvvfWWzZo1y+rq6tzOdmVlZWSeSy65xP71r3/ZE0884eZfuXKlnXDCCZH733vvPdcZ/u1vf7NPPvnEdVpXXHGF3XbbbVu9njrLVatWRS56XGuuv/56u/XWW+2uu+6yt99+27p162aTJ092nazvW9/6ltXX19srr7zilmXcuHFuWlsHFkiedG93WhYFckeNGuXuf/HFF90y/PjHP47rekLmtjEth35guu6662LO09byIfVkQrvbvHmzHXXUUfbLX/5ym9cDEifd25iWRZcbbrjBPv74Y5sxY4b7Dj3zzDO3a70gc9rd9773PXv55Zftvvvuc+3vkUcecUH+1jz22GM2depUu+qqq9yP6joW0H7b2rVrI/PQt6WfdG939HfJk+5tR/bZZx+7//777dNPP7WZM2e6pCK9h4aGhriuqy7HA9Lc2rVrPTXl1157zd3etGmTl5OT4z3xxBOReT799FM3z5tvvhnzec477zxv4sSJkdv/+c9/3GM2btzY7mUJhULewIEDvT/+8Y+RaVqevLw875FHHnG3S0pK3PPOnj07Mk9ZWZmbNmvWrA68cyRTurW7u+++2+vfv7/X0NAQmefDDz90r/XFF1904J2jK7axaIsXL3aPnzt3bpPp27p8SC3p1u6ibe9roHOkcxvzPf74415ubq5XV1e3Ta+FzGl3L7zwgtezZ09v/fr1HVqe/fff3zv//PMjt7V/NnjwYG/atGlbzUvflr7Sud356O+SIxPazgcffOCWb+HChR16LTRFeQSkPf/0tD59+kR+YdIvU8oq9O26667udLY333yz1efxnyPaXnvt5U4512nl//3vf1tdlsWLF7ts2ejX7tmzpzt9wH9tnc6gX7IefPBB98uZMm7vvvtu96uYfp1Ceki3dqfTYHJzcy0Y3NLt69QVeeONNzrwztEV21h7bOvyIbWkW7tD+smENqbXVjmO7OzshDw/0qfdPfPMM7bvvvu6M56GDBliu+yyi1166aVWVVUV8zlqa2vd60e/tvbPdJvvy8ySCe2O/i450r3tKM6hrNuRI0e6cg3YduxpIK2FQiG7+OKL7eCDD7Y99tjDTVPwSsEp1e2MNmDAgJjlB/73v/+5lP/nnnsuMk07/DrVXJ2aAl733nuvq5mnU8vHjx/f4vP4z6/XivXaqpf20ksvuTo1qlmjDk8BW51q17t37+1cI+gM6djujjjiCHdKyx//+Ee76KKL3Bfp5Zdf7u7T6aNILanWxtpjW5YPqSUd2x3SSya0sXXr1tk111zj6sIjPSSy3S1atMj9+K0aj0899ZRrH+edd56tX7/eBSxaonl0unBL+23Uf8wcmdDu6O+SI53bzh133GGXXXaZO9ZUoppKPWi5se3ItEVaU90X1Rd79NFHt/k59HgVyVZ9FtVc8amTUTFtZb8edNBBrji3rm+66SZ3/0MPPeQKgPuX119/vV2vp9ouWm4FavUYFfBWAPeYY44heJYm0rHd7b777vbAAw/Y9OnTrbCw0AYOHOh++dSXbXT2LVJDOrYxpD/aHWhjrSsrK7MpU6bYbrvtZr/5zW9oMGkikX2bgitKyNB35/7772/f/OY37cYbb3T7XMpc03do9Heq5kPXkO7tjv4uedK57Zxyyik2d+5cV3NXWbyqnxs9tg86jkxbpK0LLrjAnn32WZs9e7YNHTo0Ml3BKKXwa/To6F+iNLKi7os2f/58O/LII122xK9+9as2X1Mdm38q+be//W13+rlPpxf4GYt6LWV8RL+2TtcTDT6m5d64caM7tc7/RUq/Qqmz9LMfkZrStd3JD37wA3fRdA1Upi9sfUnvuOOO27w+0DXaWHt0ZPmQetK13SF9pHsbKy8vdwPe6SwpZSfl5OR06PHIzHan/S61JZWl8mnEdiVpLF++3GV+z5s3L3KffizXaO1ZWVlNRn2P9dpIT+ne7ujvkifd246eV5edd97ZDjzwQHcmsb4zTz755DisnS6qWY1bIOVp0CUVwVbh688//3yr+/0i3U8++WRk2oIFC7Yq0v3xxx+7gZl+/vOft/u1J02a5B1//PFtDgh1ww03RKaVlpY2GRDqmWee8YLBoFdeXt7ksbvssov3+9//vt3Lgs6V7u2uJffdd59XWFjIoD0pIpXbWEcGImtr+ZBa0r3dRWOwntSUCW1M36kHHnigd9hhh3mVlZXtfn1kfrvTQK8FBQVN9uuffvppt6+/efPmVgf1ueCCC5oM6jNkyBAGIktzmdDu6O+SIxPaTnPV1dXute6///423j1aQ9AWaefcc891Ix6++uqr3qpVqyKX6E7mnHPO8YYPH+698sor3pw5c7wJEya4i++jjz7yiouLvR/+8IdNnkOjNPpuuukm14F98cUXbv6LLrrIdWYvvfRSq8t37bXXer169fL++c9/eh9++KF37LHHeiNHjvSqqqrc/SUlJV7fvn29E044wZs3b5732WefeZdeeqnrhHUbqSnd25386U9/8t577z3X5m677Tb3JXrLLbfEfV0hM9uYRplVMOO5555zO4iPPvqou63nb+/yIfVkQrvT35p2zz33uHlmz57tbnd0ZGQkRrq3MQUwDjjgAG/PPfd0I2BHv359fX1C1hnSp90p8DF06FDvO9/5jvfJJ5+4kd533nln76yzzmp1+dTO9OP6jBkzvPnz53tnn322249bvXp1ZB76tvST7u2O/i550r3tfPnll94f/vAHt1xLlizx/vvf/3rHHHOM16dPH2/NmjUJWWddBUFbpB3tULd0if4FR4Gq8847z+vdu7fLJFSWRfQB3lVXXdXic4wYMSIyz3XXXefttNNOXn5+vutsDj/8cNdBtudXsl//+tfegAEDXMd25JFHuiBZtHfffdf7xje+4Z63R48eLnvj+eefj9s6QvxlQrv70Y9+5J4zNzfXGzt2rPfggw/Gbf0g89uYlqOl59Zrtnf5kHoyod3Fen0yO1JDurcxP4O7pYuyc9G12518+umnLqtbP4YrGDJ16tRWM9aif0xXAEb7Zcpie+utt5rcT9+WftK93dHfJU+6t50VK1Z4Rx99tMvyVTKanvcHP/iBywbG9gnov2SXaAAAAAAAAAAAhDFkOAAAAAAAAACkEIK2AAAAAAAAAJBCCNoCAAAAAAAAQAohaAsAAAAAAAAAKYSgLQAAAAAAAACkEIK2AAAAAAAAAJBCCNoCAAAAAAAAQAohaAsAAAAAAAAAKYSgLQAAAAAAAACkEIK2AAAAAAAAAJBCCNoCAAAAAAAAQAohaAsAAAAAAAAAKYSgLQAAAAAAAACkEIK2AAAAAAAAAJBCCNoCAAAAAAAAQAohaAsAAAAAAAAAKYSgLQAAAAAAAACkEIK2AAAAQJwFAgH7zW9+w3oFAADANiFoCwAAgJQyY8YMF/T0L/n5+TZ48GCbPHmy3XrrrVZeXp7sRQQAAAASKjuxTw8AAABsm9/+9rc2cuRIq6urs9WrV9urr75qF198sd144432zDPP2NixY1m1AAAAyEgEbQEAAJCSjj76aNt3330jt6+44gp75ZVX7Fvf+pZ9+9vftk8//dQKCgpafGxlZaV169atE5cWAAAAiB/KIwAAACBtHHHEEfbrX//alixZYn/729/ctB//+MfWvXt3+/LLL+2b3/ym9ejRw0455RR33+uvv27f/e53bfjw4ZaXl2fDhg2zSy65xKqqqiLPqaxdlWH48MMPI9P+/ve/u2knnHBCk9cfM2aMnXTSSZHbNTU17vmKi4vd6yqYvHz58haXfe7cuS4QXVRU5Jb3yCOPtLfeeity/6ZNmywrK8uVgPCtW7fOgsGg9e3b1zzPi0w/99xzbeDAgZHbhx9+uO2xxx42f/58mzhxohUWFtqQIUPs+uuv3+Z1DQAAgOQhaAsAAIC08qMf/chd//vf/45Mq6+vdzVv+/fvbzfccIOdeOKJbvoTTzxhmzdvdkHOP/3pT24eXZ966qmRxx5yyCEuQDt79uzINAV7FSx94403ItNKSkpswYIFduihh0amnXXWWXbzzTfbN77xDbv22mstJyfHpkyZstUyf/LJJ/a1r33NPvjgA7vssstc4Hnx4sUu2Pr222+7eXr16uUCr9HLodfXsm3YsMEFZKOXT88XbePGjXbUUUfZuHHjbPr06bbrrrvaL37xC3vhhRe2Y20DAAAgGSiPAAAAgLQydOhQ69mzp8usjc54VUbttGnTmsx73XXXNSmhcPbZZ9uoUaPsl7/8pS1dutRl4Pbp08d22203Fwi94IIL3Hz6W4FfBX0VqFUA1A/g+sFSBWCV7XveeefZ7bff7qadf/75Lss3OmtXfvWrX7navHqOHXfc0U1T4Hj06NEuiPvaa69FnvvJJ5+MPE7LoaCylkF/77777pEArt5LtJUrV9qDDz4YCWqfeeaZNmLECLvvvvtchi8AAADSB5m2AAAASDsqL1BeXt5kmrJpm4sO2KrOrcoNHHTQQa7UgMoV+BQsVVBU9LwKyCoo2q9fv8h0XfvZsPL888+76wsvvLDJa2qwtGgNDQ0uK/i4446LBGxl0KBB9oMf/MAFcsvKyiLLsWbNGvvss88ir6nM3ujl0/xa/uaZtlonP/zhDyO3c3Nzbf/997dFixa1c60CAAAgVRC0BQAAQNqpqKhwNWR92dnZLgO3OWXTquatsmkV1FTt2cMOO8zdV1paGplPAdBVq1bZwoUL7X//+58rSTBhwoQmwVJdH3zwwa5sgqiurv7eaaedmrymsmejqayCSjQ0n+7XyA2FQrZs2bLIcvivpSCzAsuapsBt9HKoLq7KIETT+9dyR+vdu7crmwAAAID0QnkEAAAApBUN9KWAq8oc+DTImB9Mjc5w/frXv+7KCai2q0ocdOvWzVasWOECuQqW+lSCQFRPVpmp48ePd/MqYKqBwRQkVgD197//fULf2+DBg23kyJFuOXbYYQeXUavgsYLNF110kQsUK2irbOHm71eDmLUkegAzAAAApAeCtgAAAEgrf/3rX921BhVrzUcffWSff/65PfDAA00GHps1a9ZW86q2rS4KiCpo62e8KsN16tSprratgsDRg5CpXqwCv6qtG51F65c28CngWlhYuNV0Ua1aBV+HDRsWmabXVtBWwdu99trLZRQrq1Z1fF988UV7//337eqrr27n2gIAAEA6ojwCAAAA0sYrr7xi11xzjQtoasCv1viZp9GZpvr7lltuaXF+BUv1/O+8804kaOsHTa+99lpXH3efffaJzO8P7qVM3Gg333zzVsvxjW98w/75z3/aV199FZmu2rUPP/ywy/JVuYPo5dB8jz32WGQ5FNhVdu2NN97oBjRrXs8WAAAAmYVMWwAAAKSkF154wWWi1tfXuwCnAqrKklWG6zPPPGP5+fmtPl7lEFRv9tJLL3UlERQY/fvf/x6zxqsCoQ899JCrC+uXS1DAVcHSmTNn2uGHH+4G9/IpoHvyySfbHXfc4co1aL6XX37Z1cVt7ne/+51bdj3veeed52rw3n333VZTU2PXX3/9Vsshysz9wx/+EJmuLF+tE5WC2G+//Tq4NgEAAJBOCNoCAAAgJV155ZXuWoFSDSS25557uizW008/vckgZLHk5OTYv/71L7vwwgtt2rRpLsh7/PHH2wUXXLDVIF7RwVIFe/v27dtkuoK2LWW3/uUvf3HlDxTsffrpp+2II46w5557rkm5A9l9991d6YUrrrjCLYvKKhxwwAH2t7/9zV1HU6mF/v3729q1ayPB4+jl23///V3gFgAAAJkr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JVWm10RDW1kdsQWrO5p9t8qsmu82Wmefh9JS7O1m2bJlVmyK4ijj9NNPt+OOO67BaTbddFPXUZgOspJVV1fbokWL3HOZaPzatWtt8eLFKdm26lAs/hr9fe2111Jep+fjz0m/fv1cWYR4wFZUF1dfli+//NK22GKLjMs/55xzbOLEiSmZtgMGDLBevXpZ165drWQtj5gtW23WJX+LcFe8Q7NenXVLYv6WA9Du0JrYt6Et0O4asbbaLFJl1mt9s4rMx3dB9RJbFS6xirJqWxMusfW6dapzB1kpiydI6Bi5JSeiyyt8W2bLLQgqLCgrSw2shUusS8eI2+5rF1VaTbjYKsr65WDtUahchm1o1ilS3aKOi9euWWtdKwLrXbbSrGc3s+58t9E6+zyUlmJvN5WV+euovq0URdBWDU5DY4YNG+aCr6pTO2TIEDfumWeecQ136NChGV+j6crKymz69Ok2atQoN+6jjz6yOXPmuPnF53vJJZe4gHD84HnatGkuqLrNNtu4xz/4wQ/sgQcesOXLl1vnzp3duI8//th9UTbccMN617miosIN6fS6YvySNVmgg2jdSpjfaKpmr4AtHZGhNdHuQBtDMWLf1tDGiergzixSFvubgY77dKJlVuMy+oKgrLSPBTPQ9mnpMXLgtnMsGpd+mOmHoQWR8tplqO5o3WlQelz/YbVD82cSWiSo/fqrbfHdRivt81B6irnd+MX4nqyEKLN13333tRNPPNFlxr700ks2fvx4O/zww61///5umq+++sp1NBbPnFVm7JgxY1y267PPPusCvqNHj3aB2l133dVNs88++7jg7NFHH23vvPOOPfnkk3buuefauHHjEgHXI4880nr27Ole+8EHH9gLL7xgZ555ph1//PH1lkZAA3zVtM18+yAAAEBBidbE6lg20AFRrAMs30JNq2ChCxoi12LbVZ2+1S1SGpoylPzEdLEgOpADYayzQdfs+G4DAEoxaCuqKaug7PDhw22//faz3Xff3W6++ebE81VVVS6TduXKlYlx11xzjR1wwAEu03aPPfZwJQ8efPDBxPNBENhjjz3m/iqY+8tf/tKOOeYYu+iiixLTKLtW2bfK9N15553tqKOOsgMPPNAmTZrUiu++iPjltT0tAwAAFDgd0yhg49d/E5yvgK4L6sSmJWibHwrEalAJswzPmlebxeMFQSzABuSA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C7LudEBzuJaldFs6IgMAlHLQdtGiRXbUUUdZ165drXv37jZmzBhbvnx5g69ZvXq1jRs3znr27GmdO3e2UaNG2fz581OmmTNnju2///7WsWNH6927t5155plWXV2dMs3dd99tO+64o5umX79+dvzxx9u3336bl/cJAACA4uIybFUiwdVSRb6kl0FYVx4hU6YtgVu0jC4IuExbZXjTERkAoJSDtgrYvv/++zZt2jR77LHH7IUXXrCxY8c2+JoJEybYo48+ag888IA9//zz9vXXX9shhxySeL6mpsYFbNeuXWsvv/yy3XHHHXb77bfbeeedl5jmpZdesmOOOcYFibV8zeu1116zE088Ma/vFwAAAMXBZdgGEfOTOsNCPja0OoRKH1m3PEKsTAVBW7S4wdV2RKaatlyQAQCsU1JHfLNmzbKpU6faX//6Vxs6dKjtvvvudv3119t9993nArGZLFmyxG699Va7+uqrbe+997YhQ4bYlClTXHD2lVdecdM89dRT9sEHH9hdd91lgwcPtp/85Cd28cUX2+TJk10gV2bMmGEDBw60U045xTbZZBO37F/96lcucAsAAAA0xnMB24h5AZ0V5b0jsjC9I7Ko+WllE2JlKlSLFGhJe1PYtrY3MjJtAQClGrRV4FQlEXbeeefEuBEjRrhshVdffTXja958802rqqpy08VtvfXWttFGG7n5xee7/fbbW58+fRLTjBw50pYuXeqyamXYsGH2xRdf2BNPPOFugVF5hX/84x+233775fEdAwAAoFj4uh3fV+CWoG1+N7SCs16GmrZl6R8ImbZoMde24pm21LQFACQpqfsv5s2b5+rNJotEItajRw/3XH2vKS8vd8HeZArQxl+jv8kB2/jz8efkBz/4gatpe9hhh7kauap3e+CBB7ps3IasWbPGDXEKBEs0GnUD8kfbVwdRbGe0JtodaGMoRuzbciUwU11VL+D4JI9tzKsN2KYk2+qBn7rdVftWk6Qn5aJ06LOPD82fiZkXDS3quiPz1JhzuIYoVvyugnZTVzHGbooiaHv22Wfb5Zdf3mhphLak8gmnnnqqq3OrLNy5c+e6zsp+/etfu/IL9bnsssvswgsvrDN+4cKFLviL/H7hVR7DdQ5A7Ti0EtodaGMoRuzbcrQda2qsptOGtnxlja2qWpCjuRaHXLaxxcvX2Eqvu1VVrTtVqrKu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ua1Tz1bLozwCAABFh6At2qU//elPrkzCVlttlRj3ve99zz744APbfPPN631deXm51dTUtNJaAgAAoFi5Tm29wPxsM22lcj2zpf+zvItWmQVlBG0BAChClEdAu7T99tu7+rWqWRt31lln2csvv+w6Hps5c6Z98skn9q9//SvREZkMHDjQXnjhBfvqq6/sm2++aaO1BwAAQMGrzbLNujyCKNM2rGmdoK1P0BYAgGJE0LZUqKZW1Yr8Dzms3XXRRRe5erVxO+ywgz3//PP28ccf2w9/+EPbaaed7LzzzrP+/funvOZ///ufbbbZZtarV6+crQsAAABKix8E5gWR7Dsii9e1DS3/agjaAgBQrCiPUOzUk6xuz1r9nVn1qtZZppaXZQ+26mAsnbJm16xJ7XX3+9//vj311FP1zmfXXXd1HZYBAAAALdG998YWKe9gXnOCtmWdYrVmozWxv/msaVumjs/K8rcMAADQJgjaFruyDmabHWQWXdt6y1TAVssFAAAAClQQKbdu62/YvBe7QGqF2epFZpEKyxvd6dZh/fzNHwAAtJmSK4+waNEiVyu1a9eu1r17dxszZowtX768wdesXr3axo0bZz179rTOnTvbqFGjbP78+SnTnHLKKTZkyBCrqKhwHWg15NNPP7UuXbq45bcKBVBVV6u1BgK2AAAAKGVlnWPBVNW1rVqZv0HB4Q492vrdAgCAPCi5TFsFbOfOnWvTpk2zqqoqGz16tI0dO9buueeeel8zYcIEe/zxx+2BBx6wbt26uY6vDjnkEHvppZdSpjv++OPt1VdftXfffbfeeWmZRxxxhKvJqk61AAAAABQZlSvYXHe7VbXCsirzvwwAANDqSipoO2vWLJs6daq9/vrrtvPOO7tx119/ve2333521VVXpXRoFbdkyRK79dZbXVB37733duOmTJligwYNsldeecXVUJVJkya5vwsXLmwwaHvuuefa1ltvbcOHDydoCwAAABSroDw2AAAANENJlUeYMWOGK0kQD9jKiBEjXI+wypDN5M0333TZsZouTkHXjTbayM0vG88884zL1p08eXIL3gUAAAAAAACAYlZSmbbz5s2z3r17p4yLRCLWo0cP91x9rykvL69Tf7ZPnz71viaTb7/91o477ji76667XD3dplqzZo0b4pYuXer+RqNRNyTT4zAME3/RcvHtGP+bvI3Ttz+QC8ltDMgH2hjaAu0OtDEUI/ZtoO2hkBT7PitahO+rKIK2Z599tl1++eWNlkZoSyeeeKIdeeSRtscee2T1ussuu8wuvPDCOuNVhkEdpKU30JqaGtexWllZWYvXudRpZ6btKZ7nub/athq3ePFil6EN5Jq+xyrLovZHG0M+0MbQFmh3oI2hGLFvA20PhaTY91nLli2zYlMUQdvTTz/dZbE2ZNNNN7W+ffvaggULUsZXV1fbokWL3HOZaPzatWtdkC4523b+/Pn1vqa+0giPPPKIq50r8asbyvS9+eabXSdmmZxzzjk2ceLElEzbAQMGWK9everN2FVWr76AHTt2TAQb0TwqjRH/vFauXOm2bc+ePbP67IFsaL+g762+48X4Q4q2RxsD7Q7FiH0baHcoJezzQLupq7Ky+DrmLIqgrYIbGhozbNgwF3xVndohQ4Ykgqna4Q0dOjTjazSdslanT59uo0aNcuM++ugjmzNnjptfU6n+bTxrU/71r3+57OCXX37ZNthgg3pfV1FR4YZ0CuZkCuj069fPBXyUiYuWiQfWtZ3jwW8F7hWwJRiOfFL7qu87DtDGUKjYt4E2hmLEvg20PRSSYt5n+UX4nooiaNtUgwYNsn333deVKrjppptcFuX48ePt8MMPt/79+7tpvvrqKxs+fLjdeeedtssuu1i3bt1szJgxLttVtW+V3XryySe7gO2uu+6amPenn37qbp1XndtVq1bZzJkz3fhtttnG1cTVspO98cYbrkFtt912Of8CKnCr2r3xLFE0jwK28cxafVYK3gdBwOYEAAAAAABAXpVU0FbuvvtuF6hVYFaBOGXPTpo0KfG8Ap3KpNWt8HHXXHNNYlp1CjZy5Ei78cYbU+Z7wgkn2PPPP594vNNOO7m/s2fPtoEDB1prU3CRAGPLg7YK1CrFvhiv2AAAAAAAAKB98kLdA46CoZq2yv5V8ej6atoid0Fb1UBW1jJBW7QW2h1oYyhG7NtAG0MxYt8G2h4KSbHvs5YWYbys+D4lAAAAAAAAAChgBG0BAAAAAAAAoB0puZq2hS5ezUJp38j/rQPLli2jpi1aFe0OtDEUI/ZtoI2hGLFvA20PhaTY91lLa+NkxVQFlqBtgdEXTAYMGNDWqwIAAAAAAAC0q7hZt27drBjQEVkBXhn5+uuvrUuXLuZ5XluvTlHTVRoFx7/44ouiKWKN9o92B9oYihH7NtDGUIzYt4G2h0JS7PusMAxdwLZ///5Fk0lMpm2BUcPbcMMN23o1Sop2ZsW4Q0P7RrsDbQzFiH0baGMoRuzbQNtDISnmfVa3IsmwjSuO0DMAAAAAAAAAFAmCtgAAAAAAAADQjhC0BepRUVFh559/vvsLtBbaHWhjKEbs20AbQzFi3wbaHgoJ+6zCQ0dkAAAAAAAAANCOkGkLAAAAAAAAAO0IQVsAAAAAAAAAaEcI2gIAAAAAAABAO0LQFgXnsssus+9///vWpUsX6927tx188MH20UcfpUyzevVqGzdunPXs2dM6d+5so0aNsvnz5yeef+edd+yII46wAQMGWIcOHWzQoEF23XXXpczjueeeM8/z6gzz5s1rcP3CMLTzzjvP+vXr5+Y9YsQI++STT1Km+fjjj+2ggw6y9ddf37p27Wq77767PfvssznZPsiPYmh3b731lv34xz+27t27u3UcO3asLV++PCfbB8Xfxh588EHbZ5993LI1/cyZM+tM09j6of0phnZ3880321577eV+TzXN4sWLW7xdkDuF3sYWLVpkJ598sm211VZu2RtttJGdcsoptmTJkpxsHxR2u5M1a9bY73//e9t4441dJz8DBw602267rdF1nDx5spu2srLShg4daq+99lrK8+zbCk+htzv2d22n0NuO/OpXv7LNNtvMLbtXr14u3vHhhx+2aLuAoC0K0PPPP+92Vq+88opNmzbNqqqq3MH2ihUrEtNMmDDBHn30UXvggQfc9F9//bUdcsghiefffPNNtzO866677P3333c7rXPOOcduuOGGOsvTznLu3LmJQa9ryBVXXGGTJk2ym266yV599VXr1KmTjRw50u1k4w444ACrrq62Z555xq3Ljjvu6MY1dmKBtlPo7U7rokDu5ptv7p6fOnWqW4fjjjsup9sJxdvGtB66wHT55ZfXO01j64f2pxja3cqVK23fffe13/3ud83eDsifQm9jWhcNV111lb333nt2++23u9/QMWPGtGi7oHja3S9+8QubPn263Xrrra793XvvvS7I35D777/fJk6caOeff767qK5zAR23LViwIDEN+7bCU+jtjv1d2yn0tiNDhgyxKVOm2KxZs+zJJ590SUV6DzU1NTndViUnBArcggULQjXl559/3j1evHhxWFZWFj7wwAOJaWbNmuWmmTFjRr3z+c1vfhP+6Ec/Sjx+9tln3Wu+++67Jq9LNBoN+/btG1555ZWJcVqfioqK8N5773WPFy5c6Ob7wgsvJKZZunSpGzdt2rQs3jnaUqG1u7/85S9h7969w5qamsQ07777rlvWJ598ksU7Rym2sWSzZ892r3/77bdTxjd3/dC+FFq7S9bSZaB1FHIbi/v73/8elpeXh1VVVc1aFoqn3f2///f/wm7duoXffvttVuuzyy67hOPGjUs81vFZ//79w8suu6zOtOzbClcht7s49ndtoxjazjvvvOPW79NPP81qWUhFeQQUvPjtaT169EhcYdKVKWUVxm299dbudrYZM2Y0OJ/4PJINHjzY3XKu28pfeumlBtdl9uzZLls2edndunVztw/El63bGXQl684773RXzpRx+5e//MVdFdPVKRSGQmt3ug2mvLzcfH/dbl+3rsi///3vLN45SrGNNUVz1w/tS6G1OxSeYmhjWrbKcUQikbzMH4XT7h555BHbeeed3R1PG2ywgW255ZZ2xhln2KpVq+qdx9q1a93yk5et4zM95veyuBRDu2N/1zYKve0ozqGs20022cSVa0DzcaSBghaNRu20006zH/zgB7bddtu5cQpeKTilup3J+vTpU2/5gZdfftml/D/++OOJcTrg163m2qkp4PXXv/7V1czTreXf+973Ms4nPn8tq75lq17a008/7erUqGaNdngK2OpWu/XWW6+FWwStoRDb3d577+1uabnyyivt1FNPdT+kZ599tntOt4+ifWlvbawpmrN+aF8Ksd2hsBRDG/vmm2/s4osvdnXhURjy2e4+++wzd/FbNR4feugh1z5+85vf2LfffusCFploGt0unOm4jfqPxaMY2h37u7ZRyG3nxhtvtN/+9rfuXFOJair1oPVG85Fpi4Kmui+qL3bfffc1ex56vYpkqz6Laq7EaSejYtrKft1tt91ccW79veaaa9zzd999tysAHh9efPHFJi1PtV203grU6jUq4K0A7oEHHkjwrEAUYrvbdttt7Y477rA///nP1rFjR+vbt6+78qkf2+TsW7QPhdjGUPhod6CNNWzp0qW2//772zbbbGMXXHABDaZA5HPfpuCKEjL027nLLrvYfvvtZ1dffbU75lLmmn5Dk39TNR1KQ2TA9g4AAGgOSURBVKG3O/Z3baeQ285RRx1lb7/9tqu5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T35iv/zlL13gTFmKp512ms2ePdv+9a9/5XxZyuZNDto2pKKiwmVmIHe0PbVd24MFCxa4v+nBeiDdihUr2CgZKOi6du3aZm2bKVOm2E9/+lO3TwZQvPsyZdLre94eMur1e7/PPvtwRwwAFJm2/4UBgBKy9957u78K3MYDAxdffLFtttlmLuCnLJTf/e53tmbNmpTXvfHGGzZy5EiX6dihQweXvZueSZFc01Z/zzzzTPdvTRsv0xC/PTFT/cnPPvvM3VLfo0cPlwW866672uOPP56xftvf//53dxvehhtu6E5Yhg8fbp9++mnKtJ988omNGjXK+vbt66bRtIcffrgtWbLEWptuG+zZs6eFYZgYd/LJJ7v3MmnSpMQ4la7QOGVEJ1MGbWPvN7mmrbZzr1693L+VYRTf/sk1hz/88EM79NBD3fbWPHfeeWd75JFHWvxetR577rmn+7c+Ty23oUxfBZjULnv37u3a4DbbbFPn/et9vf/++/b8888n3ktLsofffPNNV2833pZvuummRl+j5WVaZqZawvq8rr32Wtt2223dtu3Tp4/96le/su+++87yJdffH5k8ebJtuummbjvtsssu9uKLL9a7HZpC26pz584uE3u//fazLl262FFHHZXVNtN36I9//KNbd73PH/3oR65t5MqyZcvcxS19pmqPapc//vGP7a233sqYwaflaz022GADV0c20wWMMWPGuPej97XjjjvaHXfckbGG8lVXXeW2QXx/rOzy73//+4kstnjbb6hMiPbt7777ro0YMaLOc9rG1113nW2//fZuXbSP2Hfffd3+Pd3DDz/saopqPfSZTJ06NeV5ZdL/5je/sa222sq1D+3f1P7Sb0GPl+p56aWX3H5Qy1Sd65/97Ge2cOHCOuunfVT//v0Tn622cabfC5Xd0eekOppax80339yVhEiv4XvffffZkCFDXFvr2rWre+/aBk0JwJ1++umJ+et96vNJ3ocn1wBubHulO/bYY93vqUrJpFPgS8trzAMPPODem7a/5qULtF999VXG75zGH3zwwe7f+gzOOOMMq6mpsaZ66qmnXKa32o320Q8++GDK8/XVK6+vlu//+3//z/1OxD8XtXNlaDa2DmoXyiDXsUtTfse0fLVLUXuKf4caKxuk+f7iF79w20rbV5/H73//+zrvV+3zyCOPtPXWW8923333nB9XNdZ+M9W0jdcDbsr+Sd9jXeDRd1L7ugkTJtiTTz5ZZ55NPZ7SvlJ3dak0EgCgOFAeAQBakYIlohNsUQauAgg66dEJ6quvvmqXXXaZzZo1yx566KFE0EEnkTp50a1vyqbQCVj6SVuyQw45xD7++GO79957XU1TnZRIPJCYTsFKBdF0S/8pp5zi1k/rpZMJdcKlE/xkf/rTn1xmiU48ddKgkxEFf7T+ogw1nQzpJEnBUZ1o6KT1sccecyf73bp1s9b0wx/+0G0HBZfinasoAKb3oL96z/Fx8Vses3m/6bSdFfg86aST3LbT5yE77LCD+6v1+MEPfuBO5PSZ6oRNgTyd1P/zn/9MbG8FQJp68qVtqmxfBdo030svvdS9L52MK2BVH62nghz6rJWp/eijj7pgkJY9btw4N40CWfocFXCInzg3NM+GKAiogKFOyHXyr/et7aTbS3N1S6e2gYIFCrRpGyiQdsMNN9jbb7/tglfxLPPly5c3qf6fpm+ozeb6+xP/XBSMUtvViby+82ofCk7ohL25FNDQd1MBDgXB4mVamrrNzjvvPBe01WeoQcFU7Z/Ss1Kb03bl17/+tdtmeu8KTn377bcuCKF94ve+972UdqSAp75bakt6jWrIKqiiOxxEty0rgKKAuOanoIwCbQqkaT906qmn1rmAofYwduxYF+zR56Ygst6zxumzEH3W9VFZGkle1zgFj7WNtX7a9+uz0D7nlVdeccGuOL1f7d/1PVSwSBeWFLCZM2dO4rdDHQ1qWQrcqD2ofajN6P0qWJRefkffX7Wd888/302r77S2yf3335+Y5pxzznFt8cADD3Rt5J133nF/078jaucK+Gmfrnaz0UYbuXXR6+fOnevmLdOmTXPfcV2UUEBX9DmqPaVv+2QKzOq78+yzz7ptpmClAlm6EKllal+erCnbK93RRx9td955p5tvckdf8+bNs2eeecZtp4bEvyvav+o3W/sABfP03vSdSb7LQcFZbcehQ4e679zTTz9tf/7zn11QUfu+xihgd9hhh7nvhoLNaqcKhCowrSBdtrTu2tdqv6/PTOuqddb8FADNRL/dOk7Reqj8RxAETfod02+p9if6TBQ4HTRokJtf/G8muuih75r2CfreKeiqYyf9NuliVzJthy222ML93sUD+rk6rmpu+23q/kkXJnTBVN8ZzU/HSQqcq90ny+Z4SgFmbQd9H9uiAzsAQB6EAICcmzJlis4ewqeffjpcuHBh+MUXX4T33Xdf2LNnz7BDhw7hl19+Gc6cOdNNc8IJJ6S89owzznDjn3nmGff4oYceco9ff/31Bpepac4///zE4yuvvNKNmz17dp1pN9544/DYY49NPD7ttNPctC+++GJi3LJly8JNNtkkHDhwYFhTU+PGPfvss266QYMGhWvWrElMe91117nx//nPf9zjt99+2z1+4IEHwvZgwYIFbn1uvPFG93jx4sWh7/vhz3/+87BPnz6J6U455ZSwR48eYTQazer9irantmucPvf0zyRu+PDh4fbbbx+uXr06MU7L3G233cItttgiMU6fnebRlEHrGhdf7/Ttr3VJ/+lfuXJlnfUbOXJkuOmmm6aM23bbbcM999wzbAm9Xsv/85//nBin7Tp48OCwd+/e4dq1a1Pet75Hya/NtPz07a42rNfefffdKdNNnTq1zni9tinbNn25+f7+6DntK77//e+HVVVVieluv/32jOvTVPH3e/bZZ6eMb+o20/eovLw83H///RPfEfnd737npkveJs1tu926dQvHjRvXpHZ05513JsZpm/Xt2zccNWpUYty1117rprvrrrsS49TGhg0bFnbu3DlcunRpyrp27drVvcdk2u+mt8WGnHvuuW56ff7JtD/XeO1j0iVvS02jbfzpp58mxr3zzjtu/PXXX9/g93bGjBl1tkv8t2jEiBEpy5kwYUIYBIHbF8q8efPCSCQSHnzwwSnzvOCCC+p8thdffHHYqVOn8OOPP06ZVu1K85wzZ457fOqpp7ptWl1dHWbj4Ycfdsv84x//mDL+0EMPDT3PS9k2Td1e8e0Q/z3Ud3LDDTcMDzvssJRlXH311W4Zn332Wb3rpzak/dV2220Xrlq1KjH+sccec8s477zz6nznLrroopR57LTTTuGQIUMa3Rba1+j1//znPxPjlixZEvbr18/No6F9e6b3rc+7S5cu4dChQ1PWXZLbh75j2ueLll1WVhaeeOKJiX1ZNr9j+h1K/543ZI899nDr+Pnnn9e7fvH3e8QRR6RMk8vjqqa03/g+Pfm9NXX/pN9BTaf2HqfPZOutt06ZZzbHU19//bWb9vLLL290WgBAYaA8AgDkkW6RVSaHbvFURpQyFZXpocwUdUgmumU1mTJDJH5rdTxjR1kVmW7lzAWt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gHaKTFsAAAAAAAAArVQeIcscUr80c05L810DAAAAAAAAaJuOyLIdsjR58mQbOHCgVVZW2tChQ+21116rd9pbbrnFfvjDH9p6663nhhEjRtSZ/rjjjjPP81KGfffd1wouaKuNstVWW9ngwYPdcMIJJ9gFF1xgp512mnv+ueeec+Ob6vbbb3cb429/+1ti3GOPPWZ77bVX4rGe33777RPL1PDtt98mnv/ss8/M9327+OKL68y7W7dubvrtttvOfvSjH9nHH3/cpPVqyjy33XZb+8lPfmJz5sxx2yG+buXl5SnbaNmyZU3eHgAAAAAAAECh8fygWUM27r//fps4caKdf/759tZbb9mOO+5oI0eOtAULFmScXnHKI444wp599lmbMWOGDRgwwPbZZx/76quvUqZTkHbu3LmJ4d5777WCzLTVBpo5c6Yb/vrXv7Z4fhtvvLGdd955tnbt2nqnefHFFxPL1NCzZ8/Ec7fddpvtvffeNmXKFAvDMOV1CtRq+vfee8+GDBmSCC43pinzfP/9923LLbe0CRMmuO0QX7f+/funbKMuXbpkvU0AAAAAAACAguHVlkfIZvCyC19effXVduKJJ9ro0aNtm222sZtuusk6duzo4niZ3H333fab3/zGJVVuvfXWLn4XjUZt+vTpKdNVVFRY3759E4OycvOpYMojaMN973vfc+nN2aqpqXHZr5MmTXLB0WeeeabeaYcPH26ff/55TuepaP5HH31kzbFmzRpbunRpygAAAAAAAACUUqbt0rT4mGJm6ZTs+eabb7oSB3G6S16PlUXbFCtXrrSqqirr0aNHnYzc3r17uzvnTzrppJQ7/AsqaHvYYYclbv1/6KGHcjLPSy+91C6//PJ6A5eqPxFfpjJd45588knbcMMNXXR9zJgxduutt2Z8vaLoWtfDDz+80XVp6jwV3H3ggQdcBm9zXHbZZa7UQnxQijYAAAAAAABQcFz2bLY1bX33UsXEkmNkipml++abb1wsrk+fPinj9XjevHlNWsWzzjrL3SGfHPhVaYQ777zTZd8qNvn888+7cqhaVr5E8jVj3fqfXLf2nXfeafE8Fcn+6U9/6jbOsGHDMpZH6N69e53xCqgef/zx7t9HHXWUK7Pw3XffJdKYVbNC66q6s4qiv/rqq42uS1PnKcoQ/vOf/9ys93zOOee4OhxxClgTuAUAAAAA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XDnv37izJC+++KJppHn11VcHjJ8xY4YsX77clF3o3bu3PPTQQ6YVb6UNbZU2R96xY4ds3bpVOnfuXGi6jtMUuyycrvP777+X119/3ZQxKA/6GEU9fklmzZplgmrvoLUzAAAAAAAAgMrGsu0yDUozMf+MTDOzokycOLHYzsK8g3+p1LLS3FBr3xbVynbKlClyzTXXyJVXXmkaoU6YMMHUva3UHZE5bSrshiVLlsj69evN/7Wcgr7ZoTJp0iR59NFHfff1lwSCWwAAAAAAAFQ2ZalRa/80/8GDByUhIcE3Pj4+vsj5x44dK4MHDy5xndp3ltaZPXr0aMB4LWGak5NjppXmb3/7mynhOnDgwKAaqT755JOmdXBx2x32oW1WVpZceumlpnMxrS1xxx13BEzXcQ8++GCZ1n3VVVeZ5ceMGVNonTrNv6btvHnzpLxpi92iHr8k+kYG+2YCAAAAAAAA4UobazoObe0f59fA1j+0LU6DBg3MUBq9Gj43N9dcGe+9yn7dunViWZYJWYMpjaD9WQXzWFr3VkukOsn4wqo8whtvvCELFy40ifi4cePMk1+7dq1vuta5/e6770wd2rIYMWKEZGZmmqbLXsuWLZP333/f1JZwmz6GtuBdvHixb5zuHM8//7zrjw0AAAAAAACEkga2lsPBdhjyBktr0fbo0cP0PbV582bZsGGDjBo1Svr27SupqalmnkOHDknr1q3NdH979+6VDz74QB544IFC633rrbfkhRdekO3bt5v5NOucOXOmjB492tH2hbylbZ8+faR69epy6tQpadu2raxatcqXZmuIq+UBtKMubZ6sva1pwFqzZs0yPVajRo3M8hoIT5482dSwuPjii02QmpKSUubnoCULGjdu7Luv5Qu0RW1B+hgffvihKbegBYlr164t1apVM73JAQAAAAAAABHf0tZhaVTbxVKqWiJVg9q0tDSJjY01nYZlZGT4pufn58vu3btNGQR/L730kskCu3XrVmidmvUtWLDAXGmv296qVSt59tlnTTjsRIwdDkVk4Sgg1mLLR7Kzg2oSDgAAAMC5b2c/XGjc+ROe46WsZMat+k+hcXN7XRSSbQEAb67TMDlZjh07FlW5jjfPaj70zxIb56wxpvVDnux78b6oe81C3tIWAAAAAAAAQOT7OR2RRZuIC22117eimibfdNNNMmfOnDKvt0OH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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "weights_df_b, pos_df_b, gross_pnl_b, net_pnl_b = run_bucketed_filtered_oos(\n", " prices_test = prices_test,\n", " prices_train = prices_train,\n", " fold_records = fold_records_hyst,\n", " selected_keys = diversified_df[\"basket\"].tolist(),\n", " exit_thresh = EXIT_THRESHOLD,\n", " tcost_bps = TCOST_BPS,\n", " )" ] }, { "cell_type": "markdown", "id": "b48d87f3", "metadata": {}, "source": [ "### 10. Market Neutrality Analysis" ] }, { "cell_type": "markdown", "id": "e67542a3", "metadata": {}, "source": [ "Evaluated market neutrality to ensure that the strategy’s returns are driven by **relative value (spread dynamics)** rather than broad market movements. Since the baskets are constructed using cointegration, they are theoretically hedged, but in practice, **residual exposure to market direction can still remain** due to imperfect weights, regime shifts, or dynamic rebalancing." ] }, { "cell_type": "code", "execution_count": 37, "id": "e44000a7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " OLS Regression Results \n", "==============================================================================\n", "Dep. Variable: y R-squared: 0.003\n", "Model: OLS Adj. R-squared: -0.006\n", "Method: Least Squares F-statistic: 0.3017\n", "Date: Fri, 20 Mar 2026 Prob (F-statistic): 0.740\n", "Time: 12:16:24 Log-Likelihood: 987.68\n", "No. Observations: 220 AIC: -1969.\n", "Df Residuals: 217 BIC: -1959.\n", "Df Model: 2 \n", "Covariance Type: nonrobust \n", "==============================================================================\n", " coef std err t P>|t| [0.025 0.975]\n", "------------------------------------------------------------------------------\n", "const 0.0003 0.000 1.440 0.151 -9.86e-05 0.001\n", "BTC 0.0055 0.015 0.360 0.719 -0.025 0.036\n", "ETH 0.0003 0.010 0.029 0.977 -0.019 0.020\n", "==============================================================================\n", "Omnibus: 240.434 Durbin-Watson: 2.514\n", "Prob(Omnibus): 0.000 Jarque-Bera (JB): 16263.199\n", "Skew: 4.099 Prob(JB): 0.00\n", "Kurtosis: 44.316 Cond. No. 96.0\n", "==============================================================================\n", "\n", "Notes:\n", "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n", "\n", "BTC beta : 0.0055 (t=0.36)\n", "ETH beta : 0.0003 (t=0.03)\n", "Alpha (daily) : 0.000267 (t=1.44)\n", "R-squared : 0.0028\n", "\n", "Corr(PnL, BTC ret) : 0.053\n", "Corr(PnL, ETH ret) : 0.047\n", "Corr(PnL, Alt index): 0.061\n", "\n", "Mean PnL on BTC-up days : 0.000522 (n=100)\n", "Mean PnL on BTC-down days : 0.000027 (n=120)\n", "Ratio (down/up) : 0.05x\n" ] } ], "source": [ "# ── 1. Net market beta regression ────────────────────────────────────────────\n", "btc_ret = np.log(prices[\"BTCUSDT\"]).diff().reindex(net_pnl_b.index).fillna(0)\n", "eth_ret = np.log(prices[\"ETHUSDT\"]).diff().reindex(net_pnl_b.index).fillna(0)\n", "\n", "# Regress net PnL on BTC and ETH returns\n", "X = sm.add_constant(pd.DataFrame({\"BTC\": btc_ret, \"ETH\": eth_ret}))\n", "y = net_pnl_b\n", "\n", "ols = sm.OLS(y, X).fit()\n", "print(ols.summary())\n", "print(f\"\\nBTC beta : {ols.params['BTC']:.4f} (t={ols.tvalues['BTC']:.2f})\")\n", "print(f\"ETH beta : {ols.params['ETH']:.4f} (t={ols.tvalues['ETH']:.2f})\")\n", "print(f\"Alpha (daily) : {ols.params['const']:.6f} (t={ols.tvalues['const']:.2f})\")\n", "print(f\"R-squared : {ols.rsquared:.4f}\")\n", "\n", "# ── 2. Rolling beta (60-day) to see if exposure is stable or drifting ────────\n", "rolling_beta_btc = []\n", "for i in range(60, len(net_pnl_b)):\n", " window_pnl = net_pnl_b.iloc[i-60:i]\n", " window_btc = btc_ret.iloc[i-60:i]\n", " X_w = sm.add_constant(window_btc)\n", " try:\n", " b = sm.OLS(window_pnl, X_w).fit().params[\"BTCUSDT\"]\n", " except Exception:\n", " b = np.nan\n", " rolling_beta_btc.append((net_pnl_b.index[i], b))\n", "\n", "rolling_beta_btc = pd.Series(\n", " [x[1] for x in rolling_beta_btc],\n", " index=[x[0] for x in rolling_beta_btc]\n", ")\n", "\n", "# ── 3. Correlation of daily PnL with broad alt market ────────────────────────\n", "# Build equal-weight alt index from your universe (excl BTC/ETH)\n", "alt_cols = [c for c in prices.columns if c not in (\"BTCUSDT\", \"ETHUSDT\")]\n", "alt_ret = np.log(prices[alt_cols]).diff().mean(axis=1).reindex(net_pnl_b.index)\n", "\n", "print(f\"\\nCorr(PnL, BTC ret) : {net_pnl_b.corr(btc_ret):.3f}\")\n", "print(f\"Corr(PnL, ETH ret) : {net_pnl_b.corr(eth_ret):.3f}\")\n", "print(f\"Corr(PnL, Alt index): {net_pnl_b.corr(alt_ret):.3f}\")\n", "\n", "# ── 4. PnL decomposition: up-market vs down-market days ──────────────────────\n", "btc_up = net_pnl_b[btc_ret > 0]\n", "btc_down = net_pnl_b[btc_ret <= 0]\n", "\n", "print(f\"\\nMean PnL on BTC-up days : {btc_up.mean():.6f} (n={len(btc_up)})\")\n", "print(f\"Mean PnL on BTC-down days : {btc_down.mean():.6f} (n={len(btc_down)})\")\n", "print(f\"Ratio (down/up) : {btc_down.mean()/btc_up.mean():.2f}x\")" ] }, { "cell_type": "markdown", "id": "32bd454b", "metadata": {}, "source": [ "`Key Takeaways`\n", "- The regression results indicate that the strategy has **negligible exposure to major market factors** with both BTC and ETH betas close to zero and statistically insignificant. \n", "- The very low R² confirms that **portfolio returns are not explained by broad market movements** but are instead driven by relative value dynamics within the baskets. \n", "- The near-zero correlations between PnL and BTC, ETH, and the altcoin index further reinforce that the strategy is **effectively market neutral in practice**. \n", "- The estimated alpha is positive but not statistically significant — this is primarily a **sample size problem** rather than evidence of a weak edge. With only 220 OOS days, there simply aren't enough observations to confirm the signal yet. \n", "- There’s some asymmetry in up vs down markets, but given the effective sample size and dependence across trades, I wouldn’t treat it as statistically significant without further testing.\n", "- The observed Sharpe of 1.76 is built on only 9 round-trip trades and is therefore **not statistically meaningful** at this stage. Several dozen independent trades at minimum would be needed before the Sharpe ratio becomes a reliable indicator of true edge." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }