Files
stefan-jansen--machine-lear…/02_financial_data_universe/10_crypto_perps_eda.ipynb
T
2026-07-13 13:26:28 +08:00

848 lines
27 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "34eda0f3",
"metadata": {
"papermill": {
"duration": 0.002064,
"end_time": "2026-06-13T02:33:28.977337+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:28.975273+00:00",
"status": "completed"
}
},
"source": [
"# Crypto Perps — Exploratory Data Analysis\n",
"\n",
"**Docker image**: `ml4t`\n",
"\n",
"## Purpose\n",
"\n",
"Profile the Binance Futures hourly OHLCV dataset for 19 perpetual contracts\n",
"alongside the 8-hourly Premium Index that captures the perpetualspot\n",
"basis. The notebook anchors data shape, units, coverage, and OHLC integrity\n",
"before the strategy work begins in Chapter 6.\n",
"\n",
"## Learning Objectives\n",
"\n",
"- Load hourly OHLCV and 8-hourly premium index data via the canonical loaders.\n",
"- Document premium-index units (decimal, multiply by 100 for percent).\n",
"- Quantify cross-frequency join coverage between OHLCV and premium data.\n",
"- Run OHLC invariant checks and inspect for time-stamp gaps.\n",
"\n",
"## Book Reference\n",
"\n",
"Chapter 2 §2.2 (asset-class market data landscape — digital assets).\n",
"\n",
"## Prerequisites\n",
"\n",
"- Familiarity with daily OHLCV equity data (`01_us_equities_eda`).\n",
"- The Binance Futures parquet at `$ML4T_DATA_PATH/crypto/` (OHLCV + premium).\n",
"- Methodology continues in `11_crypto_premium_analysis`; the case-study\n",
" pipeline lives under `case_studies/crypto_perps_funding/`."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "f2410448",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:28.980599Z",
"iopub.status.busy": "2026-06-13T02:33:28.980491Z",
"iopub.status.idle": "2026-06-13T02:33:30.219451Z",
"shell.execute_reply": "2026-06-13T02:33:30.218950Z"
},
"papermill": {
"duration": 1.241442,
"end_time": "2026-06-13T02:33:30.220191+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:28.978749+00:00",
"status": "completed"
}
},
"outputs": [],
"source": [
"\"\"\"Crypto Perps EDA — hourly OHLCV and premium index exploration.\"\"\"\n",
"\n",
"import polars as pl\n",
"\n",
"from data import load_crypto_perps, load_crypto_premium\n",
"from utils.data_quality import check_ohlc_invariants, per_asset_stats"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f028d40b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.223486Z",
"iopub.status.busy": "2026-06-13T02:33:30.223403Z",
"iopub.status.idle": "2026-06-13T02:33:30.225251Z",
"shell.execute_reply": "2026-06-13T02:33:30.224946Z"
},
"papermill": {
"duration": 0.004413,
"end_time": "2026-06-13T02:33:30.225693+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.221280+00:00",
"status": "completed"
},
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"MAX_SYMBOLS = 0 # 0 = all symbols"
]
},
{
"cell_type": "markdown",
"id": "62781cf4",
"metadata": {
"papermill": {
"duration": 0.000842,
"end_time": "2026-06-13T02:33:30.227452+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.226610+00:00",
"status": "completed"
}
},
"source": [
"## 1. Load and Inspect OHLCV\n",
"\n",
"Hourly OHLCV data from Binance Futures for 19 cryptocurrencies.\n",
"Trading is 24/7 (8,760 hours/year vs 252 days for equities)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "b3bc2125",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.229623Z",
"iopub.status.busy": "2026-06-13T02:33:30.229559Z",
"iopub.status.idle": "2026-06-13T02:33:30.269019Z",
"shell.execute_reply": "2026-06-13T02:33:30.268500Z"
},
"papermill": {
"duration": 0.040998,
"end_time": "2026-06-13T02:33:30.269305+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.228307+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== OHLCV Dataset ===\n",
"Shape: (866484, 7)\n",
"Columns: ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'symbol']\n"
]
}
],
"source": [
"ohlcv = load_crypto_perps(frequency=\"1h\")\n",
"\n",
"print(\"=== OHLCV Dataset ===\")\n",
"print(f\"Shape: {ohlcv.shape}\")\n",
"print(f\"Columns: {ohlcv.columns}\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "599855a3",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.271848Z",
"iopub.status.busy": "2026-06-13T02:33:30.271778Z",
"iopub.status.idle": "2026-06-13T02:33:30.274032Z",
"shell.execute_reply": "2026-06-13T02:33:30.273658Z"
},
"papermill": {
"duration": 0.003918,
"end_time": "2026-06-13T02:33:30.274317+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.270399+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Schema:\n",
" timestamp: Datetime(time_unit='ms', time_zone='UTC')\n",
" open: Float64\n",
" high: Float64\n",
" low: Float64\n",
" close: Float64\n",
" volume: Float64\n",
" symbol: String\n"
]
}
],
"source": [
"# Schema overview\n",
"print(\"\\nSchema:\")\n",
"for col, dtype in ohlcv.schema.items():\n",
" print(f\" {col}: {dtype}\")"
]
},
{
"cell_type": "markdown",
"id": "51d44bf8",
"metadata": {
"papermill": {
"duration": 0.000888,
"end_time": "2026-06-13T02:33:30.276173+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.275285+00:00",
"status": "completed"
}
},
"source": [
"## 2. Coverage Summary"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "5c9db57a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.278342Z",
"iopub.status.busy": "2026-06-13T02:33:30.278276Z",
"iopub.status.idle": "2026-06-13T02:33:30.285289Z",
"shell.execute_reply": "2026-06-13T02:33:30.284906Z"
},
"papermill": {
"duration": 0.00843,
"end_time": "2026-06-13T02:33:30.285512+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.277082+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Coverage ===\n",
"Number of symbols: 19\n",
"\n",
"Symbols: AAVEUSDT, ADAUSDT, APTUSDT, ATOMUSDT, AVAXUSDT, BNBUSDT, BTCUSDT, COMPUSDT, DOGEUSDT, DOTUSDT, ETHUSDT, INJUSDT, LINKUSDT, MKRUSDT, NEARUSDT, SOLUSDT, SUIUSDT, UNIUSDT, XRPUSDT\n"
]
}
],
"source": [
"# Symbols and date range\n",
"symbols = ohlcv[\"symbol\"].unique().sort().to_list()\n",
"print(\"=== Coverage ===\")\n",
"print(f\"Number of symbols: {len(symbols)}\")\n",
"print(f\"\\nSymbols: {', '.join(symbols)}\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "92032764",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.288070Z",
"iopub.status.busy": "2026-06-13T02:33:30.287999Z",
"iopub.status.idle": "2026-06-13T02:33:30.296853Z",
"shell.execute_reply": "2026-06-13T02:33:30.296505Z"
},
"papermill": {
"duration": 0.010672,
"end_time": "2026-06-13T02:33:30.297260+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.286588+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Date range: 2020-01-01 00:00:00+00:00 to 2025-12-31 23:00:00+00:00\n",
"Unique hours: 52,608\n"
]
}
],
"source": [
"# Overall date range\n",
"date_range = ohlcv.select(\n",
" [\n",
" pl.col(\"timestamp\").min().alias(\"start\"),\n",
" pl.col(\"timestamp\").max().alias(\"end\"),\n",
" pl.col(\"timestamp\").n_unique().alias(\"unique_hours\"),\n",
" ]\n",
")\n",
"\n",
"print(f\"\\nDate range: {date_range['start'][0]} to {date_range['end'][0]}\")\n",
"print(f\"Unique hours: {date_range['unique_hours'][0]:,}\")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c9658965",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.300212Z",
"iopub.status.busy": "2026-06-13T02:33:30.300078Z",
"iopub.status.idle": "2026-06-13T02:33:30.310731Z",
"shell.execute_reply": "2026-06-13T02:33:30.310442Z"
},
"papermill": {
"duration": 0.012868,
"end_time": "2026-06-13T02:33:30.311341+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.298473+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Symbol Statistics (top 5 by volume):\n"
]
},
{
"data": {
"text/html": [
"<div><style>\n",
".dataframe > thead > tr,\n",
".dataframe > tbody > tr {\n",
" text-align: right;\n",
" white-space: pre-wrap;\n",
"}\n",
"</style>\n",
"<small>shape: (5, 6)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>symbol</th><th>rows</th><th>start</th><th>end</th><th>avg_price</th><th>avg_volume</th></tr><tr><td>str</td><td>u32</td><td>datetime[ms, UTC]</td><td>datetime[ms, UTC]</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>&quot;DOGEUSDT&quot;</td><td>48015</td><td>2020-07-10 09:00:00 UTC</td><td>2025-12-31 23:00:00 UTC</td><td>0.137195</td><td>2.7592e8</td></tr><tr><td>&quot;XRPUSDT&quot;</td><td>52360</td><td>2020-01-06 08:00:00 UTC</td><td>2025-12-31 23:00:00 UTC</td><td>0.89747</td><td>5.2917e7</td></tr><tr><td>&quot;ADAUSDT&quot;</td><td>51880</td><td>2020-01-31 08:00:00 UTC</td><td>2025-12-31 23:00:00 UTC</td><td>0.644055</td><td>3.0205e7</td></tr><tr><td>&quot;SUIUSDT&quot;</td><td>23360</td><td>2023-05-03 16:00:00 UTC</td><td>2025-12-31 23:00:00 UTC</td><td>1.917873</td><td>1.2587e7</td></tr><tr><td>&quot;NEARUSDT&quot;</td><td>45568</td><td>2020-10-15 08:00:00 UTC</td><td>2025-12-31 23:00:00 UTC</td><td>4.297627</td><td>2.1940e6</td></tr></tbody></table></div>"
],
"text/plain": [
"shape: (5, 6)\n",
"┌──────────┬───────┬─────────────────────────┬─────────────────────────┬───────────┬────────────┐\n",
"│ symbol ┆ rows ┆ start ┆ end ┆ avg_price ┆ avg_volume │\n",
"│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n",
"│ str ┆ u32 ┆ datetime[ms, UTC] ┆ datetime[ms, UTC] ┆ f64 ┆ f64 │\n",
"╞══════════╪═══════╪═════════════════════════╪═════════════════════════╪═══════════╪════════════╡\n",
"│ DOGEUSDT ┆ 48015 ┆ 2020-07-10 09:00:00 UTC ┆ 2025-12-31 23:00:00 UTC ┆ 0.137195 ┆ 2.7592e8 │\n",
"│ XRPUSDT ┆ 52360 ┆ 2020-01-06 08:00:00 UTC ┆ 2025-12-31 23:00:00 UTC ┆ 0.89747 ┆ 5.2917e7 │\n",
"│ ADAUSDT ┆ 51880 ┆ 2020-01-31 08:00:00 UTC ┆ 2025-12-31 23:00:00 UTC ┆ 0.644055 ┆ 3.0205e7 │\n",
"│ SUIUSDT ┆ 23360 ┆ 2023-05-03 16:00:00 UTC ┆ 2025-12-31 23:00:00 UTC ┆ 1.917873 ┆ 1.2587e7 │\n",
"│ NEARUSDT ┆ 45568 ┆ 2020-10-15 08:00:00 UTC ┆ 2025-12-31 23:00:00 UTC ┆ 4.297627 ┆ 2.1940e6 │\n",
"└──────────┴───────┴─────────────────────────┴─────────────────────────┴───────────┴────────────┘"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Per-symbol statistics\n",
"symbol_stats = per_asset_stats(\n",
" ohlcv,\n",
" time_col=\"timestamp\",\n",
" asset_col=\"symbol\",\n",
" price_col=\"close\",\n",
" volume_col=\"volume\",\n",
")\n",
"\n",
"print(\"\\nSymbol Statistics (top 5 by volume):\")\n",
"symbol_stats.sort(\"avg_volume\", descending=True).head(5)"
]
},
{
"cell_type": "markdown",
"id": "74c26d5a",
"metadata": {
"papermill": {
"duration": 0.001876,
"end_time": "2026-06-13T02:33:30.315436+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.313560+00:00",
"status": "completed"
}
},
"source": [
"## 3. Premium Index Data\n",
"\n",
"The premium index measures the spread between perpetual futures and spot prices:\n",
"\n",
"**Premium = (Perpetual Price - Spot Price) / Spot Price**\n",
"\n",
"- Positive premium: Futures above spot (bullish sentiment)\n",
"- Negative premium: Futures below spot (bearish sentiment)\n",
"\n",
"### Units\n",
"\n",
"Premium values are stored as **decimals** (0.001 = 0.1%). When displaying,\n",
"multiply by 100 for percentage representation."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "5bd439f9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.318383Z",
"iopub.status.busy": "2026-06-13T02:33:30.318294Z",
"iopub.status.idle": "2026-06-13T02:33:30.330166Z",
"shell.execute_reply": "2026-06-13T02:33:30.329851Z"
},
"papermill": {
"duration": 0.013671,
"end_time": "2026-06-13T02:33:30.330466+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.316795+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Premium Dataset ===\n",
"Shape: (107839, 6)\n",
"Columns: ['timestamp', 'symbol', 'premium_index_open', 'premium_index_high', 'premium_index_low', 'premium_index_close']\n"
]
}
],
"source": [
"premium = load_crypto_premium(frequency=\"8h\")\n",
"print(\"=== Premium Dataset ===\")\n",
"print(f\"Shape: {premium.shape}\")\n",
"print(f\"Columns: {premium.columns}\")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "ac1c5ece",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.333347Z",
"iopub.status.busy": "2026-06-13T02:33:30.333260Z",
"iopub.status.idle": "2026-06-13T02:33:30.336126Z",
"shell.execute_reply": "2026-06-13T02:33:30.335774Z"
},
"papermill": {
"duration": 0.004926,
"end_time": "2026-06-13T02:33:30.336661+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.331735+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Premium Range (decimals):\n",
" Mean: -0.000111 (-0.0111%)\n",
" Std: 0.001137 (0.1137%)\n",
" Min: -0.191547 (-19.1547%)\n",
" Max: 0.012701 (1.2701%)\n"
]
}
],
"source": [
"# Premium range — values are decimals, not percentages\n",
"premium_range = premium.select(\n",
" [\n",
" pl.col(\"premium_index_close\").min().alias(\"min\"),\n",
" pl.col(\"premium_index_close\").max().alias(\"max\"),\n",
" pl.col(\"premium_index_close\").mean().alias(\"mean\"),\n",
" pl.col(\"premium_index_close\").std().alias(\"std\"),\n",
" ]\n",
")\n",
"\n",
"print(\"\\nPremium Range (decimals):\")\n",
"print(f\" Mean: {premium_range['mean'][0]:.6f} ({premium_range['mean'][0] * 100:.4f}%)\")\n",
"print(f\" Std: {premium_range['std'][0]:.6f} ({premium_range['std'][0] * 100:.4f}%)\")\n",
"print(f\" Min: {premium_range['min'][0]:.6f} ({premium_range['min'][0] * 100:.4f}%)\")\n",
"print(f\" Max: {premium_range['max'][0]:.6f} ({premium_range['max'][0] * 100:.4f}%)\")"
]
},
{
"cell_type": "markdown",
"id": "bd327365",
"metadata": {
"papermill": {
"duration": 0.001018,
"end_time": "2026-06-13T02:33:30.338980+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.337962+00:00",
"status": "completed"
}
},
"source": [
"## 4. Data Quality"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b2303360",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.341624Z",
"iopub.status.busy": "2026-06-13T02:33:30.341557Z",
"iopub.status.idle": "2026-06-13T02:33:30.349627Z",
"shell.execute_reply": "2026-06-13T02:33:30.349335Z"
},
"papermill": {
"duration": 0.009977,
"end_time": "2026-06-13T02:33:30.350085+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.340108+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== OHLC Invariants ===\n",
" [OK] high_gte_low: 100.00%\n",
" [OK] high_gte_open: 100.00%\n",
" [OK] high_gte_close: 100.00%\n",
" [OK] low_lte_open: 100.00%\n",
" [OK] low_lte_close: 100.00%\n",
" [OK] volume_non_negative: 100.00%\n"
]
}
],
"source": [
"# OHLC invariants\n",
"invariants = check_ohlc_invariants(ohlcv)\n",
"print(\"=== OHLC Invariants ===\")\n",
"for row in invariants.iter_rows(named=True):\n",
" status = \"[OK]\" if row[\"valid_pct\"] >= 99.99 else \"[WARN]\"\n",
" print(f\" {status} {row['check']}: {row['valid_pct']:.2f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "81337cc6",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.352943Z",
"iopub.status.busy": "2026-06-13T02:33:30.352862Z",
"iopub.status.idle": "2026-06-13T02:33:30.355138Z",
"shell.execute_reply": "2026-06-13T02:33:30.354906Z"
},
"papermill": {
"duration": 0.004193,
"end_time": "2026-06-13T02:33:30.355537+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.351344+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Null values: OHLCV=0, Premium=0\n"
]
}
],
"source": [
"# Check for nulls\n",
"ohlcv_nulls = ohlcv.null_count().sum_horizontal()[0]\n",
"premium_nulls = premium.null_count().sum_horizontal()[0]\n",
"print(f\"\\nNull values: OHLCV={ohlcv_nulls}, Premium={premium_nulls}\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "ed1e0266",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.358207Z",
"iopub.status.busy": "2026-06-13T02:33:30.358135Z",
"iopub.status.idle": "2026-06-13T02:33:30.363600Z",
"shell.execute_reply": "2026-06-13T02:33:30.363206Z"
},
"papermill": {
"duration": 0.007243,
"end_time": "2026-06-13T02:33:30.363925+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.356682+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Gaps > 1 hour in BTC: 0\n"
]
}
],
"source": [
"# Check for gaps > 1 hour (use BTC as reference)\n",
"btc = ohlcv.filter(pl.col(\"symbol\") == \"BTCUSDT\").sort(\"timestamp\")\n",
"btc_gaps = btc.with_columns(pl.col(\"timestamp\").diff().dt.total_hours().alias(\"hours_diff\")).filter(\n",
" pl.col(\"hours_diff\") > 1\n",
")\n",
"\n",
"print(f\"\\nGaps > 1 hour in BTC: {len(btc_gaps)}\")\n",
"if len(btc_gaps) > 0:\n",
" print(\"(Small gaps expected during exchange maintenance)\")"
]
},
{
"cell_type": "markdown",
"id": "ffa0b798",
"metadata": {
"papermill": {
"duration": 0.001072,
"end_time": "2026-06-13T02:33:30.366245+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.365173+00:00",
"status": "completed"
}
},
"source": [
"## 5. Joining OHLCV and Premium\n",
"\n",
"Use left join to preserve all OHLCV rows and identify missing premium coverage."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "c311a381",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.369052Z",
"iopub.status.busy": "2026-06-13T02:33:30.368962Z",
"iopub.status.idle": "2026-06-13T02:33:30.412049Z",
"shell.execute_reply": "2026-06-13T02:33:30.411644Z"
},
"papermill": {
"duration": 0.045382,
"end_time": "2026-06-13T02:33:30.412749+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.367367+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Join Coverage ===\n",
"OHLCV rows: 866,484\n",
"Premium rows: 107,839\n",
"Combined rows: 866,484\n",
"Missing premium: 758,716 (87.56%)\n",
"Coverage: 12.44%\n"
]
}
],
"source": [
"# Left join to identify missing premium data\n",
"combined = ohlcv.join(premium, on=[\"timestamp\", \"symbol\"], how=\"left\")\n",
"\n",
"# Coverage analysis\n",
"total_rows = len(combined)\n",
"missing_premium = combined.filter(pl.col(\"premium_index_close\").is_null()).height\n",
"coverage_pct = (total_rows - missing_premium) / total_rows * 100\n",
"\n",
"print(\"=== Join Coverage ===\")\n",
"print(f\"OHLCV rows: {len(ohlcv):,}\")\n",
"print(f\"Premium rows: {len(premium):,}\")\n",
"print(f\"Combined rows: {total_rows:,}\")\n",
"print(f\"Missing premium: {missing_premium:,} ({100 - coverage_pct:.2f}%)\")\n",
"print(f\"Coverage: {coverage_pct:.2f}%\")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "011f6a3b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-06-13T02:33:30.417422Z",
"iopub.status.busy": "2026-06-13T02:33:30.417324Z",
"iopub.status.idle": "2026-06-13T02:33:30.434943Z",
"shell.execute_reply": "2026-06-13T02:33:30.434671Z"
},
"papermill": {
"duration": 0.020165,
"end_time": "2026-06-13T02:33:30.435349+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.415184+00:00",
"status": "completed"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Missing premium by symbol (top 5):\n"
]
},
{
"data": {
"text/html": [
"<div><style>\n",
".dataframe > thead > tr,\n",
".dataframe > tbody > tr {\n",
" text-align: right;\n",
" white-space: pre-wrap;\n",
"}\n",
"</style>\n",
"<small>shape: (5, 2)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>symbol</th><th>len</th></tr><tr><td>str</td><td>u32</td></tr></thead><tbody><tr><td>&quot;BTCUSDT&quot;</td><td>46053</td></tr><tr><td>&quot;ETHUSDT&quot;</td><td>46053</td></tr><tr><td>&quot;XRPUSDT&quot;</td><td>45833</td></tr><tr><td>&quot;LINKUSDT&quot;</td><td>45710</td></tr><tr><td>&quot;ADAUSDT&quot;</td><td>45416</td></tr></tbody></table></div>"
],
"text/plain": [
"shape: (5, 2)\n",
"┌──────────┬───────┐\n",
"│ symbol ┆ len │\n",
"│ --- ┆ --- │\n",
"│ str ┆ u32 │\n",
"╞══════════╪═══════╡\n",
"│ BTCUSDT ┆ 46053 │\n",
"│ ETHUSDT ┆ 46053 │\n",
"│ XRPUSDT ┆ 45833 │\n",
"│ LINKUSDT ┆ 45710 │\n",
"│ ADAUSDT ┆ 45416 │\n",
"└──────────┴───────┘"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Where does the missing premium concentrate?\n",
"missing_by_symbol = (\n",
" combined.filter(pl.col(\"premium_index_close\").is_null())\n",
" .group_by(\"symbol\")\n",
" .len()\n",
" .sort(\"len\", descending=True)\n",
")\n",
"print(\"Missing premium by symbol (top 5):\")\n",
"missing_by_symbol.head(5)"
]
},
{
"cell_type": "markdown",
"id": "869f500e",
"metadata": {
"papermill": {
"duration": 0.001191,
"end_time": "2026-06-13T02:33:30.437914+00:00",
"exception": false,
"start_time": "2026-06-13T02:33:30.436723+00:00",
"status": "completed"
}
},
"source": [
"## Key Takeaways\n",
"\n",
"1. **24/7 trading**: Crypto runs continuously — 52,608 unique hours across\n",
" six full years (2020-01-01 to 2025-12-31), ~8,760 hours/year for the\n",
" longest-history symbols.\n",
"2. **Universe**: 19 perpetual contracts span 866,484 OHLCV bars; symbol\n",
" coverage is non-uniform because contracts list at different dates.\n",
"3. **Premium units**: Stored as decimals (0.001 = 0.1%); always multiply by\n",
" 100 for percentage display in figures or text.\n",
"4. **Frequency mismatch**: OHLCV is hourly, premium is 8-hourly, so a left\n",
" join lands ~12.4% premium coverage on the hourly grid by design.\n",
"5. **Clean data**: OHLC invariants hold for 100% of records on this\n",
" snapshot; BTC has zero gaps > 1 hour.\n",
"\n",
"## Next Steps\n",
"\n",
"- `11_crypto_premium_analysis`: Premium dynamics, basis seasonality, and\n",
" alignment to the 8-hourly funding cadence.\n",
"- Chapter 8: Feature engineering for premium signals\n",
" (`case_studies/crypto_perps_funding/03_financial_features.py`).\n",
"- Chapter 16: Backtests for the funding-arbitrage case study."
]
}
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "tags,-all"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.14.3"
},
"papermill": {
"default_parameters": {},
"duration": 2.819325,
"end_time": "2026-06-13T02:33:31.153715+00:00",
"environment_variables": {},
"exception": null,
"input_path": "02_financial_data_universe/10_crypto_perps_eda.ipynb",
"output_path": "02_financial_data_universe/10_crypto_perps_eda.ipynb",
"parameters": {},
"start_time": "2026-06-13T02:33:28.334390+00:00",
"version": "2.7.0"
},
"ml4t_provenance": {
"source_py_blob": "ad9e3464a73b7086edefb3b2413e3c50096de365",
"executed_at": "2026-06-13T02:33:31.465291+00:00",
"executor": "cpu-local",
"production": true,
"parameters": {},
"notes": "executed-state finalization batch (2026-06-13 run)"
}
},
"nbformat": 4,
"nbformat_minor": 5
}