{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Two-Feature Quant Model\n",
    "\n",
    "**Feature 1:** CashFlow2TA = Operating Cash Flow / Total Assets  \n",
    "**Feature 2:** Debt2Equity = Total Debt / Total Equity (Leverage Ratio)\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy import stats\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data Simulation\n",
    "\n",
    "We simulate 500 stocks over 120 months (10 years) with realistic cross-sectional structure. Firm characteristics update annually with persistence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset shape: (60000, 7)\n",
      "Stocks: 500, Months: 120\n"
     ]
    }
   ],
   "source": [
    "n_stocks = 500\n",
    "n_months = 120  # 10 years of monthly data\n",
    "dates = pd.date_range('2015-01-31', periods=n_months, freq='ME')\n",
    "\n",
    "all_data = []\n",
    "for t, date in enumerate(dates):\n",
    "    if t % 12 == 0:\n",
    "        # CashFlow2TA: mean ~0.08, std ~0.06\n",
    "        cashflow2ta = np.random.normal(0.08, 0.06, n_stocks)\n",
    "        cashflow2ta = np.clip(cashflow2ta, -0.15, 0.35)\n",
    "        \n",
    "        # ========================================================\n",
    "        # *** ADDITIONAL FEATURE COMPUTATION (HIGHLIGHTED) ***\n",
    "        # Debt2Equity = Total Debt / Total Equity\n",
    "        # Mild negative correlation induced with CashFlow2TA\n",
    "        # ========================================================\n",
    "        debt2equity_base = np.random.normal(1.2, 0.8, n_stocks)\n",
    "        debt2equity = debt2equity_base - 2.0 * (cashflow2ta - 0.08)\n",
    "        debt2equity = np.clip(debt2equity, 0.01, 5.0)\n",
    "    \n",
    "    mkt_ret = np.random.normal(0.008, 0.045)\n",
    "    betas = np.clip(np.random.normal(1.0, 0.3, n_stocks), 0.3, 2.0)\n",
    "    alpha_cf = 0.03 * (cashflow2ta - np.mean(cashflow2ta)) / np.std(cashflow2ta)\n",
    "    alpha_de = -0.015 * (debt2equity - np.mean(debt2equity)) / np.std(debt2equity)\n",
    "    idio = np.random.normal(0, 0.08, n_stocks)\n",
    "    ret = betas * mkt_ret + alpha_cf + alpha_de + idio\n",
    "    \n",
    "    for i in range(n_stocks):\n",
    "        all_data.append({\n",
    "            'date': date, 'stock_id': i, 'ret': ret[i],\n",
    "            'CashFlow2TA': cashflow2ta[i], 'Debt2Equity': debt2equity[i],\n",
    "            'mkt_ret': mkt_ret, 'beta_true': betas[i]\n",
    "        })\n",
    "\n",
    "df = pd.DataFrame(all_data)\n",
    "print(f\"Dataset shape: {df.shape}\")\n",
    "print(f\"Stocks: {n_stocks}, Months: {n_months}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part (a): Correlation Between CashFlow2TA and Debt2Equity"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Average cross-sectional correlation: -0.1506\n",
      "Std of monthly correlations: 0.0541\n",
      "\n",
      "Economic Reasoning:\n",
      "- The correlation between CashFlow2TA and Debt2Equity is moderately NEGATIVE (-0.15).\n",
      "- Firms with strong operating cash flows tend to rely less on debt financing.\n",
      "- Low-to-moderate correlation is BENEFICIAL for model performance because:\n",
      "  (1) The two features capture DIFFERENT dimensions of firm quality.\n",
      "  (2) The composite signal has higher information content than either alone.\n",
      "  (3) If correlation were too high (>0.7), the second feature adds little.\n",
      "  (4) Moderate negative correlation creates richer cross-sectional dispersion.\n"
     ]
    }
   ],
   "source": [
    "monthly_corrs = []\n",
    "for date in dates:\n",
    "    sub = df[df['date'] == date]\n",
    "    corr = sub['CashFlow2TA'].corr(sub['Debt2Equity'])\n",
    "    monthly_corrs.append(corr)\n",
    "\n",
    "avg_corr = np.mean(monthly_corrs)\n",
    "print(f\"Average cross-sectional correlation: {avg_corr:.4f}\")\n",
    "print(f\"Std of monthly correlations: {np.std(monthly_corrs):.4f}\")\n",
    "print(f\"\\nEconomic Reasoning:\")\n",
    "print(f\"- The correlation between CashFlow2TA and Debt2Equity is moderately NEGATIVE ({avg_corr:.2f}).\")\n",
    "print(f\"- Firms with strong operating cash flows tend to rely less on debt financing.\")\n",
    "print(f\"- Low-to-moderate correlation is BENEFICIAL for model performance because:\")\n",
    "print(f\"  (1) The two features capture DIFFERENT dimensions of firm quality.\")\n",
    "print(f\"  (2) The composite signal has higher information content than either alone.\")\n",
    "print(f\"  (3) If correlation were too high (>0.7), the second feature adds little.\")\n",
    "print(f\"  (4) Moderate negative correlation creates richer cross-sectional dispersion.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part (b): New Feature  -  Debt2Equity\n",
    "\n",
    "**Definition:** Debt2Equity = Total Debt / Total Equity\n",
    "\n",
    "**Justification:**\n",
    "- Measures financial risk and capital structure\n",
    "- High leverage increases default risk; excessively leveraged firms underperform risk-adjusted\n",
    "- Combined with CashFlow2TA: HIGH CashFlow2TA + LOW Debt2Equity = quality firms\n",
    "- Expected to have MODERATE NEGATIVE correlation with CashFlow2TA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*** HIGHLIGHTED FEATURE COMPUTATION ***\n",
      "Debt2Equity = Total Debt / Total Equity\n",
      "(Computed in data simulation cell above  -  lines with debt2equity)\n"
     ]
    }
   ],
   "source": [
    "print(\"*** HIGHLIGHTED FEATURE COMPUTATION ***\")\n",
    "print(\"Debt2Equity = Total Debt / Total Equity\")\n",
    "print(\"(Computed in data simulation cell above  -  lines with debt2equity)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part (c): Decile Portfolios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Decile Portfolio Average Returns and t-Statistics:\n",
      "-------------------------------------------------------\n",
      " Portfolio  Avg Monthly Return (%)  t-statistic\n",
      "  Decile 0                 -4.9436     -12.3049\n",
      "  Decile 1                 -2.5155      -6.3565\n",
      "  Decile 2                 -1.4195      -3.3256\n",
      "  Decile 3                 -0.3616      -0.8585\n",
      "  Decile 4                  0.5789       1.4400\n",
      "  Decile 5                  1.4537       3.6258\n",
      "  Decile 6                  2.5151       6.0499\n",
      "  Decile 7                  3.3975       7.9899\n",
      "  Decile 8                  4.4583      11.1150\n",
      "  Decile 9                  6.7071      16.8522\n",
      "Decile 9-0                 11.6506      77.9020\n"
     ]
    }
   ],
   "source": [
    "decile_returns = {d: [] for d in range(10)}\n",
    "\n",
    "for date in dates:\n",
    "    sub = df[df['date'] == date].copy()\n",
    "    sub['rank_cf'] = sub['CashFlow2TA'].rank(pct=True)\n",
    "    sub['rank_de'] = 1 - sub['Debt2Equity'].rank(pct=True)  # Invert: low debt = high rank\n",
    "    sub['composite'] = 0.5 * sub['rank_cf'] + 0.5 * sub['rank_de']\n",
    "    sub['decile'] = pd.qcut(sub['composite'], 10, labels=False)\n",
    "    \n",
    "    for d in range(10):\n",
    "        port_ret = sub[sub['decile'] == d]['ret'].mean()\n",
    "        decile_returns[d].append(port_ret)\n",
    "\n",
    "ls_returns = [decile_returns[9][i] - decile_returns[0][i] for i in range(n_months)]\n",
    "decile_returns['9-0'] = ls_returns\n",
    "\n",
    "results_c = []\n",
    "for key in list(range(10)) + ['9-0']:\n",
    "    rets = np.array(decile_returns[key])\n",
    "    avg_ret = np.mean(rets) * 100\n",
    "    t_stat = stats.ttest_1samp(rets, 0).statistic\n",
    "    results_c.append({\n",
    "        'Portfolio': f'Decile {key}',\n",
    "        'Avg Monthly Return (%)': round(avg_ret, 4),\n",
    "        't-statistic': round(t_stat, 4)\n",
    "    })\n",
    "\n",
    "df_results_c = pd.DataFrame(results_c)\n",
    "print(\"Decile Portfolio Average Returns and t-Statistics:\")\n",
    "print(\"-\" * 55)\n",
    "print(df_results_c.to_string(index=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part (d): Market-Neutral Portfolio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Estimated Beta of Decile 9: 0.9769\n",
      "Estimated Beta of Decile 8: 0.9912\n",
      "\n",
      "*** Long $10.0m in Decile 9 ***\n",
      "*** Short $9.8556m in Decile 8 to achieve market neutrality ***\n",
      "\n",
      "Formula: Short Amount = $10m x (beta_D9 / beta_D8) = $10m x (0.9769 / 0.9912) = $9.8556m\n",
      "\n",
      "Market-Neutral Portfolio:\n",
      "  Average Monthly Return (%): 1.1650\n",
      "  t-statistic: 15.0676\n",
      "  Portfolio Beta (should be ~0): 0.0000\n"
     ]
    }
   ],
   "source": [
    "mkt_returns = df.groupby('date')['mkt_ret'].first().values\n",
    "\n",
    "beta_d9 = np.cov(decile_returns[9], mkt_returns)[0, 1] / np.var(mkt_returns)\n",
    "beta_d8 = np.cov(decile_returns[8], mkt_returns)[0, 1] / np.var(mkt_returns)\n",
    "\n",
    "print(f\"Estimated Beta of Decile 9: {beta_d9:.4f}\")\n",
    "print(f\"Estimated Beta of Decile 8: {beta_d8:.4f}\")\n",
    "\n",
    "# Market-neutral: Long $10m D9, Short $X D8\n",
    "# $10m * beta_d9 = $X * beta_d8 => X = $10m * beta_d9 / beta_d8\n",
    "short_amount = 10.0 * beta_d9 / beta_d8\n",
    "\n",
    "print(f\"\\n*** Long $10.0m in Decile 9 ***\")\n",
    "print(f\"*** Short ${short_amount:.4f}m in Decile 8 to achieve market neutrality ***\")\n",
    "print(f\"\\nFormula: Short Amount = $10m x (beta_D9 / beta_D8) = $10m x ({beta_d9:.4f} / {beta_d8:.4f}) = ${short_amount:.4f}m\")\n",
    "\n",
    "# Market-neutral portfolio returns\n",
    "mn_returns = np.array([\n",
    "    (10.0 * decile_returns[9][i] - short_amount * decile_returns[8][i]) / (10.0 + short_amount)\n",
    "    for i in range(n_months)\n",
    "])\n",
    "\n",
    "mn_avg = np.mean(mn_returns) * 100\n",
    "mn_tstat = stats.ttest_1samp(mn_returns, 0).statistic\n",
    "beta_mn = np.cov(mn_returns, mkt_returns)[0, 1] / np.var(mkt_returns)\n",
    "\n",
    "print(f\"\\nMarket-Neutral Portfolio:\")\n",
    "print(f\"  Average Monthly Return (%): {mn_avg:.4f}\")\n",
    "print(f\"  t-statistic: {mn_tstat:.4f}\")\n",
    "print(f\"  Portfolio Beta (should be ~0): {beta_mn:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part (e): Market Model Alpha and Beta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Market Model: R_p,t = alpha + beta x R_m,t + epsilon_t\n",
      "----------------------------------------------------------------------\n",
      "     Portfolio  Alpha (monthly %)  t(Alpha)    Beta  t(Beta)\n",
      "      Decile 0            -5.8822  -55.0649  0.9829  40.5951\n",
      "      Decile 1            -3.4319  -29.2436  0.9597  36.0780\n",
      "      Decile 2            -2.4189  -22.0099  1.0466  42.0145\n",
      "      Decile 3            -1.3440  -11.7204  1.0288  39.5788\n",
      "      Decile 4            -0.3551   -3.0889  0.9780  37.5379\n",
      "      Decile 5             0.5233    4.5028  0.9742  36.9829\n",
      "      Decile 6             1.5491   13.0630  1.0116  37.6354\n",
      "      Decile 7             2.4024   21.7374  1.0420  41.5949\n",
      "      Decile 8             3.5197   33.7916  0.9830  41.6355\n",
      "      Decile 9             5.7819   51.1356  0.9688  37.7996\n",
      "    Decile 9-0            11.6641   75.8759 -0.0142  -0.4062\n",
      "Market-Neutral             1.1650   14.6485  0.0000   0.0000\n"
     ]
    }
   ],
   "source": [
    "def ols_regression(y, x):\n",
    "    n = len(y)\n",
    "    x_mean, y_mean = np.mean(x), np.mean(y)\n",
    "    beta = np.sum((x - x_mean) * (y - y_mean)) / np.sum((x - x_mean) ** 2)\n",
    "    alpha = y_mean - beta * x_mean\n",
    "    y_hat = alpha + beta * x\n",
    "    residuals = y - y_hat\n",
    "    mse = np.sum(residuals ** 2) / (n - 2)\n",
    "    se_beta = np.sqrt(mse / np.sum((x - x_mean) ** 2))\n",
    "    se_alpha = np.sqrt(mse * (1/n + x_mean**2 / np.sum((x - x_mean) ** 2)))\n",
    "    return alpha, beta, alpha/se_alpha, beta/se_beta\n",
    "\n",
    "all_portfolios = {}\n",
    "for d in range(10):\n",
    "    all_portfolios[f'Decile {d}'] = np.array(decile_returns[d])\n",
    "all_portfolios['Decile 9-0'] = np.array(decile_returns['9-0'])\n",
    "all_portfolios['Market-Neutral'] = mn_returns\n",
    "\n",
    "results_e = []\n",
    "for name, port_rets in all_portfolios.items():\n",
    "    alpha, beta, t_alpha, t_beta = ols_regression(port_rets, mkt_returns)\n",
    "    results_e.append({\n",
    "        'Portfolio': name,\n",
    "        'Alpha (monthly %)': round(alpha * 100, 4),\n",
    "        't(Alpha)': round(t_alpha, 4),\n",
    "        'Beta': round(beta, 4),\n",
    "        't(Beta)': round(t_beta, 4)\n",
    "    })\n",
    "\n",
    "df_results_e = pd.DataFrame(results_e)\n",
    "print(\"Market Model: R_p,t = alpha + beta x R_m,t + epsilon_t\")\n",
    "print(\"-\" * 70)\n",
    "print(df_results_e.to_string(index=False))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.0"
  },
  "authors": [
   {
    "name": "Eshan Mukherjee"
   }
  ]
 },
 "nbformat": 4,
 "nbformat_minor": 4
}