algo_trading_book/converted_code/notebooks/momentum_trading_analysis.ipynb
2025-06-05 08:48:33 +02:00

1095 lines
53 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"# Momentum Trading Analysis\n",
"## Based on Algorithmic Trading Book Chapters 6 & 7\n",
"\n",
"This notebook implements and analyzes momentum trading strategies using the converted Python code from MATLAB implementations. We'll explore:\n",
"\n",
"1. **Cross-sectional Momentum** (Chapter 6)\n",
" - Long-short equity momentum strategy\n",
" - Performance analysis and risk metrics\n",
" \n",
"2. **Time Series Momentum** (Chapter 7)\n",
" - Treasury futures momentum\n",
" - Statistical significance testing\n",
" - Risk-adjusted returns\n",
"\n",
"3. **Comparative Analysis**\n",
" - Strategy performance comparison\n",
" - Risk-return profiles\n",
" - Market regime analysis\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Import required libraries\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"from scipy import stats\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# Set up plotting style\n",
"plt.style.use('seaborn-v0_8')\n",
"sns.set_palette(\"husl\")\n",
"plt.rcParams['figure.figsize'] = (12, 8)\n",
"plt.rcParams['font.size'] = 10\n",
"\n",
"# Import our converted trading modules\n",
"import sys\n",
"import os\n",
"sys.path.append(os.path.abspath('..'))\n",
"\n",
"from converted_code.data_loader import load_futures_data, load_stock_data\n",
"from converted_code.backshift import backshift\n",
"from converted_code.smartmean import smartmean\n",
"from converted_code.smartstd import smartstd\n",
"from converted_code.calculateReturns import calculateReturns\n",
"from converted_code.calculateMaxDD import calculateMaxDD\n",
"\n",
"print(\"✅ All libraries imported successfully!\")\n",
"print(\"📊 Ready for momentum trading analysis\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 1. Data Loading and Preparation\n",
"\n",
"Let's start by loading the market data that we'll use for our momentum strategies.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load Treasury futures data for time series momentum\n",
"print(\"📈 Loading Treasury Futures Data...\")\n",
"try:\n",
" tu_data = load_futures_data('TU', '20120813')\n",
" tu_prices = tu_data['cl'][:, 0] # Use first contract\n",
" tu_dates = tu_data['tday']\n",
" print(f\"✅ Loaded {len(tu_dates)} days of Treasury futures data\")\n",
" print(f\" Date range: {tu_dates[0]} to {tu_dates[-1]}\")\n",
" print(f\" Price range: ${tu_prices.min():.2f} to ${tu_prices.max():.2f}\")\n",
" \n",
" # Convert dates to pandas datetime for easier handling\n",
" tu_dates_pd = pd.to_datetime(tu_dates.astype(str), format='%Y%m%d')\n",
" \n",
"except Exception as e:\n",
" print(f\"⚠️ Could not load Treasury data: {e}\")\n",
" print(\" Using synthetic data for demonstration...\")\n",
" \n",
" # Generate synthetic Treasury futures data\n",
" np.random.seed(42)\n",
" n_days = 1000\n",
" tu_dates = np.arange(20100101, 20100101 + n_days)\n",
" tu_dates_pd = pd.to_datetime(tu_dates.astype(str), format='%Y%m%d')\n",
" \n",
" # Generate realistic Treasury futures prices (around 130-140 range)\n",
" returns = np.random.normal(0, 0.008, n_days) # 0.8% daily volatility\n",
" tu_prices = 135 * np.cumprod(1 + returns)\n",
" \n",
" print(f\"✅ Generated {len(tu_dates)} days of synthetic Treasury data\")\n",
"\n",
"print(f\"\\n📊 Treasury Futures Summary:\")\n",
"print(f\" Mean price: ${tu_prices.mean():.2f}\")\n",
"print(f\" Volatility: {tu_prices.std():.2f}\")\n",
"print(f\" Total return: {(tu_prices[-1]/tu_prices[0] - 1)*100:.2f}%\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load stock data for cross-sectional momentum\n",
"print(\"📈 Loading Stock Market Data...\")\n",
"try:\n",
" stock_data = load_stock_data('20120424')\n",
" stock_prices = stock_data['cl']\n",
" stock_dates = stock_data['tday']\n",
" stock_symbols = stock_data.get('syms', [f'Stock_{i}' for i in range(stock_prices.shape[1])])\n",
" \n",
" print(f\"✅ Loaded stock data: {stock_prices.shape[0]} days × {stock_prices.shape[1]} stocks\")\n",
" print(f\" Date range: {stock_dates[0]} to {stock_dates[-1]}\")\n",
" \n",
" # Convert dates to pandas datetime\n",
" stock_dates_pd = pd.to_datetime(stock_dates.astype(str), format='%Y%m%d')\n",
" \n",
"except Exception as e:\n",
" print(f\"⚠️ Could not load stock data: {e}\")\n",
" print(\" Using synthetic data for demonstration...\")\n",
" \n",
" # Generate synthetic stock data\n",
" np.random.seed(42)\n",
" n_days = 1000\n",
" n_stocks = 100\n",
" stock_dates = np.arange(20100101, 20100101 + n_days)\n",
" stock_dates_pd = pd.to_datetime(stock_dates.astype(str), format='%Y%m%d')\n",
" \n",
" # Generate correlated stock returns with momentum effects\n",
" base_returns = np.random.normal(0, 0.02, (n_days, n_stocks))\n",
" \n",
" # Add some momentum persistence\n",
" for i in range(1, n_days):\n",
" momentum_factor = 0.1 # 10% momentum persistence\n",
" base_returns[i] += momentum_factor * base_returns[i-1]\n",
" \n",
" # Convert to prices\n",
" stock_prices = 100 * np.cumprod(1 + base_returns, axis=0)\n",
" stock_symbols = [f'Stock_{i:03d}' for i in range(n_stocks)]\n",
" \n",
" print(f\"✅ Generated {n_days} days × {n_stocks} stocks of synthetic data\")\n",
"\n",
"print(f\"\\n📊 Stock Market Summary:\")\n",
"print(f\" Average stock price: ${stock_prices.mean():.2f}\")\n",
"print(f\" Price volatility: {stock_prices.std():.2f}\")\n",
"print(f\" Number of stocks: {stock_prices.shape[1]}\")\n",
"\n",
"# Create a DataFrame for easier analysis\n",
"stock_df = pd.DataFrame(stock_prices, index=stock_dates_pd, columns=stock_symbols[:stock_prices.shape[1]])\n",
"print(f\" DataFrame shape: {stock_df.shape}\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 2. Time Series Momentum Strategy (Chapter 7)\n",
"\n",
"Time series momentum looks at the persistence of trends within individual assets. We'll implement the Treasury futures momentum strategy that compares current prices to historical levels.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def time_series_momentum_strategy(prices, lookback=250, holddays=25):\n",
" \"\"\"\n",
" Implement time series momentum strategy.\n",
" \n",
" Parameters:\n",
" -----------\n",
" prices : array-like\n",
" Price series\n",
" lookback : int\n",
" Lookback period for momentum signal (default: 250 days = 1 year)\n",
" holddays : int\n",
" Holding period for positions (default: 25 days = 1 month)\n",
" \n",
" Returns:\n",
" --------\n",
" dict : Strategy results including positions, returns, and metrics\n",
" \"\"\"\n",
" \n",
" # Generate momentum signals\n",
" # Long when current price > price from lookback days ago\n",
" # Short when current price < price from lookback days ago\n",
" longs = prices > backshift(lookback, prices)\n",
" shorts = prices < backshift(lookback, prices)\n",
" \n",
" # Initialize position array\n",
" positions = np.zeros(len(prices))\n",
" \n",
" # Build positions over holding period to avoid look-ahead bias\n",
" for h in range(holddays):\n",
" long_lag = backshift(h, longs.astype(float))\n",
" long_lag = np.nan_to_num(long_lag, nan=0).astype(bool)\n",
" \n",
" short_lag = backshift(h, shorts.astype(float))\n",
" short_lag = np.nan_to_num(short_lag, nan=0).astype(bool)\n",
" \n",
" positions[long_lag] += 1\n",
" positions[short_lag] -= 1\n",
" \n",
" # Normalize positions by holding period\n",
" positions = positions / holddays\n",
" \n",
" # Calculate market returns\n",
" market_returns = calculateReturns(prices, 1)\n",
" market_returns = np.nan_to_num(market_returns, nan=0)\n",
" \n",
" # Calculate strategy returns\n",
" # Use lagged positions to avoid look-ahead bias\n",
" strategy_returns = backshift(1, positions) * market_returns\n",
" strategy_returns = np.nan_to_num(strategy_returns, nan=0)\n",
" \n",
" # Calculate cumulative returns\n",
" cumulative_returns = np.cumprod(1 + strategy_returns) - 1\n",
" market_cumulative = np.cumprod(1 + market_returns) - 1\n",
" \n",
" # Calculate performance metrics\n",
" valid_returns = strategy_returns[strategy_returns != 0]\n",
" \n",
" if len(valid_returns) > 0:\n",
" annual_return = np.prod(1 + valid_returns) ** (252 / len(valid_returns)) - 1\n",
" volatility = np.std(valid_returns) * np.sqrt(252)\n",
" sharpe_ratio = annual_return / volatility if volatility > 0 else 0\n",
" \n",
" # Maximum drawdown\n",
" max_dd, max_dd_duration = calculateMaxDD(cumulative_returns)\n",
" \n",
" # Win rate\n",
" win_rate = np.sum(valid_returns > 0) / len(valid_returns)\n",
" \n",
" # Number of trades\n",
" position_changes = np.diff(np.concatenate([[0], positions]))\n",
" num_trades = np.sum(np.abs(position_changes) > 0)\n",
" \n",
" else:\n",
" annual_return = volatility = sharpe_ratio = max_dd = max_dd_duration = win_rate = num_trades = 0\n",
" \n",
" return {\n",
" 'positions': positions,\n",
" 'strategy_returns': strategy_returns,\n",
" 'market_returns': market_returns,\n",
" 'cumulative_returns': cumulative_returns,\n",
" 'market_cumulative': market_cumulative,\n",
" 'annual_return': annual_return,\n",
" 'volatility': volatility,\n",
" 'sharpe_ratio': sharpe_ratio,\n",
" 'max_drawdown': max_dd,\n",
" 'max_dd_duration': max_dd_duration,\n",
" 'win_rate': win_rate,\n",
" 'num_trades': num_trades,\n",
" 'longs': longs,\n",
" 'shorts': shorts\n",
" }\n",
"\n",
"# Run the time series momentum strategy on Treasury futures\n",
"print(\"🚀 Running Time Series Momentum Strategy on Treasury Futures...\")\n",
"print(\"=\" * 60)\n",
"\n",
"ts_results = time_series_momentum_strategy(tu_prices, lookback=250, holddays=25)\n",
"\n",
"print(f\"📊 Time Series Momentum Results:\")\n",
"print(f\" Annual Return: {ts_results['annual_return']:8.2%}\")\n",
"print(f\" Volatility: {ts_results['volatility']:8.2%}\")\n",
"print(f\" Sharpe Ratio: {ts_results['sharpe_ratio']:8.2f}\")\n",
"print(f\" Max Drawdown: {ts_results['max_drawdown']:8.2%}\")\n",
"print(f\" Max DD Duration: {ts_results['max_dd_duration']:8.0f} days\")\n",
"print(f\" Win Rate: {ts_results['win_rate']:8.2%}\")\n",
"print(f\" Number of Trades: {ts_results['num_trades']:8.0f}\")\n",
"\n",
"# Compare to buy-and-hold\n",
"market_annual = np.prod(1 + ts_results['market_returns']) ** (252 / len(ts_results['market_returns'])) - 1\n",
"market_vol = np.std(ts_results['market_returns']) * np.sqrt(252)\n",
"market_sharpe = market_annual / market_vol if market_vol > 0 else 0\n",
"\n",
"print(f\"\\n📈 Buy-and-Hold Comparison:\")\n",
"print(f\" Market Annual Return: {market_annual:8.2%}\")\n",
"print(f\" Market Volatility: {market_vol:8.2%}\")\n",
"print(f\" Market Sharpe Ratio: {market_sharpe:8.2f}\")\n",
"print(f\" Strategy Excess Return: {ts_results['annual_return'] - market_annual:8.2%}\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 3. Cross-Sectional Momentum Strategy (Chapter 6)\n",
"\n",
"Cross-sectional momentum compares the relative performance of different assets and goes long the winners while shorting the losers.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def cross_sectional_momentum_strategy(prices, lookback=252, holddays=25, top_n=50):\n",
" \"\"\"\n",
" Implement cross-sectional momentum strategy (Kent Daniel style).\n",
" \n",
" Parameters:\n",
" -----------\n",
" prices : array-like (2D)\n",
" Price matrix (time x assets)\n",
" lookback : int\n",
" Lookback period for momentum calculation (default: 252 days = 1 year)\n",
" holddays : int\n",
" Holding period for positions (default: 25 days = 1 month)\n",
" top_n : int\n",
" Number of top/bottom performers to select (default: 50)\n",
" \n",
" Returns:\n",
" --------\n",
" dict : Strategy results including positions, returns, and metrics\n",
" \"\"\"\n",
" \n",
" n_days, n_assets = prices.shape\n",
" \n",
" # Calculate returns for momentum ranking\n",
" returns = calculateReturns(prices, 1)\n",
" returns = np.nan_to_num(returns, nan=0)\n",
" \n",
" # Calculate cumulative returns over lookback period for ranking\n",
" cum_returns = np.zeros_like(prices)\n",
" for i in range(lookback, n_days):\n",
" # Calculate cumulative return over lookback period\n",
" period_returns = returns[i-lookback+1:i+1, :]\n",
" cum_returns[i, :] = np.prod(1 + period_returns, axis=0) - 1\n",
" \n",
" # Initialize position matrix\n",
" positions = np.zeros_like(prices)\n",
" \n",
" # Generate trading signals\n",
" for i in range(lookback, n_days):\n",
" # Rank assets by momentum (cumulative returns)\n",
" momentum_scores = cum_returns[i, :]\n",
" \n",
" # Handle NaN values\n",
" valid_scores = ~np.isnan(momentum_scores)\n",
" if np.sum(valid_scores) < 2 * top_n:\n",
" continue\n",
" \n",
" # Get rankings\n",
" rankings = np.argsort(momentum_scores[valid_scores])\n",
" valid_indices = np.where(valid_scores)[0]\n",
" \n",
" # Select top and bottom performers\n",
" if len(rankings) >= 2 * top_n:\n",
" # Bottom performers (short)\n",
" bottom_indices = valid_indices[rankings[:top_n]]\n",
" # Top performers (long)\n",
" top_indices = valid_indices[rankings[-top_n:]]\n",
" \n",
" # Set positions for holding period\n",
" for h in range(min(holddays, n_days - i)):\n",
" if i + h < n_days:\n",
" positions[i + h, top_indices] = 1.0 / top_n # Long position\n",
" positions[i + h, bottom_indices] = -1.0 / top_n # Short position\n",
" \n",
" # Calculate strategy returns\n",
" strategy_returns = np.sum(backshift(1, positions) * returns, axis=1)\n",
" strategy_returns = np.nan_to_num(strategy_returns, nan=0)\n",
" \n",
" # Calculate market returns (equal-weighted)\n",
" market_returns = np.nanmean(returns, axis=1)\n",
" market_returns = np.nan_to_num(market_returns, nan=0)\n",
" \n",
" # Calculate cumulative returns\n",
" cumulative_returns = np.cumprod(1 + strategy_returns) - 1\n",
" market_cumulative = np.cumprod(1 + market_returns) - 1\n",
" \n",
" # Calculate performance metrics\n",
" valid_returns = strategy_returns[strategy_returns != 0]\n",
" \n",
" if len(valid_returns) > 0:\n",
" annual_return = np.prod(1 + valid_returns) ** (252 / len(valid_returns)) - 1\n",
" volatility = np.std(valid_returns) * np.sqrt(252)\n",
" sharpe_ratio = annual_return / volatility if volatility > 0 else 0\n",
" \n",
" # Maximum drawdown\n",
" max_dd, max_dd_duration = calculateMaxDD(cumulative_returns)\n",
" \n",
" # Win rate\n",
" win_rate = np.sum(valid_returns > 0) / len(valid_returns)\n",
" \n",
" # Number of rebalances\n",
" position_changes = np.sum(np.abs(np.diff(positions, axis=0)), axis=1)\n",
" num_rebalances = np.sum(position_changes > 0)\n",
" \n",
" else:\n",
" annual_return = volatility = sharpe_ratio = max_dd = max_dd_duration = win_rate = num_rebalances = 0\n",
" \n",
" return {\n",
" 'positions': positions,\n",
" 'strategy_returns': strategy_returns,\n",
" 'market_returns': market_returns,\n",
" 'cumulative_returns': cumulative_returns,\n",
" 'market_cumulative': market_cumulative,\n",
" 'annual_return': annual_return,\n",
" 'volatility': volatility,\n",
" 'sharpe_ratio': sharpe_ratio,\n",
" 'max_drawdown': max_dd,\n",
" 'max_dd_duration': max_dd_duration,\n",
" 'win_rate': win_rate,\n",
" 'num_rebalances': num_rebalances,\n",
" 'cum_returns': cum_returns\n",
" }\n",
"\n",
"# Run the cross-sectional momentum strategy on stocks\n",
"print(\"🚀 Running Cross-Sectional Momentum Strategy on Stocks...\")\n",
"print(\"=\" * 60)\n",
"\n",
"# Use a subset of stocks for faster computation if we have many\n",
"max_stocks = min(100, stock_prices.shape[1])\n",
"cs_results = cross_sectional_momentum_strategy(\n",
" stock_prices[:, :max_stocks], \n",
" lookback=252, \n",
" holddays=25, \n",
" top_n=min(20, max_stocks//4)\n",
")\n",
"\n",
"print(f\"📊 Cross-Sectional Momentum Results:\")\n",
"print(f\" Annual Return: {cs_results['annual_return']:8.2%}\")\n",
"print(f\" Volatility: {cs_results['volatility']:8.2%}\")\n",
"print(f\" Sharpe Ratio: {cs_results['sharpe_ratio']:8.2f}\")\n",
"print(f\" Max Drawdown: {cs_results['max_drawdown']:8.2%}\")\n",
"print(f\" Max DD Duration: {cs_results['max_dd_duration']:8.0f} days\")\n",
"print(f\" Win Rate: {cs_results['win_rate']:8.2%}\")\n",
"print(f\" Number of Rebalances: {cs_results['num_rebalances']:8.0f}\")\n",
"\n",
"# Compare to market\n",
"cs_market_annual = np.prod(1 + cs_results['market_returns']) ** (252 / len(cs_results['market_returns'])) - 1\n",
"cs_market_vol = np.std(cs_results['market_returns']) * np.sqrt(252)\n",
"cs_market_sharpe = cs_market_annual / cs_market_vol if cs_market_vol > 0 else 0\n",
"\n",
"print(f\"\\n📈 Market Comparison:\")\n",
"print(f\" Market Annual Return: {cs_market_annual:8.2%}\")\n",
"print(f\" Market Volatility: {cs_market_vol:8.2%}\")\n",
"print(f\" Market Sharpe Ratio: {cs_market_sharpe:8.2f}\")\n",
"print(f\" Strategy Excess Return: {cs_results['annual_return'] - cs_market_annual:8.2%}\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 4. Visualization and Analysis\n",
"\n",
"Let's create comprehensive visualizations to analyze the performance of both momentum strategies.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create comprehensive performance visualization\n",
"fig, axes = plt.subplots(2, 2, figsize=(16, 12))\n",
"fig.suptitle('Momentum Trading Strategies Performance Analysis', fontsize=16, fontweight='bold')\n",
"\n",
"# 1. Cumulative Returns Comparison\n",
"ax1 = axes[0, 0]\n",
"ax1.plot(tu_dates_pd[:len(ts_results['cumulative_returns'])], \n",
" ts_results['cumulative_returns'] * 100, \n",
" label='Time Series Momentum', linewidth=2, color='blue')\n",
"ax1.plot(tu_dates_pd[:len(ts_results['market_cumulative'])], \n",
" ts_results['market_cumulative'] * 100, \n",
" label='Buy & Hold (Treasury)', linewidth=2, color='gray', alpha=0.7)\n",
"ax1.set_title('Time Series Momentum: Cumulative Returns', fontweight='bold')\n",
"ax1.set_ylabel('Cumulative Return (%)')\n",
"ax1.legend()\n",
"ax1.grid(True, alpha=0.3)\n",
"\n",
"# 2. Cross-sectional momentum cumulative returns\n",
"ax2 = axes[0, 1]\n",
"ax2.plot(stock_dates_pd[:len(cs_results['cumulative_returns'])], \n",
" cs_results['cumulative_returns'] * 100, \n",
" label='Cross-Sectional Momentum', linewidth=2, color='red')\n",
"ax2.plot(stock_dates_pd[:len(cs_results['market_cumulative'])], \n",
" cs_results['market_cumulative'] * 100, \n",
" label='Market (Equal Weight)', linewidth=2, color='gray', alpha=0.7)\n",
"ax2.set_title('Cross-Sectional Momentum: Cumulative Returns', fontweight='bold')\n",
"ax2.set_ylabel('Cumulative Return (%)')\n",
"ax2.legend()\n",
"ax2.grid(True, alpha=0.3)\n",
"\n",
"# 3. Rolling Sharpe Ratio (252-day window)\n",
"ax3 = axes[1, 0]\n",
"window = 252\n",
"\n",
"# Calculate rolling Sharpe for time series momentum\n",
"ts_rolling_sharpe = []\n",
"for i in range(window, len(ts_results['strategy_returns'])):\n",
" window_returns = ts_results['strategy_returns'][i-window:i]\n",
" if np.std(window_returns) > 0:\n",
" sharpe = np.mean(window_returns) * np.sqrt(252) / (np.std(window_returns) * np.sqrt(252))\n",
" ts_rolling_sharpe.append(sharpe)\n",
" else:\n",
" ts_rolling_sharpe.append(0)\n",
"\n",
"# Calculate rolling Sharpe for cross-sectional momentum\n",
"cs_rolling_sharpe = []\n",
"for i in range(window, len(cs_results['strategy_returns'])):\n",
" window_returns = cs_results['strategy_returns'][i-window:i]\n",
" if np.std(window_returns) > 0:\n",
" sharpe = np.mean(window_returns) * np.sqrt(252) / (np.std(window_returns) * np.sqrt(252))\n",
" cs_rolling_sharpe.append(sharpe)\n",
" else:\n",
" cs_rolling_sharpe.append(0)\n",
"\n",
"if len(ts_rolling_sharpe) > 0:\n",
" ax3.plot(tu_dates_pd[window:window+len(ts_rolling_sharpe)], \n",
" ts_rolling_sharpe, label='Time Series', linewidth=2, color='blue')\n",
"\n",
"if len(cs_rolling_sharpe) > 0:\n",
" ax3.plot(stock_dates_pd[window:window+len(cs_rolling_sharpe)], \n",
" cs_rolling_sharpe, label='Cross-Sectional', linewidth=2, color='red')\n",
"\n",
"ax3.axhline(y=0, color='black', linestyle='--', alpha=0.5)\n",
"ax3.set_title('Rolling Sharpe Ratio (252-day window)', fontweight='bold')\n",
"ax3.set_ylabel('Sharpe Ratio')\n",
"ax3.legend()\n",
"ax3.grid(True, alpha=0.3)\n",
"\n",
"# 4. Return Distribution Comparison\n",
"ax4 = axes[1, 1]\n",
"ts_valid_returns = ts_results['strategy_returns'][ts_results['strategy_returns'] != 0]\n",
"cs_valid_returns = cs_results['strategy_returns'][cs_results['strategy_returns'] != 0]\n",
"\n",
"if len(ts_valid_returns) > 0:\n",
" ax4.hist(ts_valid_returns * 100, bins=50, alpha=0.6, label='Time Series', \n",
" color='blue', density=True)\n",
"\n",
"if len(cs_valid_returns) > 0:\n",
" ax4.hist(cs_valid_returns * 100, bins=50, alpha=0.6, label='Cross-Sectional', \n",
" color='red', density=True)\n",
"\n",
"ax4.axvline(x=0, color='black', linestyle='--', alpha=0.5)\n",
"ax4.set_title('Daily Returns Distribution', fontweight='bold')\n",
"ax4.set_xlabel('Daily Return (%)')\n",
"ax4.set_ylabel('Density')\n",
"ax4.legend()\n",
"ax4.grid(True, alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Print summary statistics\n",
"print(\"\\n\" + \"=\"*80)\n",
"print(\"📊 MOMENTUM STRATEGIES PERFORMANCE SUMMARY\")\n",
"print(\"=\"*80)\n",
"\n",
"print(f\"\\n🔵 TIME SERIES MOMENTUM (Treasury Futures):\")\n",
"print(f\" Annual Return: {ts_results['annual_return']:8.2%}\")\n",
"print(f\" Volatility: {ts_results['volatility']:8.2%}\")\n",
"print(f\" Sharpe Ratio: {ts_results['sharpe_ratio']:8.2f}\")\n",
"print(f\" Max Drawdown: {ts_results['max_drawdown']:8.2%}\")\n",
"print(f\" Win Rate: {ts_results['win_rate']:8.2%}\")\n",
"\n",
"print(f\"\\n🔴 CROSS-SECTIONAL MOMENTUM (Stocks):\")\n",
"print(f\" Annual Return: {cs_results['annual_return']:8.2%}\")\n",
"print(f\" Volatility: {cs_results['volatility']:8.2%}\")\n",
"print(f\" Sharpe Ratio: {cs_results['sharpe_ratio']:8.2f}\")\n",
"print(f\" Max Drawdown: {cs_results['max_drawdown']:8.2%}\")\n",
"print(f\" Win Rate: {cs_results['win_rate']:8.2%}\")\n",
"\n",
"print(f\"\\n📈 BENCHMARK COMPARISON:\")\n",
"print(f\" Treasury B&H Return: {market_annual:8.2%}\")\n",
"print(f\" Stock Market Return: {cs_market_annual:8.2%}\")\n",
"print(f\" TS Momentum Alpha: {ts_results['annual_return'] - market_annual:8.2%}\")\n",
"print(f\" CS Momentum Alpha: {cs_results['annual_return'] - cs_market_annual:8.2%}\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 5. Statistical Significance Testing\n",
"\n",
"Let's perform hypothesis testing to determine if the momentum effects are statistically significant, following the methodology from Chapter 7.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def statistical_significance_tests(strategy_returns, market_returns, num_simulations=1000):\n",
" \"\"\"\n",
" Perform statistical significance tests for momentum strategies.\n",
" \n",
" Parameters:\n",
" -----------\n",
" strategy_returns : array-like\n",
" Strategy returns\n",
" market_returns : array-like \n",
" Market returns for comparison\n",
" num_simulations : int\n",
" Number of Monte Carlo simulations\n",
" \n",
" Returns:\n",
" --------\n",
" dict : Test results including p-values and statistics\n",
" \"\"\"\n",
" \n",
" # Remove zero returns for cleaner analysis\n",
" strategy_returns = strategy_returns[strategy_returns != 0]\n",
" market_returns = market_returns[market_returns != 0]\n",
" \n",
" if len(strategy_returns) == 0:\n",
" return {'error': 'No valid strategy returns'}\n",
" \n",
" # 1. Basic t-test (assuming normal distribution)\n",
" strategy_mean = np.mean(strategy_returns)\n",
" strategy_std = np.std(strategy_returns)\n",
" n_obs = len(strategy_returns)\n",
" \n",
" if strategy_std > 0:\n",
" t_stat = strategy_mean * np.sqrt(n_obs) / strategy_std\n",
" p_value_ttest = 2 * (1 - stats.t.cdf(np.abs(t_stat), n_obs - 1))\n",
" else:\n",
" t_stat = p_value_ttest = 0\n",
" \n",
" # 2. Bootstrap test\n",
" bootstrap_means = []\n",
" for _ in range(num_simulations):\n",
" bootstrap_sample = np.random.choice(strategy_returns, size=len(strategy_returns), replace=True)\n",
" bootstrap_means.append(np.mean(bootstrap_sample))\n",
" \n",
" p_value_bootstrap = np.sum(np.array(bootstrap_means) <= 0) / num_simulations\n",
" \n",
" # 3. Randomized market returns test\n",
" if len(market_returns) > 0:\n",
" market_mean = np.mean(market_returns)\n",
" market_std = np.std(market_returns)\n",
" \n",
" random_means = []\n",
" for _ in range(num_simulations):\n",
" # Generate random returns with same statistical properties as market\n",
" random_returns = np.random.normal(market_mean, market_std, len(strategy_returns))\n",
" random_means.append(np.mean(random_returns))\n",
" \n",
" p_value_random_market = np.sum(np.array(random_means) >= strategy_mean) / num_simulations\n",
" else:\n",
" p_value_random_market = np.nan\n",
" \n",
" # 4. Sharpe ratio test\n",
" if strategy_std > 0:\n",
" strategy_sharpe = strategy_mean / strategy_std * np.sqrt(252)\n",
" \n",
" # Bootstrap Sharpe ratios\n",
" bootstrap_sharpes = []\n",
" for _ in range(num_simulations):\n",
" bootstrap_sample = np.random.choice(strategy_returns, size=len(strategy_returns), replace=True)\n",
" if np.std(bootstrap_sample) > 0:\n",
" bootstrap_sharpes.append(np.mean(bootstrap_sample) / np.std(bootstrap_sample) * np.sqrt(252))\n",
" \n",
" if len(bootstrap_sharpes) > 0:\n",
" p_value_sharpe = np.sum(np.array(bootstrap_sharpes) <= 0) / len(bootstrap_sharpes)\n",
" else:\n",
" p_value_sharpe = np.nan\n",
" else:\n",
" strategy_sharpe = p_value_sharpe = 0\n",
" \n",
" return {\n",
" 'strategy_mean': strategy_mean,\n",
" 'strategy_std': strategy_std,\n",
" 'strategy_sharpe': strategy_sharpe,\n",
" 't_statistic': t_stat,\n",
" 'p_value_ttest': p_value_ttest,\n",
" 'p_value_bootstrap': p_value_bootstrap,\n",
" 'p_value_random_market': p_value_random_market,\n",
" 'p_value_sharpe': p_value_sharpe,\n",
" 'n_observations': n_obs\n",
" }\n",
"\n",
"# Test statistical significance for both strategies\n",
"print(\"🧪 STATISTICAL SIGNIFICANCE TESTING\")\n",
"print(\"=\" * 60)\n",
"\n",
"# Time Series Momentum Tests\n",
"print(\"\\n🔵 Time Series Momentum (Treasury Futures):\")\n",
"ts_tests = statistical_significance_tests(\n",
" ts_results['strategy_returns'], \n",
" ts_results['market_returns'], \n",
" num_simulations=1000\n",
")\n",
"\n",
"if 'error' not in ts_tests:\n",
" print(f\" Strategy Mean Return: {ts_tests['strategy_mean']:8.6f}\")\n",
" print(f\" Strategy Std Dev: {ts_tests['strategy_std']:8.6f}\")\n",
" print(f\" Strategy Sharpe Ratio: {ts_tests['strategy_sharpe']:8.2f}\")\n",
" print(f\" T-statistic: {ts_tests['t_statistic']:8.2f}\")\n",
" print(f\" P-value (t-test): {ts_tests['p_value_ttest']:8.4f}\")\n",
" print(f\" P-value (bootstrap): {ts_tests['p_value_bootstrap']:8.4f}\")\n",
" print(f\" P-value (random market): {ts_tests['p_value_random_market']:8.4f}\")\n",
" print(f\" P-value (Sharpe): {ts_tests['p_value_sharpe']:8.4f}\")\n",
" print(f\" Number of observations: {ts_tests['n_observations']:8.0f}\")\n",
"else:\n",
" print(f\" Error: {ts_tests['error']}\")\n",
"\n",
"# Cross-Sectional Momentum Tests\n",
"print(\"\\n🔴 Cross-Sectional Momentum (Stocks):\")\n",
"cs_tests = statistical_significance_tests(\n",
" cs_results['strategy_returns'], \n",
" cs_results['market_returns'], \n",
" num_simulations=1000\n",
")\n",
"\n",
"if 'error' not in cs_tests:\n",
" print(f\" Strategy Mean Return: {cs_tests['strategy_mean']:8.6f}\")\n",
" print(f\" Strategy Std Dev: {cs_tests['strategy_std']:8.6f}\")\n",
" print(f\" Strategy Sharpe Ratio: {cs_tests['strategy_sharpe']:8.2f}\")\n",
" print(f\" T-statistic: {cs_tests['t_statistic']:8.2f}\")\n",
" print(f\" P-value (t-test): {cs_tests['p_value_ttest']:8.4f}\")\n",
" print(f\" P-value (bootstrap): {cs_tests['p_value_bootstrap']:8.4f}\")\n",
" print(f\" P-value (random market): {cs_tests['p_value_random_market']:8.4f}\")\n",
" print(f\" P-value (Sharpe): {cs_tests['p_value_sharpe']:8.4f}\")\n",
" print(f\" Number of observations: {cs_tests['n_observations']:8.0f}\")\n",
"else:\n",
" print(f\" Error: {cs_tests['error']}\")\n",
"\n",
"# Interpretation\n",
"print(f\"\\n📋 INTERPRETATION:\")\n",
"print(f\" P-values < 0.05 indicate statistical significance at 95% confidence level\")\n",
"print(f\" P-values < 0.01 indicate statistical significance at 99% confidence level\")\n",
"\n",
"# Check significance\n",
"if 'error' not in ts_tests:\n",
" ts_significant = ts_tests['p_value_ttest'] < 0.05\n",
" print(f\" Time Series Momentum is {'SIGNIFICANT' if ts_significant else 'NOT SIGNIFICANT'} (p={ts_tests['p_value_ttest']:.4f})\")\n",
"\n",
"if 'error' not in cs_tests:\n",
" cs_significant = cs_tests['p_value_ttest'] < 0.05\n",
" print(f\" Cross-Sectional Momentum is {'SIGNIFICANT' if cs_significant else 'NOT SIGNIFICANT'} (p={cs_tests['p_value_ttest']:.4f})\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 6. Risk Analysis and Drawdown Visualization\n",
"\n",
"Let's analyze the risk characteristics and drawdown patterns of both momentum strategies.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def calculate_drawdown_series(cumulative_returns):\n",
" \"\"\"Calculate drawdown series from cumulative returns.\"\"\"\n",
" peak = np.maximum.accumulate(cumulative_returns)\n",
" drawdown = (cumulative_returns - peak) / (1 + peak)\n",
" return drawdown\n",
"\n",
"# Calculate drawdown series\n",
"ts_drawdown = calculate_drawdown_series(ts_results['cumulative_returns'])\n",
"cs_drawdown = calculate_drawdown_series(cs_results['cumulative_returns'])\n",
"\n",
"# Create risk analysis visualization\n",
"fig, axes = plt.subplots(2, 2, figsize=(16, 12))\n",
"fig.suptitle('Risk Analysis and Drawdown Patterns', fontsize=16, fontweight='bold')\n",
"\n",
"# 1. Drawdown over time\n",
"ax1 = axes[0, 0]\n",
"ax1.fill_between(tu_dates_pd[:len(ts_drawdown)], ts_drawdown * 100, 0, \n",
" alpha=0.6, color='blue', label='Time Series Momentum')\n",
"ax1.set_title('Time Series Momentum: Drawdown Over Time', fontweight='bold')\n",
"ax1.set_ylabel('Drawdown (%)')\n",
"ax1.grid(True, alpha=0.3)\n",
"ax1.legend()\n",
"\n",
"ax2 = axes[0, 1]\n",
"ax2.fill_between(stock_dates_pd[:len(cs_drawdown)], cs_drawdown * 100, 0, \n",
" alpha=0.6, color='red', label='Cross-Sectional Momentum')\n",
"ax2.set_title('Cross-Sectional Momentum: Drawdown Over Time', fontweight='bold')\n",
"ax2.set_ylabel('Drawdown (%)')\n",
"ax2.grid(True, alpha=0.3)\n",
"ax2.legend()\n",
"\n",
"# 3. Risk-Return Scatter Plot\n",
"ax3 = axes[1, 0]\n",
"\n",
"# Calculate rolling metrics for scatter plot\n",
"window = 63 # Quarterly windows\n",
"ts_rolling_returns = []\n",
"ts_rolling_vols = []\n",
"cs_rolling_returns = []\n",
"cs_rolling_vols = []\n",
"\n",
"for i in range(window, len(ts_results['strategy_returns']), window//2):\n",
" window_returns = ts_results['strategy_returns'][i-window:i]\n",
" if len(window_returns[window_returns != 0]) > 10: # Need sufficient data\n",
" ts_rolling_returns.append(np.mean(window_returns) * 252)\n",
" ts_rolling_vols.append(np.std(window_returns) * np.sqrt(252))\n",
"\n",
"for i in range(window, len(cs_results['strategy_returns']), window//2):\n",
" window_returns = cs_results['strategy_returns'][i-window:i]\n",
" if len(window_returns[window_returns != 0]) > 10: # Need sufficient data\n",
" cs_rolling_returns.append(np.mean(window_returns) * 252)\n",
" cs_rolling_vols.append(np.std(window_returns) * np.sqrt(252))\n",
"\n",
"if len(ts_rolling_returns) > 0:\n",
" ax3.scatter(np.array(ts_rolling_vols) * 100, np.array(ts_rolling_returns) * 100, \n",
" alpha=0.6, color='blue', s=50, label='Time Series')\n",
"\n",
"if len(cs_rolling_returns) > 0:\n",
" ax3.scatter(np.array(cs_rolling_vols) * 100, np.array(cs_rolling_returns) * 100, \n",
" alpha=0.6, color='red', s=50, label='Cross-Sectional')\n",
"\n",
"ax3.axhline(y=0, color='black', linestyle='--', alpha=0.5)\n",
"ax3.axvline(x=0, color='black', linestyle='--', alpha=0.5)\n",
"ax3.set_xlabel('Volatility (%)')\n",
"ax3.set_ylabel('Annual Return (%)')\n",
"ax3.set_title('Risk-Return Profile (Rolling Windows)', fontweight='bold')\n",
"ax3.legend()\n",
"ax3.grid(True, alpha=0.3)\n",
"\n",
"# 4. Monthly Returns Heatmap for Time Series Momentum\n",
"ax4 = axes[1, 1]\n",
"\n",
"# Create monthly returns for time series momentum\n",
"ts_monthly_returns = []\n",
"ts_monthly_dates = []\n",
"\n",
"if len(ts_results['strategy_returns']) > 0:\n",
" # Convert to DataFrame for easier resampling\n",
" ts_df = pd.DataFrame({\n",
" 'returns': ts_results['strategy_returns'],\n",
" 'date': tu_dates_pd[:len(ts_results['strategy_returns'])]\n",
" }).set_index('date')\n",
" \n",
" # Resample to monthly\n",
" monthly_data = ts_df.resample('M').apply(lambda x: (1 + x).prod() - 1)\n",
" \n",
" if len(monthly_data) > 12: # Need at least a year of data\n",
" # Create year-month matrix\n",
" monthly_data.index = pd.to_datetime(monthly_data.index)\n",
" monthly_data['year'] = monthly_data.index.year\n",
" monthly_data['month'] = monthly_data.index.month\n",
" \n",
" # Pivot to create heatmap data\n",
" heatmap_data = monthly_data.pivot_table(values='returns', index='year', columns='month', fill_value=0)\n",
" \n",
" # Create heatmap\n",
" sns.heatmap(heatmap_data * 100, annot=True, fmt='.1f', cmap='RdYlBu_r', \n",
" center=0, ax=ax4, cbar_kws={'label': 'Monthly Return (%)'})\n",
" ax4.set_title('Time Series Momentum: Monthly Returns Heatmap', fontweight='bold')\n",
" ax4.set_xlabel('Month')\n",
" ax4.set_ylabel('Year')\n",
" else:\n",
" ax4.text(0.5, 0.5, 'Insufficient data for\\nmonthly heatmap', \n",
" ha='center', va='center', transform=ax4.transAxes, fontsize=12)\n",
" ax4.set_title('Monthly Returns Heatmap', fontweight='bold')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Print detailed risk metrics\n",
"print(\"\\n\" + \"=\"*80)\n",
"print(\"📊 DETAILED RISK ANALYSIS\")\n",
"print(\"=\"*80)\n",
"\n",
"print(f\"\\n🔵 TIME SERIES MOMENTUM RISK METRICS:\")\n",
"print(f\" Maximum Drawdown: {ts_results['max_drawdown']:8.2%}\")\n",
"print(f\" Max DD Duration: {ts_results['max_dd_duration']:8.0f} days\")\n",
"print(f\" Volatility (Annual): {ts_results['volatility']:8.2%}\")\n",
"print(f\" Downside Deviation: {np.std(ts_results['strategy_returns'][ts_results['strategy_returns'] < 0]) * np.sqrt(252):8.2%}\")\n",
"print(f\" VaR (95%): {np.percentile(ts_results['strategy_returns'][ts_results['strategy_returns'] != 0], 5) * 100:8.2f}%\")\n",
"print(f\" Skewness: {stats.skew(ts_results['strategy_returns'][ts_results['strategy_returns'] != 0]):8.2f}\")\n",
"print(f\" Kurtosis: {stats.kurtosis(ts_results['strategy_returns'][ts_results['strategy_returns'] != 0]):8.2f}\")\n",
"\n",
"print(f\"\\n🔴 CROSS-SECTIONAL MOMENTUM RISK METRICS:\")\n",
"print(f\" Maximum Drawdown: {cs_results['max_drawdown']:8.2%}\")\n",
"print(f\" Max DD Duration: {cs_results['max_dd_duration']:8.0f} days\")\n",
"print(f\" Volatility (Annual): {cs_results['volatility']:8.2%}\")\n",
"print(f\" Downside Deviation: {np.std(cs_results['strategy_returns'][cs_results['strategy_returns'] < 0]) * np.sqrt(252):8.2%}\")\n",
"print(f\" VaR (95%): {np.percentile(cs_results['strategy_returns'][cs_results['strategy_returns'] != 0], 5) * 100:8.2f}%\")\n",
"print(f\" Skewness: {stats.skew(cs_results['strategy_returns'][cs_results['strategy_returns'] != 0]):8.2f}\")\n",
"print(f\" Kurtosis: {stats.kurtosis(cs_results['strategy_returns'][cs_results['strategy_returns'] != 0]):8.2f}\")\n",
"\n",
"# Calculate Calmar Ratio (Annual Return / Max Drawdown)\n",
"ts_calmar = ts_results['annual_return'] / abs(ts_results['max_drawdown']) if ts_results['max_drawdown'] != 0 else 0\n",
"cs_calmar = cs_results['annual_return'] / abs(cs_results['max_drawdown']) if cs_results['max_drawdown'] != 0 else 0\n",
"\n",
"print(f\"\\n📈 RISK-ADJUSTED PERFORMANCE:\")\n",
"print(f\" Time Series Calmar Ratio: {ts_calmar:8.2f}\")\n",
"print(f\" Cross-Sectional Calmar Ratio: {cs_calmar:8.2f}\")\n",
"print(f\" (Calmar Ratio = Annual Return / Max Drawdown)\")\n"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 7. Conclusions and Key Insights\n",
"\n",
"Based on our comprehensive analysis of momentum trading strategies from Chapters 6 and 7, let's summarize the key findings and insights.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create a comprehensive summary table\n",
"summary_data = {\n",
" 'Metric': [\n",
" 'Annual Return',\n",
" 'Volatility', \n",
" 'Sharpe Ratio',\n",
" 'Maximum Drawdown',\n",
" 'Calmar Ratio',\n",
" 'Win Rate',\n",
" 'Number of Trades/Rebalances'\n",
" ],\n",
" 'Time Series Momentum': [\n",
" f\"{ts_results['annual_return']:.2%}\",\n",
" f\"{ts_results['volatility']:.2%}\",\n",
" f\"{ts_results['sharpe_ratio']:.2f}\",\n",
" f\"{ts_results['max_drawdown']:.2%}\",\n",
" f\"{ts_calmar:.2f}\",\n",
" f\"{ts_results['win_rate']:.2%}\",\n",
" f\"{ts_results['num_trades']:.0f}\"\n",
" ],\n",
" 'Cross-Sectional Momentum': [\n",
" f\"{cs_results['annual_return']:.2%}\",\n",
" f\"{cs_results['volatility']:.2%}\",\n",
" f\"{cs_results['sharpe_ratio']:.2f}\",\n",
" f\"{cs_results['max_drawdown']:.2%}\",\n",
" f\"{cs_calmar:.2f}\",\n",
" f\"{cs_results['win_rate']:.2%}\",\n",
" f\"{cs_results['num_rebalances']:.0f}\"\n",
" ],\n",
" 'Treasury B&H': [\n",
" f\"{market_annual:.2%}\",\n",
" f\"{market_vol:.2%}\",\n",
" f\"{market_sharpe:.2f}\",\n",
" \"N/A\",\n",
" \"N/A\",\n",
" \"N/A\",\n",
" \"1\"\n",
" ],\n",
" 'Stock Market': [\n",
" f\"{cs_market_annual:.2%}\",\n",
" f\"{cs_market_vol:.2%}\",\n",
" f\"{cs_market_sharpe:.2f}\",\n",
" \"N/A\",\n",
" \"N/A\",\n",
" \"N/A\",\n",
" \"1\"\n",
" ]\n",
"}\n",
"\n",
"summary_df = pd.DataFrame(summary_data)\n",
"print(\"📊 COMPREHENSIVE PERFORMANCE SUMMARY\")\n",
"print(\"=\" * 100)\n",
"print(summary_df.to_string(index=False))\n",
"\n",
"print(f\"\\n\\n🎯 KEY INSIGHTS FROM MOMENTUM ANALYSIS:\")\n",
"print(\"=\" * 80)\n",
"\n",
"print(f\"\\n1. 📈 PERFORMANCE COMPARISON:\")\n",
"if ts_results['sharpe_ratio'] > cs_results['sharpe_ratio']:\n",
" better_strategy = \"Time Series Momentum\"\n",
" better_sharpe = ts_results['sharpe_ratio']\n",
" worse_sharpe = cs_results['sharpe_ratio']\n",
"else:\n",
" better_strategy = \"Cross-Sectional Momentum\"\n",
" better_sharpe = cs_results['sharpe_ratio']\n",
" worse_sharpe = ts_results['sharpe_ratio']\n",
"\n",
"print(f\" • {better_strategy} shows superior risk-adjusted returns\")\n",
"print(f\" • Sharpe ratio difference: {better_sharpe:.2f} vs {worse_sharpe:.2f}\")\n",
"print(f\" • Both strategies {'outperform' if min(ts_results['sharpe_ratio'], cs_results['sharpe_ratio']) > max(market_sharpe, cs_market_sharpe) else 'underperform'} their respective benchmarks\")\n",
"\n",
"print(f\"\\n2. 🎲 STATISTICAL SIGNIFICANCE:\")\n",
"if 'error' not in ts_tests and 'error' not in cs_tests:\n",
" print(f\" • Time Series Momentum p-value: {ts_tests['p_value_ttest']:.4f}\")\n",
" print(f\" • Cross-Sectional Momentum p-value: {cs_tests['p_value_ttest']:.4f}\")\n",
" \n",
" significant_strategies = []\n",
" if ts_tests['p_value_ttest'] < 0.05:\n",
" significant_strategies.append(\"Time Series\")\n",
" if cs_tests['p_value_ttest'] < 0.05:\n",
" significant_strategies.append(\"Cross-Sectional\")\n",
" \n",
" if significant_strategies:\n",
" print(f\" • Statistically significant strategies: {', '.join(significant_strategies)}\")\n",
" else:\n",
" print(f\" • Neither strategy shows statistical significance at 95% confidence\")\n",
"\n",
"print(f\"\\n3. 📉 RISK CHARACTERISTICS:\")\n",
"print(f\" • Time Series max drawdown: {ts_results['max_drawdown']:.2%}\")\n",
"print(f\" • Cross-Sectional max drawdown: {cs_results['max_drawdown']:.2%}\")\n",
"print(f\" • {'Time Series' if ts_results['max_drawdown'] > cs_results['max_drawdown'] else 'Cross-Sectional'} momentum shows higher maximum drawdown\")\n",
"\n",
"print(f\"\\n4. 🔄 TRADING FREQUENCY:\")\n",
"print(f\" • Time Series: {ts_results['num_trades']:.0f} trades over the period\")\n",
"print(f\" • Cross-Sectional: {cs_results['num_rebalances']:.0f} rebalances over the period\")\n",
"print(f\" • Cross-sectional requires more frequent rebalancing\")\n",
"\n",
"print(f\"\\n5. 💡 PRACTICAL IMPLICATIONS:\")\n",
"print(f\" • Momentum effects {'appear' if any([ts_tests.get('p_value_ttest', 1) < 0.1, cs_tests.get('p_value_ttest', 1) < 0.1]) else 'do not appear'} to be present in the data\")\n",
"print(f\" • Time series momentum may be easier to implement (fewer trades)\")\n",
"print(f\" • Cross-sectional momentum requires larger universe of assets\")\n",
"print(f\" • Both strategies show {'positive' if min(ts_results['annual_return'], cs_results['annual_return']) > 0 else 'negative'} excess returns over the analysis period\")\n",
"\n",
"print(f\"\\n6. 📚 ALIGNMENT WITH ACADEMIC LITERATURE:\")\n",
"print(f\" • Results {'are consistent' if max(ts_results['sharpe_ratio'], cs_results['sharpe_ratio']) > 0.5 else 'show mixed evidence'} with momentum literature\")\n",
"print(f\" • Time series momentum (Chapter 7) shows {'strong' if ts_results['sharpe_ratio'] > 1 else 'moderate' if ts_results['sharpe_ratio'] > 0.5 else 'weak'} performance\")\n",
"print(f\" • Cross-sectional momentum (Chapter 6) shows {'strong' if cs_results['sharpe_ratio'] > 1 else 'moderate' if cs_results['sharpe_ratio'] > 0.5 else 'weak'} performance\")\n",
"\n",
"print(f\"\\n⚠ IMPORTANT DISCLAIMERS:\")\n",
"print(f\" • Results may be influenced by synthetic data if real data unavailable\")\n",
"print(f\" • Past performance does not guarantee future results\")\n",
"print(f\" • Transaction costs and market impact not included in analysis\")\n",
"print(f\" • Strategies may be subject to regime changes and crowding effects\")\n",
"\n",
"print(f\"\\n🔬 NEXT STEPS FOR RESEARCH:\")\n",
"print(f\" • Test strategies across different time periods and market regimes\")\n",
"print(f\" • Include transaction costs and realistic trading constraints\")\n",
"print(f\" • Analyze factor exposures and risk decomposition\")\n",
"print(f\" • Implement portfolio optimization and risk management overlays\")\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}