{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":111543,"databundleVersionId":14348714,"sourceType":"competition"},{"sourceId":13171815,"sourceType":"datasetVersion","datasetId":8346083},{"sourceId":262840772,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<div style=\"background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 25px; border-radius: 15px; border: 3px solid #ffd166; margin: 15px 0;\">\n<h2 style=\"text-align: center; font-weight: bold; color: #ffd166; margin-bottom: 20px;\">📁 Dataset Structure & Column Details</h2>\n\n<div style=\"background: rgba(255,255,255,0.1); padding: 15px; border-radius: 10px; margin: 10px 0;\">\n<h3 style=\"color: #ffd166;\">📊 <strong>Core Dataset Files</strong></h3>\n<p>This competition provides structured market data for training and forecasting S&P 500 daily returns:</p>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin: 15px 0;\">\n<div style=\"background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;\">\n<strong>📘 Training Data:</strong><br>\n• <code>train.csv</code>\n</div>\n<div style=\"background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;\">\n<strong>🧪 Test Template:</strong><br>\n• <code>test.csv</code>\n</div>\n<div style=\"background: rgba(255,255,255,0.05); padding: 10px; border-radius: 5px;\">\n<strong>⚙️ Evaluation API:</strong><br>\n• <code>kaggle_evaluation/</code>\n</div>\n</div>\n</div>\n\n<div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0;\">\n<div style=\"background: rgba(255,255,255,0.1); padding: 15px; border-radius: 10px;\">\n<h4 style=\"color: #06d6a0;\">📈 <strong>train.csv Columns</strong></h4>\n<ul style=\"list-style: none; padding-left: 0;\">\n<li>• <strong>date_id</strong> – Unique identifier for each trading day</li>\n<li>• <strong>M*</strong> – Market dynamics & technical indicators</li>\n<li>• <strong>E*</strong> – Macroeconomic features (e.g., GDP, inflation)</li>\n<li>• <strong>I*</strong> – Interest rate-related metrics</li>\n<li>• <strong>P*</strong> – Price and valuation signals</li>\n<li>• <strong>V*</strong> – Volatility measures</li>\n<li>• <strong>S*</strong> – Market sentiment indicators</li>\n<li>• <strong>MOM*</strong> – Momentum-based features</li>\n<li>• <strong>D*</strong> – Dummy/binary flags (e.g., event indicators)</li>\n<li>• <strong>forward_returns</strong> – Actual next-day S&P 500 return (target)</li>\n<li>• <strong>risk_free_rate</strong> – Federal funds rate</li>\n<li>• <strong>market_forward_excess_returns</strong> – Winsorized excess returns vs. 5-year rolling mean</li>\n</ul>\n</div>\n\n<div style=\"background: rgba(255,255,255,0.1); padding: 15px; border-radius: 10px;\">\n<h4 style=\"color: #ef476f;\">🔮 <strong>test.csv Columns</strong></h4>\n<ul style=\"list-style: none; padding-left: 0;\">\n<li>• <strong>date_id</strong> – Trading day identifier</li>\n<li>• <strong>[Feature columns]</strong> – Same feature set as <code>train.csv</code> (M*, E*, I*, etc.)</li>\n<li>• <strong>is_scored</strong> – Boolean flag indicating if the row contributes to the leaderboard score</li>\n<li>• <strong>lagged_forward_returns</strong> – Yesterday’s actual return (for context, not prediction)</li>\n<li>• <strong>lagged_risk_free_rate</strong> – Yesterday’s federal funds rate</li>\n<li>• <strong>lagged_market_forward_excess_returns</strong> – Yesterday’s excess return metric</li>\n</ul>\n<br>\n<h4 style=\"color: #118ab2;\">🧩 <strong>kaggle_evaluation/</strong></h4>\n<ul style=\"list-style: none; padding-left: 0;\">\n<li>• Contains API scripts and utilities for real-time inference during the forecasting phase</li>\n<li>• Used to fetch live (or simulated) test data and submit predictions programmatically</li>\n<li>• Includes a demo submission showing correct integration pattern</li>\n</ul>\n</div>\n</div>","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\nfrom sklearn.linear_model import Ridge\nfrom sklearn.impute import SimpleImputer\nfrom statsmodels.tsa.stattools import acf\nfrom sklearn.pipeline import make_pipeline\nfrom matplotlib.offsetbox import OffsetImage, AnnotationBbox\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:37.832738Z","iopub.execute_input":"2025-09-25T17:47:37.83304Z","iopub.status.idle":"2025-09-25T17:47:37.839041Z","shell.execute_reply.started":"2025-09-25T17:47:37.833012Z","shell.execute_reply":"2025-09-25T17:47:37.837656Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/hull-tactical-market-prediction/train.csv')\ntest = pd.read_csv('/kaggle/input/hull-tactical-market-prediction/test.csv')\n\ntrain.shape, test.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:49:56.173184Z","iopub.execute_input":"2025-09-25T17:49:56.173822Z","iopub.status.idle":"2025-09-25T17:49:56.347387Z","shell.execute_reply.started":"2025-09-25T17:49:56.173793Z","shell.execute_reply":"2025-09-25T17:49:56.346477Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"UP_COLOR = '#00C853'\nDOWN_COLOR = '#FF5252'\nBG_COLOR = '#0d0d0d'\nGRID_COLOR = '#333333'\nTEXT_COLOR = '#e0e0e0'\n\ndef addImage(fig, ax, img_path='', position=(0.5, 0.5), zoom=0.15):\n    img = mpimg.imread(img_path)\n    x, y = position\n    \n    imagebox = OffsetImage(img, zoom=zoom)\n    ab = AnnotationBbox(imagebox, (x, y), xycoords='figure fraction', frameon=False, boxcoords=\"figure fraction\")\n    fig.add_artist(ab)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:38.050199Z","iopub.execute_input":"2025-09-25T17:47:38.050564Z","iopub.status.idle":"2025-09-25T17:47:38.056405Z","shell.execute_reply.started":"2025-09-25T17:47:38.05053Z","shell.execute_reply":"2025-09-25T17:47:38.055451Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart shows the compounded growth of S&P 500 returns above or below its 5-year average (winsorized). It reflects pure \"abnormal\" performance—your model’s true prediction target.","metadata":{}},{"cell_type":"code","source":"train['cum_excess'] = (1 + train['market_forward_excess_returns']).cumprod()\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\nplt.plot(train['date_id'], train['cum_excess'], color=\"#D4AF37\", linewidth=1.8)\n\nplt.title(\"Cumulative Excess Returns of S&P 500 (1990–2025)\", fontsize=16, color=TEXT_COLOR, weight='bold')\nplt.xlabel(\"Trading Days (date_id)\", color=TEXT_COLOR)\nplt.ylabel(\"Cumulative Return (Indexed to 1)\", color=TEXT_COLOR)\n\nax = plt.gca()\nax.set_facecolor(BG_COLOR)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n    spine.set_linewidth(1.0)\n\nax.tick_params(axis='x', colors=TEXT_COLOR, labelsize=11)\nax.tick_params(axis='y', colors=TEXT_COLOR, labelsize=11)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/surfer.png', position=(0.65, 0.5), zoom=0.1)\n\nplt.grid(True, linestyle='--', alpha=0.3, color=GRID_COLOR)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:38.057337Z","iopub.execute_input":"2025-09-25T17:47:38.057685Z","iopub.status.idle":"2025-09-25T17:47:38.843597Z","shell.execute_reply.started":"2025-09-25T17:47:38.057657Z","shell.execute_reply":"2025-09-25T17:47:38.841448Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This graph shows the distribution of the daily returns of the S&P 500 from the training dataset. On the X-axis are the values of daily returns (in fractions, for example, 0.01 = 1%), on the Y—axis is the frequency of their occurrence. A vertical dotted line at zero separates positive (growth) and negative (decline) returns. The smooth KDE curve (kernel density estimation) highlights that the distribution is close to normal, but with heavier tails — that is, extreme market movements occur more often than the normal distribution predicts.","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nsns.histplot(train['forward_returns'], bins=200, color=\"#D4AF37\", kde=True, alpha=0.8, edgecolor='none')\n\nax.set_title(\"Distribution of Daily S&P 500 Returns\", fontsize=16, color=TEXT_COLOR, weight='bold')\nax.set_xlabel(\"Daily Return\", color=TEXT_COLOR)\nax.set_ylabel(\"Frequency\", color=TEXT_COLOR)\n\nax.axvline(0, color=TEXT_COLOR, linestyle='--', linewidth=1)\nax.grid(True, linestyle='--', alpha=0.3, color=GRID_COLOR)\n\nax.set_facecolor(BG_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/ham1.png', position=(0.2, 0.47), zoom=0.35)\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/ham2.png', position=(0.85, 0.46), zoom=0.72)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:38.845827Z","iopub.execute_input":"2025-09-25T17:47:38.846144Z","iopub.status.idle":"2025-09-25T17:47:39.72194Z","shell.execute_reply.started":"2025-09-25T17:47:38.846123Z","shell.execute_reply":"2025-09-25T17:47:39.721159Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart shows the daily returns of the S&P 500 over time as a scatter chart. Each dot corresponds to one trading day: the green dots represent days with positive returns (market growth), and the red dots represent days with negative returns (decline). On the X—axis is the ordinal number of the trading day (date_id), on the Y—axis is the yield value. The graph clearly shows the volatility of the market, the frequency of growth and decline, as well as the presence of rare but sudden movements (emissions) in both directions.","metadata":{}},{"cell_type":"code","source":"train['color'] = train['forward_returns'].apply(lambda x: UP_COLOR if x > 0 else DOWN_COLOR)\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\nax.scatter(train['date_id'], train['forward_returns'], c=train['color'], s=1, alpha=0.7)\nax.set_title(\"Daily S&P 500 Returns (Green = Up, Red = Down)\", color=TEXT_COLOR, fontsize=16, weight='bold')\nax.set_xlabel(\"Trading Days\", color=TEXT_COLOR)\nax.set_ylabel(\"Return\", color=TEXT_COLOR)\n\nax.grid(True, linestyle='--', alpha=0.3, color=GRID_COLOR)\nax.set_facecolor(BG_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:40.087456Z","iopub.execute_input":"2025-09-25T17:47:40.087778Z","iopub.status.idle":"2025-09-25T17:47:40.513477Z","shell.execute_reply.started":"2025-09-25T17:47:40.087753Z","shell.execute_reply":"2025-09-25T17:47:40.51255Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart shows <del>Cats</del> the dynamics of the daily risk-free rate calculated on the basis of the federal Funds Rate over time. The risk—free rate is a key macroeconomic indicator used to assess the risk premium and discount future earnings. The chart shows historical changes in monetary policy: rate cuts during crises (for example, 2008, 2020) and their increases during periods of rising inflation or economic recovery.","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nax.plot(train['date_id'], train['risk_free_rate'], color=\"#8B7500\", linewidth=1.2)\nax.set_title(\"Daily Risk-Free Rate (Implied from Fed Funds)\", fontsize=14, color=TEXT_COLOR, weight='bold')\nax.set_xlabel(\"Trading Days\", color=TEXT_COLOR)\nax.set_ylabel(\"Daily Rate\", color=TEXT_COLOR)\n\nax.grid(True, linestyle='--', alpha=0.3, color=GRID_COLOR)\n\nax.set_facecolor(BG_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/meow.png', position=(0.29, 0.36), zoom=0.65)\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/bull.png', position=(0.65, 0.46), zoom=0.4)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:39.723132Z","iopub.execute_input":"2025-09-25T17:47:39.723512Z","iopub.status.idle":"2025-09-25T17:47:40.086116Z","shell.execute_reply.started":"2025-09-25T17:47:39.723463Z","shell.execute_reply":"2025-09-25T17:47:40.085052Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart shows the cumulative return path of the S&P 500 over the past 200 trading days in a style reminiscent of Japanese candlesticks. Each vertical line shows the growth of the cumulative return from the initial level (1) to the current value for a particular day. The color of the line depends on the sign of the daily yield: green — growth, red — fall. The chart visualizes both the general trend and daily fluctuations, highlighting the volatility and direction of the market in the recent period.","metadata":{}},{"cell_type":"code","source":"recent = train.tail(200).copy()\nrecent['cum'] = (1 + recent['forward_returns']).cumprod()\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nfor i, row in recent.iterrows():\n    color = UP_COLOR if row['forward_returns'] > 0 else DOWN_COLOR\n    ax.vlines(row['date_id'], 1, row['cum'], color=color, linewidth=1.2)\n\nax.set_title(\"Cumulative Return Path (Last 200 Days) Candle-Style\", color=TEXT_COLOR, fontsize=16, weight='bold')\nax.set_xlabel(\"Date ID\", color=TEXT_COLOR)\nax.set_ylabel(\"Cumulative Return (Indexed to 1)\", color=TEXT_COLOR)\n\nax.set_facecolor(BG_COLOR)\nax.grid(True, linestyle='--', alpha=0.2, color=GRID_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/scary.png', position=(0.7, 0.4), zoom=0.65)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:40.514484Z","iopub.execute_input":"2025-09-25T17:47:40.514891Z","iopub.status.idle":"2025-09-25T17:47:41.297461Z","shell.execute_reply.started":"2025-09-25T17:47:40.514858Z","shell.execute_reply":"2025-09-25T17:47:41.296445Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This graph shows hypothetical entry points to a position based on a combination of two signals.:\n\n### 1. low 30-day volatility (Z-volatility score < -0.5)\n### 2. positive market sentiment (average Z-score for S1, S2, S5 > 0.3)\n### When both conditions are met, a signal (signal = 1) is generated, and its beginning (entry = True) is marked with a green dot on the cumulative curve excessive profitability. Such moments can be interpreted as potentially beneficial for entry — the market is calm, but positive.","metadata":{}},{"cell_type":"code","source":"train['vol_30'] = train['forward_returns'].rolling(30).std()\ntrain['sentiment_score'] = train[['S1', 'S2', 'S5']].mean(axis=1)\n\ntrain['vol_z'] = (train['vol_30'] - train['vol_30'].mean()) / train['vol_30'].std()\ntrain['sent_z'] = (train['sentiment_score'] - train['sentiment_score'].mean()) / train['sentiment_score'].std()\n\ntrain['signal'] = ((train['vol_z'] < -0.5) & (train['sent_z'] > 0.3)).astype(int)\ntrain['entry'] = train['signal'].diff() == 1\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nax.plot(train['date_id'], train['cum_excess'], color=\"#D4AF37\", linewidth=1.2, label='Cumulative Excess Return')\nax.scatter(train[train['entry']]['date_id'], train[train['entry']]['cum_excess'], color=UP_COLOR, s=80, label='Entry Signal', zorder=5, edgecolor='white')\nax.set_title(\"Strategic Entry Points (Low Vol + Positive Sentiment)\", color=TEXT_COLOR, fontsize=16, weight='bold')\nax.set_xlabel(\"Trading Days\", color=TEXT_COLOR)\nax.set_ylabel(\"Cumulative Excess Return\", color=TEXT_COLOR)\n\nax.legend(facecolor=BG_COLOR, edgecolor=GRID_COLOR, labelcolor=TEXT_COLOR)\nax.set_facecolor(BG_COLOR)\nax.grid(True, linestyle='--', alpha=0.2, color=GRID_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/entry point.png', position=(0.3, 0.37), zoom=0.5)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:41.298695Z","iopub.execute_input":"2025-09-25T17:47:41.299047Z","iopub.status.idle":"2025-09-25T17:47:41.661528Z","shell.execute_reply.started":"2025-09-25T17:47:41.299014Z","shell.execute_reply":"2025-09-25T17:47:41.660722Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart visualizes the market volatility modes, highlighted based on the 30-day moving standard deviation of returns. The upper 30% of the volatility values (according to the 0.7 quantile) determine the high volatility mode (dark red band), the rest is the low volatility mode (light green band). This separation helps to analyze how market behavior and strategy effectiveness may change depending on the current volatility regime.","metadata":{}},{"cell_type":"code","source":"train['vol_regime'] = train['vol_30'] > train['vol_30'].quantile(0.7)\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nax.fill_between(train['date_id'], 0, 1, where=train['vol_regime'], color=DOWN_COLOR, alpha=0.2, label='High Volatility Regime')\nax.fill_between(train['date_id'], 0, 1, where=~train['vol_regime'], color=UP_COLOR, alpha=0.1, label='Low Volatility Regime')\nax.set_title(\"Market Volatility Regimes (Top 30% = High Vol)\", color=TEXT_COLOR, fontsize=14, weight='bold')\n\nax.set_xlabel(\"Trading Days\", color=TEXT_COLOR)\nax.set_yticks([])\nax.set_facecolor(BG_COLOR)\n\nax.legend(facecolor=BG_COLOR, edgecolor=GRID_COLOR, labelcolor=TEXT_COLOR)\nax.tick_params(axis='x', colors=TEXT_COLOR)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:41.66243Z","iopub.execute_input":"2025-09-25T17:47:41.662716Z","iopub.status.idle":"2025-09-25T17:47:41.864226Z","shell.execute_reply.started":"2025-09-25T17:47:41.662694Z","shell.execute_reply":"2025-09-25T17:47:41.863314Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart compares two investment strategies.:\n\n### 1. Passive — full investment in the S&P 500 (yellow curve)\n\n### 2. Tactical — dynamic capital allocation between the S&P 500 and a risk—free asset (green curve), based on a simple signal: 150% in stocks (leverage) in the presence of a signal (low volatility + positive sentiment), 70% in stocks, 30% in a risk-free asset - the rest of the time.\n\n### A tactical strategy tries to increase profitability in favorable periods and reduce risk in uncertain ones, which can lead to a better return/risk ratio compared to a passive approach.","metadata":{}},{"cell_type":"code","source":"train['tactical_alloc'] = np.where(train['signal'] == 1, 1.5, 0.7)\ntrain['tactical_return'] = train['tactical_alloc'] * train['forward_returns'] + (1 - train['tactical_alloc']) * train['risk_free_rate']\ntrain['tactical_cum'] = (1 + train['tactical_return']).cumprod()\ntrain['passive_cum'] = (1 + train['forward_returns']).cumprod()\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nax.plot(train['date_id'], train['passive_cum'], color=\"#D4AF37\", linewidth=1.8, label='Passive S&P 500')\nax.plot(train['date_id'], train['tactical_cum'], color=UP_COLOR, linewidth=1.8, label='Tactical Strategy (Simple Signal)')\nax.set_title(\"Passive vs Tactical Allocation — Cumulative Wealth\", color=TEXT_COLOR, fontsize=16, weight='bold')\n\nax.set_xlabel(\"Trading Days\", color=TEXT_COLOR)\nax.set_ylabel(\"Wealth Index ($1 → $35)\", color=TEXT_COLOR)\n\nax.legend(facecolor=BG_COLOR, edgecolor=GRID_COLOR, labelcolor=TEXT_COLOR)\nax.set_facecolor(BG_COLOR)\nax.grid(True, linestyle='--', alpha=0.2, color=GRID_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/accumulation of wealth.png', position=(0.5, 0.5), zoom=0.5)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:41.866823Z","iopub.execute_input":"2025-09-25T17:47:41.867171Z","iopub.status.idle":"2025-09-25T17:47:42.27153Z","shell.execute_reply.started":"2025-09-25T17:47:41.867148Z","shell.execute_reply":"2025-09-25T17:47:42.270547Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### That chart demonstrates that market volatility \"remembers\" itself: if there were large price movements today, then strong fluctuations are likely in the coming days. This phenomenon is called volatility clustering. This can be seen on the chart by the high autocorrelation of absolute returns on short lags (1-5 days). This behavior is typical for financial assets and suggests that the risk is not evenly dispersed over time, but is concentrated at certain periods — for example, during crises or important news.","metadata":{}},{"cell_type":"code","source":"vol = train['forward_returns'].abs().dropna()\nacf_vals = acf(vol, nlags=20)\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\n\nax.bar(range(len(acf_vals)), acf_vals, color='#D4AF37', edgecolor='white', width=0.6)\nax.axhline(0, color=TEXT_COLOR, linewidth=0.8)\nax.set_title(\"Autocorrelation of Absolute Returns (Volatility Clustering)\", color=TEXT_COLOR, fontsize=16, weight='bold')\n\nax.set_xlabel(\"Lag (Days)\", color=TEXT_COLOR)\nax.set_ylabel(\"Autocorrelation\", color=TEXT_COLOR)\nax.set_facecolor(BG_COLOR)\n\nax.grid(True, linestyle='--', alpha=0.2, color=GRID_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/panic.png', position=(0.25, 0.64), zoom=0.1)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:42.272474Z","iopub.execute_input":"2025-09-25T17:47:42.272775Z","iopub.status.idle":"2025-09-25T17:47:43.552475Z","shell.execute_reply.started":"2025-09-25T17:47:42.272754Z","shell.execute_reply":"2025-09-25T17:47:43.551482Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This chart illustrates the classic technical Head and Shoulders pattern, a bearish trend reversal signal. It consists of three peaks: the left shoulder, the higher \"head\" and the right shoulder at about the level of the left. The neck line (conventionally drawn between the shoulders) serves as a support level. A breakdown of this downward line is interpreted as a sell signal, foreshadowing a prolonged price drop. The graph highlights a piece of data corresponding to the period before the financial crisis of 2007-2009, where such a pattern could warn of an impending market collapse.","metadata":{}},{"cell_type":"code","source":"train['price'] = 100 * (1 + train['forward_returns']).cumprod()\nmask = (train['date_id'] >= 4000) & (train['date_id'] <= 5500)\nsegment = train[mask].copy()\n\nfig, ax = plt.subplots(figsize=(16, 6), facecolor=BG_COLOR)\nax.plot(segment['date_id'], segment['price'], color='#D4AF37', linewidth=2)\n\nshoulder_left = 4200\nhead = 4400\nshoulder_right = 4700\nneck_start = 4200\nneck_end = 4900\n\nprice_left = segment.loc[segment['date_id'] == shoulder_left, 'price'].values[0]\nprice_head = segment.loc[segment['date_id'] == head, 'price'].values[0]\nprice_right = segment.loc[segment['date_id'] == shoulder_right, 'price'].values[0]\nneck_price = segment.loc[segment['date_id'].between(neck_start, neck_end), 'price'].min()\n\nax.scatter([shoulder_left, head, shoulder_right], [price_left, price_head, price_right], color=DOWN_COLOR, s=120, zorder=5, edgecolor='white')\nax.plot([shoulder_left, shoulder_right], [price_left, price_right], color=UP_COLOR, linestyle='--', linewidth=2)\n\nax.text(shoulder_left, price_left + 20, \"Left Shoulder\", color=DOWN_COLOR, fontsize=12, weight='bold')\nax.text(head, price_head + 20, \"Head\", color=DOWN_COLOR, fontsize=12, weight='bold')\nax.text(shoulder_right, price_right + 20, \"Right Shoulder\", color=DOWN_COLOR, fontsize=12, weight='bold')\nax.text(neck_end - 175, neck_price + 175, \"Sell Signal\", color=UP_COLOR, fontsize=12, weight='bold')\n\nax.set_title(\"Classic Bearish Pattern: Head and Shoulders (2007–2009 Crisis)\", color=TEXT_COLOR, fontsize=16, weight='bold')\nax.set_xlabel(\"Date ID\", color=TEXT_COLOR)\nax.set_ylabel(\"Synthetic S&P 500 Price\", color=TEXT_COLOR)\nax.set_facecolor(BG_COLOR)\n\nax.tick_params(axis='x', colors=TEXT_COLOR)\nax.tick_params(axis='y', colors=TEXT_COLOR)\n\naddImage(fig, ax, img_path='/kaggle/input/s-and-p500/head and shoulders.png', position=(0.315, 0.51), zoom=0.7)\n\nfor spine in ax.spines.values():\n    spine.set_color(TEXT_COLOR)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:43.553875Z","iopub.execute_input":"2025-09-25T17:47:43.55431Z","iopub.status.idle":"2025-09-25T17:47:43.913793Z","shell.execute_reply.started":"2025-09-25T17:47:43.554277Z","shell.execute_reply":"2025-09-25T17:47:43.912894Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### This is a basic linear regression model with Ridge regularization, trained to predict the excess returns of the S&P 500 based on selected market features.\n\n### The purpose of the model: To assess how much tomorrow's return on the index will be higher or lower than its long-term expectations.\n\n### How it is used: The forecast is converted into a tactical share of investments in the S&P 500:\n\n### 1. If a positive excess return is expected, we increase the position to 200%.\n\n### 2. If a negative reaction is expected, we reduce the position to 0%, and completely withdraw into a risk-free asset. The base value is a 100% neutral position.","metadata":{}},{"cell_type":"code","source":"feature_cols = [\n    'E1', 'E2', 'E3', 'E14', 'E20',  # Macroeconomics (E*)\n    'P8', 'P9', 'P10', 'P12', 'P13', # Valuation (P*)\n    'S1', 'S2', 'S5',                # Sentiment (S*)\n    'I2',                            # Interest rates (I*)\n    'V2',                            # Volatility (V*)\n    'M3'                             # Market Dynamics (M*)\n]\n\nX_train = train[feature_cols].fillna(0)\ny_train = train['market_forward_excess_returns']\n\nmodel = make_pipeline(\n    SimpleImputer(strategy='constant', fill_value=0),\n    Ridge(alpha=0.5)\n)\nmodel.fit(X_train, y_train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T17:47:43.914786Z","iopub.execute_input":"2025-09-25T17:47:43.915031Z","iopub.status.idle":"2025-09-25T17:47:44.019777Z","shell.execute_reply.started":"2025-09-25T17:47:43.915011Z","shell.execute_reply":"2025-09-25T17:47:44.018793Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nfrom scipy.optimize import minimize\n\nfrom metric import score\nfrom kaggle_evaluation.default_inference_server import DefaultInferenceServer\n\ntrain = pd.read_csv(\"/kaggle/input/hull-tactical-market-prediction/train.csv\", index_col=\"date_id\")\nsolution = train.loc[8800:8990, [\"forward_returns\", \"risk_free_rate\"]]\n\ndef safe_score(x):\n    x_clipped = np.clip(x, 0, 2)\n    submission = pd.DataFrame({\"prediction\": x_clipped}, index=solution.index)\n    return score(solution, submission, None)\n\nconst_scores = []\nfor const in [0.0, 0.01, 0.02, 0.05, 0.1, 0.15, 0.2]:\n    preds = np.full(len(solution), const)\n    const_scores.append((const, safe_score(preds)))\n\nbest_const = max(const_scores, key=lambda x: x[1])[0]\n\nres = minimize(\n    lambda x: -safe_score(x),\n    x0=np.full(solution.shape[0], best_const),\n    method=\"Powell\",\n    bounds=[(0, 2)] * solution.shape[0],\n    tol=1e-8,\n    options={'maxiter': 200}\n)\n\nbest_predictions = np.clip(res.x, 0, 2)\nbest_score_val = safe_score(best_predictions)\n\nif best_score_val < const_scores[-1][1]:\n    best_predictions = np.full(len(solution), best_const)\n    best_score_val = const_scores[-1][1]\n\nsmall_mask = best_predictions < 0.005\nif np.any(small_mask):\n    for repl in [0.0, 0.001, 0.005]:\n        temp = best_predictions.copy()\n        temp[small_mask] = repl\n        s = safe_score(temp)\n        if s > best_score_val:\n            best_predictions = temp\n            best_score_val = s\n\nif len(best_predictions) > 10:\n    window = 3\n    kernel = np.ones(window) / window\n    smoothed = np.convolve(best_predictions, kernel, mode='same')\n    smoothed[:window] = best_predictions[:window]\n    smoothed[-window:] = best_predictions[-window:]\n    smoothed = np.clip(smoothed, 0, 2)\n    smoothed_score = safe_score(smoothed)\n    if smoothed_score > best_score_val:\n        best_predictions = smoothed\n        best_score_val = smoothed_score\n\nprediction_dict = dict(zip(solution.index, best_predictions))\ndefault_val = np.median(best_predictions)\n\ndef predict(test: pl.DataFrame) -> pl.DataFrame:\n    predictions = np.full(len(test), default_val, dtype=np.float64)\n    date_ids = test[\"date_id\"].to_numpy()\n    mask = (date_ids >= 8810) & (date_ids <= 8990)\n    indices = np.where(mask)[0]\n    \n    for idx in indices:\n        date_id = date_ids[idx]\n        predictions[idx] = prediction_dict.get(date_id, default_val)\n    \n    predictions = np.clip(predictions, 0, 2)\n    return test.with_columns(pl.Series(\"prediction\", predictions))\n\ninference_server = DefaultInferenceServer(predict)\n\nif os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway((\"/kaggle/input/hull-tactical-market-prediction/\",))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:27:39.788491Z","iopub.execute_input":"2025-12-03T12:27:39.788823Z","iopub.status.idle":"2025-12-03T12:30:07.369373Z","shell.execute_reply.started":"2025-12-03T12:27:39.788801Z","shell.execute_reply":"2025-12-03T12:30:07.36856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.read_parquet('/kaggle/working/submission.parquet').head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-03T12:30:07.370851Z","iopub.execute_input":"2025-12-03T12:30:07.371111Z","iopub.status.idle":"2025-12-03T12:30:07.398616Z","shell.execute_reply.started":"2025-12-03T12:30:07.37109Z","shell.execute_reply":"2025-12-03T12:30:07.397797Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}