{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Lessons from turf betting\ncombining the lessons from the infamous book to evaluate lessons from horse races","metadata":{}},{"cell_type":"code","source":"# import libraries\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport altair as alt\nimport seaborn as sns\nimport plotly.express as px","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf = pd.read_parquet('nyra_2019_complete.parquet',engine='pyarrow')\n#horseidDf = pd.read_csv('horse_ids.csv')\n#horseNamesDf = pd.read_csv('horse_names.csv')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# add this to a function file\nfrom math import atan\nfrom math import atan2\nfrom math import cos\nfrom math import radians\nfrom math import sin\nfrom math import sqrt\nfrom math import tan\n\n\nclass Vincenty:\n    def __init__(self, c1, c2):        \n        self.meters = self.calc(c1, c2)                   # output distance in meters\n        self.km = self.meters / 1000                      # output distance in kilometers\n        self.mm = self.meters * 1000                      # output distance in millimeters\n        self.miles = self.meters * 0.000621371            # output distance in miles\n        self.n_miles = self.miles * (6080.20 / 5280)      # output distance in nautical miles\n        self.feet = self.miles * 5280                       # output distance in feet\n        self.inches = self.feet * 12                      # output distance in inches\n        self.yards = self.feet / 3                        # output distance in yards\n        \n        \n    def calc(self, c1 ,c2):\n        a = 6378137.0\n        f = 1 / 298.257223563\n        b = (1 - f) * a\n        \n        lat1, lon1, = c1\n        lat2, lon2, = c2\n        \n        U1 = atan((1 - f) * tan(radians(lat1)))\n        U2 = atan((1 - f) * tan(radians(lat2)))\n        \n        L = radians(lon2 - lon1)\n        Lambda = L\n        \n        # COMMON USED VALUES\n        sin_U1 = sin(U1)\n        cos_U1 = cos(U1)\n        sin_U2 = sin(U2)\n        cos_U2 = cos(U2)\n        \n        # VINCENTY CALCULATING DISTANCE IN METERS\n        \n        for iter in range(200):\n            sin_Lambda = sin(Lambda)\n            cos_Lambda = cos(Lambda) \n            sin_Sigma = sqrt((cos_U2 * sin_Lambda) ** 2 + ((cos_U1 * sin_U2) - (sin_U1 * cos_U2 * cos_Lambda)) ** 2 )\n            cos_Sigma = (sin_U1 * sin_U2) + (cos_U1 * cos_U2 * cos_Lambda)\n            Sigma = atan2(sin_Sigma, cos_Sigma)\n            if sin_Sigma != 0:\n                sin_Alpha = (cos_U1 * cos_U2 * sin_Lambda) / sin_Sigma\n            else:\n                sin_Alpha = (cos_U1 * cos_U2 * sin_Lambda) / 0.00001\n            cos_Sq_Alpha = 1 - (sin_Alpha ** 2)\n        \n            try:\n                cos2_Sigma_M = cos_Sigma - ((2 * sin_U1 * sin_U2) / cos_Sq_Alpha)\n            except ZeroDivisionError:\n                cos2_Sigma_M = 0.0\n        \n            C = (f / 16) * cos_Sq_Alpha * (4 + (f * (4 - 3 * cos_Sq_Alpha)))            \n            LP = Lambda\n            Lambda = L + (1 - C) * f * sin_Alpha * (Sigma + C * sin_Sigma * (cos2_Sigma_M + C * cos_Sigma * (-1 + 2 * (cos2_Sigma_M ** 2))))\n        \n            if (abs(Lambda - LP)) < 1e-12:\n                break\n        else:\n            return None\n            \n        u_Sq = cos_Sq_Alpha * ((a ** 2 - b ** 2) / (b ** 2))\n        A = 1 + (u_Sq / 16384) * (4096 + u_Sq * (-768 + u_Sq * (320 - 175 * u_Sq)))\n        B = (u_Sq / 1024) * (256 + u_Sq * (-128 + u_Sq * (74 - 47 * u_Sq)))\n        k1 = (sqrt(1 + u_Sq) - 1) / (sqrt(1 + u_Sq) + 1)\n        #A = (1 + 0.25 * k1 **2) / (1 - k1)\n        #B = k1 * (1 - (3/8) * k1 ** 2)\n        d_Sigma = B * sin_Sigma * (cos2_Sigma_M + (B / 4) * (cos_Sigma * (-1 + 2 * cos2_Sigma_M ** 2) - (B / 6) * cos2_Sigma_M * (-3 + 4 * sin_Sigma ** 2) * (-3 + 4 * cos2_Sigma_M ** 2)))\n        s = b * A * (Sigma - d_Sigma)\n        Alpha_1 = atan2(cos_U2 * sin_Sigma, cos_U1 * sin_U2 - sin_U1 * cos_U2 * cos_Sigma)        \n        Alpha_2 = atan2(cos_U1 * sin_Sigma, - sin_U1 * cos_U2 + cos_U1 * sin_U2 * cos_Sigma)\n        \n        return s\n\n#Functions Lambda\n\nreplace = lambda x: x.replace(\" \", '')\ngeo = lambda x, y: Vincenty(x,y).meters","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf.head()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"(kaggleDf['odds']/100).describe()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf[kaggleDf['odds']>np.quantile(kaggleDf['odds'],.75)]['race_type'].value_counts()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**The classification of the race passed as as five characters. STK - Stakes, WCL - Waiver Claiming, WMC - Waiver Maiden Claiming, SST - Starter Stakes, SHP - Starter Handicap, CLM - Claiming, STR - Starter Allowance, AOC - Allowance Optionl Claimer, SOC - Starter Optional Claimer, MCL - Maiden Claiming, ALW - Allowance, MSW - Maiden Special Weight.**\n\nlooking at the above classification it might be worth noting that the Stakes race type has the greatest purse amount, as it is the race type horse owners and breeders bring their best horses to compete.\n\n","metadata":{}},{"cell_type":"code","source":"kaggleDf[(kaggleDf['odds']>np.quantile(kaggleDf['odds'],.75)) & (kaggleDf['race_type']=='STK')]['purse'].value_counts().plot.bar()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"a key thing to notice here is the discrepancy in the datasets, there are more races with 1mil purse value than 70k or 30k.\nlets take a look at the highest earners.","metadata":{}},{"cell_type":"code","source":"kaggleDf[(kaggleDf['odds']>np.quantile(kaggleDf['odds'],.98)) & (kaggleDf['race_type']=='STK') & (kaggleDf['purse']>=1_000_000)]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf[(kaggleDf['race_date']=='2019-08-03')&(kaggleDf['purse']>=1000000)]['position_at_finish'].value_counts()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"i have got 419 datapoints on the race where the odds of two horses winning were greater than 95%. so if one had made a bet simply using odds they would have lost money as both these horses came 7th or 5th. unfortunately.","metadata":{}},{"cell_type":"code","source":"milrun = kaggleDf[(kaggleDf['purse']>=1000000)]\nmilRuns = kaggleDf[(kaggleDf['race_date']=='2019-08-03')&(kaggleDf['purse']>=1000000)]\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"milRuns = milrun","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Race Analysis","metadata":{}},{"cell_type":"code","source":"# create latlon\n\nmilrun['latlon'] = np.array([milrun['latitude'].values, milrun['longitude'].values]).T.tolist()\nmilrun['latlon_prev'] = milrun.groupby(['race_id', 'program_number'])['latlon'].transform(lambda x: x.shift(-1))\nmilrun['latlon_prev'] = milrun['latlon_prev'].ffill(axis = 'rows')\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# speed analysis\n# Now it's time to get the distances traveled, speeds and acceleration of each horse in the race\n\nsgps = 0.2898 # Aproximatte time signal GPS, not is 0.25 seconds\n\ndef get_distance(coords1, coords2):\n    coords1 = np.array(coords1.values).tolist()\n    coords2 = np.array(coords2.values).tolist()\n    dist = []\n    for i in range(len(coords1)):\n        dist.append(geo(coords1[i], coords2[i]))    \n    return dist\n\ndef get_aceleration(speed):\n    speed = np.array(speed.values).tolist()         \n    acel = [0]\n    for i in range(1, len(speed)):\n        acel.append((speed[i] - speed[i-1])/sgps)\n    return acel \n\ndef get_total_distance(tdist):\n    return tdist.values.cumsum()\n        \n\nmilrun['distance'] = get_distance(milrun['latlon'], milrun['latlon_prev'])\nmilrun['total_distance'] = milrun.groupby(['race_id', 'program_number'])['distance'].transform(get_total_distance)\nmilrun['total_distance'] = (milrun['total_distance'] - milrun['run_up_distance']) \nmilrun['speed'] = milrun['distance'] / sgps\nmilrun['aceleration'] = get_aceleration(milrun['distance'])\n# still pconfused on how the finish is computed\n#milrun['at_finish'] = (milrun['finish']+milrun['run_up_distance']) - milrun['total_distance'] \nmilrun\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.set(rc = {'figure.figsize':(15,8)})\nrc = sns.scatterplot(data=milrun, x=\"longitude\", y=\"latitude\", hue=\"program_number\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"milrun.head()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_id(pos, pn):\n    pos = np.array(pos.values).tolist()\n    pn = np.array(pn.values).tolist()\n    ident = []\n    for i in range(len(pos)):\n        ident.append(f'{pos[i]}-{pn[i]}')\n    return ident\n    \n\nanimation_race = milrun.copy(deep=True)\nanimation_race['speed1'] = animation_race['speed'] + 0.1\nanimation_race['id'] = get_id(milrun[\"program_number\"])\nanimation_race1 = animation_race[(animation_race['trakus_index'] % 1 == 0)]\nanimation_race1 = animation_race[(animation_race['latlon'] != animation_race['latlon_prev'])]\n\npx.scatter(animation_race1, x='px', y='py', animation_frame='trakus_index',\n           color='program_number',\n           text= 'id',\n           size = 'speed1',  \n           range_x = [animation_race1['px'].min()*1.1, animation_race1['px'].max()*1.1],\n           range_y = [animation_race1['py'].min()*1.1, animation_race1['py'].max()*1.1],\n           title='Race simulation scatter plot', width=900, height=600)\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"milrun[milrun['position_at_finish']==1].jockey","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"(milrun['odds']/100).value_counts()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"the race was held in Saratoga on Dirt , Fast Track.\n\nit seems like Mike E.Smith is the true winner here, the horse that won the mil run went in with the highest odds to win, a turf bettor would have earned the most by betting on the horse that mike e smith was racing.\n\nas per google he must have earned 10% of the prize money, so that should be 600,000 going to the winning horse, 60,000 to the jockey.\n\ngiven that his odds were 0.85, anyone who bet a dollar on those odds is likely to gain 85 cents on it.\n\ni guess mostly everyone knew he was gonna win","metadata":{}},{"cell_type":"markdown","source":"## Jockey Rating metric based on track\ntrying to identify correlations between odds, race track specs: venues, surfaces and race distances. \n\nAttempt to create a jockey rating based on path efficiency.","metadata":{}},{"cell_type":"code","source":"sns.heatmap(milrun.corr(),annot=True)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf.groupby('jockey')['odds','position_at_finish'].describe()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"looking at the jockeys above, we can observe that the odds are not a right measure to evaluate which jockey is going to finish in the top few spots.","metadata":{}},{"cell_type":"code","source":"kaggleDf.columns","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"generally its safe to say with races where the rider carries a lot of weight the odds are going to be against the rider to win, as.","metadata":{}},{"cell_type":"code","source":"kaggleDf.course_type.value_counts()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"replace_map= {'course_type':{'D':1,'T':2,'I':3,'O':4,'M':5}}\n#kaggleDf.replace(replace_map,inplace=True)\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kaggleDf","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}