{ "cells": [ { "cell_type": "markdown", "id": "5ea40925", "metadata": {}, "source": [ "## Polygon Data Fetch and Update\n", "\n", "This section sets up configuration paths and defines utilities for fetching daily OHLCV (Open, High, Low, Close, Volume) from Polygon.io. \n", "It creates a local dataset that automatically updates with the latest trading data for selected tickers. \n", "The dataset is saved to CSV for persistence and incremental updates." ] }, { "cell_type": "code", "execution_count": 1, "id": "fb0f11ab", "metadata": {}, "outputs": [], "source": [ "# Imports for Polygon Updates\n", "\n", "import os\n", "import pandas as pd\n", "from datetime import date, timedelta\n", "from polygon import RESTClient" ] }, { "cell_type": "markdown", "id": "d5e78ac8", "metadata": {}, "source": [ "#### Paths and Configuration\n", "\n", "This block sets the base directory for the project, defines the data storage path, and initializes the polygon api key. \n", "It also specifies which stock tickers will be used for fetching market data." ] }, { "cell_type": "code", "execution_count": null, "id": "cd5ecc8e", "metadata": {}, "outputs": [], "source": [ "BASE_DIR = os.path.dirname(os.getcwd())\n", "DATA_PATH = os.path.join(BASE_DIR, \"data\")\n", "\n", "# Polygon API key\n", "POLYGON_API_KEY = \"POLYGON_API_KEY\"\n", "\n", "# ---- Ticker Config ----\n", "TICKERS = {\n", " \"TSLA\": \"TSLA\",\n", " \"NVDA\": \"NVDA\",\n", " \"SPY\": \"SPY\"\n", "}" ] }, { "cell_type": "markdown", "id": "11604bee", "metadata": {}, "source": [ "#### Polygon Data Fetcher\n", "\n", "The `fetch_new_data()` function connects to the Polygon.io API to retrieve OHLCV data for a specified ticker. \n", "It automatically merges new records into an existing CSV file, ensuring historical data is preserved. \n", "If the file doesn’t exist or is empty, a new dataset is initialized." ] }, { "cell_type": "code", "execution_count": 3, "id": "d595865d", "metadata": {}, "outputs": [], "source": [ "def fetch_new_data(csv_path, api_key, ticker):\n", " csv_path = os.path.abspath(csv_path)\n", " os.makedirs(os.path.dirname(csv_path), exist_ok=True)\n", "\n", " try:\n", " if os.path.exists(csv_path) and os.path.getsize(csv_path) == 0:\n", " raise pd.errors.EmptyDataError\n", " df = pd.read_csv(csv_path)\n", " print(f\"Loaded existing {ticker} dataset\")\n", " except (FileNotFoundError, pd.errors.EmptyDataError):\n", " print(f\"{csv_path} not found or empty — starting fresh for {ticker}.\")\n", " df = pd.DataFrame(columns=[\"Source\",\"Start\",\"End\",\"Open\",\"High\",\"Low\",\"Close\",\"Volume\",\"Market Cap\"])\n", "\n", " df[\"End\"] = pd.to_datetime(df[\"End\"], errors=\"coerce\").dt.date\n", " start_date = df[\"End\"].max() if len(df) and df[\"End\"].notna().any() else date(2015, 8, 7)\n", " end_date = date.today()\n", "\n", " if start_date >= end_date:\n", " print(f\"{ticker} already up to date.\")\n", " return df\n", "\n", " print(f\"Fetching {ticker} data {start_date} → {end_date}\")\n", " client = RESTClient(api_key=api_key)\n", " all_data = []\n", " try:\n", " for a in client.list_aggs(\n", " ticker=ticker, multiplier=1, timespan=\"day\",\n", " from_=start_date.isoformat(), to=end_date.isoformat(), limit=5000\n", " ):\n", " bar_date = (pd.to_datetime(a.timestamp, unit=\"ms\") + timedelta(days=1)).date()\n", " all_data.append({\n", " \"Source\": \"Polygon\",\n", " \"Start\": bar_date - timedelta(days=1),\n", " \"End\": bar_date,\n", " \"Open\": a.open,\n", " \"High\": a.high,\n", " \"Low\": a.low,\n", " \"Close\": a.close,\n", " \"Volume\": a.volume,\n", " \"Market Cap\": a.close * a.volume if a.volume else None\n", " })\n", " except Exception as e:\n", " print(f\"Error fetching {ticker}: {e}\")\n", " return df\n", "\n", " if not all_data:\n", " print(f\"No new {ticker} data fetched.\")\n", " return df\n", "\n", " new_df = pd.DataFrame(all_data)\n", " combined = (\n", " pd.concat([df, new_df], ignore_index=True)\n", " .drop_duplicates(subset=[\"End\"], keep=\"last\")\n", " .sort_values(\"End\")\n", " .reset_index(drop=True)\n", " )\n", " combined.to_csv(csv_path, index=False)\n", " print(f\"Updated {os.path.basename(csv_path)} — added {len(new_df)} new rows.\")\n", " return combined" ] }, { "cell_type": "markdown", "id": "a2b2daa2", "metadata": {}, "source": [ "#### Dataset Update Pipeline\n", " \n", "This function iterates through all configured tickers and calls `fetch_new_data()` for each. \n", "It ensures all datasets are refreshed in one run, simplifying updates." ] }, { "cell_type": "code", "execution_count": 4, "id": "52439ea7", "metadata": {}, "outputs": [], "source": [ "def update_datasets():\n", " \"\"\"Update all tickers defined in TICKERS using Polygon data.\"\"\"\n", " print(\"\\nUpdating datasets...\\n\")\n", " for fname, symbol in TICKERS.items():\n", " csv_path = os.path.join(DATA_PATH, f\"{fname}.csv\")\n", " fetch_new_data(csv_path, POLYGON_API_KEY, symbol)" ] }, { "cell_type": "code", "execution_count": 5, "id": "f04f9d88", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Updating datasets...\n", "\n", "Loaded existing TSLA dataset\n", "TSLA already up to date.\n", "Loaded existing NVDA dataset\n", "NVDA already up to date.\n", "Loaded existing SPY dataset\n", "SPY already up to date.\n" ] } ], "source": [ "# Run Dataset Update\n", "\n", "update_datasets()" ] }, { "cell_type": "markdown", "id": "c454667c", "metadata": {}, "source": [ "## Model Training Pipeline (LSTM)\n", "\n", "This section defines, trains, and saves LSTM neural network models to forecast future stock prices. \n", "It includes configuration parameters, data preparation, model definition, and training routines." ] }, { "cell_type": "code", "execution_count": 6, "id": "9a379d05", "metadata": {}, "outputs": [], "source": [ "import os\n", "import argparse\n", "import joblib\n", "import numpy as np\n", "import pandas as pd\n", "from datetime import date, timedelta\n", "\n", "import torch\n", "import torch.nn as nn\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.preprocessing import StandardScaler" ] }, { "cell_type": "markdown", "id": "b09f5939", "metadata": {}, "source": [ "### Paths and Model Configuration\n", "\n", "Defines global paths and configurations for training models. \n", "Each ticker has a set of forecasting horizons (1 day, 1 week, etc.) with hyperparameters tuned for that timeframe." ] }, { "cell_type": "code", "execution_count": 7, "id": "e3eb75a3", "metadata": {}, "outputs": [], "source": [ "BASE_DIR = os.path.dirname(os.getcwd())\n", "DATA_PATH = os.path.join(BASE_DIR, \"data\")\n", "MODELS_DIR = os.path.join(BASE_DIR, \"models\")\n", "\n", "DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", "\n", "# ---- Ticker Configuration ----\n", "TICKERS = {\n", " \"TSLA\": \"TSLA\",\n", " \"NVDA\": \"NVDA\",\n", " \"SPY\": \"SPY\"\n", "}\n", "\n", "# ---- Horizon Settings ----\n", "HORIZON_CONFIGS = {\n", " \"1d\": {\"days\": 1, \"seq_len_short\": 90, \"epochs\": 200, \"hidden_size\": 256, \"num_layers\": 3,\n", " \"dropout\": 0.2, \"lr\": 3e-4, \"weight_decay\": 1e-4, \"patience\": 25, \"min_lr\": 1e-5},\n", " \"1w\": {\"days\": 5, \"seq_len_short\": 90, \"epochs\": 150, \"hidden_size\": 256, \"num_layers\": 3,\n", " \"dropout\": 0.25, \"lr\": 3e-4, \"weight_decay\": 5e-5, \"patience\": 20, \"min_lr\": 1e-5},\n", " \"4w\": {\"days\": 20, \"seq_len_short\": 120, \"epochs\": 200, \"hidden_size\": 160, \"num_layers\": 2,\n", " \"dropout\": 0.3, \"lr\": 2.5e-4, \"weight_decay\": 5e-5, \"patience\": 25, \"min_lr\": 5e-6},\n", " \"6m\": {\"days\": 120, \"seq_len_short\": 160, \"epochs\": 250, \"hidden_size\": 160, \"num_layers\": 2,\n", " \"dropout\": 0.35, \"lr\": 2e-4, \"weight_decay\": 1e-5, \"patience\": 30, \"min_lr\": 5e-6},\n", " \"1y\": {\"days\": 240, \"seq_len_short\": 160, \"epochs\": 250, \"hidden_size\": 160, \"num_layers\": 2,\n", " \"dropout\": 0.35, \"lr\": 2e-4, \"weight_decay\": 1e-5, \"patience\": 30, \"min_lr\": 5e-6},\n", "}" ] }, { "cell_type": "markdown", "id": "25c0817e", "metadata": {}, "source": [ "### LSTM Model Definition\n", "\n", "The `LSTMModel` class defines a neural network that processes sequential time-series data. \n", "It uses stacked LSTM layers followed by a linear layer to predict the next closing price." ] }, { "cell_type": "code", "execution_count": null, "id": "ce351ff5", "metadata": {}, "outputs": [], "source": [ "class LSTMModel(nn.Module):\n", " def __init__(self, input_size=5, hidden_size=256, num_layers=3, dropout=0.2, bidirectional=True):\n", " super(LSTMModel, self).__init__()\n", " self.bidirectional = bidirectional\n", " self.num_directions = 2 if bidirectional else 1\n", "\n", " self.lstm = nn.LSTM(\n", " input_size=input_size,\n", " hidden_size=hidden_size,\n", " num_layers=num_layers,\n", " dropout=dropout,\n", " batch_first=True,\n", " bidirectional=bidirectional\n", " )\n", "\n", " self.fc = nn.Linear(hidden_size * self.num_directions, 1)\n", "\n", " def forward(self, x):\n", " out, _ = self.lstm(x)\n", " out = self.fc(out[:, -1, :])\n", " return out\n" ] }, { "cell_type": "markdown", "id": "b3863052", "metadata": {}, "source": [ "### Data Preparation Utilities\n", "\n", "The `prepare_targets()` function creates target values by shifting the closing price forward by a defined number of days (the forecast horizon)." ] }, { "cell_type": "code", "execution_count": 9, "id": "1821ba90", "metadata": {}, "outputs": [], "source": [ "def prepare_targets(df, horizon_days):\n", " \"\"\"Shift Close column forward to create next-step prediction target.\"\"\"\n", " df[\"target\"] = df[\"Close\"].shift(-horizon_days)\n", " return df.dropna(subset=[\"target\"])" ] }, { "cell_type": "markdown", "id": "f8ecf7fc", "metadata": {}, "source": [ "### Training Function for a Single Ticker\n", "\n", "This function handles all steps of training an LSTM model for one ticker and one prediction horizon:\n", "- Data cleaning and normalization\n", "- Sequence generation for training\n", "- Model training with LR scheduling (ReduceLROnPlateau)\n", "- Model, scaler, and configuration persistence to disk" ] }, { "cell_type": "code", "execution_count": 10, "id": "04a4bcb2", "metadata": {}, "outputs": [], "source": [ "def train_single_ticker(file_name, df, horizon_name, hcfg):\n", " \"\"\"Train an LSTM model for a single ticker and horizon.\"\"\"\n", " df = df.dropna(subset=[\"Open\",\"High\",\"Low\",\"Close\",\"Volume\"])\n", " df = prepare_targets(df, hcfg[\"days\"])\n", "\n", " features = df[[\"Open\",\"High\",\"Low\",\"Close\",\"Volume\"]].values\n", " targets = df[\"target\"].values\n", "\n", " x_scaler = StandardScaler().fit(features)\n", " X = x_scaler.transform(features)\n", "\n", " y_scaler = StandardScaler().fit(targets.reshape(-1,1))\n", " y = y_scaler.transform(targets.reshape(-1,1)).flatten()\n", "\n", " seq_len = hcfg[\"seq_len_short\"]\n", " X_seq, y_seq = [], []\n", " for i in range(len(X) - seq_len):\n", " X_seq.append(X[i:i+seq_len])\n", " y_seq.append(y[i+seq_len-1])\n", " X_seq, y_seq = np.array(X_seq), np.array(y_seq)\n", "\n", " if len(X_seq) < 100:\n", " print(f\"Not enough samples for {file_name}, skipping.\")\n", " return\n", "\n", " X_train, X_val, y_train, y_val = train_test_split(X_seq, y_seq, test_size=0.2, shuffle=False)\n", " X_train, y_train = torch.tensor(X_train, dtype=torch.float32).to(DEVICE), torch.tensor(y_train, dtype=torch.float32).to(DEVICE)\n", " X_val, y_val = torch.tensor(X_val, dtype=torch.float32).to(DEVICE), torch.tensor(y_val, dtype=torch.float32).to(DEVICE)\n", "\n", " model = LSTMModel(X_train.shape[2], hcfg[\"hidden_size\"], hcfg[\"num_layers\"], hcfg[\"dropout\"]).to(DEVICE)\n", " optimizer = torch.optim.AdamW(model.parameters(), lr=hcfg[\"lr\"], weight_decay=hcfg[\"weight_decay\"])\n", " criterion = nn.MSELoss()\n", " scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n", " optimizer, \"min\", factor=0.5, patience=hcfg[\"patience\"], min_lr=hcfg[\"min_lr\"]\n", " )\n", " print(f\"Starting the {file_name}-{horizon_name} model\\n\")\n", " for epoch in range(hcfg[\"epochs\"]):\n", " model.train()\n", " optimizer.zero_grad()\n", " loss = criterion(model(X_train).squeeze(), y_train)\n", " loss.backward()\n", " optimizer.step()\n", "\n", " model.eval()\n", " with torch.no_grad():\n", " val_loss = criterion(model(X_val).squeeze(), y_val)\n", " scheduler.step(val_loss)\n", "\n", " if (epoch+1) % 10 == 0 or epoch == 0:\n", " print(f\"[{file_name}-{horizon_name}] Epoch {epoch+1}/{hcfg['epochs']} | \"\n", " f\"Train {loss.item():.6f} | Val {val_loss.item():.6f} | \"\n", " f\"LR {optimizer.param_groups[0]['lr']:.6f}\")\n", "\n", " # Save model and scalers\n", " out_dir = os.path.join(MODELS_DIR, file_name)\n", " os.makedirs(out_dir, exist_ok=True)\n", " torch.save(model.state_dict(), os.path.join(out_dir, f\"{file_name}_{horizon_name}_model.pth\"))\n", " joblib.dump(x_scaler, os.path.join(out_dir, f\"{file_name}_{horizon_name}_scaler.pkl\"))\n", " joblib.dump(y_scaler, os.path.join(out_dir, f\"{file_name}_{horizon_name}_y_scaler.pkl\"))\n", "\n", " # Save configuration\n", " import json\n", " config_out = {\n", " \"input_size\": X_train.shape[-1],\n", " \"hidden_size\": hcfg[\"hidden_size\"],\n", " \"num_layers\": hcfg[\"num_layers\"],\n", " \"dropout\": hcfg[\"dropout\"],\n", " \"seq_len\": hcfg[\"seq_len_short\"],\n", " }\n", " with open(os.path.join(out_dir, f\"{file_name}_{horizon_name}_config.json\"), \"w\") as f:\n", " json.dump(config_out, f, indent=2)\n", "\n", " print(f\"\\nSaved {file_name}-{horizon_name} model\")" ] }, { "cell_type": "markdown", "id": "17bd47bb", "metadata": {}, "source": [ "### Main Training Pipeline — `train_models()`\n", "\n", "This function manages end-to-end training for all tickers and all prediction horizons.\n", "\n", "It performs the following:\n", "- Iterates through each ticker symbol defined in `TICKERS`.\n", "- Loads the corresponding CSV dataset (previously generated by the data-fetching step).\n", "- For each defined forecast horizon (1d, 1w, 4w, etc.), it calls `train_single_ticker()` with the proper hyperparameters.\n", "\n", "This function allows you to train multiple models in one go — one for each `(ticker, horizon)` combination." ] }, { "cell_type": "code", "execution_count": 11, "id": "b4071ebc", "metadata": {}, "outputs": [], "source": [ "def train_models():\n", " print(\"\\n🏋️ Training per-ticker models...\\n\")\n", " for file_name in TICKERS.keys():\n", " csv_path = os.path.join(DATA_PATH, f\"{file_name}.csv\")\n", " if not os.path.exists(csv_path):\n", " print(f\"Missing file for {file_name}, skipping.\")\n", " continue\n", " df = pd.read_csv(csv_path)\n", " for horizon_name, hcfg in HORIZON_CONFIGS.items():\n", " train_single_ticker(file_name, df, horizon_name, hcfg)\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "ab4b17fc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "🏋️ Training per-ticker models...\n", "\n", "Starting the TSLA-1d model\n", "\n", "[TSLA-1d] Epoch 1/200 | Train 0.989368 | Val 1.625912 | LR 0.000300\n", "[TSLA-1d] Epoch 10/200 | Train 0.382233 | Val 0.595948 | LR 0.000300\n", "[TSLA-1d] Epoch 20/200 | Train 0.097595 | Val 0.093759 | LR 0.000300\n", "[TSLA-1d] Epoch 30/200 | Train 0.083056 | Val 0.109078 | LR 0.000300\n", "[TSLA-1d] Epoch 40/200 | Train 0.070707 | Val 0.064946 | LR 0.000150\n", "[TSLA-1d] Epoch 50/200 | Train 0.060554 | Val 0.065365 | LR 0.000150\n", "[TSLA-1d] Epoch 60/200 | Train 0.056664 | Val 0.054368 | LR 0.000150\n", "[TSLA-1d] Epoch 70/200 | Train 0.051916 | Val 0.043491 | LR 0.000150\n", "[TSLA-1d] Epoch 80/200 | Train 0.047130 | Val 0.040595 | LR 0.000150\n", "[TSLA-1d] Epoch 90/200 | Train 0.045040 | Val 0.033899 | LR 0.000150\n", "[TSLA-1d] Epoch 100/200 | Train 0.040943 | Val 0.031572 | LR 0.000150\n", "[TSLA-1d] Epoch 110/200 | Train 0.038594 | Val 0.028850 | LR 0.000150\n", "[TSLA-1d] Epoch 120/200 | Train 0.036619 | Val 0.028221 | LR 0.000150\n", "[TSLA-1d] Epoch 130/200 | Train 0.034420 | Val 0.026401 | LR 0.000150\n", "[TSLA-1d] Epoch 140/200 | Train 0.033171 | Val 0.025424 | LR 0.000150\n", "[TSLA-1d] Epoch 150/200 | Train 0.032795 | Val 0.025129 | LR 0.000150\n", "[TSLA-1d] Epoch 160/200 | Train 0.031365 | Val 0.025818 | LR 0.000150\n", "[TSLA-1d] Epoch 170/200 | Train 0.030428 | Val 0.024874 | LR 0.000150\n", "[TSLA-1d] Epoch 180/200 | Train 0.030696 | Val 0.024408 | LR 0.000150\n", "[TSLA-1d] Epoch 190/200 | Train 0.030040 | Val 0.023858 | LR 0.000150\n", "[TSLA-1d] Epoch 200/200 | Train 0.028697 | Val 0.023266 | LR 0.000150\n", "\n", "Saved TSLA-1d model\n", "Starting the TSLA-1w model\n", "\n", "[TSLA-1w] Epoch 1/150 | Train 0.985759 | Val 1.553194 | LR 0.000300\n", "[TSLA-1w] Epoch 10/150 | Train 0.406017 | Val 0.602566 | LR 0.000300\n", "[TSLA-1w] Epoch 20/150 | Train 0.169808 | Val 0.175814 | LR 0.000300\n", "[TSLA-1w] Epoch 30/150 | Train 0.158074 | Val 0.178957 | LR 0.000300\n", "[TSLA-1w] Epoch 40/150 | Train 0.142398 | Val 0.131611 | LR 0.000150\n", "[TSLA-1w] Epoch 50/150 | Train 0.133660 | Val 0.140136 | LR 0.000150\n", "[TSLA-1w] Epoch 60/150 | Train 0.131413 | Val 0.134492 | LR 0.000075\n", "[TSLA-1w] Epoch 70/150 | Train 0.127930 | Val 0.123094 | LR 0.000075\n", "[TSLA-1w] Epoch 80/150 | Train 0.127155 | Val 0.119685 | LR 0.000037\n", "[TSLA-1w] Epoch 90/150 | Train 0.127033 | Val 0.120071 | LR 0.000037\n", "[TSLA-1w] Epoch 100/150 | Train 0.124906 | Val 0.119669 | LR 0.000019\n", "[TSLA-1w] Epoch 110/150 | Train 0.124178 | Val 0.118396 | LR 0.000019\n", "[TSLA-1w] Epoch 120/150 | Train 0.123845 | Val 0.117113 | LR 0.000010\n", "[TSLA-1w] Epoch 130/150 | Train 0.123174 | Val 0.116409 | LR 0.000010\n", "[TSLA-1w] Epoch 140/150 | Train 0.123582 | Val 0.115706 | LR 0.000010\n", "[TSLA-1w] Epoch 150/150 | Train 0.122521 | Val 0.114964 | LR 0.000010\n", "\n", "Saved TSLA-1w model\n", "Starting the TSLA-4w model\n", "\n", "[TSLA-4w] Epoch 1/200 | Train 0.878442 | Val 1.674448 | LR 0.000250\n", "[TSLA-4w] Epoch 10/200 | Train 0.700982 | Val 1.297215 | LR 0.000250\n", "[TSLA-4w] Epoch 20/200 | Train 0.481135 | Val 0.634033 | LR 0.000250\n", "[TSLA-4w] Epoch 30/200 | Train 0.426923 | Val 0.460944 | LR 0.000250\n", "[TSLA-4w] Epoch 40/200 | Train 0.392930 | Val 0.540019 | LR 0.000250\n", "[TSLA-4w] Epoch 50/200 | Train 0.371997 | Val 0.488721 | LR 0.000250\n", "[TSLA-4w] Epoch 60/200 | Train 0.358301 | Val 0.456506 | LR 0.000125\n", "[TSLA-4w] Epoch 70/200 | Train 0.342980 | Val 0.450528 | LR 0.000125\n", "[TSLA-4w] Epoch 80/200 | Train 0.321214 | Val 0.459684 | LR 0.000063\n", "[TSLA-4w] Epoch 90/200 | Train 0.309986 | Val 0.468064 | LR 0.000063\n", "[TSLA-4w] Epoch 100/200 | Train 0.300474 | Val 0.476476 | LR 0.000063\n", "[TSLA-4w] Epoch 110/200 | Train 0.295836 | Val 0.480377 | LR 0.000031\n", "[TSLA-4w] Epoch 120/200 | Train 0.292569 | Val 0.481498 | LR 0.000031\n", "[TSLA-4w] Epoch 130/200 | Train 0.290802 | Val 0.482261 | LR 0.000016\n", "[TSLA-4w] Epoch 140/200 | Train 0.287922 | Val 0.482307 | LR 0.000016\n", "[TSLA-4w] Epoch 150/200 | Train 0.285015 | Val 0.482283 | LR 0.000016\n", "[TSLA-4w] Epoch 160/200 | Train 0.282292 | Val 0.482188 | LR 0.000008\n", "[TSLA-4w] Epoch 170/200 | Train 0.281461 | Val 0.481983 | LR 0.000008\n", "[TSLA-4w] Epoch 180/200 | Train 0.281907 | Val 0.481709 | LR 0.000008\n", "[TSLA-4w] Epoch 190/200 | Train 0.280425 | Val 0.481410 | LR 0.000005\n", "[TSLA-4w] Epoch 200/200 | Train 0.277699 | Val 0.481006 | LR 0.000005\n", "\n", "Saved TSLA-4w model\n", "Starting the TSLA-6m model\n", "\n", "[TSLA-6m] Epoch 1/250 | Train 0.711856 | Val 1.776021 | LR 0.000200\n", "[TSLA-6m] Epoch 10/250 | Train 0.625947 | Val 1.538097 | LR 0.000200\n", "[TSLA-6m] Epoch 20/250 | Train 0.530185 | Val 1.173886 | LR 0.000200\n", "[TSLA-6m] Epoch 30/250 | Train 0.459422 | Val 0.630858 | LR 0.000200\n", "[TSLA-6m] Epoch 40/250 | Train 0.401124 | Val 0.554993 | LR 0.000200\n", "[TSLA-6m] Epoch 50/250 | Train 0.279561 | Val 0.151460 | LR 0.000200\n", "[TSLA-6m] Epoch 60/250 | Train 0.186314 | Val 0.187298 | LR 0.000200\n", "[TSLA-6m] Epoch 70/250 | Train 0.142044 | Val 0.323760 | LR 0.000200\n", "[TSLA-6m] Epoch 80/250 | Train 0.120276 | Val 0.432777 | LR 0.000200\n", "[TSLA-6m] Epoch 90/250 | Train 0.105144 | Val 0.435892 | LR 0.000100\n", "[TSLA-6m] Epoch 100/250 | Train 0.098239 | Val 0.422644 | LR 0.000100\n", "[TSLA-6m] Epoch 110/250 | Train 0.090300 | Val 0.373421 | LR 0.000100\n", "[TSLA-6m] Epoch 120/250 | Train 0.082377 | Val 0.304653 | LR 0.000050\n", "[TSLA-6m] Epoch 130/250 | Train 0.081198 | Val 0.261005 | LR 0.000050\n", "[TSLA-6m] Epoch 140/250 | Train 0.076920 | Val 0.220080 | LR 0.000050\n", "[TSLA-6m] Epoch 150/250 | Train 0.074771 | Val 0.186453 | LR 0.000025\n", "[TSLA-6m] Epoch 160/250 | Train 0.073974 | Val 0.167309 | LR 0.000025\n", "[TSLA-6m] Epoch 170/250 | Train 0.072321 | Val 0.151397 | LR 0.000025\n", "[TSLA-6m] Epoch 180/250 | Train 0.071292 | Val 0.140698 | LR 0.000013\n", "[TSLA-6m] Epoch 190/250 | Train 0.071720 | Val 0.135712 | LR 0.000013\n", "[TSLA-6m] Epoch 200/250 | Train 0.070796 | Val 0.130222 | LR 0.000013\n", "[TSLA-6m] Epoch 210/250 | Train 0.071793 | Val 0.124081 | LR 0.000006\n", "[TSLA-6m] Epoch 220/250 | Train 0.071877 | Val 0.122853 | LR 0.000006\n", "[TSLA-6m] Epoch 230/250 | Train 0.069818 | Val 0.118748 | LR 0.000006\n", "[TSLA-6m] Epoch 240/250 | Train 0.071107 | Val 0.116569 | LR 0.000006\n", "[TSLA-6m] Epoch 250/250 | Train 0.070953 | Val 0.114428 | LR 0.000006\n", "\n", "Saved TSLA-6m model\n", "Starting the TSLA-1y model\n", "\n", "[TSLA-1y] Epoch 1/250 | Train 0.145648 | Val 2.524380 | LR 0.000200\n", "[TSLA-1y] Epoch 10/250 | Train 0.136779 | Val 2.367088 | LR 0.000200\n", "[TSLA-1y] Epoch 20/250 | Train 0.121568 | Val 2.177613 | LR 0.000200\n", "[TSLA-1y] Epoch 30/250 | Train 0.086537 | Val 1.609000 | LR 0.000200\n", "[TSLA-1y] Epoch 40/250 | Train 0.063862 | Val 0.751865 | LR 0.000200\n", "[TSLA-1y] Epoch 50/250 | Train 0.049029 | Val 0.641502 | LR 0.000200\n", "[TSLA-1y] Epoch 60/250 | Train 0.046382 | Val 0.204478 | LR 0.000200\n", "[TSLA-1y] Epoch 70/250 | Train 0.043644 | Val 0.250959 | LR 0.000200\n", "[TSLA-1y] Epoch 80/250 | Train 0.042135 | Val 0.186863 | LR 0.000200\n", "[TSLA-1y] Epoch 90/250 | Train 0.040076 | Val 0.180971 | LR 0.000200\n", "[TSLA-1y] Epoch 100/250 | Train 0.040115 | Val 0.152473 | LR 0.000200\n", "[TSLA-1y] Epoch 110/250 | Train 0.039151 | Val 0.154870 | LR 0.000200\n", "[TSLA-1y] Epoch 120/250 | Train 0.037691 | Val 0.135338 | LR 0.000200\n", "[TSLA-1y] Epoch 130/250 | Train 0.036603 | Val 0.147296 | LR 0.000200\n", "[TSLA-1y] Epoch 140/250 | Train 0.035807 | Val 0.153504 | LR 0.000200\n", "[TSLA-1y] Epoch 150/250 | Train 0.034848 | Val 0.133423 | LR 0.000100\n", "[TSLA-1y] Epoch 160/250 | Train 0.034490 | Val 0.149818 | LR 0.000100\n", "[TSLA-1y] Epoch 170/250 | Train 0.033539 | Val 0.165795 | LR 0.000100\n", "[TSLA-1y] Epoch 180/250 | Train 0.034119 | Val 0.201550 | LR 0.000100\n", "[TSLA-1y] Epoch 190/250 | Train 0.032419 | Val 0.173489 | LR 0.000050\n", "[TSLA-1y] Epoch 200/250 | Train 0.031945 | Val 0.178020 | LR 0.000050\n", "[TSLA-1y] Epoch 210/250 | Train 0.031443 | Val 0.178629 | LR 0.000050\n", "[TSLA-1y] Epoch 220/250 | Train 0.031780 | Val 0.167491 | LR 0.000025\n", "[TSLA-1y] Epoch 230/250 | Train 0.030554 | Val 0.183559 | LR 0.000025\n", "[TSLA-1y] Epoch 240/250 | Train 0.031448 | Val 0.173409 | LR 0.000025\n", "[TSLA-1y] Epoch 250/250 | Train 0.029798 | Val 0.183563 | LR 0.000013\n", "\n", "Saved TSLA-1y model\n", "Starting the NVDA-1d model\n", "\n", "[NVDA-1d] Epoch 1/200 | Train 0.243042 | Val 2.172327 | LR 0.000300\n", "[NVDA-1d] Epoch 10/200 | Train 0.076996 | Val 0.202158 | LR 0.000300\n", "[NVDA-1d] Epoch 20/200 | Train 0.035840 | Val 0.149017 | LR 0.000300\n", "[NVDA-1d] Epoch 30/200 | Train 0.027793 | Val 0.119485 | LR 0.000300\n", "[NVDA-1d] Epoch 40/200 | Train 0.024325 | Val 0.100336 | LR 0.000150\n", "[NVDA-1d] Epoch 50/200 | Train 0.022161 | Val 0.144022 | LR 0.000150\n", "[NVDA-1d] Epoch 60/200 | Train 0.020446 | Val 0.123376 | LR 0.000150\n", "[NVDA-1d] Epoch 70/200 | Train 0.019642 | Val 0.130794 | LR 0.000075\n", "[NVDA-1d] Epoch 80/200 | Train 0.019286 | Val 0.110303 | LR 0.000075\n", "[NVDA-1d] Epoch 90/200 | Train 0.018636 | Val 0.103079 | LR 0.000037\n", "[NVDA-1d] Epoch 100/200 | Train 0.018252 | Val 0.095922 | LR 0.000037\n", "[NVDA-1d] Epoch 110/200 | Train 0.018111 | Val 0.087822 | LR 0.000037\n", "[NVDA-1d] Epoch 120/200 | Train 0.017857 | Val 0.081478 | LR 0.000019\n", "[NVDA-1d] Epoch 130/200 | Train 0.017683 | Val 0.076320 | LR 0.000019\n", "[NVDA-1d] Epoch 140/200 | Train 0.017644 | Val 0.072386 | LR 0.000019\n", "[NVDA-1d] Epoch 150/200 | Train 0.017511 | Val 0.070015 | LR 0.000010\n", "[NVDA-1d] Epoch 160/200 | Train 0.017719 | Val 0.067294 | LR 0.000010\n", "[NVDA-1d] Epoch 170/200 | Train 0.017368 | Val 0.065108 | LR 0.000010\n", "[NVDA-1d] Epoch 180/200 | Train 0.017306 | Val 0.062801 | LR 0.000010\n", "[NVDA-1d] Epoch 190/200 | Train 0.017095 | Val 0.060973 | LR 0.000010\n", "[NVDA-1d] Epoch 200/200 | Train 0.017308 | Val 0.058694 | LR 0.000010\n", "\n", "Saved NVDA-1d model\n", "Starting the NVDA-1w model\n", "\n", "[NVDA-1w] Epoch 1/150 | Train 0.232277 | Val 2.062002 | LR 0.000300\n", "[NVDA-1w] Epoch 10/150 | Train 0.088370 | Val 0.507786 | LR 0.000300\n", "[NVDA-1w] Epoch 20/150 | Train 0.067947 | Val 0.473481 | LR 0.000300\n", "[NVDA-1w] Epoch 30/150 | Train 0.058658 | Val 0.206256 | LR 0.000300\n", "[NVDA-1w] Epoch 40/150 | Train 0.054586 | Val 0.335165 | LR 0.000150\n", "[NVDA-1w] Epoch 50/150 | Train 0.053088 | Val 0.348732 | LR 0.000150\n", "[NVDA-1w] Epoch 60/150 | Train 0.051900 | Val 0.330730 | LR 0.000075\n", "[NVDA-1w] Epoch 70/150 | Train 0.051163 | Val 0.355174 | LR 0.000075\n", "[NVDA-1w] Epoch 80/150 | Train 0.050977 | Val 0.337446 | LR 0.000037\n", "[NVDA-1w] Epoch 90/150 | Train 0.050556 | Val 0.327132 | LR 0.000037\n", "[NVDA-1w] Epoch 100/150 | Train 0.050349 | Val 0.326534 | LR 0.000019\n", "[NVDA-1w] Epoch 110/150 | Train 0.050364 | Val 0.325102 | LR 0.000019\n", "[NVDA-1w] Epoch 120/150 | Train 0.049921 | Val 0.322998 | LR 0.000010\n", "[NVDA-1w] Epoch 130/150 | Train 0.049604 | Val 0.320874 | LR 0.000010\n", "[NVDA-1w] Epoch 140/150 | Train 0.049837 | Val 0.318920 | LR 0.000010\n", "[NVDA-1w] Epoch 150/150 | Train 0.049026 | Val 0.316595 | LR 0.000010\n", "\n", "Saved NVDA-1w model\n", "Starting the NVDA-4w model\n", "\n", "[NVDA-4w] Epoch 1/200 | Train 0.177587 | Val 2.154533 | LR 0.000250\n", "[NVDA-4w] Epoch 10/200 | Train 0.145038 | Val 1.484190 | LR 0.000250\n", "[NVDA-4w] Epoch 20/200 | Train 0.126202 | Val 0.770190 | LR 0.000250\n", "[NVDA-4w] Epoch 30/200 | Train 0.121573 | Val 0.800396 | LR 0.000250\n", "[NVDA-4w] Epoch 40/200 | Train 0.118933 | Val 0.713176 | LR 0.000250\n", "[NVDA-4w] Epoch 50/200 | Train 0.117480 | Val 0.826908 | LR 0.000250\n", "[NVDA-4w] Epoch 60/200 | Train 0.115668 | Val 0.767066 | LR 0.000250\n", "[NVDA-4w] Epoch 70/200 | Train 0.113237 | Val 0.812185 | LR 0.000125\n", "[NVDA-4w] Epoch 80/200 | Train 0.108389 | Val 0.841165 | LR 0.000125\n", "[NVDA-4w] Epoch 90/200 | Train 0.093537 | Val 0.932764 | LR 0.000063\n", "[NVDA-4w] Epoch 100/200 | Train 0.079703 | Val 0.897578 | LR 0.000063\n", "[NVDA-4w] Epoch 110/200 | Train 0.069738 | Val 0.903513 | LR 0.000063\n", "[NVDA-4w] Epoch 120/200 | Train 0.062866 | Val 0.935800 | LR 0.000031\n", "[NVDA-4w] Epoch 130/200 | Train 0.058325 | Val 1.026884 | LR 0.000031\n", "[NVDA-4w] Epoch 140/200 | Train 0.054165 | Val 1.208260 | LR 0.000031\n", "[NVDA-4w] Epoch 150/200 | Train 0.049493 | Val 1.330298 | LR 0.000016\n", "[NVDA-4w] Epoch 160/200 | Train 0.047884 | Val 1.435850 | LR 0.000016\n", "[NVDA-4w] Epoch 170/200 | Train 0.043805 | Val 1.530390 | LR 0.000008\n", "[NVDA-4w] Epoch 180/200 | Train 0.043180 | Val 1.586353 | LR 0.000008\n", "[NVDA-4w] Epoch 190/200 | Train 0.041047 | Val 1.642542 | LR 0.000008\n", "[NVDA-4w] Epoch 200/200 | Train 0.040147 | Val 1.690130 | LR 0.000005\n", "\n", "Saved NVDA-4w model\n", "Starting the NVDA-6m model\n", "\n", "[NVDA-6m] Epoch 1/250 | Train 0.632913 | Val 2.838938 | LR 0.000200\n", "[NVDA-6m] Epoch 10/250 | Train 0.623039 | Val 2.675219 | LR 0.000200\n", "[NVDA-6m] Epoch 20/250 | Train 0.617009 | Val 2.853697 | LR 0.000200\n", "[NVDA-6m] Epoch 30/250 | Train 0.608667 | Val 3.011831 | LR 0.000200\n", "[NVDA-6m] Epoch 40/250 | Train 0.575114 | Val 3.406503 | LR 0.000100\n", "[NVDA-6m] Epoch 50/250 | Train 0.499246 | Val 3.713333 | LR 0.000100\n", "[NVDA-6m] Epoch 60/250 | Train 0.300525 | Val 3.717271 | LR 0.000100\n", "[NVDA-6m] Epoch 70/250 | Train 0.196028 | Val 0.916658 | LR 0.000100\n", "[NVDA-6m] Epoch 80/250 | Train 0.166460 | Val 0.463662 | LR 0.000100\n", "[NVDA-6m] Epoch 90/250 | Train 0.175504 | Val 0.414393 | LR 0.000100\n", "[NVDA-6m] Epoch 100/250 | Train 0.161152 | Val 0.398987 | LR 0.000100\n", "[NVDA-6m] Epoch 110/250 | Train 0.156894 | Val 0.363259 | LR 0.000100\n", "[NVDA-6m] Epoch 120/250 | Train 0.153072 | Val 0.372599 | LR 0.000100\n", "[NVDA-6m] Epoch 130/250 | Train 0.150657 | Val 0.427207 | LR 0.000100\n", "[NVDA-6m] Epoch 140/250 | Train 0.145735 | Val 0.471579 | LR 0.000100\n", "[NVDA-6m] Epoch 150/250 | Train 0.141289 | Val 0.470167 | LR 0.000050\n", "[NVDA-6m] Epoch 160/250 | Train 0.138249 | Val 0.478850 | LR 0.000050\n", "[NVDA-6m] Epoch 170/250 | Train 0.134855 | Val 0.484418 | LR 0.000050\n", "[NVDA-6m] Epoch 180/250 | Train 0.129947 | Val 0.504183 | LR 0.000025\n", "[NVDA-6m] Epoch 190/250 | Train 0.128040 | Val 0.511557 | LR 0.000025\n", "[NVDA-6m] Epoch 200/250 | Train 0.124770 | Val 0.523227 | LR 0.000025\n", "[NVDA-6m] Epoch 210/250 | Train 0.122181 | Val 0.516339 | LR 0.000013\n", "[NVDA-6m] Epoch 220/250 | Train 0.120828 | Val 0.514970 | LR 0.000013\n", "[NVDA-6m] Epoch 230/250 | Train 0.117595 | Val 0.517474 | LR 0.000013\n", "[NVDA-6m] Epoch 240/250 | Train 0.117428 | Val 0.519171 | LR 0.000006\n", "[NVDA-6m] Epoch 250/250 | Train 0.115986 | Val 0.519765 | LR 0.000006\n", "\n", "Saved NVDA-6m model\n", "Starting the NVDA-1y model\n", "\n", "[NVDA-1y] Epoch 1/250 | Train 1.094458 | Val 2.646668 | LR 0.000200\n", "[NVDA-1y] Epoch 10/250 | Train 0.868547 | Val 2.014205 | LR 0.000200\n", "[NVDA-1y] Epoch 20/250 | Train 0.611257 | Val 1.085351 | LR 0.000200\n", "[NVDA-1y] Epoch 30/250 | Train 0.527374 | Val 0.173836 | LR 0.000200\n", "[NVDA-1y] Epoch 40/250 | Train 0.476706 | Val 0.546059 | LR 0.000200\n", "[NVDA-1y] Epoch 50/250 | Train 0.440482 | Val 0.434962 | LR 0.000200\n", "[NVDA-1y] Epoch 60/250 | Train 0.387151 | Val 0.267163 | LR 0.000200\n", "[NVDA-1y] Epoch 70/250 | Train 0.283716 | Val 0.098832 | LR 0.000100\n", "[NVDA-1y] Epoch 80/250 | Train 0.139021 | Val 0.065288 | LR 0.000100\n", "[NVDA-1y] Epoch 90/250 | Train 0.075908 | Val 0.077735 | LR 0.000100\n", "[NVDA-1y] Epoch 100/250 | Train 0.056079 | Val 0.088190 | LR 0.000100\n", "[NVDA-1y] Epoch 110/250 | Train 0.047573 | Val 0.088180 | LR 0.000100\n", "[NVDA-1y] Epoch 120/250 | Train 0.044939 | Val 0.091630 | LR 0.000050\n", "[NVDA-1y] Epoch 130/250 | Train 0.041955 | Val 0.089673 | LR 0.000050\n", "[NVDA-1y] Epoch 140/250 | Train 0.041468 | Val 0.095948 | LR 0.000050\n", "[NVDA-1y] Epoch 150/250 | Train 0.040048 | Val 0.095171 | LR 0.000025\n", "[NVDA-1y] Epoch 160/250 | Train 0.040690 | Val 0.091751 | LR 0.000025\n", "[NVDA-1y] Epoch 170/250 | Train 0.035246 | Val 0.092356 | LR 0.000025\n", "[NVDA-1y] Epoch 180/250 | Train 0.038337 | Val 0.093947 | LR 0.000013\n", "[NVDA-1y] Epoch 190/250 | Train 0.037034 | Val 0.093811 | LR 0.000013\n", "[NVDA-1y] Epoch 200/250 | Train 0.036380 | Val 0.093875 | LR 0.000013\n", "[NVDA-1y] Epoch 210/250 | Train 0.036057 | Val 0.092912 | LR 0.000006\n", "[NVDA-1y] Epoch 220/250 | Train 0.033454 | Val 0.093208 | LR 0.000006\n", "[NVDA-1y] Epoch 230/250 | Train 0.034776 | Val 0.094238 | LR 0.000006\n", "[NVDA-1y] Epoch 240/250 | Train 0.037060 | Val 0.094823 | LR 0.000005\n", "[NVDA-1y] Epoch 250/250 | Train 0.035075 | Val 0.094687 | LR 0.000005\n", "\n", "Saved NVDA-1y model\n", "Starting the SPY-1d model\n", "\n", "[SPY-1d] Epoch 1/200 | Train 0.295262 | Val 2.053666 | LR 0.000300\n", "[SPY-1d] Epoch 10/200 | Train 0.106336 | Val 0.317885 | LR 0.000300\n", "[SPY-1d] Epoch 20/200 | Train 0.037754 | Val 0.228067 | LR 0.000300\n", "[SPY-1d] Epoch 30/200 | Train 0.028056 | Val 0.068125 | LR 0.000300\n", "[SPY-1d] Epoch 40/200 | Train 0.023045 | Val 0.110299 | LR 0.000150\n", "[SPY-1d] Epoch 50/200 | Train 0.021519 | Val 0.098965 | LR 0.000150\n", "[SPY-1d] Epoch 60/200 | Train 0.020410 | Val 0.110126 | LR 0.000150\n", "[SPY-1d] Epoch 70/200 | Train 0.019358 | Val 0.100090 | LR 0.000075\n", "[SPY-1d] Epoch 80/200 | Train 0.019277 | Val 0.096896 | LR 0.000075\n", "[SPY-1d] Epoch 90/200 | Train 0.018575 | Val 0.089610 | LR 0.000075\n", "[SPY-1d] Epoch 100/200 | Train 0.018672 | Val 0.087692 | LR 0.000037\n", "[SPY-1d] Epoch 110/200 | Train 0.018559 | Val 0.086650 | LR 0.000037\n", "[SPY-1d] Epoch 120/200 | Train 0.018102 | Val 0.083853 | LR 0.000019\n", "[SPY-1d] Epoch 130/200 | Train 0.018134 | Val 0.081605 | LR 0.000019\n", "[SPY-1d] Epoch 140/200 | Train 0.018062 | Val 0.080252 | LR 0.000019\n", "[SPY-1d] Epoch 150/200 | Train 0.017913 | Val 0.079614 | LR 0.000010\n", "[SPY-1d] Epoch 160/200 | Train 0.017772 | Val 0.078864 | LR 0.000010\n", "[SPY-1d] Epoch 170/200 | Train 0.017661 | Val 0.077985 | LR 0.000010\n", "[SPY-1d] Epoch 180/200 | Train 0.017653 | Val 0.076811 | LR 0.000010\n", "[SPY-1d] Epoch 190/200 | Train 0.017408 | Val 0.075818 | LR 0.000010\n", "[SPY-1d] Epoch 200/200 | Train 0.017700 | Val 0.075096 | LR 0.000010\n", "\n", "Saved SPY-1d model\n", "Starting the SPY-1w model\n", "\n", "[SPY-1w] Epoch 1/150 | Train 0.303024 | Val 2.198621 | LR 0.000300\n", "[SPY-1w] Epoch 10/150 | Train 0.146156 | Val 0.525914 | LR 0.000300\n", "[SPY-1w] Epoch 20/150 | Train 0.073451 | Val 0.373679 | LR 0.000300\n", "[SPY-1w] Epoch 30/150 | Train 0.058757 | Val 0.142075 | LR 0.000300\n", "[SPY-1w] Epoch 40/150 | Train 0.053520 | Val 0.248647 | LR 0.000150\n", "[SPY-1w] Epoch 50/150 | Train 0.050611 | Val 0.194770 | LR 0.000150\n", "[SPY-1w] Epoch 60/150 | Train 0.050046 | Val 0.212066 | LR 0.000075\n", "[SPY-1w] Epoch 70/150 | Train 0.049356 | Val 0.208849 | LR 0.000075\n", "[SPY-1w] Epoch 80/150 | Train 0.048627 | Val 0.203867 | LR 0.000037\n", "[SPY-1w] Epoch 90/150 | Train 0.048482 | Val 0.197887 | LR 0.000037\n", "[SPY-1w] Epoch 100/150 | Train 0.048265 | Val 0.192041 | LR 0.000019\n", "[SPY-1w] Epoch 110/150 | Train 0.048158 | Val 0.190062 | LR 0.000019\n", "[SPY-1w] Epoch 120/150 | Train 0.048104 | Val 0.189302 | LR 0.000010\n", "[SPY-1w] Epoch 130/150 | Train 0.048065 | Val 0.188555 | LR 0.000010\n", "[SPY-1w] Epoch 140/150 | Train 0.048086 | Val 0.187191 | LR 0.000010\n", "[SPY-1w] Epoch 150/150 | Train 0.048028 | Val 0.185956 | LR 0.000010\n", "\n", "Saved SPY-1w model\n", "Starting the SPY-4w model\n", "\n", "[SPY-4w] Epoch 1/200 | Train 0.264479 | Val 2.222950 | LR 0.000250\n", "[SPY-4w] Epoch 10/200 | Train 0.208323 | Val 1.482163 | LR 0.000250\n", "[SPY-4w] Epoch 20/200 | Train 0.169975 | Val 0.615845 | LR 0.000250\n", "[SPY-4w] Epoch 30/200 | Train 0.151768 | Val 0.685493 | LR 0.000250\n", "[SPY-4w] Epoch 40/200 | Train 0.142490 | Val 0.615593 | LR 0.000250\n", "[SPY-4w] Epoch 50/200 | Train 0.137344 | Val 0.634546 | LR 0.000125\n", "[SPY-4w] Epoch 60/200 | Train 0.135647 | Val 0.676712 | LR 0.000125\n", "[SPY-4w] Epoch 70/200 | Train 0.133650 | Val 0.686108 | LR 0.000125\n", "[SPY-4w] Epoch 80/200 | Train 0.132018 | Val 0.665277 | LR 0.000063\n", "[SPY-4w] Epoch 90/200 | Train 0.130601 | Val 0.661248 | LR 0.000063\n", "[SPY-4w] Epoch 100/200 | Train 0.129501 | Val 0.660637 | LR 0.000031\n", "[SPY-4w] Epoch 110/200 | Train 0.127560 | Val 0.655823 | LR 0.000031\n", "[SPY-4w] Epoch 120/200 | Train 0.127672 | Val 0.650221 | LR 0.000031\n", "[SPY-4w] Epoch 130/200 | Train 0.126210 | Val 0.646942 | LR 0.000016\n", "[SPY-4w] Epoch 140/200 | Train 0.126054 | Val 0.645309 | LR 0.000016\n", "[SPY-4w] Epoch 150/200 | Train 0.125960 | Val 0.643201 | LR 0.000016\n", "[SPY-4w] Epoch 160/200 | Train 0.125537 | Val 0.641725 | LR 0.000008\n", "[SPY-4w] Epoch 170/200 | Train 0.124604 | Val 0.640412 | LR 0.000008\n", "[SPY-4w] Epoch 180/200 | Train 0.124647 | Val 0.639400 | LR 0.000005\n", "[SPY-4w] Epoch 190/200 | Train 0.124529 | Val 0.638738 | LR 0.000005\n", "[SPY-4w] Epoch 200/200 | Train 0.124421 | Val 0.638169 | LR 0.000005\n", "\n", "Saved SPY-4w model\n", "Starting the SPY-6m model\n", "\n", "[SPY-6m] Epoch 1/250 | Train 0.515100 | Val 3.015894 | LR 0.000200\n", "[SPY-6m] Epoch 10/250 | Train 0.469893 | Val 2.657214 | LR 0.000200\n", "[SPY-6m] Epoch 20/250 | Train 0.462193 | Val 2.315605 | LR 0.000200\n", "[SPY-6m] Epoch 30/250 | Train 0.455270 | Val 2.355778 | LR 0.000200\n", "[SPY-6m] Epoch 40/250 | Train 0.446984 | Val 2.242209 | LR 0.000200\n", "[SPY-6m] Epoch 50/250 | Train 0.414811 | Val 1.758905 | LR 0.000200\n", "[SPY-6m] Epoch 60/250 | Train 0.277776 | Val 0.480134 | LR 0.000200\n", "[SPY-6m] Epoch 70/250 | Train 0.267627 | Val 0.580160 | LR 0.000200\n", "[SPY-6m] Epoch 80/250 | Train 0.255462 | Val 0.321140 | LR 0.000200\n", "[SPY-6m] Epoch 90/250 | Train 0.240617 | Val 0.370269 | LR 0.000200\n", "[SPY-6m] Epoch 100/250 | Train 0.220049 | Val 0.402769 | LR 0.000100\n", "[SPY-6m] Epoch 110/250 | Train 0.189607 | Val 0.329660 | LR 0.000100\n", "[SPY-6m] Epoch 120/250 | Train 0.140321 | Val 0.272520 | LR 0.000100\n", "[SPY-6m] Epoch 130/250 | Train 0.098742 | Val 0.268079 | LR 0.000050\n", "[SPY-6m] Epoch 140/250 | Train 0.074183 | Val 0.243871 | LR 0.000050\n", "[SPY-6m] Epoch 150/250 | Train 0.062353 | Val 0.257828 | LR 0.000050\n", "[SPY-6m] Epoch 160/250 | Train 0.059605 | Val 0.271594 | LR 0.000050\n", "[SPY-6m] Epoch 170/250 | Train 0.057267 | Val 0.273034 | LR 0.000050\n", "[SPY-6m] Epoch 180/250 | Train 0.054352 | Val 0.260341 | LR 0.000025\n", "[SPY-6m] Epoch 190/250 | Train 0.055900 | Val 0.251459 | LR 0.000025\n", "[SPY-6m] Epoch 200/250 | Train 0.059609 | Val 0.251627 | LR 0.000025\n", "[SPY-6m] Epoch 210/250 | Train 0.055706 | Val 0.248610 | LR 0.000013\n", "[SPY-6m] Epoch 220/250 | Train 0.058714 | Val 0.249856 | LR 0.000013\n", "[SPY-6m] Epoch 230/250 | Train 0.057403 | Val 0.254082 | LR 0.000013\n", "[SPY-6m] Epoch 240/250 | Train 0.056893 | Val 0.252647 | LR 0.000006\n", "[SPY-6m] Epoch 250/250 | Train 0.055823 | Val 0.253266 | LR 0.000006\n", "\n", "Saved SPY-6m model\n", "Starting the SPY-1y model\n", "\n", "[SPY-1y] Epoch 1/250 | Train 0.803201 | Val 3.246505 | LR 0.000200\n", "[SPY-1y] Epoch 10/250 | Train 0.632945 | Val 2.600635 | LR 0.000200\n", "[SPY-1y] Epoch 20/250 | Train 0.426420 | Val 1.561665 | LR 0.000200\n", "[SPY-1y] Epoch 30/250 | Train 0.328226 | Val 0.188689 | LR 0.000200\n", "[SPY-1y] Epoch 40/250 | Train 0.278980 | Val 0.629971 | LR 0.000200\n", "[SPY-1y] Epoch 50/250 | Train 0.240760 | Val 0.202713 | LR 0.000200\n", "[SPY-1y] Epoch 60/250 | Train 0.189531 | Val 0.047232 | LR 0.000200\n", "[SPY-1y] Epoch 70/250 | Train 0.110281 | Val 0.982015 | LR 0.000200\n", "[SPY-1y] Epoch 80/250 | Train 0.087188 | Val 0.723882 | LR 0.000200\n", "[SPY-1y] Epoch 90/250 | Train 0.048969 | Val 0.117758 | LR 0.000200\n", "[SPY-1y] Epoch 100/250 | Train 0.038429 | Val 0.033650 | LR 0.000100\n", "[SPY-1y] Epoch 110/250 | Train 0.031154 | Val 0.022587 | LR 0.000100\n", "[SPY-1y] Epoch 120/250 | Train 0.026710 | Val 0.019503 | LR 0.000100\n", "[SPY-1y] Epoch 130/250 | Train 0.025020 | Val 0.017933 | LR 0.000100\n", "[SPY-1y] Epoch 140/250 | Train 0.022470 | Val 0.018074 | LR 0.000100\n", "[SPY-1y] Epoch 150/250 | Train 0.020958 | Val 0.018397 | LR 0.000100\n", "[SPY-1y] Epoch 160/250 | Train 0.019957 | Val 0.018332 | LR 0.000100\n", "[SPY-1y] Epoch 170/250 | Train 0.017813 | Val 0.018172 | LR 0.000050\n", "[SPY-1y] Epoch 180/250 | Train 0.017340 | Val 0.018255 | LR 0.000050\n", "[SPY-1y] Epoch 190/250 | Train 0.016543 | Val 0.018318 | LR 0.000050\n", "[SPY-1y] Epoch 200/250 | Train 0.016079 | Val 0.018406 | LR 0.000025\n", "[SPY-1y] Epoch 210/250 | Train 0.015909 | Val 0.018472 | LR 0.000025\n", "[SPY-1y] Epoch 220/250 | Train 0.015755 | Val 0.018556 | LR 0.000025\n", "[SPY-1y] Epoch 230/250 | Train 0.015587 | Val 0.018613 | LR 0.000013\n", "[SPY-1y] Epoch 240/250 | Train 0.014945 | Val 0.018650 | LR 0.000013\n", "[SPY-1y] Epoch 250/250 | Train 0.015143 | Val 0.018663 | LR 0.000013\n", "\n", "Saved SPY-1y model\n" ] } ], "source": [ "# Run Training\n", "# Saves model weights, scalers, and json configuration to disk\n", "\n", "train_models()" ] }, { "cell_type": "markdown", "id": "e16c9395", "metadata": {}, "source": [ "## Prediction Pipeline — Using Trained Models\n", "\n", "After training, this section loads the saved models and performs Monte Carlo Dropout (MC Dropout)-based predictions. \n", "MC Dropout provides not only a mean prediction but also an estimate of uncertainty — expressed as confidence intervals around the forecasted value." ] }, { "cell_type": "code", "execution_count": 13, "id": "41b9e60f", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "import pandas as pd\n", "import numpy as np\n", "import torch\n", "import joblib\n", "\n", "# ---- CONFIG ----\n", "TICKERS = [\"TSLA\", \"NVDA\", \"SPY\"]\n", "HORIZONS = [\"1d\", \"1w\", \"4w\", \"6m\", \"1y\"]\n", "\n", "BASE_DIR = os.path.dirname(os.getcwd())\n", "DATA_PATH = os.path.join(BASE_DIR, \"data\")\n", "MODELS_DIR = os.path.join(BASE_DIR, \"models\")\n", "DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n" ] }, { "cell_type": "markdown", "id": "45aebff4", "metadata": {}, "source": [ "### Helper Functions for Prediction\n", "\n", "These functions manage model loading, input preparation, and MC Dropout sampling. \n", "Together, they allow probabilistic forecasting — giving not just a single point prediction but a confidence interval for uncertainty estimation." ] }, { "cell_type": "code", "execution_count": null, "id": "bc75998c", "metadata": {}, "outputs": [], "source": [ "def enable_mc_dropout(model):\n", " \"\"\"Enable dropout layers during inference for Monte Carlo sampling.\"\"\"\n", " for m in model.modules():\n", " if isinstance(m, torch.nn.Dropout):\n", " m.train()\n", " return model\n", "\n", "import torch.nn.functional as F\n", "\n", "def mc_dropout_predict(model, X_tensor, y_scaler, horizon=None, n_samples=200):\n", " \"\"\"\n", " Perform Monte Carlo Dropout predictions with confidence intervals.\n", " \n", " Runs the model multiple times with dropout enabled, collects predictions,\n", " and computes mean and standard deviation to approximate prediction uncertainty.\n", " \"\"\"\n", " model.eval()\n", " model = enable_mc_dropout(model)\n", "\n", " dropout_p = HORIZON_CONFIGS.get(horizon, {}).get(\"dropout\", 0.2)\n", "\n", " preds = []\n", " with torch.no_grad():\n", " for _ in range(n_samples):\n", " out, _ = model.lstm(X_tensor)\n", " out = out[:, -1, :]\n", " out = F.dropout(out, p=dropout_p, training=True)\n", " y_pred = model.fc(out)\n", " preds.append(y_pred.item())\n", "\n", " preds = np.array(preds)\n", " \n", " # Convert back to original scale\n", " preds_orig = y_scaler.inverse_transform(preds.reshape(-1, 1)).flatten()\n", "\n", " mean_orig = preds_orig.mean()\n", " # 95% CI using percentiles\n", " lower_bound = np.percentile(preds_orig, 2.5)\n", " upper_bound = np.percentile(preds_orig, 97.5)\n", " plus_minus_percent = ((upper_bound - lower_bound) / (2 * mean_orig) * 100) if mean_orig != 0 else 0\n", "\n", " return {\n", " \"predicted_price\": float(mean_orig),\n", " \"plus_minus_percent\": float(plus_minus_percent),\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": float(lower_bound),\n", " \"upper_bound\": float(upper_bound),\n", " }\n", "\n", "\n", "def prepare_input_sequence(df, x_scaler, seq_len):\n", " \"\"\"\n", " Prepare scaled input sequence for model inference.\n", " \n", " Takes the most recent `seq_len` observations,\n", " scales them using the same feature scaler used during training,\n", " and reshapes them into the expected LSTM input shape.\n", " \"\"\"\n", " features = df[[\"Open\", \"High\", \"Low\", \"Close\", \"Volume\"]].values\n", " X_scaled = x_scaler.transform(features)\n", " X_seq = X_scaled[-seq_len:]\n", " return torch.tensor(X_seq, dtype=torch.float32).unsqueeze(0).to(DEVICE)\n", "\n", "\n", "def load_per_ticker_model(ticker, horizon):\n", " \"\"\"\n", " Load a trained per-ticker model and scalers for a given horizon.\n", " \n", " Reads the saved PyTorch model weights, StandardScaler objects, and model configuration\n", " from the appropriate folder. Returns the loaded model and required preprocessing objects. \n", " \"\"\"\n", " out_dir = os.path.join(MODELS_DIR, ticker)\n", " model_path = os.path.join(out_dir, f\"{ticker}_{horizon}_model.pth\")\n", " x_scaler_path = os.path.join(out_dir, f\"{ticker}_{horizon}_scaler.pkl\")\n", " y_scaler_path = os.path.join(out_dir, f\"{ticker}_{horizon}_y_scaler.pkl\")\n", " config_path = os.path.join(out_dir, f\"{ticker}_{horizon}_config.json\")\n", "\n", " if not all(os.path.exists(p) for p in [model_path, x_scaler_path, y_scaler_path]):\n", " raise FileNotFoundError(f\"Missing model/scalers for {ticker} ({horizon})\")\n", "\n", " x_scaler = joblib.load(x_scaler_path)\n", " y_scaler = joblib.load(y_scaler_path)\n", " cfg = {}\n", " if os.path.exists(config_path):\n", " with open(config_path, \"r\") as f:\n", " cfg = json.load(f)\n", "\n", " model = LSTMModel(\n", " input_size=cfg.get(\"input_size\", len(x_scaler.mean_)),\n", " hidden_size=cfg.get(\"hidden_size\", 128),\n", " num_layers=cfg.get(\"num_layers\", 2),\n", " dropout=cfg.get(\"dropout\", 0.2),\n", " )\n", " model.load_state_dict(torch.load(model_path, map_location=DEVICE, weights_only=True))\n", " model.to(DEVICE)\n", " # model.eval()\n", "\n", " seq_len = cfg.get(\"seq_len\", 90)\n", " return model, x_scaler, y_scaler, seq_len" ] }, { "cell_type": "markdown", "id": "6cbcf14a", "metadata": {}, "source": [ "### Master Prediction Function — `predict_all_horizons()`\n", "\n", "This function runs the entire prediction process for one ticker across all horizons. \n", "It does the following:\n", "\n", "- Loads the corresponding model for each horizon.\n", "- Prepares recent input data.\n", "- Runs MC Dropout predictions to estimate future prices and uncertainty.\n", "\n", "The results are returned as a nested JSON-style dictionary.\n" ] }, { "cell_type": "code", "execution_count": 25, "id": "8181855f", "metadata": {}, "outputs": [], "source": [ "def predict_all_horizons(ticker):\n", " \"\"\"Run predictions for all horizons for a given ticker.\"\"\"\n", " csv_path = os.path.join(DATA_PATH, f\"{ticker}.csv\")\n", " if not os.path.exists(csv_path):\n", " raise FileNotFoundError(f\"Data not found for {ticker}\")\n", "\n", " df = pd.read_csv(csv_path)\n", " results = {ticker: {}}\n", "\n", " for horizon in HORIZONS:\n", " try:\n", " model, x_scaler, y_scaler, seq_len = load_per_ticker_model(ticker, horizon)\n", " X = prepare_input_sequence(df, x_scaler, seq_len)\n", " results[ticker][horizon] = mc_dropout_predict(model, X, y_scaler)\n", " except Exception as e:\n", " results[ticker][horizon] = {\"error\": str(e)}\n", "\n", " return results" ] }, { "cell_type": "markdown", "id": "df8f7e25", "metadata": {}, "source": [ "### Predict Prices for Each Ticker Across All Horizons\n", "\n", "The following cells run the full prediction pipeline for each ticker — `TSLA`, `NVDA`, and `SPY`.\n", "\n", "Each result contains:\n", "- The predicted price \n", "- Upper and lower confidence bounds \n", "- Percentage uncertainty \n", "\n", "Results are printed as formatted JSON for readability." ] }, { "cell_type": "code", "execution_count": 26, "id": "1a25b731", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"TSLA\": {\n", " \"1d\": {\n", " \"predicted_price\": 423.91230532391535,\n", " \"plus_minus_percent\": 2.3914301847756922,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 412.9317875065574,\n", " \"upper_bound\": 433.2069211595466\n", " },\n", " \"1w\": {\n", " \"predicted_price\": 407.7461827158806,\n", " \"plus_minus_percent\": 2.163292407804676,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 398.96363850792915,\n", " \"upper_bound\": 416.6051229355412\n", " },\n", " \"4w\": {\n", " \"predicted_price\": 363.48014489202194,\n", " \"plus_minus_percent\": 2.045421190708838,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 355.5858774692911,\n", " \"upper_bound\": 370.45527728457233\n", " },\n", " \"6m\": {\n", " \"predicted_price\": 328.07313051899285,\n", " \"plus_minus_percent\": 1.3357260655574374,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 323.72439556623965,\n", " \"upper_bound\": 332.48871220310457\n", " },\n", " \"1y\": {\n", " \"predicted_price\": 520.29693916529,\n", " \"plus_minus_percent\": 4.406149604075269,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 495.7471013563936,\n", " \"upper_bound\": 541.597224406488\n", " }\n", " }\n", "}\n" ] } ], "source": [ "# Predict TSLA Prices Across All Horizons\n", "\n", "tsla_results = predict_all_horizons(\"TSLA\")\n", "print(json.dumps(tsla_results, indent=2))\n" ] }, { "cell_type": "code", "execution_count": 27, "id": "e78d20d7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"NVDA\": {\n", " \"1d\": {\n", " \"predicted_price\": 172.25536523423122,\n", " \"plus_minus_percent\": 2.488616515822286,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 167.4600444151152,\n", " \"upper_bound\": 176.03359535233335\n", " },\n", " \"1w\": {\n", " \"predicted_price\": 158.6524630063703,\n", " \"plus_minus_percent\": 1.5882329909554165,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 156.00808643841097,\n", " \"upper_bound\": 161.047627955272\n", " },\n", " \"4w\": {\n", " \"predicted_price\": 111.4459510379248,\n", " \"plus_minus_percent\": 3.2590817486645647,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 108.00032006131363,\n", " \"upper_bound\": 115.26454936111894\n", " },\n", " \"6m\": {\n", " \"predicted_price\": 182.8584424621916,\n", " \"plus_minus_percent\": 2.8325311235647166,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 178.0588885590392,\n", " \"upper_bound\": 188.4179331486537\n", " },\n", " \"1y\": {\n", " \"predicted_price\": 174.85005900948832,\n", " \"plus_minus_percent\": 1.897449522628199,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 171.29182605873194,\n", " \"upper_bound\": 177.92720927871326\n", " }\n", " }\n", "}\n" ] } ], "source": [ "# Predict NVDA Prices Across All Horizons\n", "\n", "nvda_results = predict_all_horizons(\"NVDA\")\n", "print(json.dumps(nvda_results, indent=2))\n" ] }, { "cell_type": "code", "execution_count": 28, "id": "4977e3f2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"SPY\": {\n", " \"1d\": {\n", " \"predicted_price\": 639.4759939581851,\n", " \"plus_minus_percent\": 0.9328109486629402,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 633.1582537018373,\n", " \"upper_bound\": 645.0884578732636\n", " },\n", " \"1w\": {\n", " \"predicted_price\": 630.7255956339875,\n", " \"plus_minus_percent\": 0.6683810144576589,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 626.5505602170144,\n", " \"upper_bound\": 634.9818604860995\n", " },\n", " \"4w\": {\n", " \"predicted_price\": 614.5067533748938,\n", " \"plus_minus_percent\": 0.6793809839600208,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 610.0966852794588,\n", " \"upper_bound\": 618.4463693346171\n", " },\n", " \"6m\": {\n", " \"predicted_price\": 635.1835210557811,\n", " \"plus_minus_percent\": 2.0738076262050384,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 621.0336738514119,\n", " \"upper_bound\": 647.3786424515168\n", " },\n", " \"1y\": {\n", " \"predicted_price\": 679.4754742937942,\n", " \"plus_minus_percent\": 1.1306809899754051,\n", " \"confidence_percent\": 95.0,\n", " \"lower_bound\": 671.8940501727745,\n", " \"upper_bound\": 687.2594502115448\n", " }\n", " }\n", "}\n" ] } ], "source": [ "# Predict SPY Prices Across All Horizons\n", "\n", "spy_results = predict_all_horizons(\"SPY\")\n", "print(json.dumps(spy_results, indent=2))\n" ] }, { "cell_type": "markdown", "id": "df4af951", "metadata": {}, "source": [ "## Model Evaluation Pipeline\n", "\n", "This final section evaluates the accuracy of each trained LSTM model by comparing its predictions to historical data. \n", "Metrics such as MAE, RMSE, and MAPE are calculated to quantify how well each model performs. \n", "These evaluations help identify which ticker-horizon combinations yield reliable forecasts." ] }, { "cell_type": "code", "execution_count": 29, "id": "9ce3cdb6", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "import numpy as np\n", "import torch\n", "import joblib\n", "import pandas as pd\n", "from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error" ] }, { "cell_type": "markdown", "id": "39ac25f3", "metadata": {}, "source": [ "### Evaluate Individual Model — `evaluate_model_accuracy()`\n", "\n", "This function loads a single trained model, runs it on the test portion of its dataset,\n", "and computes standard regression metrics:\n", "\n", "- MAE (Mean Absolute Error): Average absolute difference between predicted and actual prices. \n", "- RMSE (Root Mean Squared Error): Penalizes larger errors more heavily. \n", "- MAPE (Mean Absolute Percentage Error): Expresses error as a percentage of the actual value.\n", "\n", "The function returns a dictionary of these metrics for later aggregation." ] }, { "cell_type": "code", "execution_count": 35, "id": "f85ba1c7", "metadata": {}, "outputs": [], "source": [ "def evaluate_model_accuracy(ticker, horizon):\n", " base_dir = os.path.dirname(os.getcwd())\n", " data_path = os.path.join(base_dir, \"data\", f\"{ticker}.csv\")\n", " model_dir = os.path.join(base_dir, \"models\", ticker)\n", " model_path = os.path.join(model_dir, f\"{ticker}_{horizon}_model.pth\")\n", " x_scaler_path = os.path.join(model_dir, f\"{ticker}_{horizon}_scaler.pkl\")\n", " y_scaler_path = os.path.join(model_dir, f\"{ticker}_{horizon}_y_scaler.pkl\")\n", " config_path = os.path.join(model_dir, f\"{ticker}_{horizon}_config.json\")\n", "\n", " # Load config\n", " with open(config_path, \"r\") as f:\n", " cfg = json.load(f)\n", " seq_len = cfg.get(\"seq_len\", 90)\n", "\n", " # Load model\n", " model = LSTMModel(\n", " input_size=cfg.get(\"input_size\", 5),\n", " hidden_size=cfg.get(\"hidden_size\", 256),\n", " num_layers=cfg.get(\"num_layers\", 3),\n", " dropout=cfg.get(\"dropout\", 0.2),\n", " bidirectional=True\n", " )\n", " model.load_state_dict(torch.load(model_path, map_location=\"cpu\", weights_only=True))\n", " model.eval()\n", "\n", " # Load scalers and data\n", " x_scaler = joblib.load(x_scaler_path)\n", " y_scaler = joblib.load(y_scaler_path)\n", " df = pd.read_csv(data_path)\n", "\n", " # Split into train/test (last 20% test)\n", " n_test = int(len(df) * 0.2)\n", " train_data = df[:-n_test]\n", " test_data = df[-n_test:]\n", "\n", " # Prepend last `seq_len` rows of training data to test set\n", " test_data_with_history = pd.concat([train_data[-seq_len:], test_data], ignore_index=True)\n", "\n", " features = test_data_with_history[[\"Open\", \"High\", \"Low\", \"Close\", \"Volume\"]].values\n", " scaled_features = x_scaler.transform(features)\n", "\n", " preds, actuals = [], []\n", " for i in range(seq_len, len(scaled_features)):\n", " X_seq = scaled_features[i - seq_len:i]\n", " X_tensor = torch.tensor(X_seq, dtype=torch.float32).unsqueeze(0)\n", " with torch.no_grad():\n", " y_pred = model(X_tensor).item()\n", " preds.append(y_pred)\n", " actuals.append(scaled_features[i, 3]) # scaled close\n", "\n", " # Rescale predictions\n", " preds = y_scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten()\n", " actuals = y_scaler.inverse_transform(np.array(actuals).reshape(-1, 1)).flatten()\n", "\n", " mae = mean_absolute_error(actuals, preds)\n", " rmse = np.sqrt(mean_squared_error(actuals, preds))\n", " mape = mean_absolute_percentage_error(actuals, preds) * 100\n", "\n", " print(f\"{ticker} ({horizon}) Accuracy:\")\n", " print(f\"MAE: {mae:.3f}\")\n", " print(f\"RMSE: {rmse:.3f}\")\n", " print(f\"MAPE: {mape:.2f}%\")\n", "\n", " return {\"mae\": mae, \"rmse\": rmse, \"mape\": mape}\n" ] }, { "cell_type": "markdown", "id": "27e1b0fd", "metadata": {}, "source": [ "### Evaluate All Models — `evaluate_all_models()`\n", "\n", "This orchestrates the evaluation process for every trained `(ticker, horizon)` pair. \n", "It collects results from `evaluate_model_accuracy()` and compiles them into a nested dictionary." ] }, { "cell_type": "code", "execution_count": null, "id": "ea6b88ea", "metadata": {}, "outputs": [], "source": [ "def evaluate_all_models(tickers=[\"TSLA\", \"NVDA\", \"SPY\"],\n", " horizons=[\"1d\", \"1w\", \"4w\", \"6m\", \"1y\"]):\n", " \"\"\"\n", " Evaluate all trained LSTM models and return a nested JSON-style dict.\n", " Horizons are skipped if the dataset is too small for the required seq_len.\n", " \"\"\"\n", " all_results = {}\n", "\n", " for ticker in tickers:\n", " ticker_results = {}\n", " print(\"---\", ticker, \"---\")\n", " \n", " # Load data once per ticker\n", " data_path = os.path.join(DATA_PATH, f\"{ticker}.csv\")\n", " if not os.path.exists(data_path):\n", " print(f\"{ticker} data not found, skipping all horizons\")\n", " ticker_results = {h: {\"skipped\": \"data file not found\"} for h in horizons}\n", " all_results[ticker] = ticker_results\n", " continue\n", " df = pd.read_csv(data_path)\n", " n_rows = len(df)\n", "\n", " for horizon in horizons:\n", " print(f\"\\nEvaluating {ticker} ({horizon}) ...\")\n", " # Get the required seq_len for this horizon\n", " cfg = HORIZON_CONFIGS.get(horizon, {})\n", " seq_len = cfg.get(\"seq_len_short\", 90)\n", " \n", " # Skip if not enough rows for one sequence\n", " if n_rows < seq_len:\n", " print(f\"{ticker} ({horizon}) skipped: dataset too small for seq_len={seq_len}\")\n", " ticker_results[horizon] = {\"skipped\": f\"dataset too small for seq_len={seq_len}\"}\n", " continue\n", "\n", " # Otherwise evaluate normally\n", " try:\n", " metrics = evaluate_model_accuracy(ticker, horizon)\n", " ticker_results[horizon] = metrics\n", " except Exception as e:\n", " print(f\"{ticker} ({horizon}) failed: {e}\")\n", " ticker_results[horizon] = {\"error\": str(e)}\n", "\n", " all_results[ticker] = ticker_results\n", " print(\"\\n\")\n", "\n", " return all_results\n", "\n" ] }, { "cell_type": "code", "execution_count": 39, "id": "deddfb03", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- TSLA ---\n", "\n", "Evaluating TSLA (1d) ...\n", "TSLA (1d) Accuracy:\n", "MAE: 11.274\n", "RMSE: 14.571\n", "MAPE: 3.26%\n", "\n", "Evaluating TSLA (1w) ...\n", "TSLA (1w) Accuracy:\n", "MAE: 17.107\n", "RMSE: 22.643\n", "MAPE: 4.57%\n", "\n", "Evaluating TSLA (4w) ...\n", "TSLA (4w) Accuracy:\n", "MAE: 33.549\n", "RMSE: 47.712\n", "MAPE: 8.13%\n", "\n", "Evaluating TSLA (6m) ...\n", "TSLA (6m) Accuracy:\n", "MAE: 82.759\n", "RMSE: 98.760\n", "MAPE: 18.99%\n", "\n", "Evaluating TSLA (1y) ...\n", "TSLA (1y) Accuracy:\n", "MAE: 138.730\n", "RMSE: 167.625\n", "MAPE: 20.07%\n", "\n", "\n", "--- NVDA ---\n", "\n", "Evaluating NVDA (1d) ...\n", "NVDA (1d) Accuracy:\n", "MAE: 7.969\n", "RMSE: 9.024\n", "MAPE: 4.57%\n", "\n", "Evaluating NVDA (1w) ...\n", "NVDA (1w) Accuracy:\n", "MAE: 18.779\n", "RMSE: 20.309\n", "MAPE: 10.70%\n", "\n", "Evaluating NVDA (4w) ...\n", "NVDA (4w) Accuracy:\n", "MAE: 44.741\n", "RMSE: 53.412\n", "MAPE: 24.49%\n", "\n", "Evaluating NVDA (6m) ...\n", "NVDA (6m) Accuracy:\n", "MAE: 32.223\n", "RMSE: 36.601\n", "MAPE: 17.60%\n", "\n", "Evaluating NVDA (1y) ...\n", "NVDA (1y) Accuracy:\n", "MAE: 28.496\n", "RMSE: 31.210\n", "MAPE: 13.61%\n", "\n", "\n", "--- SPY ---\n", "\n", "Evaluating SPY (1d) ...\n", "SPY (1d) Accuracy:\n", "MAE: 13.786\n", "RMSE: 15.599\n", "MAPE: 2.14%\n", "\n", "Evaluating SPY (1w) ...\n", "SPY (1w) Accuracy:\n", "MAE: 21.146\n", "RMSE: 23.380\n", "MAPE: 3.27%\n", "\n", "Evaluating SPY (4w) ...\n", "SPY (4w) Accuracy:\n", "MAE: 37.140\n", "RMSE: 40.042\n", "MAPE: 5.71%\n", "\n", "Evaluating SPY (6m) ...\n", "SPY (6m) Accuracy:\n", "MAE: 32.046\n", "RMSE: 37.356\n", "MAPE: 4.73%\n", "\n", "Evaluating SPY (1y) ...\n", "SPY (1y) Accuracy:\n", "MAE: 24.741\n", "RMSE: 28.858\n", "MAPE: 3.48%\n", "\n", "\n", "{'TSLA': {'1d': {'mae': 11.273503338386709, 'rmse': 14.570877711285853, 'mape': 3.2615077846956995}, '1w': {'mae': 17.1066352178035, 'rmse': 22.64252964884088, 'mape': 4.569581440979084}, '4w': {'mae': 33.549088769760594, 'rmse': 47.71179501324069, 'mape': 8.129871459373064}, '6m': {'mae': 82.75856343194404, 'rmse': 98.76049649643686, 'mape': 18.99143915680735}, '1y': {'mae': 138.72973524923947, 'rmse': 167.62468375901403, 'mape': 20.065061625674435}}, 'NVDA': {'1d': {'mae': 7.969138056779754, 'rmse': 9.024415192414201, 'mape': 4.5660302605642125}, '1w': {'mae': 18.77944954032756, 'rmse': 20.308869338724573, 'mape': 10.703610031788008}, '4w': {'mae': 44.7405477817365, 'rmse': 53.41219316127466, 'mape': 24.487382310400193}, '6m': {'mae': 32.2227811413769, 'rmse': 36.60106802603206, 'mape': 17.59915607861485}, '1y': {'mae': 28.49636464594328, 'rmse': 31.210266128288417, 'mape': 13.612866672805508}}, 'SPY': {'1d': {'mae': 13.785889912167926, 'rmse': 15.599013529507495, 'mape': 2.135290190063011}, '1w': {'mae': 21.146263254138162, 'rmse': 23.379663054232847, 'mape': 3.27016574916871}, '4w': {'mae': 37.1397995757428, 'rmse': 40.04241080024628, 'mape': 5.709834542966955}, '6m': {'mae': 32.04597323055573, 'rmse': 37.355746794940146, 'mape': 4.725915814875788}, '1y': {'mae': 24.74089369826057, 'rmse': 28.858351135134715, 'mape': 3.4835931377792466}}}\n" ] } ], "source": [ "# Run Full Evaluation\n", "\n", "results = evaluate_all_models()\n", "print(results)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.7" } }, "nbformat": 4, "nbformat_minor": 5 }