{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b1eb1116-fe0d-4c53-ab06-6d52bd64b716",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import pandas as pd\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.model_selection import train_test_split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "76221885-2345-4447-a360-26e5fda94c43",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    CO_ID   Name  Year  Quarter  Sector  Return on Assets  Return on Capital  \\\n",
      "0  2030.0  SARCO  2015        1  Energy             1.236              0.265   \n",
      "1  2030.0  SARCO  2015        2  Energy             1.246              0.703   \n",
      "2  2030.0  SARCO  2015        3  Energy             1.143              0.792   \n",
      "3  2030.0  SARCO  2015        4  Energy               NaN                NaN   \n",
      "4  2030.0  SARCO  2016        1  Energy             1.520              0.860   \n",
      "\n",
      "  Return on Equity  Return on Common Equity   Gross Profit Margin  ...  \\\n",
      "0            1.960                      1.24                100.0  ...   \n",
      "1            1.950                      1.65                100.0  ...   \n",
      "2            1.960                     -1.12                100.0  ...   \n",
      "3            1.930                     -1.23                100.0  ...   \n",
      "4            1.240                      2.65                100.0  ...   \n",
      "\n",
      "   Asset Turnover  Current Ratio  Debt/Equity  Long Term Debt/Captial  \\\n",
      "0            0.03           3.39        0.066                   0.033   \n",
      "1            0.02           3.38          NaN                     NaN   \n",
      "2            0.06           3.35          NaN                   0.021   \n",
      "3            0.04           3.39        0.026                     NaN   \n",
      "4           -1.00           2.90          NaN                     NaN   \n",
      "\n",
      "   Book Value Per Share  Start Price  End Price Stock Return  TASI Return  \\\n",
      "0                19.190         50.5      57.00     0.128713     0.053471   \n",
      "1                19.560         57.0      58.75     0.030702     0.080545   \n",
      "2                19.730         58.5      41.00    -0.299145    -0.081079   \n",
      "3                19.760         41.0      41.10     0.002439    -0.263944   \n",
      "4                23.650         41.0      33.50    -0.182927     0.037782   \n",
      "\n",
      "   Relative_Return  \n",
      "0         0.075242  \n",
      "1        -0.049844  \n",
      "2        -0.218066  \n",
      "3         0.266383  \n",
      "4        -0.220708  \n",
      "\n",
      "[5 rows x 23 columns]\n"
     ]
    }
   ],
   "source": [
    "# Load the dataset\n",
    "df = pd.read_csv('datasetNewNewNewTASI.csv')\n",
    "print(df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "91e7b57b-bb0c-498d-84e5-a26742197bf4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CO_ID</th>\n",
       "      <th>Name</th>\n",
       "      <th>Year</th>\n",
       "      <th>Quarter</th>\n",
       "      <th>Sector</th>\n",
       "      <th>Return on Assets</th>\n",
       "      <th>Return on Capital</th>\n",
       "      <th>Return on Equity</th>\n",
       "      <th>Return on Common Equity</th>\n",
       "      <th>Gross Profit Margin</th>\n",
       "      <th>...</th>\n",
       "      <th>Asset Turnover</th>\n",
       "      <th>Current Ratio</th>\n",
       "      <th>Debt/Equity</th>\n",
       "      <th>Long Term Debt/Captial</th>\n",
       "      <th>Book Value Per Share</th>\n",
       "      <th>Start Price</th>\n",
       "      <th>End Price</th>\n",
       "      <th>Stock Return</th>\n",
       "      <th>TASI Return</th>\n",
       "      <th>Relative_Return</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>1</td>\n",
       "      <td>Energy</td>\n",
       "      <td>1.236</td>\n",
       "      <td>0.265</td>\n",
       "      <td>1.960</td>\n",
       "      <td>1.24</td>\n",
       "      <td>100.0</td>\n",
       "      <td>...</td>\n",
       "      <td>0.03</td>\n",
       "      <td>3.39</td>\n",
       "      <td>0.066</td>\n",
       "      <td>0.033</td>\n",
       "      <td>19.190</td>\n",
       "      <td>50.5</td>\n",
       "      <td>57.00</td>\n",
       "      <td>0.128713</td>\n",
       "      <td>0.053471</td>\n",
       "      <td>0.075242</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>2</td>\n",
       "      <td>Energy</td>\n",
       "      <td>1.246</td>\n",
       "      <td>0.703</td>\n",
       "      <td>1.950</td>\n",
       "      <td>1.65</td>\n",
       "      <td>100.0</td>\n",
       "      <td>...</td>\n",
       "      <td>0.02</td>\n",
       "      <td>3.38</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>19.560</td>\n",
       "      <td>57.0</td>\n",
       "      <td>58.75</td>\n",
       "      <td>0.030702</td>\n",
       "      <td>0.080545</td>\n",
       "      <td>-0.049844</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>3</td>\n",
       "      <td>Energy</td>\n",
       "      <td>1.143</td>\n",
       "      <td>0.792</td>\n",
       "      <td>1.960</td>\n",
       "      <td>-1.12</td>\n",
       "      <td>100.0</td>\n",
       "      <td>...</td>\n",
       "      <td>0.06</td>\n",
       "      <td>3.35</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.021</td>\n",
       "      <td>19.730</td>\n",
       "      <td>58.5</td>\n",
       "      <td>41.00</td>\n",
       "      <td>-0.299145</td>\n",
       "      <td>-0.081079</td>\n",
       "      <td>-0.218066</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>4</td>\n",
       "      <td>Energy</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>1.930</td>\n",
       "      <td>-1.23</td>\n",
       "      <td>100.0</td>\n",
       "      <td>...</td>\n",
       "      <td>0.04</td>\n",
       "      <td>3.39</td>\n",
       "      <td>0.026</td>\n",
       "      <td>NaN</td>\n",
       "      <td>19.760</td>\n",
       "      <td>41.0</td>\n",
       "      <td>41.10</td>\n",
       "      <td>0.002439</td>\n",
       "      <td>-0.263944</td>\n",
       "      <td>0.266383</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2016</td>\n",
       "      <td>1</td>\n",
       "      <td>Energy</td>\n",
       "      <td>1.520</td>\n",
       "      <td>0.860</td>\n",
       "      <td>1.240</td>\n",
       "      <td>2.65</td>\n",
       "      <td>100.0</td>\n",
       "      <td>...</td>\n",
       "      <td>-1.00</td>\n",
       "      <td>2.90</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>23.650</td>\n",
       "      <td>41.0</td>\n",
       "      <td>33.50</td>\n",
       "      <td>-0.182927</td>\n",
       "      <td>0.037782</td>\n",
       "      <td>-0.220708</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 23 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "    CO_ID   Name  Year  Quarter  Sector  Return on Assets  Return on Capital  \\\n",
       "0  2030.0  SARCO  2015        1  Energy             1.236              0.265   \n",
       "1  2030.0  SARCO  2015        2  Energy             1.246              0.703   \n",
       "2  2030.0  SARCO  2015        3  Energy             1.143              0.792   \n",
       "3  2030.0  SARCO  2015        4  Energy               NaN                NaN   \n",
       "4  2030.0  SARCO  2016        1  Energy             1.520              0.860   \n",
       "\n",
       "  Return on Equity  Return on Common Equity   Gross Profit Margin  ...  \\\n",
       "0            1.960                      1.24                100.0  ...   \n",
       "1            1.950                      1.65                100.0  ...   \n",
       "2            1.960                     -1.12                100.0  ...   \n",
       "3            1.930                     -1.23                100.0  ...   \n",
       "4            1.240                      2.65                100.0  ...   \n",
       "\n",
       "   Asset Turnover  Current Ratio  Debt/Equity  Long Term Debt/Captial  \\\n",
       "0            0.03           3.39        0.066                   0.033   \n",
       "1            0.02           3.38          NaN                     NaN   \n",
       "2            0.06           3.35          NaN                   0.021   \n",
       "3            0.04           3.39        0.026                     NaN   \n",
       "4           -1.00           2.90          NaN                     NaN   \n",
       "\n",
       "   Book Value Per Share  Start Price  End Price Stock Return  TASI Return  \\\n",
       "0                19.190         50.5      57.00     0.128713     0.053471   \n",
       "1                19.560         57.0      58.75     0.030702     0.080545   \n",
       "2                19.730         58.5      41.00    -0.299145    -0.081079   \n",
       "3                19.760         41.0      41.10     0.002439    -0.263944   \n",
       "4                23.650         41.0      33.50    -0.182927     0.037782   \n",
       "\n",
       "   Relative_Return  \n",
       "0         0.075242  \n",
       "1        -0.049844  \n",
       "2        -0.218066  \n",
       "3         0.266383  \n",
       "4        -0.220708  \n",
       "\n",
       "[5 rows x 23 columns]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9212ecfe-b3d9-433b-8b11-909cd0634952",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Long Term Debt/Captial      299\n",
      "Debt/Equity                 264\n",
      "Net Income Margin           184\n",
      "EBITA Margin                182\n",
      "Gross Profit Margin         174\n",
      "EBIT Margin                 173\n",
      "Return on Common Equity     162\n",
      "Asset Turnover              160\n",
      "Return on Equity            159\n",
      "Return on Assets            142\n",
      "Return on Capital           140\n",
      "Current Ratio               140\n",
      "Book Value Per Share         62\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "missing = df.isnull().sum()\n",
    "missing = missing[missing > 0].sort_values(ascending=False)\n",
    "print(missing)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b3f14bf0-a379-408d-bd8e-93c6fe04b2c3",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\يوسف عثمان\\AppData\\Local\\Temp\\ipykernel_32524\\3887843883.py:10: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.\n",
      "  df[\"Quarter\"] = pd.to_datetime(df[\"Year\"].astype(str) + \"Q\" + df[\"Quarter\"].astype(str))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Missing values handled and Z-score standardization applied.\n",
      "📌 Features: ['Return on Assets', 'Return on Capital', 'Return on Equity', 'Return on Common Equity', 'Gross Profit Margin', 'EBITA Margin', 'EBIT Margin', 'Net Income Margin', 'Asset Turnover', 'Current Ratio', 'Debt/Equity', 'Long Term Debt/Captial', 'Book Value Per Share']\n",
      "🎯 Target: Relative_Return\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# Load your dataset\n",
    "df = pd.read_csv(\"datasetNewNewNewTASI.csv\")\n",
    "\n",
    "# Step 1: Strip column names of whitespace\n",
    "df.columns = df.columns.str.strip()\n",
    "\n",
    "df[\"Quarter\"] = pd.to_datetime(df[\"Year\"].astype(str) + \"Q\" + df[\"Quarter\"].astype(str))\n",
    "\n",
    "\n",
    "# Step 2: Define target and features\n",
    "target_col = 'Relative_Return'\n",
    "feature_cols = [\n",
    "    'Return on Assets', 'Return on Capital', 'Return on Equity', 'Return on Common Equity',\n",
    "    'Gross Profit Margin', 'EBITA Margin', 'EBIT Margin', 'Net Income Margin',\n",
    "    'Asset Turnover', 'Current Ratio', 'Debt/Equity', 'Long Term Debt/Captial',\n",
    "    'Book Value Per Share'\n",
    "]\n",
    "\n",
    "# Step 3: Fill missing values with forward fill then backward fill by company\n",
    "# Exclude grouping column (CO_ID) from being operated on in .apply()\n",
    "cols_to_fill = feature_cols + [target_col]\n",
    "df[cols_to_fill] = (\n",
    "    df.groupby(\"CO_ID\")[cols_to_fill]\n",
    "      .apply(lambda group: group.ffill().bfill())\n",
    "      .reset_index(drop=True)\n",
    ")\n",
    "\n",
    "# Step 4: Convert feature columns to numeric (in case of object type)\n",
    "df[feature_cols] = df[feature_cols].apply(pd.to_numeric, errors='coerce')\n",
    "\n",
    "df[feature_cols] = df[feature_cols].fillna(df[feature_cols].mean())\n",
    "\n",
    "# Step 5: Apply Z-score normalization from scratch\n",
    "for col in feature_cols:\n",
    "    mean = df[col].mean()\n",
    "    std = df[col].std()\n",
    "    df[col] = (df[col] - mean) / std\n",
    "\n",
    "# ✅ Done\n",
    "print(\"✅ Missing values handled and Z-score standardization applied.\")\n",
    "print(\"📌 Features:\", feature_cols)\n",
    "print(\"🎯 Target:\", target_col)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d162759c-64fb-4ca3-8c3c-79b6ccce01cb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CO_ID</th>\n",
       "      <th>Name</th>\n",
       "      <th>Year</th>\n",
       "      <th>Quarter</th>\n",
       "      <th>Sector</th>\n",
       "      <th>Return on Assets</th>\n",
       "      <th>Return on Capital</th>\n",
       "      <th>Return on Equity</th>\n",
       "      <th>Return on Common Equity</th>\n",
       "      <th>Gross Profit Margin</th>\n",
       "      <th>...</th>\n",
       "      <th>Asset Turnover</th>\n",
       "      <th>Current Ratio</th>\n",
       "      <th>Debt/Equity</th>\n",
       "      <th>Long Term Debt/Captial</th>\n",
       "      <th>Book Value Per Share</th>\n",
       "      <th>Start Price</th>\n",
       "      <th>End Price</th>\n",
       "      <th>Stock Return</th>\n",
       "      <th>TASI Return</th>\n",
       "      <th>Relative_Return</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>2015-01-01</td>\n",
       "      <td>Energy</td>\n",
       "      <td>-0.252465</td>\n",
       "      <td>-0.239364</td>\n",
       "      <td>-0.358703</td>\n",
       "      <td>-0.316179</td>\n",
       "      <td>-0.474507</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.001501</td>\n",
       "      <td>-0.430421</td>\n",
       "      <td>1.095549</td>\n",
       "      <td>1.249324</td>\n",
       "      <td>0.234976</td>\n",
       "      <td>50.5</td>\n",
       "      <td>57.00</td>\n",
       "      <td>0.128713</td>\n",
       "      <td>0.053471</td>\n",
       "      <td>0.247680</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>2015-04-01</td>\n",
       "      <td>Energy</td>\n",
       "      <td>-0.258880</td>\n",
       "      <td>-0.482341</td>\n",
       "      <td>-0.248664</td>\n",
       "      <td>-0.219191</td>\n",
       "      <td>-0.474088</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.001501</td>\n",
       "      <td>-0.488853</td>\n",
       "      <td>1.058266</td>\n",
       "      <td>1.249324</td>\n",
       "      <td>0.228782</td>\n",
       "      <td>57.0</td>\n",
       "      <td>58.75</td>\n",
       "      <td>0.030702</td>\n",
       "      <td>0.080545</td>\n",
       "      <td>0.086175</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>2015-07-01</td>\n",
       "      <td>Energy</td>\n",
       "      <td>-0.249258</td>\n",
       "      <td>-0.315901</td>\n",
       "      <td>-0.393915</td>\n",
       "      <td>-0.347215</td>\n",
       "      <td>-0.462763</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.027798</td>\n",
       "      <td>-0.488853</td>\n",
       "      <td>0.932941</td>\n",
       "      <td>1.344777</td>\n",
       "      <td>0.192443</td>\n",
       "      <td>58.5</td>\n",
       "      <td>41.00</td>\n",
       "      <td>-0.299145</td>\n",
       "      <td>-0.081079</td>\n",
       "      <td>-0.324735</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2015</td>\n",
       "      <td>2015-10-01</td>\n",
       "      <td>Energy</td>\n",
       "      <td>-0.250862</td>\n",
       "      <td>-0.245438</td>\n",
       "      <td>-0.364865</td>\n",
       "      <td>-0.321610</td>\n",
       "      <td>-0.464861</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.027798</td>\n",
       "      <td>-0.453286</td>\n",
       "      <td>1.101842</td>\n",
       "      <td>1.205826</td>\n",
       "      <td>0.259753</td>\n",
       "      <td>41.0</td>\n",
       "      <td>41.10</td>\n",
       "      <td>0.002439</td>\n",
       "      <td>-0.263944</td>\n",
       "      <td>0.106975</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2030.0</td>\n",
       "      <td>SARCO</td>\n",
       "      <td>2016</td>\n",
       "      <td>2016-01-01</td>\n",
       "      <td>Energy</td>\n",
       "      <td>-0.242843</td>\n",
       "      <td>-0.253942</td>\n",
       "      <td>-0.467861</td>\n",
       "      <td>-0.412391</td>\n",
       "      <td>-0.431306</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.029990</td>\n",
       "      <td>-0.453286</td>\n",
       "      <td>0.412591</td>\n",
       "      <td>1.205826</td>\n",
       "      <td>0.073103</td>\n",
       "      <td>41.0</td>\n",
       "      <td>33.50</td>\n",
       "      <td>-0.182927</td>\n",
       "      <td>0.037782</td>\n",
       "      <td>-0.424714</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 23 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "    CO_ID   Name  Year    Quarter  Sector  Return on Assets  \\\n",
       "0  2030.0  SARCO  2015 2015-01-01  Energy         -0.252465   \n",
       "1  2030.0  SARCO  2015 2015-04-01  Energy         -0.258880   \n",
       "2  2030.0  SARCO  2015 2015-07-01  Energy         -0.249258   \n",
       "3  2030.0  SARCO  2015 2015-10-01  Energy         -0.250862   \n",
       "4  2030.0  SARCO  2016 2016-01-01  Energy         -0.242843   \n",
       "\n",
       "   Return on Capital  Return on Equity  Return on Common Equity  \\\n",
       "0          -0.239364         -0.358703                -0.316179   \n",
       "1          -0.482341         -0.248664                -0.219191   \n",
       "2          -0.315901         -0.393915                -0.347215   \n",
       "3          -0.245438         -0.364865                -0.321610   \n",
       "4          -0.253942         -0.467861                -0.412391   \n",
       "\n",
       "   Gross Profit Margin  ...  Asset Turnover  Current Ratio  Debt/Equity  \\\n",
       "0            -0.474507  ...       -0.001501      -0.430421     1.095549   \n",
       "1            -0.474088  ...       -0.001501      -0.488853     1.058266   \n",
       "2            -0.462763  ...       -0.027798      -0.488853     0.932941   \n",
       "3            -0.464861  ...       -0.027798      -0.453286     1.101842   \n",
       "4            -0.431306  ...       -0.029990      -0.453286     0.412591   \n",
       "\n",
       "   Long Term Debt/Captial  Book Value Per Share  Start Price  End Price  \\\n",
       "0                1.249324              0.234976         50.5      57.00   \n",
       "1                1.249324              0.228782         57.0      58.75   \n",
       "2                1.344777              0.192443         58.5      41.00   \n",
       "3                1.205826              0.259753         41.0      41.10   \n",
       "4                1.205826              0.073103         41.0      33.50   \n",
       "\n",
       "   Stock Return  TASI Return  Relative_Return  \n",
       "0      0.128713     0.053471         0.247680  \n",
       "1      0.030702     0.080545         0.086175  \n",
       "2     -0.299145    -0.081079        -0.324735  \n",
       "3      0.002439    -0.263944         0.106975  \n",
       "4     -0.182927     0.037782        -0.424714  \n",
       "\n",
       "[5 rows x 23 columns]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c6da65e1-a740-4411-9947-0676f9dee88e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Series([], dtype: int64)\n"
     ]
    }
   ],
   "source": [
    "missing = df.isnull().sum()\n",
    "missing = missing[missing > 0].sort_values(ascending=False)\n",
    "print(missing)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 863,
   "id": "0fbcdcf5-b5be-41d7-9278-a1a3e4f8dec9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Rolling window sequences created.\n",
      "📏 X shape (samples, timesteps, features): (1850, 4, 13)\n",
      "🎯 y shape (samples,): (1850,)\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Set rolling window size\n",
    "window_size = 4\n",
    "\n",
    "# Create empty lists to hold sequences and labels\n",
    "X_sequences = []\n",
    "y_labels = []\n",
    "\n",
    "# Sort the dataframe by company and quarter to maintain time order\n",
    "df = df.sort_values(by=[\"CO_ID\", \"Quarter\"])\n",
    "\n",
    "# Loop over each company separately\n",
    "for co_id, group in df.groupby(\"CO_ID\"):\n",
    "    group = group.reset_index(drop=True)\n",
    "    \n",
    "    # Skip if not enough quarters\n",
    "    if len(group) <= window_size:\n",
    "        continue\n",
    "\n",
    "    for i in range(len(group) - window_size):\n",
    "        # Create sequence of features\n",
    "        X_seq = group.loc[i:i+window_size-1, feature_cols].values.astype(np.float32)\n",
    "        # Target is the value in the next quarter\n",
    "        y_label = group.loc[i + window_size, target_col]\n",
    "\n",
    "        X_sequences.append(X_seq)\n",
    "        y_labels.append(y_label)\n",
    "\n",
    "# Convert to numpy arrays\n",
    "X_sequences = np.array(X_sequences)\n",
    "y_labels = np.array(y_labels).astype(np.float32)\n",
    "\n",
    "# Confirm shapes\n",
    "print(\"✅ Rolling window sequences created.\")\n",
    "print(\"📏 X shape (samples, timesteps, features):\", X_sequences.shape)\n",
    "print(\"🎯 y shape (samples,):\", y_labels.shape)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 864,
   "id": "b25e9b1a-5488-48b2-a06d-1df0d5863331",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Company: 1201.0\n",
      "Dates in sequence: [Timestamp('2015-01-01 00:00:00'), Timestamp('2015-04-01 00:00:00'), Timestamp('2015-07-01 00:00:00'), Timestamp('2015-10-01 00:00:00')]\n",
      "Target date: 2016-01-01 00:00:00\n"
     ]
    }
   ],
   "source": [
    "df[\"Quarter\"] = pd.to_datetime(df[\"Quarter\"], dayfirst=True)\n",
    "df = df.sort_values(by=[\"CO_ID\", \"Quarter\"])\n",
    "\n",
    "# Example: Print dates used for first sequence in first company\n",
    "for co_id, group in df.groupby(\"CO_ID\"):\n",
    "    group = group.reset_index(drop=True)\n",
    "    print(\"Company:\", co_id)\n",
    "    print(\"Dates in sequence:\", group.loc[0:3, \"Quarter\"].tolist())\n",
    "    print(\"Target date:\", group.loc[4, \"Quarter\"])\n",
    "    break  # Remove this if you want all"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 865,
   "id": "d9042888-0db6-4808-94b5-7f15ef9c400c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<DatetimeArray>\n",
      "['2015-01-01 00:00:00', '2015-04-01 00:00:00', '2015-07-01 00:00:00',\n",
      " '2015-10-01 00:00:00', '2016-01-01 00:00:00', '2016-04-01 00:00:00',\n",
      " '2016-07-01 00:00:00', '2016-10-01 00:00:00', '2017-01-01 00:00:00',\n",
      " '2017-04-01 00:00:00']\n",
      "Length: 10, dtype: datetime64[ns]\n"
     ]
    }
   ],
   "source": [
    "print(df[\"Quarter\"].unique()[:10])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 866,
   "id": "c1ac56fa-8973-4ffc-b2a9-7d6185582cae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "123   2015-01-01\n",
      "124   2015-04-01\n",
      "125   2015-07-01\n",
      "126   2015-10-01\n",
      "127   2016-01-01\n",
      "Name: Quarter, dtype: datetime64[ns]\n"
     ]
    }
   ],
   "source": [
    "print(df[\"Quarter\"].head())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 867,
   "id": "924836da-e17a-412f-af49-0651f603087c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Train shapes: (1480, 4, 13) (1480,)\n",
      "✅ Test shapes: (370, 4, 13) (370,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# Convert everything to numpy (if not already done)\n",
    "X = np.array(X_sequences)\n",
    "y = np.array(y_labels)\n",
    "\n",
    "# Determine split index (e.g., 80% train, 20% test)\n",
    "split_idx = int(len(X) * 0.8)\n",
    "\n",
    "# Split\n",
    "X_train, X_test = X[:split_idx], X[split_idx:]\n",
    "y_train, y_test = y[:split_idx], y[split_idx:]\n",
    "\n",
    "# Print shapes\n",
    "print(\"✅ Train shapes:\", X_train.shape, y_train.shape)\n",
    "print(\"✅ Test shapes:\", X_test.shape, y_test.shape)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 868,
   "id": "65d17d4c-6786-4629-a0c4-8b15b83fbfb3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ DataLoaders created.\n",
      "Train: 1295 samples\n",
      "Validation: 277 samples\n",
      "Test: 278 samples\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.utils.data import Dataset, DataLoader, random_split\n",
    "\n",
    "# Define custom PyTorch Dataset\n",
    "class StockDataset(Dataset):\n",
    "    def __init__(self, X, y):\n",
    "        self.X = torch.tensor(X, dtype=torch.float32)\n",
    "        self.y = torch.tensor(y, dtype=torch.float32)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.X)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return self.X[idx], self.y[idx]\n",
    "\n",
    "# Create dataset\n",
    "dataset = StockDataset(X_sequences, y_labels)\n",
    "\n",
    "# Split sizes\n",
    "total_size = len(dataset)\n",
    "train_size = int(0.7 * total_size)\n",
    "val_size = int(0.15 * total_size)\n",
    "test_size = total_size - train_size - val_size\n",
    "\n",
    "# Split dataset with fixed random seed for reproducibility\n",
    "train_dataset, val_dataset, test_dataset = random_split(\n",
    "    dataset, [train_size, val_size, test_size],\n",
    "    generator=torch.Generator().manual_seed(42)\n",
    ")\n",
    "\n",
    "# Create DataLoaders\n",
    "batch_size = 128\n",
    "train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n",
    "\n",
    "# Summary\n",
    "print(\"✅ DataLoaders created.\")\n",
    "print(f\"Train: {len(train_dataset)} samples\")\n",
    "print(f\"Validation: {len(val_dataset)} samples\")\n",
    "print(f\"Test: {len(test_dataset)} samples\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 869,
   "id": "f84c3dfb-01c0-4d6b-a4c1-ec337b110fac",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Hyperparameters (tune here)\n",
    "params = {\n",
    "    \"input_size\": 13,         # Number of features\n",
    "    \"hidden_size\": 128,\n",
    "    \"num_layers\": 2,\n",
    "    \"dropout\": 0.4,\n",
    "    \"batch_size\": 128,\n",
    "    \"num_epochs\": 100,\n",
    "    \"learning_rate\": 0.0005\n",
    "}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 870,
   "id": "bb17e81e-7bfd-409c-ba72-75c13994e913",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch.utils.data import TensorDataset, DataLoader\n",
    "\n",
    "# Convert to tensors\n",
    "X_tensor = torch.tensor(X_sequences, dtype=torch.float32)\n",
    "y_tensor = torch.tensor(y_labels, dtype=torch.float32)\n",
    "\n",
    "#y_tensor = torch.clamp(y_tensor, min=-2.0, max=2.0)\n",
    "\n",
    "# Split 80% train, 20% val\n",
    "train_size = int(0.8 * len(X_tensor))\n",
    "X_train, X_val = X_tensor[:train_size], X_tensor[train_size:]\n",
    "y_train, y_val = y_tensor[:train_size], y_tensor[train_size:]\n",
    "\n",
    "# Datasets & Loaders\n",
    "train_dataset = TensorDataset(X_train, y_train)\n",
    "val_dataset = TensorDataset(X_val, y_val)\n",
    "\n",
    "train_loader = DataLoader(train_dataset, batch_size=params[\"batch_size\"], shuffle=True)\n",
    "val_loader = DataLoader(val_dataset, batch_size=params[\"batch_size\"], shuffle=False)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 871,
   "id": "01383e06-fc84-455f-a77e-90354460c619",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "\n",
    "class LSTMModel(nn.Module):\n",
    "    def __init__(self, input_size, hidden_size, num_layers, dropout):\n",
    "        super(LSTMModel, self).__init__()\n",
    "        self.lstm = nn.LSTM(input_size, hidden_size, num_layers,\n",
    "                            dropout=dropout, batch_first=True)\n",
    "        self.fc = nn.Linear(hidden_size, 1)\n",
    "\n",
    "        #self.fc = nn.Sequential(\n",
    "             #nn.Linear(hidden_size, 1),\n",
    "             #nn.Sigmoid()\n",
    "         #)\n",
    "\n",
    "\n",
    "    def forward(self, x):\n",
    "        lstm_out, _ = self.lstm(x)\n",
    "        out = lstm_out[:, -1, :]  # last time step\n",
    "        return self.fc(out)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 872,
   "id": "ca665f76-5e04-45a5-a329-38d3bda0f4ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch.optim.lr_scheduler import ReduceLROnPlateau\n",
    "\n",
    "# Instantiate model\n",
    "model = LSTMModel(params[\"input_size\"], params[\"hidden_size\"],\n",
    "                  params[\"num_layers\"], params[\"dropout\"])\n",
    "criterion = nn.MSELoss()\n",
    "#criterion = nn.SmoothL1Loss()  # Huber loss\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=params[\"learning_rate\"])\n",
    "#optimizer = torch.optim.Adam(model.parameters(), lr=params[\"learning_rate\"], weight_decay=0.0005)\n",
    "\n",
    "\n",
    "#scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10, min_lr=0.0005)\n",
    "\n",
    "#scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10)\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 873,
   "id": "9371f3f0-d9ee-498b-a995-ee623ce600fb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/100 | Train Loss: 0.1238 | Val Loss: 0.0721\n",
      "Epoch 2/100 | Train Loss: 0.1232 | Val Loss: 0.0727\n",
      "Epoch 3/100 | Train Loss: 0.1827 | Val Loss: 0.0727\n",
      "Epoch 4/100 | Train Loss: 0.1234 | Val Loss: 0.0730\n",
      "Epoch 5/100 | Train Loss: 0.1214 | Val Loss: 0.0734\n",
      "Epoch 6/100 | Train Loss: 0.1220 | Val Loss: 0.0738\n",
      "Epoch 7/100 | Train Loss: 0.1220 | Val Loss: 0.0741\n",
      "Epoch 8/100 | Train Loss: 0.1213 | Val Loss: 0.0741\n",
      "Epoch 9/100 | Train Loss: 0.1792 | Val Loss: 0.0742\n",
      "Epoch 10/100 | Train Loss: 0.1786 | Val Loss: 0.0748\n",
      "Epoch 11/100 | Train Loss: 0.1200 | Val Loss: 0.0758\n",
      "Epoch 12/100 | Train Loss: 0.1197 | Val Loss: 0.0778\n",
      "Epoch 13/100 | Train Loss: 0.1198 | Val Loss: 0.0768\n",
      "Epoch 14/100 | Train Loss: 0.1205 | Val Loss: 0.0760\n",
      "Epoch 15/100 | Train Loss: 0.1188 | Val Loss: 0.0763\n",
      "Epoch 16/100 | Train Loss: 0.1179 | Val Loss: 0.0764\n",
      "Epoch 17/100 | Train Loss: 0.1181 | Val Loss: 0.0757\n",
      "Epoch 18/100 | Train Loss: 0.1181 | Val Loss: 0.0760\n",
      "Epoch 19/100 | Train Loss: 0.1173 | Val Loss: 0.0753\n",
      "Epoch 20/100 | Train Loss: 0.1157 | Val Loss: 0.0753\n",
      "Epoch 21/100 | Train Loss: 0.1159 | Val Loss: 0.0750\n",
      "Epoch 22/100 | Train Loss: 0.1149 | Val Loss: 0.0765\n",
      "Epoch 23/100 | Train Loss: 0.1136 | Val Loss: 0.0756\n",
      "Epoch 24/100 | Train Loss: 0.1141 | Val Loss: 0.0736\n",
      "Epoch 25/100 | Train Loss: 0.1689 | Val Loss: 0.0745\n",
      "Epoch 26/100 | Train Loss: 0.1113 | Val Loss: 0.0748\n",
      "Epoch 27/100 | Train Loss: 0.1132 | Val Loss: 0.0765\n",
      "Epoch 28/100 | Train Loss: 0.1133 | Val Loss: 0.0741\n",
      "Epoch 29/100 | Train Loss: 0.1105 | Val Loss: 0.0731\n",
      "Epoch 30/100 | Train Loss: 0.1106 | Val Loss: 0.0734\n",
      "Epoch 31/100 | Train Loss: 0.1051 | Val Loss: 0.0748\n",
      "Epoch 32/100 | Train Loss: 0.1096 | Val Loss: 0.0755\n",
      "Epoch 33/100 | Train Loss: 0.1139 | Val Loss: 0.0727\n",
      "Epoch 34/100 | Train Loss: 0.1561 | Val Loss: 0.0723\n",
      "Epoch 35/100 | Train Loss: 0.1079 | Val Loss: 0.0748\n",
      "Epoch 36/100 | Train Loss: 0.0993 | Val Loss: 0.0748\n",
      "Epoch 37/100 | Train Loss: 0.1082 | Val Loss: 0.0726\n",
      "Epoch 38/100 | Train Loss: 0.1018 | Val Loss: 0.0727\n",
      "Epoch 39/100 | Train Loss: 0.1101 | Val Loss: 0.0732\n",
      "Epoch 40/100 | Train Loss: 0.0950 | Val Loss: 0.0739\n",
      "Epoch 41/100 | Train Loss: 0.0936 | Val Loss: 0.0735\n",
      "Epoch 42/100 | Train Loss: 0.1075 | Val Loss: 0.0740\n",
      "Epoch 43/100 | Train Loss: 0.1009 | Val Loss: 0.0729\n",
      "Epoch 44/100 | Train Loss: 0.0907 | Val Loss: 0.0731\n",
      "Epoch 45/100 | Train Loss: 0.0921 | Val Loss: 0.0742\n",
      "Epoch 46/100 | Train Loss: 0.0978 | Val Loss: 0.0744\n",
      "Epoch 47/100 | Train Loss: 0.0833 | Val Loss: 0.0732\n",
      "Epoch 48/100 | Train Loss: 0.0934 | Val Loss: 0.0735\n",
      "Epoch 49/100 | Train Loss: 0.0814 | Val Loss: 0.0735\n",
      "Epoch 50/100 | Train Loss: 0.0900 | Val Loss: 0.0742\n",
      "Epoch 51/100 | Train Loss: 0.0893 | Val Loss: 0.0743\n",
      "Epoch 52/100 | Train Loss: 0.0829 | Val Loss: 0.0738\n",
      "Epoch 53/100 | Train Loss: 0.0809 | Val Loss: 0.0738\n",
      "Epoch 54/100 | Train Loss: 0.0775 | Val Loss: 0.0732\n",
      "Epoch 55/100 | Train Loss: 0.0796 | Val Loss: 0.0739\n",
      "Epoch 56/100 | Train Loss: 0.0883 | Val Loss: 0.0744\n",
      "Epoch 57/100 | Train Loss: 0.0674 | Val Loss: 0.0731\n",
      "Epoch 58/100 | Train Loss: 0.0753 | Val Loss: 0.0731\n",
      "Epoch 59/100 | Train Loss: 0.0735 | Val Loss: 0.0728\n",
      "Epoch 60/100 | Train Loss: 0.0820 | Val Loss: 0.0729\n",
      "Epoch 61/100 | Train Loss: 0.0670 | Val Loss: 0.0739\n",
      "Epoch 62/100 | Train Loss: 0.0601 | Val Loss: 0.0724\n",
      "Epoch 63/100 | Train Loss: 0.0578 | Val Loss: 0.0720\n",
      "Epoch 64/100 | Train Loss: 0.0607 | Val Loss: 0.0724\n",
      "Epoch 65/100 | Train Loss: 0.0565 | Val Loss: 0.0729\n",
      "Epoch 66/100 | Train Loss: 0.0535 | Val Loss: 0.0722\n",
      "Epoch 67/100 | Train Loss: 0.0513 | Val Loss: 0.0718\n",
      "Epoch 68/100 | Train Loss: 0.0519 | Val Loss: 0.0721\n",
      "Epoch 69/100 | Train Loss: 0.0475 | Val Loss: 0.0722\n",
      "Epoch 70/100 | Train Loss: 0.0464 | Val Loss: 0.0716\n",
      "Epoch 71/100 | Train Loss: 0.0471 | Val Loss: 0.0718\n",
      "Epoch 72/100 | Train Loss: 0.0450 | Val Loss: 0.0719\n",
      "Epoch 73/100 | Train Loss: 0.0442 | Val Loss: 0.0717\n",
      "Epoch 74/100 | Train Loss: 0.0433 | Val Loss: 0.0718\n",
      "Epoch 75/100 | Train Loss: 0.0422 | Val Loss: 0.0718\n",
      "Epoch 76/100 | Train Loss: 0.0442 | Val Loss: 0.0720\n",
      "Epoch 77/100 | Train Loss: 0.0433 | Val Loss: 0.0720\n",
      "Epoch 78/100 | Train Loss: 0.0412 | Val Loss: 0.0717\n",
      "Epoch 79/100 | Train Loss: 0.0422 | Val Loss: 0.0719\n",
      "Epoch 80/100 | Train Loss: 0.0427 | Val Loss: 0.0719\n",
      "Epoch 81/100 | Train Loss: 0.0420 | Val Loss: 0.0719\n",
      "Epoch 82/100 | Train Loss: 0.0415 | Val Loss: 0.0721\n",
      "Epoch 83/100 | Train Loss: 0.0431 | Val Loss: 0.0719\n",
      "Epoch 84/100 | Train Loss: 0.0421 | Val Loss: 0.0723\n",
      "Epoch 85/100 | Train Loss: 0.0406 | Val Loss: 0.0726\n",
      "Epoch 86/100 | Train Loss: 0.0412 | Val Loss: 0.0724\n",
      "Epoch 87/100 | Train Loss: 0.0409 | Val Loss: 0.0723\n",
      "Epoch 88/100 | Train Loss: 0.0431 | Val Loss: 0.0721\n",
      "Epoch 89/100 | Train Loss: 0.0412 | Val Loss: 0.0719\n",
      "Epoch 90/100 | Train Loss: 0.0406 | Val Loss: 0.0722\n",
      "Epoch 91/100 | Train Loss: 0.0407 | Val Loss: 0.0723\n",
      "Epoch 92/100 | Train Loss: 0.0408 | Val Loss: 0.0724\n",
      "Epoch 93/100 | Train Loss: 0.0406 | Val Loss: 0.0723\n",
      "Epoch 94/100 | Train Loss: 0.0403 | Val Loss: 0.0725\n",
      "Epoch 95/100 | Train Loss: 0.0405 | Val Loss: 0.0722\n",
      "Epoch 96/100 | Train Loss: 0.0408 | Val Loss: 0.0725\n",
      "Epoch 97/100 | Train Loss: 0.0400 | Val Loss: 0.0727\n",
      "Epoch 98/100 | Train Loss: 0.0402 | Val Loss: 0.0725\n",
      "Epoch 99/100 | Train Loss: 0.0420 | Val Loss: 0.0725\n",
      "Epoch 100/100 | Train Loss: 0.0400 | Val Loss: 0.0726\n"
     ]
    }
   ],
   "source": [
    "# Training loop\n",
    "for epoch in range(params[\"num_epochs\"]):\n",
    "    model.train()\n",
    "    total_loss = 0\n",
    "    for X_batch, y_batch in train_loader:\n",
    "        optimizer.zero_grad()\n",
    "        y_pred = model(X_batch).squeeze()\n",
    "        loss = criterion(y_pred, y_batch)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        total_loss += loss.item()\n",
    "\n",
    "    avg_train_loss = total_loss / len(train_loader)\n",
    "\n",
    "    # Validation\n",
    "    model.eval()\n",
    "    val_loss = 0\n",
    "    with torch.no_grad():\n",
    "        for X_val_batch, y_val_batch in val_loader:\n",
    "            y_val_pred = model(X_val_batch).squeeze()\n",
    "            loss = criterion(y_val_pred, y_val_batch)\n",
    "            val_loss += loss.item()\n",
    "\n",
    "    avg_val_loss = val_loss / len(val_loader)\n",
    "    #scheduler.step(avg_val_loss)\n",
    "    #scheduler.step(val_loss)\n",
    "\n",
    "\n",
    "    print(f\"Epoch {epoch+1}/{params['num_epochs']} | \"\n",
    "          f\"Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 875,
   "id": "ad81cbd3-af54-400a-b39d-45eb56d0113b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     Quarter   CO_ID  Predicted    Actual  TASI Return  Relative_Return\n",
      "0 2016-01-01  1201.0  -0.029949 -0.196482     0.053471         0.243384\n",
      "1 2016-04-01  1201.0  -0.025743  0.102171     0.080545        -0.138383\n",
      "2 2016-07-01  1201.0  -0.021379 -0.295357    -0.081079        -0.102275\n",
      "3 2016-10-01  1201.0  -0.019016  0.045653    -0.263944         0.087247\n",
      "4 2017-01-01  1201.0  -0.019200 -0.032564     0.037782        -0.196482\n"
     ]
    }
   ],
   "source": [
    "# Step 1: Make predictions\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    predictions = model(X_tensor).squeeze().numpy()\n",
    "# Step 2: Rebuild the filtered DataFrame to match y_labels\n",
    "df_sorted = df.sort_values(by=[\"CO_ID\", \"Quarter\"]).reset_index(drop=True)\n",
    "\n",
    "# You MUST use the same window_size you used earlier (e.g., 4 or 8)\n",
    "window_size = 4  # <--- change this if you're using a different one\n",
    "\n",
    "# Rebuild the filtered rows (the target rows used in y_labels)\n",
    "filtered_rows = []\n",
    "for co_id, group in df_sorted.groupby(\"CO_ID\"):\n",
    "    group = group.reset_index(drop=True)\n",
    "    for i in range(len(group) - window_size):\n",
    "        filtered_rows.append(group.loc[i + window_size])\n",
    "\n",
    "df_filtered = pd.DataFrame(filtered_rows).reset_index(drop=True)\n",
    "\n",
    "# Step 3: Attach predictions and actuals\n",
    "df_filtered[\"Predicted\"] = predictions\n",
    "df_filtered[\"Actual\"] = y_labels\n",
    "\n",
    "# Step 4: Attach TASI Return and Relative_Return from df_sorted using index alignment\n",
    "df_filtered[\"TASI Return\"] = df_sorted.loc[df_filtered.index, \"TASI Return\"].values\n",
    "df_filtered[\"Relative_Return\"] = df_sorted.loc[df_filtered.index, \"Relative_Return\"].values\n",
    "\n",
    "# Step 5: Preview\n",
    "print(df_filtered[[\"Quarter\", \"CO_ID\", \"Predicted\", \"Actual\", \"TASI Return\", \"Relative_Return\"]].head())\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 876,
   "id": "27d1d4d3-c298-46fa-a880-7864f7ee0575",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['CO_ID', 'Name', 'Year', 'Quarter', 'Sector', 'Return on Assets', 'Return on Capital', 'Return on Equity', 'Return on Common Equity', 'Gross Profit Margin', 'EBITA Margin', 'EBIT Margin', 'Net Income Margin', 'Asset Turnover', 'Current Ratio', 'Debt/Equity', 'Long Term Debt/Captial', 'Book Value Per Share', 'Start Price', 'End Price', 'Stock Return', 'TASI Return', 'Relative_Return']\n"
     ]
    }
   ],
   "source": [
    "print(df_sorted.columns.tolist())  # Check what columns are available\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 877,
   "id": "895dd1a3-0e22-483f-a23b-a29fe03aa132",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE: 0.148877\n"
     ]
    }
   ],
   "source": [
    "mae = np.mean(np.abs(df_filtered[\"Predicted\"] - df_filtered[\"Actual\"]))\n",
    "print(f\"MAE: {mae:.6f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 938,
   "id": "3cbd6f57-b23a-425e-bbaf-b2f556394141",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_excess_return(df_filtered, k):\n",
    "    # Group by quarter\n",
    "    grouped = df_filtered.groupby(\"Quarter\")\n",
    "\n",
    "    portfolio_returns = []\n",
    "    benchmark_returns = []\n",
    "\n",
    "    for quarter, group in grouped:\n",
    "        # Step 1: Select top-k by Predicted return\n",
    "        topk = group.sort_values(by=\"Predicted\", ascending=False).head(k)\n",
    "\n",
    "        # Step 2: Portfolio return = mean of Actual\n",
    "        R_p_q = topk[\"Actual\"].mean()\n",
    "\n",
    "        # Step 3: Benchmark return = mean TASI return in this quarter (same for all companies)\n",
    "        R_b_q = group[\"TASI Return\"].iloc[0]  # assuming same value per quarter\n",
    "\n",
    "        portfolio_returns.append(1 + R_p_q)\n",
    "        benchmark_returns.append(1 + R_b_q)\n",
    "\n",
    "    # Step 4: Multiply all (geometric return)\n",
    "    from functools import reduce\n",
    "    import operator\n",
    "\n",
    "    def product(lst):\n",
    "        return reduce(operator.mul, lst, 1.0)\n",
    "\n",
    "    total_portfolio_return = product(portfolio_returns)\n",
    "    total_benchmark_return = product(benchmark_returns)\n",
    "\n",
    "    excess_return = total_portfolio_return - total_benchmark_return\n",
    "    return excess_return\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 941,
   "id": "b6561f58-1ca4-4a6c-a76b-a7245add8331",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Excess Return (Top-3): 7.8137\n",
      "Excess Return (Top-5): 3.3675\n"
     ]
    }
   ],
   "source": [
    "excess_ret = evaluate_excess_return(df_filtered, k=3)\n",
    "print(f\"Excess Return (Top-3): {excess_ret:.4f}\")\n",
    "excess_ret = evaluate_excess_return(df_filtered, k=5)\n",
    "print(f\"Excess Return (Top-5): {excess_ret:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 925,
   "id": "2158a381-cf62-4f5f-8e0c-3543821c9955",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_excess_return(df_filtered, k=5):\n",
    "    # Group by quarter\n",
    "    grouped = df_filtered.groupby(\"Quarter\")\n",
    "\n",
    "    portfolio_returns = []\n",
    "    benchmark_returns = []\n",
    "\n",
    "    for quarter, group in grouped:\n",
    "        # Step 1: Select top-k by Predicted return\n",
    "        topk = group.sort_values(by=\"Predicted\", ascending=False).head(k)\n",
    "\n",
    "        # Step 2: Portfolio return = mean of Actual\n",
    "        R_p_q = topk[\"Actual\"].mean()\n",
    "\n",
    "        # Step 3: Benchmark return = mean TASI return in this quarter (same for all companies)\n",
    "        R_b_q = group[\"TASI Return\"].iloc[0]  # assuming same value per quarter\n",
    "\n",
    "        portfolio_returns.append(1 + R_p_q)\n",
    "        benchmark_returns.append(1 + R_b_q)\n",
    "\n",
    "    # Step 4: Multiply all (geometric return)\n",
    "    from functools import reduce\n",
    "    import operator\n",
    "\n",
    "    def product(lst):\n",
    "        return reduce(operator.mul, lst, 1.0)\n",
    "\n",
    "    total_portfolio_return = product(portfolio_returns)\n",
    "    total_benchmark_return = product(benchmark_returns)\n",
    "\n",
    "    excess_return = total_portfolio_return - total_benchmark_return\n",
    "    return excess_return"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 926,
   "id": "85f4bd96-8d5f-4f78-9e9a-9f4d9adee2ee",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Excess Return (Top-20): 3.3675\n"
     ]
    }
   ],
   "source": [
    "excess_ret = evaluate_excess_return(df_filtered, k=5)\n",
    "print(f\"Excess Return (Top-20): {excess_ret:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 927,
   "id": "a12adc58-0c9a-4624-af04-60ef369e51ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_topk_precision(df_filtered, k=3):\n",
    "    grouped = df_filtered.groupby(\"Quarter\")\n",
    "    precision_scores = []\n",
    "\n",
    "    for quarter, group in grouped:\n",
    "        topk_pred = set(group.sort_values(\"Predicted\", ascending=False).head(k)[\"CO_ID\"])\n",
    "        topk_actual = set(group.sort_values(\"Actual\", ascending=False).head(k)[\"CO_ID\"])\n",
    "        \n",
    "        intersection = topk_pred & topk_actual\n",
    "        precision = len(intersection) / k\n",
    "        precision_scores.append(precision)\n",
    "\n",
    "    return np.mean(precision_scores)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 928,
   "id": "dfd8a411-26db-4d8a-86a7-7c1053c68380",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Top-k Precision (Top-10): 0.1441\n"
     ]
    }
   ],
   "source": [
    "topk_prec = evaluate_topk_precision(df_filtered, k=3)\n",
    "print(f\"Top-k Precision (Top-10): {topk_prec:.4f}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 942,
   "id": "de88b386-e578-4892-896d-7b61faa780c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_topk_precision(df_filtered, k=5):\n",
    "    grouped = df_filtered.groupby(\"Quarter\")\n",
    "    precision_scores = []\n",
    "\n",
    "    for quarter, group in grouped:\n",
    "        topk_pred = set(group.sort_values(\"Predicted\", ascending=False).head(k)[\"CO_ID\"])\n",
    "        topk_actual = set(group.sort_values(\"Actual\", ascending=False).head(k)[\"CO_ID\"])\n",
    "        \n",
    "        intersection = topk_pred & topk_actual\n",
    "        precision = len(intersection) / k\n",
    "        precision_scores.append(precision)\n",
    "\n",
    "    return np.mean(precision_scores)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 943,
   "id": "87219fd5-5e7e-48c6-8bf5-627f74259083",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Top-k Precision (Top-10): 0.2216\n"
     ]
    }
   ],
   "source": [
    "topk_prec = evaluate_topk_precision(df_filtered, k=5)\n",
    "print(f\"Top-k Precision (Top-10): {topk_prec:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 931,
   "id": "90576ea9-ca1c-438e-a178-145b890bdece",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_portfolio_score(df_filtered, k):\n",
    "    grouped = df_filtered.groupby(\"Quarter\")\n",
    "    returns = []\n",
    "\n",
    "    for quarter, group in grouped:\n",
    "        topk = group.sort_values(by=\"Predicted\", ascending=False).head(k)\n",
    "        R_p_q = topk[\"Relative_Return\"].mean()  # Use relative return per paper\n",
    "        returns.append(R_p_q)\n",
    "\n",
    "    mean_return = np.mean(returns)\n",
    "    std_return = np.std(returns)\n",
    "\n",
    "    portfolio_score = mean_return / std_return if std_return != 0 else 0\n",
    "    return portfolio_score\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 933,
   "id": "e465eb03-a718-4b04-80d2-db758491eec9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Portfolio Score (Top-10): -0.2738\n",
      "Portfolio Score (Top-20): -0.4848\n"
     ]
    }
   ],
   "source": [
    "score_10 = evaluate_portfolio_score(df_filtered, k=3)\n",
    "score_20 = evaluate_portfolio_score(df_filtered, k=5)\n",
    "\n",
    "print(f\"Portfolio Score (Top-10): {score_10:.4f}\")\n",
    "print(f\"Portfolio Score (Top-20): {score_20:.4f}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 888,
   "id": "9544b2c4-6c07-4fe3-be20-a72f4cf90cbb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Check the distribution of all relative returns\n",
    "df_filtered['Relative_Return'].hist(bins=50)\n",
    "plt.title(\"Distribution of Relative Return\")\n",
    "plt.xlabel(\"Relative_Return\")\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 889,
   "id": "8f286563-f920-48b2-ae3b-e8b290136a77",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2016-01-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2016-04-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2016-07-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2016-10-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2017-01-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2017-04-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2017-07-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2017-10-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2018-01-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2018-04-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2018-07-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2018-10-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2019-01-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2019-04-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2019-07-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2019-10-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2020-01-01 00:00:00: Overlap in Top-10 = 4/10\n",
      "2020-04-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2020-07-01 00:00:00: Overlap in Top-10 = 5/10\n",
      "2020-10-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2021-01-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2021-04-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2021-07-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2021-10-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2022-01-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2022-04-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2022-07-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2022-10-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2023-01-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2023-04-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2023-07-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2023-10-01 00:00:00: Overlap in Top-10 = 3/10\n",
      "2024-01-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2024-04-01 00:00:00: Overlap in Top-10 = 1/10\n",
      "2024-07-01 00:00:00: Overlap in Top-10 = 0/10\n",
      "2024-10-01 00:00:00: Overlap in Top-10 = 2/10\n",
      "2025-01-01 00:00:00: Overlap in Top-10 = 0/10\n"
     ]
    }
   ],
   "source": [
    "# Check if predicted top-10 are actually good\n",
    "for quarter, group in df_filtered.groupby(\"Quarter\"):\n",
    "    topk_pred = group.sort_values(\"Predicted\", ascending=False).head(10)\n",
    "    topk_actual = group.sort_values(\"Relative_Return\", ascending=False).head(10)\n",
    "    overlap = len(set(topk_pred['CO_ID']).intersection(set(topk_actual['CO_ID'])))\n",
    "    print(f\"{quarter}: Overlap in Top-10 = {overlap}/10\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 909,
   "id": "3236bdc5-121e-437e-b98e-da808d872f68",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE (Random Forest): 0.2203\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import mean_absolute_error\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Step 1: Prepare your features and target\n",
    "# Use the feature columns you've already defined in preprocessing\n",
    "X = df_filtered[feature_cols]  # These should be normalized and cleaned features\n",
    "y = df_filtered[\"Relative_Return\"]\n",
    "\n",
    "# Step 2: Train-test split (no shuffle for time series)\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.2, shuffle=False\n",
    ")\n",
    "\n",
    "# Step 3: Train the Random Forest model\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "\n",
    "rf = RandomForestRegressor(\n",
    "    n_estimators=1000,             # Use 1000 trees\n",
    "    criterion='squared_error',     # Use squared error as in paper\n",
    "    random_state=42,               # For reproducibility\n",
    "    n_jobs=-1                      # Use all cores for speed\n",
    ")\n",
    "\n",
    "rf.fit(X_train, y_train)\n",
    "\n",
    "# Step 4: Predict on the test set\n",
    "y_pred = rf.predict(X_test)\n",
    "\n",
    "# Step 5: Evaluate with MAE\n",
    "mae = mean_absolute_error(y_test, y_pred)\n",
    "print(f\"MAE (Random Forest): {mae:.4f}\")\n",
    "\n",
    "# Step 6: Save predictions back to df_filtered\n",
    "df_filtered.loc[X_test.index, \"Predicted\"] = y_pred\n",
    "\n",
    "# Step 7 (Optional): Feature importance plot\n",
    "importances = rf.feature_importances_\n",
    "feat_names = X.columns\n",
    "indices = np.argsort(importances)[::-1]\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 910,
   "id": "cb443061-6e0e-4333-91d5-9e7c4e7a129a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAJOCAYAAAAqFJGJAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAfohJREFUeJzt3Qm8jPX///+XJWuhKCKlhZCtiKho8YlSWiUtRGlVtKgoWkSlKEVJpU1FqxZSUqhsIUWlVJTIliwRwvxvz/fvf813zpw5tosz877O4367zc0514zjGmfmmvfr/X69X698sVgsZgAAAAAQQv4wfxkAAAAAhMACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAAiNwAIAAABAaAQWALCHvPDCC5YvX76UtzvuuGOP/JuTJk2ye+65x1atWmWZ+v8xffp089WTTz7pngcAILuCKY4BAHaj++67zw499NAsx2rUqLHHAot7773XLr/8citVqtQe+TfyMgUWZcqUcf+/AICsCCwAYA87/fTTrV69euazdevWWfHixS2vWr9+vRUrVizdpwEAGY1UKABIsw8//NBOPPFEN3DfZ599rEWLFvbdd99lecy3337rZskPO+wwK1KkiJUrV846dOhgf/31V/wxSoHq2rWr+1orJEHa1YIFC9xNX6dK49Fx/d3En6Nj33//vV188cW277772gknnBC/f9iwYVa3bl0rWrSo7bfffnbRRRfZwoULd+m56zntvffe9vvvv9uZZ57pvq5QoYINGjTI3T979mw75ZRT3P/NIYccYq+++mrK9KqJEyfa1VdfbaVLl7YSJUpY27Zt7e+//0654nDUUUdZ4cKFrXz58nb99ddnSxs76aST3IrSjBkzrHHjxi6g6N69u1WqVMn9XiZMmBD/v9VjZeXKlXbrrbdazZo13XPQOSig/Oabb7L87PHjx7u/9/rrr1vv3r3toIMOcr/PU0891X7++eds5zt16lQ744wz3O9A/we1atWyAQMGZHnM3Llz7YILLnC/C/0sBbHvvffeLv0+ACAMViwAYA9bvXq1rVixIssxpdPIyy+/bO3atbNmzZrZQw895GbGn3rqKTeQ//rrr91gVsaOHWu//vqrtW/f3gUVGuAOGTLE/TllyhQ3WD3vvPPsp59+stdee80effTR+L+x//772/Lly3f6vFu1amWVK1e2Pn36WCwWc8c0GO7Ro4ddeOGFduWVV7qf+8QTT7gBuM53V9KvtmzZ4gbh+hl9+/a1V155xTp16uQG0nfeeaddcskl7rkNHjzYBQwNGzbMllqmx+vfVlD0448/uv/D3377LT6QF92nNLGmTZvatddeG3/cV199ZV9++aXttdde8Z+ngE3npKDp0ksvtbJly7og4oYbbnCBg85LdFz0uxk5cqT7P9O5LV261J5++mlr0qSJC9AUxCR68MEHLX/+/C4Y0etDz1vPU4FEQL9zBVsHHnigde7c2f3ef/jhB/vggw/c96Lf//HHH++CMe3b0f+ZgpZzzjnH3nrrLTv33HN3+vcBALssBgDYI55//nmNxlPeZO3atbFSpUrFOnbsmOXvLVmyJFayZMksx9evX5/t57/22mvuZ02cODF+7OGHH3bH5s+fn+Wx+l7HdU7JdPzuu++Of6+vdaxNmzZZHrdgwYJYgQIFYr17985yfPbs2bGCBQtmO57T/8dXX30VP9auXTt3rE+fPvFjf//9d6xo0aKxfPnyxYYPHx4/Pnfu3GznGvzMunXrxjZt2hQ/3rdvX3f83Xffdd8vW7YsVqhQodhpp50W27JlS/xxAwcOdI8bOnRo/FiTJk3cscGDB2d7DkcddZS7P9mGDRuy/Nzg/7xw4cKx++67L37ss88+cz+7WrVqsY0bN8aPDxgwwB3X/6Vs3rw5duihh8YOOeQQ9/+RaOvWrfGvTz311FjNmjXdv594f6NGjWKVK1fOdp4AsCeRCgUAe5jSejT7nHgT/ak0nDZt2rgVjeBWoEABa9CggX322Wfxn6G0o8CGDRvc44477jj3/cyZM/fIeV9zzTVZvn/77bdt69atbrUi8Xw1k66VjcTz3Vla/Qho5eHII490s+/6twI6pvu0OpDsqquuyrLioBWJggUL2ujRo933n3zyiW3atMm6dOniVgoCHTt2dGlLo0aNyvLzlCql1aEdpccHP1crMFrx0MqGzjnV70c/u1ChQvHvlQonwXPT6s/8+fPd+SavAgUrMEq/+vTTT93/0dq1a+O/D/3bWgGbN2+eLVq0aIefAwCERSoUAOxh9evXT7l5WwM/0R6CVDTgDWgQqTSe4cOH27Jly7I8Tqk0e0JyupHOVwscCiJSSRzY7wztC1C6VqKSJUu6/QfBIDrxeKq9E8nnpEG9Uoi0t0SUFiUa6CfS4F77VoL7A0otShz4b48CLu190B4OBQQKLgLa95Hs4IMPzvK99lBI8Nx++eWX7VYP054M/T6UmqZbKnqt6LkAQG4gsACANNFgNNhnoVn/ZJpxD2hWWqVktTm7Tp06buCsv9+8efP4z9mW5AF6IHEAnCxxlSQ4X/0cbTbXqkoyndOuSPWztnU82O+xJyU/9+3RPhQN7rWhvlevXm4jtVYwtOKQ6vezO55b8HO1T0MrFKkcccQRO/zzACAsAgsASJPDDz/c/XnAAQe4DcU50Sz2uHHj3IpFz549s6147EgAEcyIJ1dASp6p3975auCrlYwqVapYJtH/xcknnxz//p9//rE///zTVVQSVZQSbdjWCkVA6VFaYdjW//+O/P+++eab7t9/7rnnshzX/3ewiX5XXhtz5szJ8dyC56GVoh09fwDYk9hjAQBpollmpTtptvu///7Ldn9QySmY3U6ezX7sscey/Z2g10RyAKF/RwNclWVNpNSdHaXKTDoXBTjJ56LvE0vf5jZVyEr8P1S1p82bN7vKTqKBt1KbHn/88SznrkBAqWQq8bsj9P+bqqu5/l+S/0/eeOONXd7jcMwxx7gATr/j5H8v+HcUkKpSlapPKYhKtiuVwAAgDFYsACBNNNjXAPiyyy5zA0mVNtVeA/V00GZilREdOHCge1xQilWDZ+XMf/zxx26mPZn6S4jKoernaTb7rLPOcgNibZBWmVP9qT0fCjJUnnZnZtHvv/9+69atm9u7oJKm6ruh83jnnXfcBmql5aSDVh7UC0IpY1qVUMCkkr0tW7Z09+v/VeetoEjpYzoePO7YY491JWV3hP5/9TvT/4PSjDS41x4ZlYVVh3Vtym7UqJHrv6GyuYmrIztDaVT6d/S7U+qbfq72jKhnhUrMfvTRR/HCAHqe6p+hjej691TqdvLkyfbHH39k66MBAHsSgQUApJEa0KnHgQb8Dz/8sG3cuNEFDqoSlFiVSI3h1ENBA0nNWJ922mlur0NyfwQNkpXjr54PY8aMcXn4GvgrsFAalWaxlbajXgeazdfP0OB4R6lXgtKg1CdDg3SpWLGiO59gEJ8OCsA0kNdzVPClSltanUhMXVIfCwUYeuxNN93k9kEoGNKK0Y5uPNfPV/qYgjxVYlKfCgUWaqCn7uT6PY0YMcIFigoO9f8VZkVLlbb0/9yvXz/3u1RwpwAiUL16dZs+fbp7jJoFatVIv8+jjz46S9ocAOSGfKo5myv/EgAAu5kG0wrA1OQuVeUtAEDuYY8FAAAAgNAILAAAAACERmABAAAAwP/AQhsRK1Wq5DqvNmjQwKZNm5bjY1UJ4/zzz3eP14a8VKUWd/ZnAgD8dfnll7vN7OyvAIA8HliocsbNN99sd999t82cOdNq167tqmAsW7Ys5ePXr1/vSumpekqqLrW78jMBAAAAeF4VSqsJKo2o0n+iUnoqW6iSitsr0acViS5durjb7vqZAAAAADzrY6FmRjNmzHANixIbAqk7qhr75ObPVN143QIKRlauXGmlS5fOUgMdAAAAyEtisZjr26O+SRpXZ2RgsWLFCtuyZYuVLVs2y3F9r86iufkzH3jggXijJwAAAABZLVy40A466CDbFjpvm7kVDu3LCKxevdoOPvhg9x9YokSJtJxTjbs/snSZc2+zyDyfPfFcAAAA8oo1a9a4bQX77LPPdh+btsCiTJkyVqBAAVu6dGmW4/o+p43Ze+pnFi5c2N2SKahIV2CRv3AxS5c98ZzT9XzS9fsDAACIkh3ZHpC2qlCFChWyunXr2rhx47LsbdD3DRs2zJifCQAAAGD70poKpfSjdu3aufrj9evXd30p1q1bZ+3bt3f3t23b1ipUqOD2QASbs7///vv414sWLbJZs2bZ3nvvbUccccQO/UwAAAAAEQssWrdubcuXL7eePXvakiVLrE6dOjZmzJj45uvff/89y+7zxYsX29FHHx3//pFHHnG3Jk2a2Pjx43foZwIAAACIWB+LTN6kUrJkSbeJO105+pXuGGXpsuDBFpF5PnviuQAAAOQVa3ZiXJzWztsAAAAAooHAAgAAAEBoBBYAAAAAQiOwAAAAABAagQUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAAiNwAIAAABAaAQWAAAAAEIjsAAAAAAQGoEFAAAAgNAILAAAAACERmABAAAAIDQCCwAAAAChEVgAAAAACI3AAgAAAEBoBBYAAAAAQiOwAAAAABAagQUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAAiNwAIAAABAaAQWAAAAAEIjsAAAAAAQGoEFAAAAgNAILAAAAACERmABAAAAIDQCCwAAAAChEVgAAAAACI3AAgAAAEBoBBYAAAAAQiOwAAAAABAagQUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAA4H9gMWjQIKtUqZIVKVLEGjRoYNOmTdvm49944w2rWrWqe3zNmjVt9OjRWe7/559/rFOnTnbQQQdZ0aJFrXr16jZ48OA9/CwAAACAvC2tgcWIESPs5ptvtrvvvttmzpxptWvXtmbNmtmyZctSPn7SpEnWpk0bu+KKK+zrr7+2c845x93mzJkTf4x+3pgxY2zYsGH2ww8/WJcuXVyg8d577+XiMwMAAADylrQGFv3797eOHTta+/bt4ysLxYoVs6FDh6Z8/IABA6x58+bWtWtXq1atmvXq1cuOOeYYGzhwYJbgo127dnbSSSe5lZCrrrrKBSzbWwkBAAAAsOsKWpps2rTJZsyYYd26dYsfy58/vzVt2tQmT56c8u/ouFYkEmmFY+TIkfHvGzVq5FYnOnToYOXLl7fx48fbTz/9ZI8++miO57Jx40Z3C6xZsybks0OUVbpjVFr+3QUPtkjLvwsAAJDRKxYrVqywLVu2WNmyZbMc1/dLlixJ+Xd0fHuPf+KJJ9zqh/ZYFCpUyK1waB9H48aNczyXBx54wEqWLBm/VaxYMfTzAwAAAPKStG/e3t0UWEyZMsWtWmhFpF+/fnb99dfbJ598kuPf0arJ6tWr47eFCxfm6jkDAAAAvktbKlSZMmWsQIECtnTp0izH9X25cuVS/h0d39bj//33X+vevbu988471qLF/0sbqVWrls2aNcseeeQRl2aVSuHChd0NAAAAgGcrFkpTqlu3ro0bNy5+bOvWre77hg0bpvw7Op74eBk7dmz88f/995+7aa9GIgUw+tkAAAAAIrZiIdqIrQpO9erVs/r169tjjz1m69atc1WipG3btlahQgW3B0I6d+5sTZo0celNWpEYPny4TZ8+3YYMGeLuL1GihLtfVaPUw+KQQw6xCRMm2EsvveQqUAEAAACIYGDRunVrW758ufXs2dNtwK5Tp47rQRFs0P7999+zrD6o4tOrr75qd911l0t5qly5sqsIVaNGjfhjFGxoz8Qll1xiK1eudMFF79697ZprrknLcwQAAADygrQGFqLmdbqlolKxyVq1auVuOdF+i+eff363niMAAACAPFYVCgAAAEDuI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAPC/jwWA9Kl0x6i0/dsLHmyRtn8bAADsfqxYAAAAAAiNwAIAAABAaAQWAAAAAEIjsAAAAAAQGoEFAAAAgNAILAAAAACERmABAAAAIDQCCwAAAAChEVgAAAAACI3AAgAAAEBoBBYAAAAAQiOwAAAAABAagQUAAACA0AgsAAAAAIRWMPyPAID0q3THqLT92wsebJG2fxsAgEzBigUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAAiNwAIAAABAaAQWAAAAAEIjsAAAAAAQGoEFAAAAgNAILAAAAACERmABAAAAIDQCCwAAAAChEVgAAAAACI3AAgAAAEBoBBYAAAAAQiOwAAAAABAagQUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAIDQCCwAAAAAhEZgAQAAACA0AgsAAAAAoRFYAAAAAPA/sBg0aJBVqlTJihQpYg0aNLBp06Zt8/FvvPGGVa1a1T2+Zs2aNnr06GyP+eGHH6xly5ZWsmRJK168uB177LH2+++/78FnAQAAAORtaQ0sRowYYTfffLPdfffdNnPmTKtdu7Y1a9bMli1blvLxkyZNsjZt2tgVV1xhX3/9tZ1zzjnuNmfOnPhjfvnlFzvhhBNc8DF+/Hj79ttvrUePHi4QAQAAABDBwKJ///7WsWNHa9++vVWvXt0GDx5sxYoVs6FDh6Z8/IABA6x58+bWtWtXq1atmvXq1cuOOeYYGzhwYPwxd955p51xxhnWt29fO/roo+3www93qxcHHHBALj4zAAAAIG9JW2CxadMmmzFjhjVt2vT/TiZ/fvf95MmTU/4dHU98vGiFI3j81q1bbdSoUValShV3XMGE0qtGjhy5zXPZuHGjrVmzJssNAAAAQC4EFi+//LIdf/zxVr58efvtt9/csccee8zefffdHfr7K1assC1btljZsmWzHNf3S5YsSfl3dHxbj1cK1T///GMPPvigW9n4+OOP7dxzz7XzzjvPJkyYkOO5PPDAA24/RnCrWLHiDj0HAAAAACECi6eeesrtjVDK0apVq1yAIKVKlXLBRbpoxULOPvtsu+mmm6xOnTp2xx132JlnnunSrHLSrVs3W716dfy2cOHCXDxrAAAAII8GFk888YQ988wzbj9DgQIF4sfr1atns2fP3qGfUaZMGfd3ly5dmuW4vi9XrlzKv6Pj23q8fmbBggXdfo1E2o+xrapQhQsXthIlSmS5AQAAANjDgcX8+fPdxuhUA/R169bt0M8oVKiQ1a1b18aNG5dlxUHfN2zYMOXf0fHEx8vYsWPjj9fPVGnZH3/8MctjfvrpJzvkkEN26LwAAAAA7LyCu/B37NBDD7VZs2ZlG6yPGTPGrQ7sKKVTtWvXzq101K9f36VRKTBRlShp27atVahQwe2BkM6dO1uTJk2sX79+1qJFCxs+fLhNnz7dhgwZEv+ZqhjVunVra9y4sZ188snunN5//31XehYAAABABgUWCgiuv/5627Bhg8ViMdfU7rXXXnMBwLPPPrvDP0cBwPLly61nz55uA7b2RCgQCDZoK31JlaICjRo1sldffdXuuusu6969u1WuXNlVfKpRo0b8Mdqsrf0UOpcbb7zRjjzySHvrrbdcbwsAAAAAGRRYXHnllVa0aFE3wF+/fr1dfPHFrjqU+kxcdNFFO/WzOnXq5G6ppFplaNWqlbttS4cOHdwNAAAAQAYHFnLJJZe4mwILlXilAR0AAACQdxXc1c3bmzdvdqlI6pStm8ybN8/22msvq1Sp0u4+TwAAAABRqwp1+eWX26RJk7Idnzp1qrsPAAAAQN6yS4HF119/7bpuJzvuuONctSgAAAAAecsupULly5fP1q5dm+24ulYHXbgBALum0h2j0vZvL3iwRdr+bQBAHlyxUI8IlXNNDCL0tY5R1hUAAADIe3ZpxeKhhx5ywYV6RJx44onu2Oeff25r1qyxTz/9dHefIwAAAIAorlhUr17dvv32W7vwwgtt2bJlLi1KXbLnzp2bpVkdAAAAgLxhl/tYqCFenz59du/ZAAAAAMhbgcWqVats2rRpbsVi69atWe7T6gUAAACAvGOXAov333/fdd1Wx+0SJUq4KlEBfU1gAQAAAOQtu7TH4pZbbrEOHTq4wEIrF3///Xf8tnLlyt1/lgAAAACiF1gsWrTIbrzxRitWrNjuPyMAAAAAeSOwaNasmU2fPn33nw0AAACAvLPHokWLFta1a1f7/vvvrWbNmrbXXntlub9ly5a76/wAAAAARDWw6Nixo/vzvvvuy3afNm8nduQGAAAAEH27FFgkl5cFAAAAkLft0h4LAAAAANgtDfLWrVtnEyZMsN9//902bdqU5T5VjAIAAACQd+xSYPH111/bGWecYevXr3cBxn777WcrVqxw5WcPOOAAAgsAAAAgj9mlVKibbrrJzjrrLNcQr2jRojZlyhT77bffrG7duvbII4/s/rMEAAAAEL3AYtasWa77dv78+a1AgQK2ceNGq1ixovXt29e6d++++88SAAAAQPQCC/WtUFAhSn3SPgspWbKkLVy4cPeeIQAAAIBo7rE4+uij7auvvrLKlStbkyZNrGfPnm6Pxcsvv2w1atTY/WcJAAAAIHorFn369LEDDzzQfd27d2/bd9997dprr7Xly5fb008/vbvPEQAAAEAUVyzq1asX/1qpUGPGjNmd5wQAAAAgL6xYnHLKKbZq1apsx9esWePuAwAAAJC37NKKxfjx47M1xZMNGzbY559/vjvOCwAQEZXuGJWWf3fBgy3S8u8CQF61U4HFt99+G//6+++/tyVLlsS/37Jli0uJqlChwu49QwAAAADRCizq1Klj+fLlc7dUKU9qlvfEE0/szvMDAAAAELXAYv78+RaLxeywww6zadOm2f777x+/r1ChQm4jtxrmAQAAAMhbdiqwOOSQQ+y///6zdu3aWenSpd33AAAAAFBwV7puv/POO64pHgAAeUm6NqILm9EBRLLc7Nlnn20jR47c/WcDAAAAIO+Um61cubLdd9999uWXX1rdunWtePHiWe6/8cYbd9f5AQAAAIhqYPHcc89ZqVKlbMaMGe6WSBWjCCwAAACAvGWXAgtVhwIAAACAUHssEqn8rG4AAAAA8q5dWrGQl156yR5++GGbN2+e+75KlSrWtWtXu+yyy3bn+QEAgD2AClcAMiKw6N+/v/Xo0cM6depkxx9/vDv2xRdf2DXXXGMrVqywm266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dpi9++67rnSrNm6rFPVll12W7tMFACQhsEDGUcrTK6+8YnPnzo1X67jkkktczW4A0aZqcB9//LH99NNP7nt1sFbhBlWMAgBkNgILAACQp8qaP/jgg64K2bJly+KlzQPBfh4AO48Gecgof/31l5UuXTqeAvHMM8+4PRZnnXWWNW7cON2nB2AP02AvpwHf0KFD03ZeiI4rr7zSJkyY4NLpDjzwQBrNAbsRKxbICLNnz3bBg4IJVX0ZPny4NW/e3M0sKQVCf6oM7TnnnJPuUwWwh6hIw3333Wf16tVLOeB755130nZuiI5SpUrZqFGj4r2SAOw+BBbICKeffroVLFjQ7rjjDnv55Zftgw8+cJu4tWIh6satTqlTpkxJ96kC2EMUTPTt25eN2dijDj30UBs9erTbvwdg9yKwQMY0xvv000+tVq1a9s8//7i69V999ZUrPynayH3cccfZqlWr0n2qAPYQpUGq0/bhhx+e7lNBhA0bNsxVGXvxxRetWLFi6T4dIFIILJARlO60ZMkS1whP9tlnH1dmVmUmZenSpVa+fHnbsmVLms8UwJ5y++2329577209evRI96kgwo4++mjXjFHDn0qVKtlee+2V5f6ZM2em7dwA37F5GxkjOZ+aDXVA3qLGmEOGDLFPPvnErV4mD/j69++ftnNDdLBXD9hzCCyQMS6//HIrXLhwfIBxzTXXWPHixd33apgHINq+/fZbq1OnTrxZZiImGrA7bN682b2WOnToYAcddFC6TweIHFKhkBHat2+/Q497/vnn9/i5AACiS6m2qkSoNCgAuxeBBQAg4/zxxx/uT2aVsbudffbZdt5551m7du3SfSpA5JAKBQDICGqId//991u/fv1cdbhgdvmWW26xO++80xV5AHZHeXOVNteqhSoPBim3gZYtW6bt3ADfsWIBAMgI3bp1s+eee841ygual33xxRd2zz33WMeOHa13797pPkVEwLYCVO2/oPogsOsILAAAGUElpQcPHpxtxlg9B6677jpbtGhR2s4NALB9rCsDADLCypUrrWrVqtmO65juAwBkNvZYAAAyQu3atW3gwIH2+OOPZzmuY7oP2B3uu+++bd7fs2fPXDsXIGpIhQIAZIQJEyZYixYt7OCDD7aGDRu6Y5MnT7aFCxfa6NGj7cQTT0z3KSIinbcT/ffffzZ//nwrWLCgHX744XTeBkIgsAAAZIzFixfboEGDbO7cue77atWquf0V2n8B7Clr1qxxTVrPPfdcu+yyy9J9OoC3CCwAABnf00LpK0OGDEn3qSDCVH72rLPOsgULFqT7VABvsXkbAJDR/vrrL1eGFtiTVq9e7W4Adh2btwEAQJ6RXBxAiRt//vmnvfzyy655HoBdR2ABAADyjEcffTRbw7z999/f2rVr55o0Ath1BBYAACDPUAUoAHsGgQUAIK3OO++8bd6/atWqXDsXRF+HDh1swIABts8++2Q5vm7dOrvhhhts6NChaTs3wHdUhQIApFX79u136HHPP//8Hj8XRF+BAgXcnooDDjggy/EVK1ZYuXLlbPPmzWk7N8B3rFgAANKKgAG51atCc6m6rV271ooUKRK/b8uWLa4JY3KwAWDnEFgAAIDIK1WqlOXLl8/dqlSpku1+Hb/33nvTcm5AVJAKBQAAIm/ChAluteKUU06xt956y/bbb7/4fYUKFbJDDjmEDu9ASAQWAAAgz/jtt9/s4IMPdisUAHYvOm8DAIA8QysTX3zxhV166aXWqFEjW7RokTuuBnk6DmDXEVgAAIA8Q2lQzZo1s6JFi9rMmTNt48aN7vjq1autT58+6T49wGsEFgAAIM+4//77bfDgwfbMM8/YXnvtFT9+/PHHu0ADwK4jsAAAAHnGjz/+aI0bN852vGTJkjRjBEIisAAAAHmGmuD9/PPP2Y5rf8Vhhx2WlnMCooLAAgAA5BkdO3a0zp0729SpU11lqMWLF9srr7xit956q1177bXpPj3AazTIAwAAecYdd9xhW7dutVNPPdXWr1/v0qIKFy7sAosbbrgh3acHeI0+FgAAIM/ZtGmTS4n6559/rHr16rb33nvbv//+66pFAdg1pEIBAIA8R922FVDUr1/fVYfq37+/HXrooek+LcBrBBYAACDy1K+iW7duVq9ePdcYb+TIke74888/7wKKRx991G666aZ0nybgNVKhAABA5N1+++329NNPW9OmTW3SpEm2fPlya9++vU2ZMsW6d+9urVq1sgIFCqT7NAGvsXkbAABE3htvvGEvvfSStWzZ0ubMmWO1atWyzZs32zfffOOqQwEIjxULAACQJ/ZUzJ8/3ypUqOC+1ybtadOmWc2aNdN9akBksMcCAABE3pYtW1xwEShYsKCrBAVg9yEVCgAARJ4SNC6//HLXs0I2bNhg11xzjRUvXjzL495+++00nSHgPwILAAAQee3atcvy/aWXXpq2cwGiij0WAAAAAEJjjwUAAACA0AgsAAAAAIRGYAEAAAAgNAILAAAAAKERWAAAAAAIjcACAAAAQGgEFgAAAABCI7AAAAAAEBqBBQAAAAAL6/8DsIFl6Dh9ZyIAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "# Sort features by importance\n",
    "sorted_idx = importances.argsort()[::-1]\n",
    "sorted_features = feature_names[sorted_idx]\n",
    "sorted_importances = importances[sorted_idx]\n",
    "\n",
    "# Plot sorted importances\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.bar(x=sorted_features, height=sorted_importances, width=0.6)\n",
    "plt.title(\"Feature Importance\")\n",
    "plt.xticks(rotation=90)\n",
    "plt.ylabel(\"Importance\")\n",
    "plt.ylim(0, 0.10)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 915,
   "id": "07f8976d-307c-43e6-b551-f65ddbfc6bb3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# df_test should be created after predicting with RF\n",
    "df_test = X_test.copy()\n",
    "df_test['Predicted'] = y_pred\n",
    "df_test['Relative_Return'] = y_test.values\n",
    "df_test['Quarter'] = df_filtered.loc[df_test.index, 'Quarter']\n",
    "df_test['CO_ID'] = df_filtered.loc[df_test.index, 'CO_ID']\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 916,
   "id": "474c03f8-fdf4-4237-8f31-79aa1c3a128d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_portfolio_score(df, k=10):\n",
    "    returns = []\n",
    "    for _, group in df.groupby(\"Quarter\"):\n",
    "        topk = group.sort_values(\"Predicted\", ascending=False).head(k)\n",
    "        r_p_q = topk[\"Relative_Return\"].mean()\n",
    "        returns.append(r_p_q)\n",
    "\n",
    "    mean_return = np.mean(returns)\n",
    "    std_return = np.std(returns)\n",
    "\n",
    "    return mean_return / std_return if std_return != 0 else 0\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 917,
   "id": "d9e1d243-462b-4758-9ca6-19349e3106a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_topk_precision(df, k=10):\n",
    "    correct = []\n",
    "    for _, group in df.groupby(\"Quarter\"):\n",
    "        topk_pred = group.sort_values(\"Predicted\", ascending=False).head(k)['CO_ID']\n",
    "        topk_actual = group.sort_values(\"Relative_Return\", ascending=False).head(k)['CO_ID']\n",
    "        overlap = len(set(topk_pred).intersection(set(topk_actual)))\n",
    "        correct.append(overlap / k)\n",
    "    return np.mean(correct)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 918,
   "id": "465a9cb7-7a23-4ac1-9dbb-083aae3d2e61",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate_excess_return(df, k=10):\n",
    "    excess_returns = []\n",
    "    for _, group in df.groupby(\"Quarter\"):\n",
    "        topk = group.sort_values(\"Predicted\", ascending=False).head(k)\n",
    "        r_p_q = topk[\"Relative_Return\"].mean()\n",
    "        r_m_q = group[\"Relative_Return\"].mean()\n",
    "        excess_returns.append(r_p_q - r_m_q)\n",
    "    return np.mean(excess_returns)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 919,
   "id": "7b77ab7b-d5a8-4abe-b1b4-8176f52cba0f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Portfolio Score (Top-10): -0.5149\n",
      "Top-10 Precision: 1.0000\n",
      "Top-10 Excess Return: -0.0000\n"
     ]
    }
   ],
   "source": [
    "k = 10\n",
    "\n",
    "score = evaluate_portfolio_score(df_test, k)\n",
    "precision = evaluate_topk_precision(df_test, k)\n",
    "excess = evaluate_excess_return(df_test, k)\n",
    "\n",
    "print(f\"Portfolio Score (Top-{k}): {score:.4f}\")\n",
    "print(f\"Top-{k} Precision: {precision:.4f}\")\n",
    "print(f\"Top-{k} Excess Return: {excess:.4f}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 921,
   "id": "606b0574-8515-4ce6-98cc-4ed71e0637b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RMSE: 0.3639\n"
     ]
    }
   ],
   "source": [
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.metrics import mean_squared_error\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# STEP 1: Top 10 important features from your list (in order of importance)\n",
    "top_10_features = [\n",
    "    'Current Ratio',\n",
    "    'Book Value Per Share',\n",
    "    'Return on Assets',\n",
    "    'Return on Capital',\n",
    "    'Return on Equity',\n",
    "    'Debt/Equity',\n",
    "    'Long Term Debt/Captial',\n",
    "    'Gross Profit Margin',\n",
    "    'Asset Turnover',\n",
    "    'Return on Common Equity'\n",
    "]\n",
    "\n",
    "# STEP 2: Filter train and test sets to keep only the top 10 features\n",
    "X_train_top = X_train[top_10_features].copy()\n",
    "X_test_top = X_test[top_10_features].copy()\n",
    "\n",
    "# STEP 3: Train the Random Forest model\n",
    "rf_model = RandomForestRegressor(\n",
    "    n_estimators=1000,         # as used in your paper\n",
    "    criterion='squared_error', # for regression\n",
    "    random_state=42,\n",
    "    n_jobs=-1\n",
    ")\n",
    "rf_model.fit(X_train_top, y_train)\n",
    "\n",
    "# STEP 4: Predict on test set\n",
    "y_pred = rf_model.predict(X_test_top)\n",
    "\n",
    "# Optional: Evaluate performance\n",
    "rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
    "print(f\"RMSE: {rmse:.4f}\")\n",
    "\n",
    "# STEP 5: Add predictions back to test dataframe\n",
    "df_test = X_test_top.copy()\n",
    "df_test['Predicted'] = y_pred\n",
    "df_test['Relative_Return'] = y_test.values\n",
    "df_test['Quarter'] = df_filtered.loc[df_test.index, 'Quarter']\n",
    "df_test['CO_ID'] = df_filtered.loc[df_test.index, 'CO_ID']\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87305d35-25d2-486a-a599-2036e80f4a4a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
