{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "58890f49-db49-46ca-b999-c4195db4cafe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import os\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6c2a06ef-97dc-43e3-aa9a-5feb7a2ad3ec",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "C:\\Users\\lirse\\OneDrive\\Desktop\\scripts\\clv\n"
     ]
    }
   ],
   "source": [
    "print(os.getcwd())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5844493a-806f-4c88-804d-3739283e375f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['.ipynb_checkpoints', 'clv_research.ipynb', 'live_data', 'optimal_time.csv', 'optimal_time_analyzer.py', 'research.ipynb', 'strategy.ipynb', 'strategy_bets.csv', 'strategy_equity.html', 'strategy_gate_equity.html', 'test.csv', 'test.html', 'test_0.html', 'test_1.html', '__pycache__']\n"
     ]
    }
   ],
   "source": [
    "print(os.listdir(os.getcwd()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "eddbcc72-660f-4235-a54b-b64c97b38f59",
   "metadata": {},
   "outputs": [],
   "source": [
    "dfs = []\n",
    "for f in os.listdir(os.getcwd()+'/live_data/'):\n",
    "    if '.csv' in f and f != 'test.csv':\n",
    "        df = pd.read_csv(os.getcwd()+'/live_data/'+f)\n",
    "        dfs.append(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2129aade-5e95-49f9-802b-0d52d3aa9bed",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['timestamp', 'game_date', 'league', 'home', 'away', 'status', 'elapsed',\n",
       "       'home_goals', 'away_goals', 'pin_home', 'pin_draw', 'pin_away',\n",
       "       'b365_home', 'b365_draw', 'b365_away', 'kalshi_home_mid',\n",
       "       'kalshi_home_bid', 'kalshi_home_ask', 'kalshi_draw_mid',\n",
       "       'kalshi_draw_bid', 'kalshi_draw_ask', 'kalshi_away_mid',\n",
       "       'kalshi_away_bid', 'kalshi_away_ask', 'pin_fair_home', 'pin_fair_draw',\n",
       "       'pin_fair_away', 'clv_b365_home', 'clv_b365_draw', 'clv_b365_away',\n",
       "       'clv_kalshi_home', 'clv_kalshi_draw', 'clv_kalshi_away', 'ftr',\n",
       "       'result_home', 'result_away'],\n",
       "      dtype='object')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.concat(dfs)\n",
    "df.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "12be0c27-8d6a-42ab-a452-bf7d5ede7c58",
   "metadata": {},
   "outputs": [],
   "source": [
    "def highest_clv(home, draw, away):\n",
    "    clv_dict = {'H': home, 'D': draw, 'A': away}\n",
    "    max_clv = None if pd.isna(clv_dict['H']) else max(clv_dict, key=clv_dict.get)\n",
    "    return max_clv, max(clv_dict.values())\n",
    "\n",
    "bet_df = df\n",
    "bet_df['b365_highest_clv'] = bet_df.apply(lambda row: highest_clv(row['clv_b365_home'], row['clv_b365_draw'], row['clv_b365_away'])[0], axis=1)\n",
    "bet_df['b365_highest_clv_val'] = bet_df.apply(lambda row: highest_clv(row['clv_b365_home'], row['clv_b365_draw'], row['clv_b365_away'])[1], axis=1)\n",
    "bet_df['kalshi_highest_clv'] = bet_df.apply(lambda row: highest_clv(row['clv_kalshi_home'], row['clv_kalshi_draw'], row['clv_kalshi_away'])[0], axis=1)\n",
    "bet_df['kalshi_highest_clv_val'] = bet_df.apply(lambda row: highest_clv(row['clv_kalshi_home'], row['clv_kalshi_draw'], row['clv_kalshi_away'])[1], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e976a383-9710-4e67-8687-d69d27601411",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['timestamp', 'game_date', 'league', 'home', 'away', 'status', 'elapsed',\n",
       "       'home_goals', 'away_goals', 'pin_home', 'pin_draw', 'pin_away',\n",
       "       'b365_home', 'b365_draw', 'b365_away', 'kalshi_home_mid',\n",
       "       'kalshi_home_bid', 'kalshi_home_ask', 'kalshi_draw_mid',\n",
       "       'kalshi_draw_bid', 'kalshi_draw_ask', 'kalshi_away_mid',\n",
       "       'kalshi_away_bid', 'kalshi_away_ask', 'pin_fair_home', 'pin_fair_draw',\n",
       "       'pin_fair_away', 'clv_b365_home', 'clv_b365_draw', 'clv_b365_away',\n",
       "       'clv_kalshi_home', 'clv_kalshi_draw', 'clv_kalshi_away', 'ftr',\n",
       "       'result_home', 'result_away', 'b365_highest_clv',\n",
       "       'b365_highest_clv_val', 'kalshi_highest_clv', 'kalshi_highest_clv_val',\n",
       "       'correct_b365', 'correct_kalshi'],\n",
       "      dtype='object')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bet_df['correct_b365'] = bet_df['ftr'] == bet_df['b365_highest_clv']\n",
    "bet_df['correct_b365'] = bet_df['correct_b365'].apply(lambda x: 1 if x == True else 0)\n",
    "bet_df['correct_kalshi'] = bet_df['ftr'] == bet_df['kalshi_highest_clv']\n",
    "bet_df['correct_kalshi'] = bet_df['correct_kalshi'].apply(lambda x: 1 if x == True else 0)\n",
    "bet_df.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "32b03913-2884-4f08-8a2b-dbfd1211ef04",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "bet_dict = {'H': '_home', 'D': '_draw', 'A': '_away'}\n",
    "df_filtered_nan = bet_df.dropna(subset=['b365_highest_clv', 'kalshi_highest_clv'])\n",
    "df_filtered_nan['b365_bet'] = df_filtered_nan.apply(lambda row: row['b365'+bet_dict[row['b365_highest_clv']]], axis=1)\n",
    "df_filtered_nan['kalshi_bet'] = df_filtered_nan.apply(lambda row: row['kalshi'+bet_dict[row['kalshi_highest_clv']]+'_ask'], axis=1)\n",
    "df_filtered_nan['kalshi_bet_b365'] = df_filtered_nan.apply(lambda row: row['kalshi'+bet_dict[row['b365_highest_clv']]+'_ask'], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ff62d5b4-e1f2-4cfa-a2fd-53a32afdaea7",
   "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>timestamp</th>\n",
       "      <th>game_date</th>\n",
       "      <th>league</th>\n",
       "      <th>home</th>\n",
       "      <th>away</th>\n",
       "      <th>status</th>\n",
       "      <th>elapsed</th>\n",
       "      <th>home_goals</th>\n",
       "      <th>away_goals</th>\n",
       "      <th>pin_home</th>\n",
       "      <th>...</th>\n",
       "      <th>result_away</th>\n",
       "      <th>b365_highest_clv</th>\n",
       "      <th>b365_highest_clv_val</th>\n",
       "      <th>kalshi_highest_clv</th>\n",
       "      <th>kalshi_highest_clv_val</th>\n",
       "      <th>correct_b365</th>\n",
       "      <th>correct_kalshi</th>\n",
       "      <th>b365_bet</th>\n",
       "      <th>kalshi_bet</th>\n",
       "      <th>kalshi_bet_b365</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Charlton</td>\n",
       "      <td>Ipswich</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>5.11</td>\n",
       "      <td>...</td>\n",
       "      <td>2.0</td>\n",
       "      <td>A</td>\n",
       "      <td>-0.051139</td>\n",
       "      <td>H</td>\n",
       "      <td>0.041901</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1.70</td>\n",
       "      <td>0.19</td>\n",
       "      <td>0.57</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Birmingham</td>\n",
       "      <td>Preston</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.67</td>\n",
       "      <td>...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.075843</td>\n",
       "      <td>A</td>\n",
       "      <td>0.052148</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1.62</td>\n",
       "      <td>0.18</td>\n",
       "      <td>0.58</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Middlesbrough</td>\n",
       "      <td>Sheffield Wednesday</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.16</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.041614</td>\n",
       "      <td>A</td>\n",
       "      <td>0.191379</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1.17</td>\n",
       "      <td>0.06</td>\n",
       "      <td>0.84</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E0</td>\n",
       "      <td>Burnley</td>\n",
       "      <td>Manchester City</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-898.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>16.43</td>\n",
       "      <td>...</td>\n",
       "      <td>1.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.001936</td>\n",
       "      <td>D</td>\n",
       "      <td>0.073243</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>17.00</td>\n",
       "      <td>0.10</td>\n",
       "      <td>0.06</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E0</td>\n",
       "      <td>Bournemouth</td>\n",
       "      <td>Leeds United</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-898.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.01</td>\n",
       "      <td>...</td>\n",
       "      <td>2.0</td>\n",
       "      <td>A</td>\n",
       "      <td>-0.060761</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.007949</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3.70</td>\n",
       "      <td>0.49</td>\n",
       "      <td>0.27</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>277</th>\n",
       "      <td>2026-06-14 12:05:38</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>SP2</td>\n",
       "      <td>Malaga</td>\n",
       "      <td>Almeria</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-174.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.27</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.037752</td>\n",
       "      <td>D</td>\n",
       "      <td>0.018964</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3.20</td>\n",
       "      <td>0.30</td>\n",
       "      <td>0.30</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>292</th>\n",
       "      <td>2026-06-14 12:21:01</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CAN</td>\n",
       "      <td>Vancouver FC</td>\n",
       "      <td>Pacific FC</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-338.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.66</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>A</td>\n",
       "      <td>0.000175</td>\n",
       "      <td>A</td>\n",
       "      <td>0.122654</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>4.50</td>\n",
       "      <td>0.20</td>\n",
       "      <td>0.20</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>294</th>\n",
       "      <td>2026-06-14 12:21:02</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CL1</td>\n",
       "      <td>Union La Calera</td>\n",
       "      <td>Universidad de Chile</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-158.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>3.57</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.011568</td>\n",
       "      <td>D</td>\n",
       "      <td>0.031800</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3.30</td>\n",
       "      <td>0.30</td>\n",
       "      <td>0.30</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>295</th>\n",
       "      <td>2026-06-14 12:21:02</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CL1</td>\n",
       "      <td>U. Catolica</td>\n",
       "      <td>Universidad de Concepcion</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-308.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.56</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.047179</td>\n",
       "      <td>A</td>\n",
       "      <td>0.139857</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1.55</td>\n",
       "      <td>0.14</td>\n",
       "      <td>0.66</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>296</th>\n",
       "      <td>2026-06-14 12:21:03</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>SP2</td>\n",
       "      <td>Malaga</td>\n",
       "      <td>Almeria</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-158.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.27</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.037752</td>\n",
       "      <td>A</td>\n",
       "      <td>0.028414</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3.20</td>\n",
       "      <td>0.27</td>\n",
       "      <td>0.30</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>14432 rows \u00d7 45 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "               timestamp   game_date league             home  \\\n",
       "0         4/22/2026 0:01  2026-04-22     E1         Charlton   \n",
       "1         4/22/2026 0:01  2026-04-22     E1       Birmingham   \n",
       "2         4/22/2026 0:01  2026-04-22     E1    Middlesbrough   \n",
       "3         4/22/2026 0:01  2026-04-22     E0          Burnley   \n",
       "4         4/22/2026 0:01  2026-04-22     E0      Bournemouth   \n",
       "..                   ...         ...    ...              ...   \n",
       "277  2026-06-14 12:05:38  2026-06-14    SP2           Malaga   \n",
       "292  2026-06-14 12:21:01  2026-06-14    CAN     Vancouver FC   \n",
       "294  2026-06-14 12:21:02  2026-06-14    CL1  Union La Calera   \n",
       "295  2026-06-14 12:21:02  2026-06-14    CL1      U. Catolica   \n",
       "296  2026-06-14 12:21:03  2026-06-14    SP2           Malaga   \n",
       "\n",
       "                          away status  elapsed  home_goals  away_goals  \\\n",
       "0                      Ipswich    PRE   -883.0         0.0         0.0   \n",
       "1                      Preston    PRE   -883.0         0.0         0.0   \n",
       "2          Sheffield Wednesday    PRE   -883.0         0.0         0.0   \n",
       "3              Manchester City    PRE   -898.0         0.0         0.0   \n",
       "4                 Leeds United    PRE   -898.0         0.0         0.0   \n",
       "..                         ...    ...      ...         ...         ...   \n",
       "277                    Almeria    PRE   -174.0         0.0         0.0   \n",
       "292                 Pacific FC    PRE   -338.0         0.0         0.0   \n",
       "294       Universidad de Chile    PRE   -158.0         0.0         0.0   \n",
       "295  Universidad de Concepcion    PRE   -308.0         0.0         0.0   \n",
       "296                    Almeria    PRE   -158.0         0.0         0.0   \n",
       "\n",
       "     pin_home  ...  result_away  b365_highest_clv  b365_highest_clv_val  \\\n",
       "0        5.11  ...          2.0                 A             -0.051139   \n",
       "1        1.67  ...          1.0                 H             -0.075843   \n",
       "2        1.16  ...          0.0                 H             -0.041614   \n",
       "3       16.43  ...          1.0                 H             -0.001936   \n",
       "4        2.01  ...          2.0                 A             -0.060761   \n",
       "..        ...  ...          ...               ...                   ...   \n",
       "277      2.27  ...          NaN                 D             -0.037752   \n",
       "292      1.66  ...          NaN                 A              0.000175   \n",
       "294      3.57  ...          NaN                 D             -0.011568   \n",
       "295      1.56  ...          NaN                 H             -0.047179   \n",
       "296      2.27  ...          NaN                 D             -0.037752   \n",
       "\n",
       "     kalshi_highest_clv  kalshi_highest_clv_val  correct_b365  correct_kalshi  \\\n",
       "0                     H                0.041901             1               0   \n",
       "1                     A                0.052148             1               0   \n",
       "2                     A                0.191379             1               0   \n",
       "3                     D                0.073243             0               0   \n",
       "4                     H               -0.007949             0               0   \n",
       "..                  ...                     ...           ...             ...   \n",
       "277                   D                0.018964             0               0   \n",
       "292                   A                0.122654             0               0   \n",
       "294                   D                0.031800             0               0   \n",
       "295                   A                0.139857             0               0   \n",
       "296                   A                0.028414             0               0   \n",
       "\n",
       "     b365_bet  kalshi_bet  kalshi_bet_b365  \n",
       "0        1.70        0.19             0.57  \n",
       "1        1.62        0.18             0.58  \n",
       "2        1.17        0.06             0.84  \n",
       "3       17.00        0.10             0.06  \n",
       "4        3.70        0.49             0.27  \n",
       "..        ...         ...              ...  \n",
       "277      3.20        0.30             0.30  \n",
       "292      4.50        0.20             0.20  \n",
       "294      3.30        0.30             0.30  \n",
       "295      1.55        0.14             0.66  \n",
       "296      3.20        0.27             0.30  \n",
       "\n",
       "[14432 rows x 45 columns]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_filtered_nan"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0bde1df3-2da1-417c-b0f0-9cedde4813f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_filtered_nan['returns_b365'] = df_filtered_nan.apply(lambda row: row['b365_bet'] if row['correct_b365'] == 1 else -1, axis=1)\n",
    "df_filtered_nan['returns_kalshi'] = df_filtered_nan.apply(lambda row: row['kalshi_bet'] if row['correct_kalshi'] == 1 else -1, axis=1)\n",
    "df_filtered_nan['returns_kalshi_b365'] = df_filtered_nan.apply(lambda row: row['kalshi_bet_b365'] if row['correct_b365'] == 1 else -1, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "89e00959-acca-4322-9226-2b01ac5b097d",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_filtered_nan['returns_kalshi_odds'] = 1/df_filtered_nan['returns_kalshi']\n",
    "df_filtered_nan['returns_kalshi_b365_odds'] = 1/df_filtered_nan['returns_kalshi_b365']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "45c06182-ad8b-438d-b589-bc20be205c95",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pick one snapshot per game: closest to TARGET_MINS before kickoff\n",
    "TARGET_MINS = 480  # 8 hours before kickoff\n",
    "df_filtered_nan['dist_to_target'] = (df_filtered_nan['elapsed'] - (-TARGET_MINS)).abs()\n",
    "df_filtered_nan = df_filtered_nan.sort_values('dist_to_target')\n",
    "df_filtered_nan = df_filtered_nan.drop_duplicates(subset=['home', 'away'], keep='first')\n",
    "df_filtered_nan = df_filtered_nan.drop(columns='dist_to_target')\n",
    "\n",
    "# Only bet when the chosen side actually has positive CLV\n",
    "clv_map = {'H': 'clv_b365_home', 'D': 'clv_b365_draw', 'A': 'clv_b365_away'}\n",
    "df_filtered_nan['max_b365_clv'] = df_filtered_nan.apply(lambda row: row[clv_map[row['b365_highest_clv']]], axis=1)\n",
    "df_filtered_nan = df_filtered_nan[df_filtered_nan['max_b365_clv'] > 0]\n",
    "\n",
    "df_filtered_nan = df_filtered_nan[df_filtered_nan['b365_highest_clv']!='D']\n",
    "df_filtered_nan = df_filtered_nan.dropna(subset=['result_home', 'result_away', 'clv_kalshi_home', 'clv_kalshi_draw', 'clv_kalshi_away'], how='any')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "9d96bebe-805b-4bd7-a001-8bcfb0f82e82",
   "metadata": {},
   "outputs": [],
   "source": [
    "def kelly_size(probability, odds, bankroll, max_fraction=0.5):\n",
    "    b = odds - 1  # net odds\n",
    "    f_star = (probability * odds - 1) / b   # binary Kelly = (p - ask)/(1 - ask)\n",
    "    f_capped = min(f_star, max_fraction)\n",
    "    return max(0, f_capped * bankroll)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "38633e9f-8a8b-4016-abbe-adbf25ce0ea3",
   "metadata": {},
   "outputs": [],
   "source": [
    "def cumulative_kelly(bets, probabilities, outcomes, bankroll=1000, max_fraction=0.5):\n",
    "    \"\"\"\n",
    "    Simulate cumulative Kelly betting over a series of bets.\n",
    "\n",
    "    Args:\n",
    "        bets: List of decimal odds for each bet\n",
    "        probabilities: List of win probabilities for each bet\n",
    "        outcomes: List of 1 (win) or 0 (loss) for each bet\n",
    "        bankroll: Starting bankroll\n",
    "        max_fraction: Max fraction of bankroll to bet (0.5 = half-Kelly)\n",
    "\n",
    "    Returns:\n",
    "        List of (bet_amount, new_bankroll) tuples for each step\n",
    "    \"\"\"\n",
    "    results = []\n",
    "    br = bankroll\n",
    "\n",
    "    for odds, prob, outcome in zip(bets, probabilities, outcomes):\n",
    "        bet = kelly_size(prob, odds, br, max_fraction)\n",
    "        if outcome == 1:\n",
    "            br += bet * (odds - 1)\n",
    "        else:\n",
    "            br -= bet\n",
    "        results.append((bet, br))\n",
    "\n",
    "    return results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7445c67d-efc3-4c91-b208-abfe01dee292",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use Pinnacle fair probability as p_win for Kelly (not implied 1/odds)\n",
    "pin_fair_map = {'H': 'pin_fair_home', 'D': 'pin_fair_draw', 'A': 'pin_fair_away'}\n",
    "df_filtered_nan['pin_fair_b365'] = df_filtered_nan.apply(lambda row: row[pin_fair_map[row['b365_highest_clv']]], axis=1)\n",
    "df_filtered_nan['pin_fair_kalshi'] = df_filtered_nan.apply(lambda row: row[pin_fair_map[row['kalshi_highest_clv']]], axis=1)\n",
    "\n",
    "kelly_b365 = cumulative_kelly(df_filtered_nan['b365_bet'], df_filtered_nan['pin_fair_b365'], df_filtered_nan['correct_b365'], max_fraction=0.025)\n",
    "df_filtered_nan['kelly_b365'] = [x[0] for x in kelly_b365]\n",
    "df_filtered_nan['kelly_b365_comp'] = [x[1] for x in kelly_b365]\n",
    "kelly_kalshi = cumulative_kelly(1/df_filtered_nan['kalshi_bet'], df_filtered_nan['pin_fair_kalshi'], df_filtered_nan['correct_kalshi'], max_fraction=0.025)\n",
    "df_filtered_nan['kelly_kalshi'] = [x[0] for x in kelly_kalshi]\n",
    "df_filtered_nan['kelly_kalshi_comp'] = [x[1] for x in kelly_kalshi]\n",
    "kelly_kalshi_b365 = cumulative_kelly(1/df_filtered_nan['kalshi_bet_b365'], df_filtered_nan['pin_fair_b365'], df_filtered_nan['correct_b365'], max_fraction=0.025)\n",
    "df_filtered_nan['kelly_kalshi_b365'] = [x[0] for x in kelly_kalshi_b365]\n",
    "df_filtered_nan['kelly_kalshi_b365_comp'] = [x[1] for x in kelly_kalshi_b365]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "0913c2ad-dfbb-4870-a552-2eb73b0d1ead",
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.express as px\n",
    "\n",
    "x = range(df_filtered_nan.shape[0])\n",
    "y = df_filtered_nan['kelly_kalshi_b365_comp']\n",
    "\n",
    "fig = px.line(x=x, y=y, title='Compounded Returns', labels={'x':'Bets', 'y':'Returns'})\n",
    "fig.write_html('test_0.html')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "833af46b-1f8d-4a0c-9dbd-7d834e77152e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# df_filtered_nan.to_csv('test.csv', index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "de201731-df9b-4d80-bc96-3a1533020189",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Summary\n",
      "==========================================================================================\n",
      "Total Bets: 26\n",
      "Correct B365: 0.11538461538461539\n",
      "Correct Kalshi: 0.07692307692307693\n",
      "Flat B365 Returns: $-8.57\n",
      "Flat Kalshi Returns: $-23.62\n",
      "Flat Kalshi w/ B365 Basis Return: $-8.99\n",
      "Final B365 Return (Compounded): $975.21\n",
      "Final Kalshi Return (Compounded): $833.64\n",
      "Final Kalshi w/ B365 Basis Return (Compounded): $887.81\n"
     ]
    }
   ],
   "source": [
    "print('Summary')\n",
    "print('==========================================================================================')\n",
    "print(f'Total Bets: {df_filtered_nan.shape[0]}')\n",
    "print(f'Correct B365: {df_filtered_nan[\"correct_b365\"].mean()}')\n",
    "print(f'Correct Kalshi: {df_filtered_nan[\"correct_kalshi\"].mean()}')\n",
    "print(f'Flat B365 Returns: ${df_filtered_nan[\"returns_b365\"].sum():,.2f}')\n",
    "print(f'Flat Kalshi Returns: ${df_filtered_nan[\"returns_kalshi\"].sum():,.2f}')\n",
    "print(f'Flat Kalshi w/ B365 Basis Return: ${df_filtered_nan[\"returns_kalshi_b365_odds\"].sum():,.2f}')\n",
    "print(f'Final B365 Return (Compounded): ${df_filtered_nan[\"kelly_b365_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Final Kalshi Return (Compounded): ${df_filtered_nan[\"kelly_kalshi_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Final Kalshi w/ B365 Basis Return (Compounded): ${df_filtered_nan[\"kelly_kalshi_b365_comp\"].iloc[-1]:,.2f}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "0f0427c0-5446-436d-b53e-49f84bb44015",
   "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>timestamp</th>\n",
       "      <th>game_date</th>\n",
       "      <th>league</th>\n",
       "      <th>home</th>\n",
       "      <th>away</th>\n",
       "      <th>status</th>\n",
       "      <th>elapsed</th>\n",
       "      <th>home_goals</th>\n",
       "      <th>away_goals</th>\n",
       "      <th>pin_home</th>\n",
       "      <th>...</th>\n",
       "      <th>clv_kalshi_away</th>\n",
       "      <th>ftr</th>\n",
       "      <th>result_home</th>\n",
       "      <th>result_away</th>\n",
       "      <th>b365_highest_clv</th>\n",
       "      <th>b365_highest_clv_val</th>\n",
       "      <th>kalshi_highest_clv</th>\n",
       "      <th>kalshi_highest_clv_val</th>\n",
       "      <th>correct_b365</th>\n",
       "      <th>correct_kalshi</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Charlton</td>\n",
       "      <td>Ipswich</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>5.11</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.012265</td>\n",
       "      <td>A</td>\n",
       "      <td>1.0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>A</td>\n",
       "      <td>-0.051139</td>\n",
       "      <td>H</td>\n",
       "      <td>0.041901</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Birmingham</td>\n",
       "      <td>Preston</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.67</td>\n",
       "      <td>...</td>\n",
       "      <td>0.052148</td>\n",
       "      <td>H</td>\n",
       "      <td>2.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.075843</td>\n",
       "      <td>A</td>\n",
       "      <td>0.052148</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E1</td>\n",
       "      <td>Middlesbrough</td>\n",
       "      <td>Sheffield Wednesday</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-883.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.16</td>\n",
       "      <td>...</td>\n",
       "      <td>0.191379</td>\n",
       "      <td>H</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.041614</td>\n",
       "      <td>A</td>\n",
       "      <td>0.191379</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E0</td>\n",
       "      <td>Burnley</td>\n",
       "      <td>Manchester City</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-898.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>16.43</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.031257</td>\n",
       "      <td>A</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.001936</td>\n",
       "      <td>D</td>\n",
       "      <td>0.073243</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4/22/2026 0:01</td>\n",
       "      <td>2026-04-22</td>\n",
       "      <td>E0</td>\n",
       "      <td>Bournemouth</td>\n",
       "      <td>Leeds United</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-898.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.01</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.024235</td>\n",
       "      <td>D</td>\n",
       "      <td>2.0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>A</td>\n",
       "      <td>-0.060761</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.007949</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>277</th>\n",
       "      <td>2026-06-14 12:05:38</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>SP2</td>\n",
       "      <td>Malaga</td>\n",
       "      <td>Almeria</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-174.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.27</td>\n",
       "      <td>...</td>\n",
       "      <td>0.010082</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.037752</td>\n",
       "      <td>D</td>\n",
       "      <td>0.018964</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>292</th>\n",
       "      <td>2026-06-14 12:21:01</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CAN</td>\n",
       "      <td>Vancouver FC</td>\n",
       "      <td>Pacific FC</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-338.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.66</td>\n",
       "      <td>...</td>\n",
       "      <td>0.122654</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>A</td>\n",
       "      <td>0.000175</td>\n",
       "      <td>A</td>\n",
       "      <td>0.122654</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>294</th>\n",
       "      <td>2026-06-14 12:21:02</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CL1</td>\n",
       "      <td>Union La Calera</td>\n",
       "      <td>Universidad de Chile</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-158.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>3.57</td>\n",
       "      <td>...</td>\n",
       "      <td>-0.032112</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.011568</td>\n",
       "      <td>D</td>\n",
       "      <td>0.031800</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>295</th>\n",
       "      <td>2026-06-14 12:21:02</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>CL1</td>\n",
       "      <td>U. Catolica</td>\n",
       "      <td>Universidad de Concepcion</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-308.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.56</td>\n",
       "      <td>...</td>\n",
       "      <td>0.139857</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>H</td>\n",
       "      <td>-0.047179</td>\n",
       "      <td>A</td>\n",
       "      <td>0.139857</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>296</th>\n",
       "      <td>2026-06-14 12:21:03</td>\n",
       "      <td>2026-06-14</td>\n",
       "      <td>SP2</td>\n",
       "      <td>Malaga</td>\n",
       "      <td>Almeria</td>\n",
       "      <td>PRE</td>\n",
       "      <td>-158.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>2.27</td>\n",
       "      <td>...</td>\n",
       "      <td>0.028414</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>D</td>\n",
       "      <td>-0.037752</td>\n",
       "      <td>A</td>\n",
       "      <td>0.028414</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>14432 rows \u00d7 42 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "               timestamp   game_date league             home  \\\n",
       "0         4/22/2026 0:01  2026-04-22     E1         Charlton   \n",
       "1         4/22/2026 0:01  2026-04-22     E1       Birmingham   \n",
       "2         4/22/2026 0:01  2026-04-22     E1    Middlesbrough   \n",
       "3         4/22/2026 0:01  2026-04-22     E0          Burnley   \n",
       "4         4/22/2026 0:01  2026-04-22     E0      Bournemouth   \n",
       "..                   ...         ...    ...              ...   \n",
       "277  2026-06-14 12:05:38  2026-06-14    SP2           Malaga   \n",
       "292  2026-06-14 12:21:01  2026-06-14    CAN     Vancouver FC   \n",
       "294  2026-06-14 12:21:02  2026-06-14    CL1  Union La Calera   \n",
       "295  2026-06-14 12:21:02  2026-06-14    CL1      U. Catolica   \n",
       "296  2026-06-14 12:21:03  2026-06-14    SP2           Malaga   \n",
       "\n",
       "                          away status  elapsed  home_goals  away_goals  \\\n",
       "0                      Ipswich    PRE   -883.0         0.0         0.0   \n",
       "1                      Preston    PRE   -883.0         0.0         0.0   \n",
       "2          Sheffield Wednesday    PRE   -883.0         0.0         0.0   \n",
       "3              Manchester City    PRE   -898.0         0.0         0.0   \n",
       "4                 Leeds United    PRE   -898.0         0.0         0.0   \n",
       "..                         ...    ...      ...         ...         ...   \n",
       "277                    Almeria    PRE   -174.0         0.0         0.0   \n",
       "292                 Pacific FC    PRE   -338.0         0.0         0.0   \n",
       "294       Universidad de Chile    PRE   -158.0         0.0         0.0   \n",
       "295  Universidad de Concepcion    PRE   -308.0         0.0         0.0   \n",
       "296                    Almeria    PRE   -158.0         0.0         0.0   \n",
       "\n",
       "     pin_home  ...  clv_kalshi_away  ftr  result_home  result_away  \\\n",
       "0        5.11  ...        -0.012265    A          1.0          2.0   \n",
       "1        1.67  ...         0.052148    H          2.0          1.0   \n",
       "2        1.16  ...         0.191379    H          1.0          0.0   \n",
       "3       16.43  ...        -0.031257    A          0.0          1.0   \n",
       "4        2.01  ...        -0.024235    D          2.0          2.0   \n",
       "..        ...  ...              ...  ...          ...          ...   \n",
       "277      2.27  ...         0.010082  NaN          NaN          NaN   \n",
       "292      1.66  ...         0.122654  NaN          NaN          NaN   \n",
       "294      3.57  ...        -0.032112  NaN          NaN          NaN   \n",
       "295      1.56  ...         0.139857  NaN          NaN          NaN   \n",
       "296      2.27  ...         0.028414  NaN          NaN          NaN   \n",
       "\n",
       "     b365_highest_clv  b365_highest_clv_val  kalshi_highest_clv  \\\n",
       "0                   A             -0.051139                   H   \n",
       "1                   H             -0.075843                   A   \n",
       "2                   H             -0.041614                   A   \n",
       "3                   H             -0.001936                   D   \n",
       "4                   A             -0.060761                   H   \n",
       "..                ...                   ...                 ...   \n",
       "277                 D             -0.037752                   D   \n",
       "292                 A              0.000175                   A   \n",
       "294                 D             -0.011568                   D   \n",
       "295                 H             -0.047179                   A   \n",
       "296                 D             -0.037752                   A   \n",
       "\n",
       "     kalshi_highest_clv_val  correct_b365  correct_kalshi  \n",
       "0                  0.041901             1               0  \n",
       "1                  0.052148             1               0  \n",
       "2                  0.191379             1               0  \n",
       "3                  0.073243             0               0  \n",
       "4                 -0.007949             0               0  \n",
       "..                      ...           ...             ...  \n",
       "277                0.018964             0               0  \n",
       "292                0.122654             0               0  \n",
       "294                0.031800             0               0  \n",
       "295                0.139857             0               0  \n",
       "296                0.028414             0               0  \n",
       "\n",
       "[14432 rows x 42 columns]"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_filtered_nan_ = df.dropna(subset=['b365_highest_clv', 'kalshi_highest_clv'])\n",
    "df_filtered_nan_"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89409757-e0e0-4f64-b10d-f812d0653993",
   "metadata": {},
   "source": [
    "# Time-based analysis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "bc4dd89d-756c-423b-83b5-df49a8eb3769",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-1349. , -1129.5,  -910. ,  -690.5,  -471. ,  -251.5,   -32. ])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import math\n",
    "import numpy as np\n",
    "\n",
    "df_filtered_nan_ = df_filtered_nan_[df_filtered_nan_['status']=='PRE']\n",
    "num_bins = math.ceil(abs(int(df_filtered_nan_['elapsed'].min()))/240)\n",
    "bins = np.histogram(df_filtered_nan_['elapsed'], bins=num_bins)[1]\n",
    "bins"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "ee953fe7-29f0-415b-be76-4a02e47647ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "def assign_bin(number):\n",
    "    bins = np.histogram(df_filtered_nan_['elapsed'], bins=num_bins)[1]\n",
    "    for i in range(len(bins)+1):\n",
    "        if number >= bins[i] and number <= bins[i+1]:\n",
    "            return i"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "ed296290-4b77-4f60-87c7-10e1ecff5063",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_filtered_nan_['bin'] = df_filtered_nan_['elapsed'].apply(lambda x: assign_bin(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "8d10b264-63ea-4185-b463-cac1515c79d5",
   "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 tr th {\n",
       "        text-align: left;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th>bin</th>\n",
       "      <th colspan=\"2\" halign=\"left\">correct_b365</th>\n",
       "      <th colspan=\"2\" halign=\"left\">correct_kalshi</th>\n",
       "      <th>bin_range</th>\n",
       "      <th>bin_mid</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th>mean</th>\n",
       "      <th>count</th>\n",
       "      <th>mean</th>\n",
       "      <th>count</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>0.202532</td>\n",
       "      <td>79</td>\n",
       "      <td>0.291139</td>\n",
       "      <td>79</td>\n",
       "      <td>1349-1130</td>\n",
       "      <td>1239</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>0.408163</td>\n",
       "      <td>196</td>\n",
       "      <td>0.255102</td>\n",
       "      <td>196</td>\n",
       "      <td>1130-910</td>\n",
       "      <td>1020</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>0.504274</td>\n",
       "      <td>351</td>\n",
       "      <td>0.304843</td>\n",
       "      <td>351</td>\n",
       "      <td>910-690</td>\n",
       "      <td>800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>0.404110</td>\n",
       "      <td>730</td>\n",
       "      <td>0.267123</td>\n",
       "      <td>730</td>\n",
       "      <td>690-471</td>\n",
       "      <td>581</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>0.397213</td>\n",
       "      <td>861</td>\n",
       "      <td>0.297329</td>\n",
       "      <td>861</td>\n",
       "      <td>471-252</td>\n",
       "      <td>361</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>5</td>\n",
       "      <td>0.388640</td>\n",
       "      <td>669</td>\n",
       "      <td>0.285501</td>\n",
       "      <td>669</td>\n",
       "      <td>252-32</td>\n",
       "      <td>142</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  bin correct_b365       correct_kalshi        bin_range bin_mid\n",
       "              mean count           mean count                   \n",
       "0   0     0.202532    79       0.291139    79  1349-1130    1239\n",
       "1   1     0.408163   196       0.255102   196   1130-910    1020\n",
       "2   2     0.504274   351       0.304843   351    910-690     800\n",
       "3   3     0.404110   730       0.267123   730    690-471     581\n",
       "4   4     0.397213   861       0.297329   861    471-252     361\n",
       "5   5     0.388640   669       0.285501   669     252-32     142"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "first_20 = round(df_filtered_nan_.shape[0]*.2)\n",
    "df_first_20 = df_filtered_nan_.head(first_20)\n",
    "df_binned = (\n",
    "    df_first_20[['correct_b365', 'correct_kalshi', 'bin']]\n",
    "    .groupby(by='bin')\n",
    "    .agg({'correct_b365': ['mean', 'count'],\n",
    "          'correct_kalshi': ['mean', 'count'],\n",
    "          # 'b365_highest_clv': ['mean']\n",
    "         })\n",
    "    .reset_index()\n",
    ")\n",
    "df_binned['bin_range'] = df_binned['bin'].apply(lambda x: f'{-1*round(bins[x])}-{-1*round(bins[x+1])}')\n",
    "df_binned['bin_mid'] = df_binned['bin'].apply(lambda x: f'{-1*round((bins[x]+bins[x+1])/2)}')\n",
    "df_binned"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "abb4fa36-3731-4e37-9aa9-9f7b22740c24",
   "metadata": {},
   "outputs": [],
   "source": [
    "best_bin = df_binned.sort_values(by=('correct_kalshi', 'mean'), ascending=False)['bin'].iloc[0]\n",
    "TARGET_MINS = int(df_binned.sort_values(by=('correct_kalshi', 'mean'), ascending=False)['bin_mid'].iloc[0])\n",
    "df_remaining = df_filtered_nan_[first_20:]\n",
    "binned_df = df_remaining#[(df_remaining['league']!='USL')&(df_remaining['league']!='BSA')]\n",
    "binned_df = binned_df[(binned_df['kalshi_highest_clv_val']>0.04)]\n",
    "binned_df = binned_df[binned_df['elapsed'] >= -TARGET_MINS]          # countdown has reached the target\n",
    "binned_df['dist_to_target'] = binned_df['elapsed'] + TARGET_MINS     # = elapsed - (-TARGET_MINS), always >= 0\n",
    "binned_df = binned_df.sort_values('dist_to_target')\n",
    "binned_df = binned_df.drop_duplicates(subset=['home', 'away'], keep='first')\n",
    "binned_df = binned_df.drop(columns='dist_to_target')\n",
    "binned_df['timestamp'] = binned_df['timestamp'].apply(lambda x: pd.to_datetime(x).timestamp())\n",
    "binned_df = binned_df.sort_values(by='timestamp')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "6e923bb4-37e5-44a9-9e7c-63cbe842f17b",
   "metadata": {},
   "outputs": [],
   "source": [
    "binned_df['b365_bet'] = binned_df.apply(lambda row: row['b365'+bet_dict[row['b365_highest_clv']]], axis=1)\n",
    "binned_df['kalshi_bet'] = binned_df.apply(lambda row: row['kalshi'+bet_dict[row['kalshi_highest_clv']]+'_ask'], axis=1)\n",
    "binned_df['kalshi_bet_b365'] = binned_df.apply(lambda row: row['kalshi'+bet_dict[row['b365_highest_clv']]+'_ask'], axis=1)\n",
    "binned_df['returns_b365'] = binned_df.apply(lambda row: row['b365_bet']-1 if row['correct_b365'] == 1 else -1, axis=1)\n",
    "binned_df['returns_kalshi'] = binned_df.apply(lambda row: 1/row['kalshi_bet']-1 if row['correct_kalshi'] == 1 else -1, axis=1)\n",
    "binned_df['returns_kalshi_b365'] = binned_df.apply(lambda row: 1/row['kalshi_bet_b365']-1 if row['correct_b365'] == 1 else -1, axis=1)\n",
    "# binned_df = binned_df[binned_df['b365_highest_clv']!='D']\n",
    "binned_df = binned_df.dropna(subset=['result_home', 'result_away', 'clv_kalshi_home', 'clv_kalshi_draw', 'clv_kalshi_away'], how='any')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "92d99b27-6e85-4556-b240-202696571476",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SKIP: 164 BET: 133 BENCH: 135\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "WINDOW, WARMUP = 10, 8\n",
    "binned_df = binned_df.sort_values('timestamp').reset_index(drop=True)\n",
    "\n",
    "hist = defaultdict(list)          # league -> net outcome of every signal seen so far\n",
    "decisions, rolls = [], []\n",
    "for _, row in binned_df.iterrows():\n",
    "    h = hist[row['league']]\n",
    "    net = row['returns_kalshi']   # flat net this signal would earn (1/ask-1 if win else -1)\n",
    "    if len(h) < WARMUP:\n",
    "        dec, roll = 'SKIP', np.nan          # not enough history -> don't bet yet\n",
    "    else:\n",
    "        roll = np.mean(h[-WINDOW:])         # rolling mean net over last WINDOW signals\n",
    "        dec = 'BET' if roll >= 0 else 'BENCH'\n",
    "    decisions.append(dec); rolls.append(roll)\n",
    "    h.append(net)                 # record outcome regardless of decision (point-in-time)\n",
    "\n",
    "binned_df['league_roll_roi'] = rolls\n",
    "binned_df['decision'] = decisions\n",
    "\n",
    "print('SKIP:', (binned_df.decision=='SKIP').sum(),\n",
    "      'BET:', (binned_df.decision=='BET').sum(),\n",
    "      'BENCH:', (binned_df.decision=='BENCH').sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "cc40ef8d-3799-4245-a526-4a436249cfd8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use Pinnacle fair probability as p_win for Kelly\n",
    "binned_df['pin_fair_b365'] = binned_df.apply(lambda row: row[pin_fair_map[row['b365_highest_clv']]], axis=1)\n",
    "binned_df['pin_fair_kalshi'] = binned_df.apply(lambda row: row[pin_fair_map[row['kalshi_highest_clv']]], axis=1)\n",
    "\n",
    "kelly_b365 = cumulative_kelly(binned_df['b365_bet'], binned_df['pin_fair_b365'], binned_df['correct_b365'], max_fraction=0.025)\n",
    "binned_df['kelly_b365'] = [x[0] for x in kelly_b365]\n",
    "binned_df['kelly_b365_comp'] = [x[1] for x in kelly_b365]\n",
    "kelly_kalshi = cumulative_kelly(1/binned_df['kalshi_bet'], binned_df['pin_fair_kalshi'], binned_df['correct_kalshi'], max_fraction=0.025)\n",
    "binned_df['kelly_kalshi'] = [x[0] for x in kelly_kalshi]\n",
    "binned_df['kelly_kalshi_comp'] = [x[1] for x in kelly_kalshi]\n",
    "kelly_kalshi_b365 = cumulative_kelly(1/binned_df['kalshi_bet_b365'], binned_df['pin_fair_b365'], binned_df['correct_b365'], max_fraction=0.025)\n",
    "binned_df['kelly_kalshi_b365'] = [x[0] for x in kelly_kalshi_b365]\n",
    "binned_df['kelly_kalshi_b365_comp'] = [x[1] for x in kelly_kalshi_b365]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "77442985-a553-48cd-b339-e15c7e5fd5ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "gated Kalshi flat ROI: -13.1%  (n=133)\n",
      "gated Kalshi compounded: $773.17\n"
     ]
    }
   ],
   "source": [
    "bet_df = binned_df[binned_df['decision'] == 'BET'].copy()\n",
    "k = cumulative_kelly(1/bet_df['kalshi_bet'], bet_df['pin_fair_kalshi'],\n",
    "                     bet_df['correct_kalshi'], max_fraction=0.025)\n",
    "bet_df['kelly_kalshi_comp'] = [x[1] for x in k]\n",
    "\n",
    "print(f\"gated Kalshi flat ROI: {100*bet_df['returns_kalshi'].mean():+.1f}%  (n={len(bet_df)})\")\n",
    "print(f\"gated Kalshi compounded: ${bet_df['kelly_kalshi_comp'].iloc[-1]:,.2f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "20a89e95-311b-456f-9f14-dfb0aad7b5bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Summary\n",
      "==========================================================================================\n",
      "Total Bets: 432\n",
      "Correct B365: 0.33796296296296297\n",
      "Correct Kalshi: 0.24074074074074073\n",
      "Flat B365 Returns: $-34.04\n",
      "Flat Kalshi Returns: $38.02\n",
      "Flat Kalshi w/ B365 Basis Return: $-22.71\n",
      "Final B365 Return (Compounded): $1,121.68\n",
      "Final Kalshi Return (Compounded): $1,541.54\n",
      "Final Kalshi w/ B365 Basis Return (Compounded): $801.84\n"
     ]
    }
   ],
   "source": [
    "print('Summary')\n",
    "print('==========================================================================================')\n",
    "print(f'Total Bets: {binned_df.shape[0]}')\n",
    "print(f'Correct B365: {binned_df[\"correct_b365\"].mean()}')\n",
    "print(f'Correct Kalshi: {binned_df[\"correct_kalshi\"].mean()}')\n",
    "print(f'Flat B365 Returns: ${binned_df[\"returns_b365\"].sum():,.2f}')\n",
    "print(f'Flat Kalshi Returns: ${binned_df[\"returns_kalshi\"].sum():,.2f}')\n",
    "print(f'Flat Kalshi w/ B365 Basis Return: ${binned_df[\"returns_kalshi_b365\"].sum():,.2f}')\n",
    "print(f'Final B365 Return (Compounded): ${binned_df[\"kelly_b365_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Final Kalshi Return (Compounded): ${binned_df[\"kelly_kalshi_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Final Kalshi w/ B365 Basis Return (Compounded): ${binned_df[\"kelly_kalshi_b365_comp\"].iloc[-1]:,.2f}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "64e1e428-5d4b-4c51-96ee-a5595a096bbe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.graph_objects as go\n",
    "import numpy as np\n",
    "\n",
    "x = np.arange(binned_df.shape[0])\n",
    "y_0 = binned_df['kelly_b365_comp']\n",
    "y_1 = binned_df['kelly_kalshi_comp']\n",
    "y_2 = binned_df['kelly_kalshi_b365_comp']\n",
    "\n",
    "fig = go.Figure()\n",
    "fig.add_trace(go.Scatter(x=x, y=y_0, name='B365', line=dict(color='blue')))\n",
    "fig.add_trace(go.Scatter(x=x, y=y_1, name='Kalshi', line=dict(color='red')))\n",
    "fig.add_trace(go.Scatter(x=x, y=y_2, name='Kalshi w/ B365', line=dict(color='green')))\n",
    "fig.add_trace(go.Scatter(x=bet_df.index, y=bet_df['kelly_kalshi_comp'],\n",
    "                         name='Kalshi (ROI-gated, skip)',\n",
    "                         line=dict(color='orange', dash='dash')))   # gated overlay\n",
    "\n",
    "fig.update_layout(\n",
    "    title='Compounded Returns',\n",
    "    xaxis_title='Bets',\n",
    "    yaxis_title='Returns',\n",
    "    hovermode='x unified'\n",
    ")\n",
    "fig.write_html('test_1.html')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3bb2d03a-97fd-419e-b107-47dd85325793",
   "metadata": {},
   "outputs": [],
   "source": [
    "binned_df.to_csv('test.csv', index=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbb0f489-e388-497b-bcad-21c3b4469b9d",
   "metadata": {},
   "source": [
    "# Early Cashout Testing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "10996ac6-9621-4c7b-8ae8-abcf2775f457",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_early = df.dropna(subset=['elapsed'])\n",
    "\n",
    "# Pre-compute sell conditions for all (home, away, outcome) combinations\n",
    "bet_dict = {'H': 'home', 'D': 'draw', 'A': 'away'}\n",
    "sell_conditions = {}\n",
    "\n",
    "for (home, away), group in df_early.groupby(['home', 'away']):\n",
    "    for bet, suffix in bet_dict.items():\n",
    "        key = (home, away, bet)\n",
    "        sell_conditions[key] = int(group[f'kalshi_{suffix}_bid'].max() > 0.8)\n",
    "\n",
    "def find_sell_point(row):\n",
    "    \"\"\"If correct, always sell. Otherwise check if bid ever exceeded 0.8.\"\"\"\n",
    "    if row['correct_b365'] == 1:\n",
    "        return 1\n",
    "    return sell_conditions.get((row['home'], row['away'], row['b365_highest_clv']), 0)\n",
    "\n",
    "# Apply to DataFrame\n",
    "df_early['early_sell'] = df_early.apply(find_sell_point, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "57deb75e-c4e4-42ff-aaf6-bf75e10d4865",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "def assign_bin(number):\n",
    "    bins = np.histogram(df_early['elapsed'], bins=num_bins)[1]\n",
    "    for i in range(len(bins)+1):\n",
    "        if number >= bins[i] and number <= bins[i+1]:\n",
    "            return i\n",
    "df_early = df_early[(df_early['elapsed']>-492.95)&(df_early['elapsed']<=-427.1)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "50ca6252-c75f-4993-b148-eb3f055afcc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_early.to_csv('test.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23d06e86-1bf0-4247-b74e-0674b48c2417",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0167f9d-d6dd-4c53-9899-c3a2d385cd9f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7aa69b8b-0e76-478b-92a4-7c552be97daa",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47e0719d-31b2-40a6-9945-22f19fb1615e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "significance-testing-header",
   "metadata": {},
   "source": [
    "## Significance Testing (Profit-focused on binned_df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "import-stats",
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy import stats\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "onesample-ztest",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== One-sample z-tests (Returns > 0?) ===\n",
      "B365:        mean=-0.0788, z=-1.0554, p=0.8544, n=432, Sig: no\n",
      "Kalshi:      mean=0.0880, z=0.8474, p=0.1984, n=432, Sig: no\n",
      "K+B365:      mean=-0.0526, z=-0.6824, p=0.7525, n=432, Sig: no\n",
      "Kalshi-gated: mean=-0.1308, z=-0.8351, p=0.7982, n=133, Sig: no\n"
     ]
    }
   ],
   "source": [
    "b365_returns = binned_df['returns_b365'].dropna()\n",
    "kalshi_returns = binned_df['returns_kalshi'].dropna()\n",
    "kalshi_b365_returns = binned_df['returns_kalshi_b365'].dropna()\n",
    "gated_returns = bet_df['returns_kalshi'].dropna()          # ROI-gated (skip) Kalshi\n",
    "\n",
    "def ztest_1samp(data, mu=0, alternative='greater'):\n",
    "    n = len(data)\n",
    "    x_bar = data.mean()\n",
    "    s = data.std(ddof=1)\n",
    "    z = (x_bar - mu) / (s / np.sqrt(n))\n",
    "    if alternative == 'greater':\n",
    "        p = 1 - stats.norm.cdf(z)\n",
    "    elif alternative == 'less':\n",
    "        p = stats.norm.cdf(z)\n",
    "    else:\n",
    "        p = 2 * (1 - stats.norm.cdf(abs(z)))\n",
    "    return z, p\n",
    "\n",
    "z_b365, p_b365 = ztest_1samp(b365_returns, alternative='greater')\n",
    "z_kalshi, p_kalshi = ztest_1samp(kalshi_returns, alternative='greater')\n",
    "z_kb, p_kb = ztest_1samp(kalshi_b365_returns, alternative='greater')\n",
    "z_g, p_g = ztest_1samp(gated_returns, alternative='greater')\n",
    "\n",
    "print('=== One-sample z-tests (Returns > 0?) ===')\n",
    "print(f'B365:        mean={b365_returns.mean():.4f}, z={z_b365:.4f}, p={p_b365:.4f}, n={len(b365_returns)}, Sig: {\"YES\" if p_b365 < 0.05 else \"no\"}')\n",
    "print(f'Kalshi:      mean={kalshi_returns.mean():.4f}, z={z_kalshi:.4f}, p={p_kalshi:.4f}, n={len(kalshi_returns)}, Sig: {\"YES\" if p_kalshi < 0.05 else \"no\"}')\n",
    "print(f'K+B365:      mean={kalshi_b365_returns.mean():.4f}, z={z_kb:.4f}, p={p_kb:.4f}, n={len(kalshi_b365_returns)}, Sig: {\"YES\" if p_kb < 0.05 else \"no\"}')\n",
    "print(f'Kalshi-gated: mean={gated_returns.mean():.4f}, z={z_g:.4f}, p={p_g:.4f}, n={len(gated_returns)}, Sig: {\"YES\" if p_g < 0.05 else \"no\"}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "confidence-intervals",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 95% CI for Mean Flat Returns ===\n",
      "B365:        (np.float64(-0.22553789834794646), np.float64(0.06794530575535386))\n",
      "Kalshi:      (np.float64(-0.11612862353237784), np.float64(0.2921453368165689))\n",
      "K+B365:      (np.float64(-0.2039559063208986), np.float64(0.09882684274685932))\n",
      "Kalshi-gated: (np.float64(-0.4404885902950574), np.float64(0.17896139948512413))\n",
      "=== Sharpe (per-bet | annualized) ===\n",
      "B365          per-bet=-0.0508  annual=-3.1002  (3727 bets/yr)\n",
      "Kalshi        per-bet=0.0408  annual=2.4890  (3727 bets/yr)\n",
      "K+B365        per-bet=-0.0328  annual=-2.0046  (3727 bets/yr)\n",
      "Kalshi-gated  per-bet=-0.0724  annual=-2.4531  (1148 bets/yr)\n",
      "=== Final Compounded Returns ===\n",
      "B365:        $1,121.68\n",
      "Kalshi:      $1,541.54\n",
      "K+B365:      $801.84\n",
      "Kalshi-gated: $773.17\n"
     ]
    }
   ],
   "source": [
    "def ci(data):\n",
    "    n = len(data)\n",
    "    mean = data.mean()\n",
    "    std_err = stats.sem(data)\n",
    "    interval = std_err * stats.t.ppf(0.975, n - 1)\n",
    "    return mean - interval, mean + interval\n",
    "\n",
    "gated_returns = bet_df['returns_kalshi'].dropna()\n",
    "\n",
    "print('=== 95% CI for Mean Flat Returns ===')\n",
    "print(f'B365:        {ci(b365_returns)}')\n",
    "print(f'Kalshi:      {ci(kalshi_returns)}')\n",
    "print(f'K+B365:      {ci(kalshi_b365_returns)}')\n",
    "print(f'Kalshi-gated: {ci(gated_returns)}')\n",
    "\n",
    "# Sharpe \u2014 per-bet, plus annualized by ACTUAL bet frequency (not sqrt(365))\n",
    "span_days = (binned_df['timestamp'].max() - binned_df['timestamp'].min()) / 86400\n",
    "bets_per_year = len(binned_df) / span_days * 365\n",
    "gated_bpy   = len(bet_df) / span_days * 365      # gated places fewer bets over the same window\n",
    "\n",
    "def sharpe(returns, periods_per_year):\n",
    "    s = returns.std(ddof=1)\n",
    "    if s == 0:\n",
    "        return 0.0, 0.0\n",
    "    per_bet = returns.mean() / s\n",
    "    return per_bet, per_bet * np.sqrt(periods_per_year)\n",
    "\n",
    "print('=== Sharpe (per-bet | annualized) ===')\n",
    "for name, r, bpy in [('B365', b365_returns, bets_per_year),\n",
    "                     ('Kalshi', kalshi_returns, bets_per_year),\n",
    "                     ('K+B365', kalshi_b365_returns, bets_per_year),\n",
    "                     ('Kalshi-gated', gated_returns, gated_bpy)]:\n",
    "    pb, ann = sharpe(r, bpy)\n",
    "    print(f'{name:13} per-bet={pb:.4f}  annual={ann:.4f}  ({bpy:.0f} bets/yr)')\n",
    "\n",
    "print('=== Final Compounded Returns ===')\n",
    "print(f'B365:        ${binned_df[\"kelly_b365_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Kalshi:      ${binned_df[\"kelly_kalshi_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'K+B365:      ${binned_df[\"kelly_kalshi_b365_comp\"].iloc[-1]:,.2f}')\n",
    "print(f'Kalshi-gated: ${bet_df[\"kelly_kalshi_comp\"].iloc[-1]:,.2f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "173efa52-3561-4cfd-959c-9f015a5fff24",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "ab756171-b89f-4f83-ad64-4bb65fef3f03",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_filtered_nan_ = df_filtered_nan_[df_filtered_nan_['elapsed']>-90]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "d8e0cbc1-3553-4269-8c84-63fb3e698a85",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_last = df_filtered_nan_.sort_values(by='timestamp', ascending=True).drop_duplicates(['home', 'away'], keep='first')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "4ddc2498-5717-481a-b01b-340eaed02878",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_last = df_last[(df_last['kalshi_highest_clv_val']>=0.04)&(df_last['kalshi_highest_clv_val']<0.11)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "6e7568a9-e44e-4039-a242-f6a160db997c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# df_last = df_last\n",
    "df_last['returns'] = df_last.apply(lambda row: row['correct_kalshi']*(1/row['kalshi_'+bet_dict[row['kalshi_highest_clv']]+'_ask'])-1, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "c2d1eeef-3421-4488-827a-8946327200a0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['timestamp', 'game_date', 'league', 'home', 'away', 'status', 'elapsed',\n",
       "       'home_goals', 'away_goals', 'pin_home', 'pin_draw', 'pin_away',\n",
       "       'b365_home', 'b365_draw', 'b365_away', 'kalshi_home_mid',\n",
       "       'kalshi_home_bid', 'kalshi_home_ask', 'kalshi_draw_mid',\n",
       "       'kalshi_draw_bid', 'kalshi_draw_ask', 'kalshi_away_mid',\n",
       "       'kalshi_away_bid', 'kalshi_away_ask', 'pin_fair_home', 'pin_fair_draw',\n",
       "       'pin_fair_away', 'clv_b365_home', 'clv_b365_draw', 'clv_b365_away',\n",
       "       'clv_kalshi_home', 'clv_kalshi_draw', 'clv_kalshi_away', 'ftr',\n",
       "       'result_home', 'result_away', 'b365_highest_clv',\n",
       "       'b365_highest_clv_val', 'kalshi_highest_clv', 'kalshi_highest_clv_val',\n",
       "       'correct_b365', 'correct_kalshi', 'bin', 'returns'],\n",
       "      dtype='object')"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_last.to_csv('test.csv')\n",
    "df_last.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b1cfa54-eea6-461b-b25f-4322351a6e25",
   "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.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}