710 lines
27 KiB
Plaintext
710 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Pairs Trading Visualization Notebook\n",
|
|
"\n",
|
|
"This notebook allows you to visualize pairs trading strategies on individual instrument pairs.\n",
|
|
"You can examine the relationship between two instruments, their dis-equilibrium, and trading signals."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### \ud83c\udfaf Key Features:\n",
|
|
"\n",
|
|
"1. **Interactive Configuration**: \n",
|
|
" - Easy switching between CRYPTO and EQUITY configurations\n",
|
|
" - Simple parameter adjustment for thresholds and training periods\n",
|
|
"\n",
|
|
"2. **Single Pair Focus**: \n",
|
|
" - Instead of running multiple pairs, focuses on one pair at a time\n",
|
|
" - Allows deep analysis of the relationship between two instruments\n",
|
|
"\n",
|
|
"3. **Step-by-Step Visualization**:\n",
|
|
" - **Raw price data**: Individual prices, normalized comparison, and price ratios\n",
|
|
" - **Training analysis**: Cointegration testing and VECM model fitting\n",
|
|
" - **Dis-equilibrium visualization**: Both raw and scaled dis-equilibrium with threshold lines\n",
|
|
" - **Strategy execution**: Trading signal generation and visualization\n",
|
|
" - **Prediction analysis**: Actual vs predicted prices with trading signals overlaid\n",
|
|
"\n",
|
|
"4. **Rich Analytics**:\n",
|
|
" - Cointegration status and VECM model details\n",
|
|
" - Statistical summaries for all stages\n",
|
|
" - Threshold crossing analysis\n",
|
|
" - Trading signal breakdown\n",
|
|
"\n",
|
|
"5. **Interactive Experimentation**:\n",
|
|
" - Easy parameter modification\n",
|
|
" - Re-run capabilities for different configurations\n",
|
|
" - Support for both StaticFitStrategy and SlidingFitStrategy\n",
|
|
"\n",
|
|
"### \ud83d\ude80 How to Use:\n",
|
|
"\n",
|
|
"1. **Start Jupyter**:\n",
|
|
" ```bash\n",
|
|
" cd src/notebooks\n",
|
|
" jupyter notebook pairs_trading_visualization.ipynb\n",
|
|
" ```\n",
|
|
"\n",
|
|
"2. **Customize Your Analysis**:\n",
|
|
" - Change `SYMBOL_A` and `SYMBOL_B` to your desired trading pair\n",
|
|
" - Switch between `CRYPTO_CONFIG` and `EQT_CONFIG`\n",
|
|
" - Only **StaticFitStrategy** is supported. \n",
|
|
" - Adjust thresholds and parameters as needed\n",
|
|
"\n",
|
|
"3. **Run and Visualize**:\n",
|
|
" - Execute cells step by step to see the analysis unfold\n",
|
|
" - Rich matplotlib visualizations show relationships and signals\n",
|
|
" - Comprehensive summary at the end\n",
|
|
"\n",
|
|
"The notebook provides exactly what you requested - a way to visualize the relationship between two instruments and their scaled dis-equilibrium, with all the stages of your pairs trading strategy clearly displayed and analyzed.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup and Imports"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import sys\n",
|
|
"import os\n",
|
|
"sys.path.append('..')\n",
|
|
"\n",
|
|
"import pandas as pd\n",
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import seaborn as sns\n",
|
|
"from typing import Dict, List, Optional\n",
|
|
"\n",
|
|
"# Import our modules\n",
|
|
"from strategies import StaticFitStrategy, SlidingFitStrategy\n",
|
|
"from tools.data_loader import load_market_data\n",
|
|
"from tools.trading_pair import TradingPair\n",
|
|
"from results import BacktestResult\n",
|
|
"\n",
|
|
"# Set plotting style\n",
|
|
"plt.style.use('seaborn-v0_8')\n",
|
|
"sns.set_palette(\"husl\")\n",
|
|
"plt.rcParams['figure.figsize'] = (12, 8)\n",
|
|
"\n",
|
|
"print(\"Setup complete!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Configuration"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Configuration - Choose between CRYPTO_CONFIG or EQT_CONFIG\n",
|
|
"\n",
|
|
"CRYPTO_CONFIG = {\n",
|
|
" \"security_type\": \"CRYPTO\",\n",
|
|
" \"data_directory\": \"../../data/crypto\",\n",
|
|
" \"datafiles\": [\n",
|
|
" \"20250519.mktdata.ohlcv.db\",\n",
|
|
" ],\n",
|
|
" \"db_table_name\": \"bnbspot_ohlcv_1min\",\n",
|
|
" \"exchange_id\": \"BNBSPOT\",\n",
|
|
" \"instrument_id_pfx\": \"PAIR-\",\n",
|
|
" \"instruments\": [\n",
|
|
" \"BTC-USDT\",\n",
|
|
" \"BCH-USDT\",\n",
|
|
" \"ETH-USDT\",\n",
|
|
" \"LTC-USDT\",\n",
|
|
" \"XRP-USDT\",\n",
|
|
" \"ADA-USDT\",\n",
|
|
" \"SOL-USDT\",\n",
|
|
" \"DOT-USDT\",\n",
|
|
" ],\n",
|
|
" \"trading_hours\": {\n",
|
|
" \"begin_session\": \"00:00:00\",\n",
|
|
" \"end_session\": \"23:59:00\",\n",
|
|
" \"timezone\": \"UTC\",\n",
|
|
" },\n",
|
|
" \"price_column\": \"close\",\n",
|
|
" \"min_required_points\": 30,\n",
|
|
" \"zero_threshold\": 1e-10,\n",
|
|
" \"dis-equilibrium_open_trshld\": 2.0,\n",
|
|
" \"dis-equilibrium_close_trshld\": 0.5,\n",
|
|
" \"training_minutes\": 120,\n",
|
|
" \"funding_per_pair\": 2000.0,\n",
|
|
"}\n",
|
|
"\n",
|
|
"EQT_CONFIG = {\n",
|
|
" \"security_type\": \"EQUITY\",\n",
|
|
" \"data_directory\": \"../../data/equity\",\n",
|
|
" \"datafiles\": {\n",
|
|
" \"0508\": \"20250508.alpaca_sim_md.db\",\n",
|
|
" \"0509\": \"20250509.alpaca_sim_md.db\",\n",
|
|
" \"0510\": \"20250510.alpaca_sim_md.db\",\n",
|
|
" \"0511\": \"20250511.alpaca_sim_md.db\",\n",
|
|
" \"0512\": \"20250512.alpaca_sim_md.db\",\n",
|
|
" \"0513\": \"20250513.alpaca_sim_md.db\",\n",
|
|
" \"0514\": \"20250514.alpaca_sim_md.db\",\n",
|
|
" \"0515\": \"20250515.alpaca_sim_md.db\",\n",
|
|
" \"0516\": \"20250516.alpaca_sim_md.db\",\n",
|
|
" \"0517\": \"20250517.alpaca_sim_md.db\",\n",
|
|
" \"0518\": \"20250518.alpaca_sim_md.db\",\n",
|
|
" \"0519\": \"20250519.alpaca_sim_md.db\",\n",
|
|
" \"0520\": \"20250520.alpaca_sim_md.db\",\n",
|
|
" \"0521\": \"20250521.alpaca_sim_md.db\",\n",
|
|
" \"0522\": \"20250522.alpaca_sim_md.db\",\n",
|
|
" },\n",
|
|
" \"db_table_name\": \"md_1min_bars\",\n",
|
|
" \"exchange_id\": \"ALPACA\",\n",
|
|
" \"instrument_id_pfx\": \"STOCK-\",\n",
|
|
" \"instruments\": [\n",
|
|
" \"COIN\",\n",
|
|
" \"GBTC\",\n",
|
|
" \"HOOD\",\n",
|
|
" \"MSTR\",\n",
|
|
" \"PYPL\",\n",
|
|
" ],\n",
|
|
" \"trading_hours\": {\n",
|
|
" \"begin_session\": \"9:30:00\",\n",
|
|
" \"end_session\": \"16:00:00\",\n",
|
|
" \"timezone\": \"America/New_York\",\n",
|
|
" },\n",
|
|
" \"price_column\": \"close\",\n",
|
|
" \"min_required_points\": 30,\n",
|
|
" \"zero_threshold\": 1e-10,\n",
|
|
" \"dis-equilibrium_open_trshld\": 2.0,\n",
|
|
" \"dis-equilibrium_close_trshld\": 1.0, #0.5,\n",
|
|
" \"training_minutes\": 120,\n",
|
|
" \"funding_per_pair\": 2000.0,\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Choose your configuration\n",
|
|
"CONFIG = EQT_CONFIG # Change to CRYPTO_CONFIG if you want to use crypto data\n",
|
|
"\n",
|
|
"print(f\"Using {CONFIG['security_type']} configuration\")\n",
|
|
"print(f\"Available instruments: {CONFIG['instruments']}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Select Trading Pair and Data File"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Select your trading pair and strategy\n",
|
|
"SYMBOL_A = \"COIN\" # Change these to your desired symbols\n",
|
|
"SYMBOL_B = \"GBTC\"\n",
|
|
"DATA_FILE = CONFIG[\"datafiles\"][\"0509\"]\n",
|
|
"\n",
|
|
"# Choose strategy\n",
|
|
"STRATEGY = StaticFitStrategy()\n",
|
|
"\n",
|
|
"print(f\"Selected pair: {SYMBOL_A} & {SYMBOL_B}\")\n",
|
|
"print(f\"Data file: {DATA_FILE}\")\n",
|
|
"print(f\"Strategy: {type(STRATEGY).__name__}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Load Market Data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load market data\n",
|
|
"datafile_path = f\"{CONFIG['data_directory']}/{DATA_FILE}\"\n",
|
|
"print(f\"Current working directory: {os.getcwd()}\")\n",
|
|
"print(f\"Loading data from: {datafile_path}\")\n",
|
|
"\n",
|
|
"market_data_df = load_market_data(datafile_path, config=CONFIG)\n",
|
|
"\n",
|
|
"print(f\"Loaded {len(market_data_df)} rows of market data\")\n",
|
|
"print(f\"Symbols in data: {market_data_df['symbol'].unique()}\")\n",
|
|
"print(f\"Time range: {market_data_df['tstamp'].min()} to {market_data_df['tstamp'].max()}\")\n",
|
|
"\n",
|
|
"# Display first few rows\n",
|
|
"market_data_df.head()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Create Trading Pair and Analyze"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Create trading pair\n",
|
|
"pair = TradingPair(\n",
|
|
" market_data=market_data_df,\n",
|
|
" symbol_a=SYMBOL_A,\n",
|
|
" symbol_b=SYMBOL_B,\n",
|
|
" price_column=CONFIG[\"price_column\"]\n",
|
|
")\n",
|
|
"\n",
|
|
"print(f\"Created trading pair: {pair}\")\n",
|
|
"print(f\"Market data shape: {pair.market_data_.shape}\")\n",
|
|
"print(f\"Column names: {pair.colnames()}\")\n",
|
|
"\n",
|
|
"# Display first few rows of pair data\n",
|
|
"pair.market_data_.head()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Split Data into Training and Testing"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Get training and testing datasets\n",
|
|
"training_minutes = CONFIG[\"training_minutes\"]\n",
|
|
"pair.get_datasets(training_minutes=training_minutes)\n",
|
|
"\n",
|
|
"print(f\"Training data: {len(pair.training_df_)} rows\")\n",
|
|
"print(f\"Testing data: {len(pair.testing_df_)} rows\")\n",
|
|
"print(f\"Training period: {pair.training_df_['tstamp'].iloc[0]} to {pair.training_df_['tstamp'].iloc[-1]}\")\n",
|
|
"print(f\"Testing period: {pair.testing_df_['tstamp'].iloc[0]} to {pair.testing_df_['tstamp'].iloc[-1]}\")\n",
|
|
"\n",
|
|
"# Check for any missing data\n",
|
|
"print(f\"Training data null values: {pair.training_df_.isnull().sum().sum()}\")\n",
|
|
"print(f\"Testing data null values: {pair.testing_df_.isnull().sum().sum()}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Visualize Raw Price Data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Plot raw price data\n",
|
|
"fig, axes = plt.subplots(3, 1, figsize=(15, 12))\n",
|
|
"\n",
|
|
"# Combined price plot\n",
|
|
"colname_a, colname_b = pair.colnames()\n",
|
|
"all_data = pd.concat([pair.training_df_, pair.testing_df_]).reset_index(drop=True)\n",
|
|
"\n",
|
|
"# Plot individual prices\n",
|
|
"axes[0].plot(all_data['tstamp'], all_data[colname_a], label=f'{SYMBOL_A}', alpha=0.8)\n",
|
|
"axes[0].plot(all_data['tstamp'], all_data[colname_b], label=f'{SYMBOL_B}', alpha=0.8)\n",
|
|
"axes[0].axvline(x=pair.training_df_['tstamp'].iloc[-1], color='red', linestyle='--', alpha=0.7, label='Train/Test Split')\n",
|
|
"axes[0].set_title(f'Price Comparison: {SYMBOL_A} vs {SYMBOL_B}')\n",
|
|
"axes[0].set_ylabel('Price')\n",
|
|
"axes[0].legend()\n",
|
|
"axes[0].grid(True)\n",
|
|
"\n",
|
|
"# Normalized prices for comparison\n",
|
|
"norm_a = all_data[colname_a] / all_data[colname_a].iloc[0]\n",
|
|
"norm_b = all_data[colname_b] / all_data[colname_b].iloc[0]\n",
|
|
"\n",
|
|
"axes[1].plot(all_data['tstamp'], norm_a, label=f'{SYMBOL_A} (normalized)', alpha=0.8)\n",
|
|
"axes[1].plot(all_data['tstamp'], norm_b, label=f'{SYMBOL_B} (normalized)', alpha=0.8)\n",
|
|
"axes[1].axvline(x=pair.training_df_['tstamp'].iloc[-1], color='red', linestyle='--', alpha=0.7, label='Train/Test Split')\n",
|
|
"axes[1].set_title('Normalized Price Comparison')\n",
|
|
"axes[1].set_ylabel('Normalized Price')\n",
|
|
"axes[1].legend()\n",
|
|
"axes[1].grid(True)\n",
|
|
"\n",
|
|
"# Price ratio\n",
|
|
"price_ratio = all_data[colname_a] / all_data[colname_b]\n",
|
|
"axes[2].plot(all_data['tstamp'], price_ratio, label=f'{SYMBOL_A}/{SYMBOL_B} Ratio', color='green', alpha=0.8)\n",
|
|
"axes[2].axvline(x=pair.training_df_['tstamp'].iloc[-1], color='red', linestyle='--', alpha=0.7, label='Train/Test Split')\n",
|
|
"axes[2].set_title('Price Ratio')\n",
|
|
"axes[2].set_ylabel('Ratio')\n",
|
|
"axes[2].set_xlabel('Time')\n",
|
|
"axes[2].legend()\n",
|
|
"axes[2].grid(True)\n",
|
|
"\n",
|
|
"plt.tight_layout()\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Train the Pair and Check Cointegration"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Train the pair and check cointegration\n",
|
|
"try:\n",
|
|
" is_cointegrated = pair.train_pair()\n",
|
|
" print(f\"Pair {pair} cointegration status: {is_cointegrated}\")\n",
|
|
"\n",
|
|
" if is_cointegrated:\n",
|
|
" print(f\"VECM Beta coefficients: {pair.vecm_fit_.beta.flatten()}\")\n",
|
|
" print(f\"Training dis-equilibrium mean: {pair.training_mu_:.6f}\")\n",
|
|
" print(f\"Training dis-equilibrium std: {pair.training_std_:.6f}\")\n",
|
|
"\n",
|
|
" # Display VECM summary\n",
|
|
" print(\"\\nVECM Model Summary:\")\n",
|
|
" print(pair.vecm_fit_.summary())\n",
|
|
" else:\n",
|
|
" print(\"Pair is not cointegrated. Cannot proceed with strategy.\")\n",
|
|
"\n",
|
|
"except Exception as e:\n",
|
|
" print(f\"Training failed: {str(e)}\")\n",
|
|
" is_cointegrated = False"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Visualize Training Period Dis-equilibrium"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if is_cointegrated:\n",
|
|
" # fig, axes = plt.subplots(, 1, figsize=(15, 10))\n",
|
|
"\n",
|
|
" # # Raw dis-equilibrium\n",
|
|
" # axes[0].plot(pair.training_df_['tstamp'], pair.training_df_['dis-equilibrium'],\n",
|
|
" # color='blue', alpha=0.8, label='Raw Dis-equilibrium')\n",
|
|
" # axes[0].axhline(y=pair.training_mu_, color='red', linestyle='--', alpha=0.7, label='Mean')\n",
|
|
" # axes[0].axhline(y=pair.training_mu_ + pair.training_std_, color='orange', linestyle='--', alpha=0.5, label='+1 Std')\n",
|
|
" # axes[0].axhline(y=pair.training_mu_ - pair.training_std_, color='orange', linestyle='--', alpha=0.5, label='-1 Std')\n",
|
|
" # axes[0].set_title('Training Period: Raw Dis-equilibrium')\n",
|
|
" # axes[0].set_ylabel('Dis-equilibrium')\n",
|
|
" # axes[0].legend()\n",
|
|
" # axes[0].grid(True)\n",
|
|
"\n",
|
|
" # Scaled dis-equilibrium\n",
|
|
" fig, axes = plt.subplots(1, 1, figsize=(15, 5))\n",
|
|
" axes.plot(pair.training_df_['tstamp'], pair.training_df_['scaled_dis-equilibrium'],\n",
|
|
" color='green', alpha=0.8, label='Scaled Dis-equilibrium')\n",
|
|
" axes.axhline(y=0, color='red', linestyle='--', alpha=0.7, label='Mean (0)')\n",
|
|
" axes.axhline(y=1, color='orange', linestyle='--', alpha=0.5, label='+1 Std')\n",
|
|
" axes.axhline(y=-1, color='orange', linestyle='--', alpha=0.5, label='-1 Std')\n",
|
|
" axes.axhline(y=CONFIG['dis-equilibrium_open_trshld'], color='purple',\n",
|
|
" linestyle=':', alpha=0.7, label=f\"Open Threshold ({CONFIG['dis-equilibrium_open_trshld']})\")\n",
|
|
" axes.axhline(y=CONFIG['dis-equilibrium_close_trshld'], color='brown',\n",
|
|
" linestyle=':', alpha=0.7, label=f\"Close Threshold ({CONFIG['dis-equilibrium_close_trshld']})\")\n",
|
|
" axes.set_title('Training Period: Scaled Dis-equilibrium')\n",
|
|
" axes.set_ylabel('Scaled Dis-equilibrium')\n",
|
|
" axes.set_xlabel('Time')\n",
|
|
" axes.legend()\n",
|
|
" axes.grid(True)\n",
|
|
"\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()\n",
|
|
"\n",
|
|
" # Print statistics\n",
|
|
" print(f\"Training dis-equilibrium statistics:\")\n",
|
|
" print(f\" Mean: {pair.training_df_['dis-equilibrium'].mean():.6f}\")\n",
|
|
" print(f\" Std: {pair.training_df_['dis-equilibrium'].std():.6f}\")\n",
|
|
" print(f\" Min: {pair.training_df_['dis-equilibrium'].min():.6f}\")\n",
|
|
" print(f\" Max: {pair.training_df_['dis-equilibrium'].max():.6f}\")\n",
|
|
"\n",
|
|
" print(f\"\\nScaled dis-equilibrium statistics:\")\n",
|
|
" print(f\" Mean: {pair.training_df_['scaled_dis-equilibrium'].mean():.6f}\")\n",
|
|
" print(f\" Std: {pair.training_df_['scaled_dis-equilibrium'].std():.6f}\")\n",
|
|
" print(f\" Min: {pair.training_df_['scaled_dis-equilibrium'].min():.6f}\")\n",
|
|
" print(f\" Max: {pair.training_df_['scaled_dis-equilibrium'].max():.6f}\")\n",
|
|
"else:\n",
|
|
" print(\"The pair is not cointegrated\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Generate Predictions and Run Strategy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if is_cointegrated:\n",
|
|
" try:\n",
|
|
" # Generate predictions\n",
|
|
" pair.predict()\n",
|
|
" print(f\"Generated predictions for {len(pair.predicted_df_)} rows\")\n",
|
|
"\n",
|
|
" # Display prediction data structure\n",
|
|
" print(f\"Prediction columns: {list(pair.predicted_df_.columns)}\")\n",
|
|
" print(f\"Prediction period: {pair.predicted_df_['tstamp'].iloc[0]} to {pair.predicted_df_['tstamp'].iloc[-1]}\")\n",
|
|
"\n",
|
|
" # Run strategy\n",
|
|
" bt_result = BacktestResult(config=CONFIG)\n",
|
|
" pair_trades = STRATEGY.run_pair(config=CONFIG, pair=pair, bt_result=bt_result)\n",
|
|
"\n",
|
|
" if pair_trades is not None and len(pair_trades) > 0:\n",
|
|
" print(f\"\\nGenerated {len(pair_trades)} trading signals:\")\n",
|
|
" print(pair_trades)\n",
|
|
" else:\n",
|
|
" print(\"\\nNo trading signals generated\")\n",
|
|
"\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"Prediction/Strategy failed: {str(e)}\")\n",
|
|
" pair_trades = None"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Visualize Predictions and Dis-equilibrium"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if is_cointegrated and hasattr(pair, 'predicted_df_'):\n",
|
|
" fig, axes = plt.subplots(4, 1, figsize=(16, 16))\n",
|
|
"\n",
|
|
" # Actual vs Predicted Prices\n",
|
|
" colname_a, colname_b = pair.colnames()\n",
|
|
"\n",
|
|
" axes[0].plot(pair.predicted_df_['tstamp'], pair.predicted_df_[colname_a],\n",
|
|
" label=f'{SYMBOL_A} Actual', alpha=0.8)\n",
|
|
" axes[0].plot(pair.predicted_df_['tstamp'], pair.predicted_df_[f'{colname_a}_pred'],\n",
|
|
" label=f'{SYMBOL_A} Predicted', alpha=0.8, linestyle='--')\n",
|
|
" axes[0].set_title('Actual vs Predicted Prices - Symbol A')\n",
|
|
" axes[0].set_ylabel('Price')\n",
|
|
" axes[0].legend()\n",
|
|
" axes[0].grid(True)\n",
|
|
"\n",
|
|
" axes[1].plot(pair.predicted_df_['tstamp'], pair.predicted_df_[colname_b],\n",
|
|
" label=f'{SYMBOL_B} Actual', alpha=0.8)\n",
|
|
" axes[1].plot(pair.predicted_df_['tstamp'], pair.predicted_df_[f'{colname_b}_pred'],\n",
|
|
" label=f'{SYMBOL_B} Predicted', alpha=0.8, linestyle='--')\n",
|
|
" axes[1].set_title('Actual vs Predicted Prices - Symbol B')\n",
|
|
" axes[1].set_ylabel('Price')\n",
|
|
" axes[1].legend()\n",
|
|
" axes[1].grid(True)\n",
|
|
"\n",
|
|
" # Raw dis-equilibrium\n",
|
|
" axes[2].plot(pair.predicted_df_['tstamp'], pair.predicted_df_['disequilibrium'],\n",
|
|
" color='blue', alpha=0.8, label='Dis-equilibrium')\n",
|
|
" axes[2].axhline(y=pair.training_mu_, color='red', linestyle='--', alpha=0.7, label='Training Mean')\n",
|
|
" axes[2].set_title('Testing Period: Raw Dis-equilibrium')\n",
|
|
" axes[2].set_ylabel('Dis-equilibrium')\n",
|
|
" axes[2].legend()\n",
|
|
" axes[2].grid(True)\n",
|
|
"\n",
|
|
" # Scaled dis-equilibrium with trading signals\n",
|
|
" axes[3].plot(pair.predicted_df_['tstamp'], pair.predicted_df_['scaled_disequilibrium'],\n",
|
|
" color='green', alpha=0.8, label='Scaled Dis-equilibrium')\n",
|
|
"\n",
|
|
" # Add threshold lines\n",
|
|
" axes[3].axhline(y=CONFIG['dis-equilibrium_open_trshld'], color='purple',\n",
|
|
" linestyle=':', alpha=0.7, label=f\"Open Threshold ({CONFIG['dis-equilibrium_open_trshld']})\")\n",
|
|
" axes[3].axhline(y=CONFIG['dis-equilibrium_close_trshld'], color='brown',\n",
|
|
" linestyle=':', alpha=0.7, label=f\"Close Threshold ({CONFIG['dis-equilibrium_close_trshld']})\")\n",
|
|
"\n",
|
|
" # Add trading signals if they exist\n",
|
|
" if pair_trades is not None and len(pair_trades) > 0:\n",
|
|
" for _, trade in pair_trades.iterrows():\n",
|
|
" color = 'red' if 'BUY' in trade['action'] else 'blue'\n",
|
|
" marker = '^' if 'BUY' in trade['action'] else 'v'\n",
|
|
" axes[3].scatter(trade['time'], trade['scaled_disequilibrium'],\n",
|
|
" color=color, marker=marker, s=100, alpha=0.8,\n",
|
|
" label=f\"{trade['action']} {trade['symbol']}\" if _ < 2 else \"\")\n",
|
|
"\n",
|
|
" axes[3].set_title('Testing Period: Scaled Dis-equilibrium with Trading Signals')\n",
|
|
" axes[3].set_ylabel('Scaled Dis-equilibrium')\n",
|
|
" axes[3].set_xlabel('Time')\n",
|
|
" axes[3].legend()\n",
|
|
" axes[3].grid(True)\n",
|
|
"\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()\n",
|
|
"\n",
|
|
" # Print prediction statistics\n",
|
|
" print(f\"\\nTesting dis-equilibrium statistics:\")\n",
|
|
" print(f\" Mean: {pair.predicted_df_['disequilibrium'].mean():.6f}\")\n",
|
|
" print(f\" Std: {pair.predicted_df_['disequilibrium'].std():.6f}\")\n",
|
|
" print(f\" Min: {pair.predicted_df_['disequilibrium'].min():.6f}\")\n",
|
|
" print(f\" Max: {pair.predicted_df_['disequilibrium'].max():.6f}\")\n",
|
|
"\n",
|
|
" print(f\"\\nTesting scaled dis-equilibrium statistics:\")\n",
|
|
" print(f\" Mean: {pair.predicted_df_['scaled_disequilibrium'].mean():.6f}\")\n",
|
|
" print(f\" Std: {pair.predicted_df_['scaled_disequilibrium'].std():.6f}\")\n",
|
|
" print(f\" Min: {pair.predicted_df_['scaled_disequilibrium'].min():.6f}\")\n",
|
|
" print(f\" Max: {pair.predicted_df_['scaled_disequilibrium'].max():.6f}\")\n",
|
|
"\n",
|
|
" # Count threshold crossings\n",
|
|
" open_crossings = (pair.predicted_df_['scaled_disequilibrium'] >= CONFIG['dis-equilibrium_open_trshld']).sum()\n",
|
|
" close_crossings = (pair.predicted_df_['scaled_disequilibrium'] <= CONFIG['dis-equilibrium_close_trshld']).sum()\n",
|
|
" print(f\"\\nThreshold crossings:\")\n",
|
|
" print(f\" Open threshold ({CONFIG['dis-equilibrium_open_trshld']}): {open_crossings} times\")\n",
|
|
" print(f\" Close threshold ({CONFIG['dis-equilibrium_close_trshld']}): {close_crossings} times\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Summary and Analysis"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"=\" * 60)\n",
|
|
"print(\"PAIRS TRADING ANALYSIS SUMMARY\")\n",
|
|
"print(\"=\" * 60)\n",
|
|
"\n",
|
|
"print(f\"\\nPair: {SYMBOL_A} & {SYMBOL_B}\")\n",
|
|
"print(f\"Strategy: {type(STRATEGY).__name__}\")\n",
|
|
"print(f\"Data file: {DATA_FILE}\")\n",
|
|
"print(f\"Training period: {training_minutes} minutes\")\n",
|
|
"\n",
|
|
"print(f\"\\nCointegration Status: {'\u2713 COINTEGRATED' if is_cointegrated else '\u2717 NOT COINTEGRATED'}\")\n",
|
|
"\n",
|
|
"if is_cointegrated:\n",
|
|
" print(f\"\\nVECM Model:\")\n",
|
|
" print(f\" Beta coefficients: {pair.vecm_fit_.beta.flatten()}\")\n",
|
|
" print(f\" Training mean: {pair.training_mu_:.6f}\")\n",
|
|
" print(f\" Training std: {pair.training_std_:.6f}\")\n",
|
|
"\n",
|
|
" if pair_trades is not None and len(pair_trades) > 0:\n",
|
|
" print(f\"\\nTrading Signals: {len(pair_trades)} generated\")\n",
|
|
" unique_times = pair_trades['time'].unique()\n",
|
|
" print(f\" Unique trade times: {len(unique_times)}\")\n",
|
|
"\n",
|
|
" # Group by time to see paired trades\n",
|
|
" for trade_time in unique_times:\n",
|
|
" trades_at_time = pair_trades[pair_trades['time'] == trade_time]\n",
|
|
" print(f\"\\n Trade at {trade_time}:\")\n",
|
|
" for _, trade in trades_at_time.iterrows():\n",
|
|
" print(f\" {trade['action']} {trade['symbol']} @ ${trade['price']:.2f} (dis-eq: {trade['scaled_disequilibrium']:.2f})\")\n",
|
|
" else:\n",
|
|
" print(f\"\\nTrading Signals: None generated\")\n",
|
|
" print(\" Possible reasons:\")\n",
|
|
" print(\" - Dis-equilibrium never exceeded open threshold\")\n",
|
|
" print(\" - Insufficient testing data\")\n",
|
|
" print(\" - Strategy-specific conditions not met\")\n",
|
|
"\n",
|
|
"else:\n",
|
|
" print(\"\\nCannot proceed with trading strategy - pair is not cointegrated\")\n",
|
|
" print(\"Consider:\")\n",
|
|
" print(\" - Trying different symbol pairs\")\n",
|
|
" print(\" - Adjusting training period length\")\n",
|
|
" print(\" - Using different data timeframe\")\n",
|
|
"\n",
|
|
"print(\"\\n\" + \"=\" * 60)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Interactive Analysis (Optional)\n",
|
|
"\n",
|
|
"You can modify the parameters below and re-run the analysis:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Interactive parameter adjustment\n",
|
|
"print(\"Current parameters:\")\n",
|
|
"print(f\" Open threshold: {CONFIG['dis-equilibrium_open_trshld']}\")\n",
|
|
"print(f\" Close threshold: {CONFIG['dis-equilibrium_close_trshld']}\")\n",
|
|
"print(f\" Training minutes: {CONFIG['training_minutes']}\")\n",
|
|
"\n",
|
|
"# Uncomment and modify these to experiment:\n",
|
|
"# CONFIG['dis-equilibrium_open_trshld'] = 1.5\n",
|
|
"# CONFIG['dis-equilibrium_close_trshld'] = 0.3\n",
|
|
"# CONFIG['training_minutes'] = 180\n",
|
|
"\n",
|
|
"print(\"\\nTo re-run with different parameters:\")\n",
|
|
"print(\"1. Modify the parameters above\")\n",
|
|
"print(\"2. Re-run from the 'Split Data into Training and Testing' cell\")\n",
|
|
"print(\"3. Or try different symbol pairs by changing SYMBOL_A and SYMBOL_B\")"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "python3.12-venv",
|
|
"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.12.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |