diff --git a/PEV 3.11-10.26.xlsx b/PEV 3.11-10.26.xlsx new file mode 100644 index 0000000..d7fae57 Binary files /dev/null and b/PEV 3.11-10.26.xlsx differ diff --git a/dao/Database.py b/dao/Database.py index db3a413..40305a8 100644 --- a/dao/Database.py +++ b/dao/Database.py @@ -1,6 +1,7 @@ import pymysql.cursors from data_model import MysqlConfig + class Database: def __init__(self, config: MysqlConfig): self.config = config diff --git a/data_model.py b/data_model.py index 6a648c3..343d9a1 100644 --- a/data_model.py +++ b/data_model.py @@ -61,9 +61,10 @@ class OddsJamOrder(BaseModel): home_or_away: Optional[str] = Field(None, description="主队或客队") start_timestamp: Optional[int] = Field(None, description="开始时间戳") selected_sportsbook: Optional[str] = Field(None, description="选定的博彩公司") + bet_status: Optional[str] = Field(None, description="投注状态") -class OddjamsBet(BaseModel): +class OddsjamBet(BaseModel): Sportsbook: str BetName: str MarketName: str diff --git a/pev_eda.ipynb b/pev_eda.ipynb new file mode 100644 index 0000000..477f159 --- /dev/null +++ b/pev_eda.ipynb @@ -0,0 +1,2030 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from datetime import datetime\n", + "from dao.Database import Database\n", + "from data_model import MysqlConfig, OddsJamOrder, OddsjamBet" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "config_file_path = 'config\\mysql_config.json'\n", + "mysql_config = MysqlConfig.parse_file(config_file_path)\n", + "dao = Database(mysql_config)\n", + "\n", + "# query\n", + "select_query = \"SELECT * FROM bet.oddsjam_order where bet_status in ('won', 'lost');\"\n", + "raw_data_list = dao.fetchall(query=select_query)\n", + "order_data_list = [OddsJamOrder(**data).model_dump() for data in raw_data_list]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'id': 'edge:10012-40335-2024-08-31:1st_half_team_total:oud-heverlee_leuven_over_0_5:oud-heverlee_leuven_under_0_5',\n", + " 'bet_id': 'edge:10012-40335-2024-08-31:1st_half_team_total:oud-heverlee_leuven_over_0_5:oud-heverlee_leuven_under_0_5',\n", + " 'game_id': '10012-40335-2024-08-31',\n", + " 'away_bet_name': 'Oud-Heverlee Leuven Under 0.5',\n", + " 'home_bet_name': 'Oud-Heverlee Leuven Over 0.5',\n", + " 'away_no_vig_price': 102.85,\n", + " 'home_no_vig_price': -102.85,\n", + " 'away_price': -110.0,\n", + " 'home_price': -102.0,\n", + " 'away_sportsbooks': ['FanDuel', 'DraftKings'],\n", + " 'home_sportsbooks': ['Bovada'],\n", + " 'is_live': False,\n", + " 'in_game_status': None,\n", + " 'period': None,\n", + " 'clock': None,\n", + " 'last_play': None,\n", + " 'home_score': 0,\n", + " 'away_score': 0,\n", + " 'sport': 'Soccer',\n", + " 'league': 'Belgium - Jupiler Pro League',\n", + " 'market': '1st Half Team Total',\n", + " 'market_width': 22.0,\n", + " 'percentage': 0.41,\n", + " 'start_date': datetime.datetime(2024, 9, 1, 2, 45),\n", + " 'home_team': 'Oud-Heverlee Leuven',\n", + " 'away_team': 'Royal Standard de Liège',\n", + " 'timestamp': 1725109490.1882415,\n", + " 'away_deep_link_map': '{\"FanDuel\": {\"urls\": {\"ios\": {\"url\": \"https://sportsbook.fanduel.com/addToBetslip?marketId=42.441130559&selectionId=7044483\", \"adds_to_slip\": true}, \"android\": {\"url\": \"https://sportsbook.fanduel.com/addToBetslip?marketId=42.441130559&selectionId=7044483\", \"adds_to_slip\": true}, \"desktop\": {\"url\": \"https://sportsbook.fanduel.com/addToBetslip?marketId=42.441130559&selectionId=7044483\", \"adds_to_slip\": true}}, \"mobile\": \"https://sportsbook.fanduel.com/addToBetslip?marketId=42.441130559&selectionId=7044483\", \"desktop\": \"https://sportsbook.fanduel.com/addToBetslip?marketId=42.441130559&selectionId=7044483\"}, \"DraftKings\": {\"urls\": {\"ios\": {\"url\": \"dksb://sb/addbet/0OU76429572U50_3\", \"adds_to_slip\": true}, \"android\": {\"url\": \"dksb://sb/addbet/0OU76429572U50_3\", \"adds_to_slip\": true}, \"desktop\": {\"url\": \"https://sportsbook.draftkings.com/event/30960358?outcomes=0OU76429572U50_3\", \"adds_to_slip\": true}}, \"mobile\": \"dksb://sb/addbet/0OU76429572U50_3\", \"desktop\": \"https://sportsbook.draftkings.com/event/30960358?outcomes=0OU76429572U50_3\"}}',\n", + " 'home_deep_link_map': '{}',\n", + " 'status': 'unplayed',\n", + " 'away_edge_percent': None,\n", + " 'bet_placed': False,\n", + " 'same_market_bet_placed': False,\n", + " 'same_game_bet_placed': False,\n", + " 'away_rec_size': None,\n", + " 'rec_size': 1,\n", + " 'home_edge_percent': 0.41,\n", + " 'order_status': 0,\n", + " 'amount': None,\n", + " 'create_time': '20240831211007',\n", + " 'sportsbook_orderid': None,\n", + " 'home_or_away': 'home',\n", + " 'start_timestamp': 1725129900000,\n", + " 'selected_sportsbook': 'Bovada',\n", + " 'bet_status': 'won'}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "order_data_list[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "db_df = pd.DataFrame(order_data_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": {}, + "outputs": [], + "source": [ + "def get_benefit(row):\n", + " home_or_away = row['home_or_away']\n", + " price = row[f'{home_or_away}_price'] / 100\n", + " if row['outcome'] == -1:\n", + " return -1\n", + " if price >= 0:\n", + " return price \n", + " else:\n", + " return 1 / abs(price) \n", + " \n", + "db_df['outcome'] = db_df['bet_status'].apply(lambda x: 1 if x == 'won' else -1)\n", + "db_df['benefit'] = db_df.apply(get_benefit, axis=1)\n", + "db_df['date'] = db_df['start_timestamp'].apply(lambda x: datetime.fromtimestamp(x // 1000).strftime(\"%Y-%m-%d\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": {}, + "outputs": [], + "source": [ + "def get_odds(row):\n", + " home_or_away = row['home_or_away']\n", + " price = row[f'{home_or_away}_price'] / 100\n", + "\n", + " if price >= 0:\n", + " return price\n", + " else:\n", + " return 1 / abs(price) " + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "metadata": {}, + "outputs": [], + "source": [ + "db_df['odds2'] = db_df.apply(get_odds, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.4551825216755248" + ] + }, + "execution_count": 144, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df['odds2'].mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "bet_status\n", + "lost 13439\n", + "won 5902\n", + "dtype: int64" + ] + }, + "execution_count": 133, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df.groupby('bet_status').size()" + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.4551825216755248" + ] + }, + "execution_count": 130, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[db_df['bet_status'].isin(['won', 'lost'])]['benefit'].mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1477.1127282023094" + ] + }, + "execution_count": 149, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[['home_price', 'away_price', 'home_or_away', 'bet_status', 'benefit']]['benefit']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 1780.000000\n", + "mean 1.165759\n", + "std 0.338662\n", + "min 0.359712\n", + "25% 0.909091\n", + "50% 1.080000\n", + "75% 1.360000\n", + "max 4.000000\n", + "Name: odds2, dtype: float64" + ] + }, + "execution_count": 166, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[(db_df['market_width'] <= 25) & (db_df['market_width'] >= 20)]['odds2'].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "bet_status\n", + "lost 932\n", + "won 848\n", + "dtype: int64" + ] + }, + "execution_count": 168, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[(db_df['market_width'] <= 25) & (db_df['market_width'] >= 20)].groupby(['bet_status']).size()" + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.4764044943820225" + ] + }, + "execution_count": 171, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "848 / (848+932)" + ] + }, + { + "cell_type": "code", + "execution_count": 170, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0318921348314607" + ] + }, + "execution_count": 170, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "848 / (848+932) * (1.166 + 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 5902.000000\n", + "mean 2.026751\n", + "std 1.825651\n", + "min 0.166667\n", + "25% 1.020000\n", + "50% 1.430000\n", + "75% 2.200000\n", + "max 25.000000\n", + "Name: odds2, dtype: float64" + ] + }, + "execution_count": 156, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[db_df['bet_status'] == 'won']['odds2'].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 13439.000000\n", + "mean 4.082506\n", + "std 3.955954\n", + "min 0.235294\n", + "25% 1.500000\n", + "50% 2.700000\n", + "75% 5.400000\n", + "max 35.000000\n", + "Name: odds2, dtype: float64" + ] + }, + "execution_count": 157, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df[db_df['bet_status'] == 'lost']['odds2'].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 19341.000000\n", + "mean 392.987177\n", + "std 1713.715890\n", + "min 5.000000\n", + "25% 33.000000\n", + "50% 75.000000\n", + "75% 244.000000\n", + "max 49049.000000\n", + "Name: market_width, dtype: float64" + ] + }, + "execution_count": 116, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "db_df['market_width'].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "lost_df = db_df[db_df['outcome'] == -1]" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 13439.000000\n", + "mean 517.244661\n", + "std 2026.572578\n", + "min 5.000000\n", + "25% 40.000000\n", + "50% 105.000000\n", + "75% 348.000000\n", + "max 49049.000000\n", + "Name: market_width, dtype: float64" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lost_df['market_width'].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "raw_df = pd.read_excel('PEV 3.11-10.26.xlsx', sheet_name='原始数据')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
away_teambest_price_away_namebest_price_away_oddbest_price_away_odd_books_newbest_price_home_namebest_price_home_oddbest_price_home_odd_books_newhome_teamleaguemarketsportdateweighted_price_away_oddweighted_price_home_oddhandicap to gooutcomeodds USodds EUbenefit
0FC AugsburgUnder 4.5-150OddsJam,William Hill,CaesarsOver 4.5162FanDuelFC Bayern MunichGermany - BundesligaTotal Goalssoccer2023-03-11-141.153142.032Over 4.51.01621.6200001.620000
1FC AugsburgFC Bayern Munich Under 3.5-137OddsJamFC Bayern Munich Over 3.5146FanDuelFC Bayern MunichGermany - BundesligaTeam Totalsoccer2023-03-11-126.848127.536FC Bayern Munich Over 3.51.01461.4600001.460000
2FC AugsburgFC Bayern Munich Under 1.5-135DraftKings,10bet,DraftKings (Tennessee)FC Bayern Munich Over 1.5128FanDuelFC Bayern MunichGermany - Bundesliga1st Half Team Totalsoccer2023-03-11-123.407123.898FC Bayern Munich Over 1.51.01281.2800001.280000
6LiverpoolBournemouth Under 0.5132FanDuelBournemouth Over 0.5-137BarstoolBournemouthEngland - Premier LeagueTeam Totalsoccer2023-03-11141.875-141.519Bournemouth Over 0.51.0-1370.7299270.729927
8FC AugsburgUnder 3.5141OddsJamOver 3.5-138FanDuelFC Bayern MunichGermany - BundesligaTotal Goalssoccer2023-03-11147.467-146.976Over 3.51.0-1380.7246380.724638
............................................................
80358New Orleans PelicansMemphis Grizzlies +1.5-143TonyBetNew Orleans Pelicans -1.5135BetMGM,partypoker,BorgataMemphis GrizzliesNBA1st Half Point SpreadBasketball2023-10-26-133.490133.546New Orleans Pelicans -1.51.01351.3500001.350000
80360Dallas MavericksSan Antonio Spurs -0.5148FanDuelDallas Mavericks +0.5-155Pinny,PinnacleSan Antonio SpursNBA1st Half Point SpreadBasketball2023-10-26146.604-146.127San Antonio Spurs -0.51.01481.4800001.480000
80361Dallas MavericksSan Antonio Spurs +0.5128FanDuelDallas Mavericks -0.5-132Pinny,PinnacleSan Antonio SpursNBA1st Half Point SpreadBasketball2023-10-26126.875-126.419San Antonio Spurs +0.51.01281.2800001.280000
80363Portland Trail BlazersLos Angeles Clippers Under 60.5-116Pinny,PinnacleLos Angeles Clippers Over 60.5110partypoker,BetMGM,BorgataLos Angeles ClippersNBA1st Half Team TotalBasketball2023-10-26-106.549106.565Los Angeles Clippers Over 60.51.01101.1000001.100000
80365Portland Trail BlazersLos Angeles Clippers -3.5104Unibet,BetRivers,betPARXPortland Trail Blazers +3.5-110BetAnySports,LowVig,BetOnline,SuperBookLos Angeles ClippersNBA1st Quarter Point SpreadBasketball2023-10-26103.013-102.757Los Angeles Clippers -3.51.01041.0400001.040000
\n", + "

37469 rows × 19 columns

\n", + "
" + ], + "text/plain": [ + " away_team best_price_away_name \\\n", + "0 FC Augsburg Under 4.5 \n", + "1 FC Augsburg FC Bayern Munich Under 3.5 \n", + "2 FC Augsburg FC Bayern Munich Under 1.5 \n", + "6 Liverpool Bournemouth Under 0.5 \n", + "8 FC Augsburg Under 3.5 \n", + "... ... ... \n", + "80358 New Orleans Pelicans Memphis Grizzlies +1.5 \n", + "80360 Dallas Mavericks San Antonio Spurs -0.5 \n", + "80361 Dallas Mavericks San Antonio Spurs +0.5 \n", + "80363 Portland Trail Blazers Los Angeles Clippers Under 60.5 \n", + "80365 Portland Trail Blazers Los Angeles Clippers -3.5 \n", + "\n", + " best_price_away_odd best_price_away_odd_books_new \\\n", + "0 -150 OddsJam,William Hill,Caesars \n", + "1 -137 OddsJam \n", + "2 -135 DraftKings,10bet,DraftKings (Tennessee) \n", + "6 132 FanDuel \n", + "8 141 OddsJam \n", + "... ... ... \n", + "80358 -143 TonyBet \n", + "80360 148 FanDuel \n", + "80361 128 FanDuel \n", + "80363 -116 Pinny,Pinnacle \n", + "80365 104 Unibet,BetRivers,betPARX \n", + "\n", + " best_price_home_name best_price_home_odd \\\n", + "0 Over 4.5 162 \n", + "1 FC Bayern Munich Over 3.5 146 \n", + "2 FC Bayern Munich Over 1.5 128 \n", + "6 Bournemouth Over 0.5 -137 \n", + "8 Over 3.5 -138 \n", + "... ... ... \n", + "80358 New Orleans Pelicans -1.5 135 \n", + "80360 Dallas Mavericks +0.5 -155 \n", + "80361 Dallas Mavericks -0.5 -132 \n", + "80363 Los Angeles Clippers Over 60.5 110 \n", + "80365 Portland Trail Blazers +3.5 -110 \n", + "\n", + " best_price_home_odd_books_new home_team \\\n", + "0 FanDuel FC Bayern Munich \n", + "1 FanDuel FC Bayern Munich \n", + "2 FanDuel FC Bayern Munich \n", + "6 Barstool Bournemouth \n", + "8 FanDuel FC Bayern Munich \n", + "... ... ... \n", + "80358 BetMGM,partypoker,Borgata Memphis Grizzlies \n", + "80360 Pinny,Pinnacle San Antonio Spurs \n", + "80361 Pinny,Pinnacle San Antonio Spurs \n", + "80363 partypoker,BetMGM,Borgata Los Angeles Clippers \n", + "80365 BetAnySports,LowVig,BetOnline,SuperBook Los Angeles Clippers \n", + "\n", + " league market sport \\\n", + "0 Germany - Bundesliga Total Goals soccer \n", + "1 Germany - Bundesliga Team Total soccer \n", + "2 Germany - Bundesliga 1st Half Team Total soccer \n", + "6 England - Premier League Team Total soccer \n", + "8 Germany - Bundesliga Total Goals soccer \n", + "... ... ... ... \n", + "80358 NBA 1st Half Point Spread Basketball \n", + "80360 NBA 1st Half Point Spread Basketball \n", + "80361 NBA 1st Half Point Spread Basketball \n", + "80363 NBA 1st Half Team Total Basketball \n", + "80365 NBA 1st Quarter Point Spread Basketball \n", + "\n", + " date weighted_price_away_odd weighted_price_home_odd \\\n", + "0 2023-03-11 -141.153 142.032 \n", + "1 2023-03-11 -126.848 127.536 \n", + "2 2023-03-11 -123.407 123.898 \n", + "6 2023-03-11 141.875 -141.519 \n", + "8 2023-03-11 147.467 -146.976 \n", + "... ... ... ... \n", + "80358 2023-10-26 -133.490 133.546 \n", + "80360 2023-10-26 146.604 -146.127 \n", + "80361 2023-10-26 126.875 -126.419 \n", + "80363 2023-10-26 -106.549 106.565 \n", + "80365 2023-10-26 103.013 -102.757 \n", + "\n", + " handicap to go outcome odds US odds EU benefit \n", + "0 Over 4.5 1.0 162 1.620000 1.620000 \n", + "1 FC Bayern Munich Over 3.5 1.0 146 1.460000 1.460000 \n", + "2 FC Bayern Munich Over 1.5 1.0 128 1.280000 1.280000 \n", + "6 Bournemouth Over 0.5 1.0 -137 0.729927 0.729927 \n", + "8 Over 3.5 1.0 -138 0.724638 0.724638 \n", + "... ... ... ... ... ... \n", + "80358 New Orleans Pelicans -1.5 1.0 135 1.350000 1.350000 \n", + "80360 San Antonio Spurs -0.5 1.0 148 1.480000 1.480000 \n", + "80361 San Antonio Spurs +0.5 1.0 128 1.280000 1.280000 \n", + "80363 Los Angeles Clippers Over 60.5 1.0 110 1.100000 1.100000 \n", + "80365 Los Angeles Clippers -3.5 1.0 104 1.040000 1.040000 \n", + "\n", + "[37469 rows x 19 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "raw_df[raw_df['outcome'] == 1]" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "metadata": {}, + "outputs": [], + "source": [ + "data_df = raw_df.copy()\n", + "data_df['investment'] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "data_df = db_df.copy()\n", + "data_df = data_df[data_df['market_width'] <= 25]\n", + "data_df = data_df[data_df['market_width'] >= 20]\n", + "# data_df = data_df[data_df['selected_sportsbook'] == '1XBet']\n", + "data_df['investment'] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "年化夏普率: 0.8002309699976565\n", + "ROI: 0.00683522830064485\n" + ] + } + ], + "source": [ + "def clac_closing_balance(day_benefit_list, pre_balance=1000, pre_benefit=0):\n", + " closing_balance_list = []\n", + " for i, benefit in enumerate(day_benefit_list):\n", + " closing_balance = pre_balance + pre_benefit / 3 + benefit * 2 / 3\n", + " closing_balance_list.append(closing_balance)\n", + " pre_balance = closing_balance\n", + " pre_benefit = benefit\n", + " return closing_balance_list\n", + "\n", + "def calc_in_transit_funds_ratio(daily_investment_list, closing_balance_list, start_closing_balance=1000):\n", + " assert len(daily_investment_list) == len(closing_balance_list)\n", + " ratio_list = []\n", + " for i, daily_investment in enumerate(daily_investment_list):\n", + " if i == 0:\n", + " ratio = daily_investment / start_closing_balance\n", + " else:\n", + " ratio = daily_investment / closing_balance_list[i-1]\n", + " ratio_list.append(ratio)\n", + " return ratio_list\n", + "\n", + "\n", + "init_balance = 1000\n", + "res_df = data_df.groupby('date').agg({'investment':'sum', 'benefit':'sum'}).rename(columns={'investment':'当日投入', 'benefit':'日收益'}).reset_index()\n", + "res_df['日收益率'] = res_df['日收益'] / res_df['当日投入']\n", + "res_df['累计收益'] = res_df['日收益'].cumsum()\n", + "res_df['累计投入'] = res_df['当日投入'].cumsum()\n", + "res_df['累计收益率'] = res_df['累计收益'] / res_df['累计投入']\n", + "\n", + "day_benefit_list = res_df['日收益'].tolist()\n", + "closing_balance_list = clac_closing_balance(day_benefit_list=day_benefit_list, pre_balance=init_balance)\n", + "res_df['日末余额(1.6天结算)'] = closing_balance_list\n", + "\n", + "daily_investment_list = res_df['当日投入'].tolist()\n", + "res_df['在途资金比例'] = calc_in_transit_funds_ratio(daily_investment_list=daily_investment_list, closing_balance_list=closing_balance_list, start_closing_balance=init_balance)\n", + "annualized_sharpe_ratio = res_df['日收益'].sum() / init_balance / res_df['日收益率'].std() * ((365 / len(res_df))**0.5)\n", + "print(f'年化夏普率: {annualized_sharpe_ratio}')\n", + "roi = res_df['日收益'].sum() / res_df['当日投入'].sum()\n", + "print(f'ROI: {roi}')" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "mode": "lines", + "name": "日末余额", + "type": "scatter", + "x": [ + "2024-08-26T00:00:00", + "2024-08-27T00:00:00", + "2024-08-28T00:00:00", + "2024-08-29T00:00:00", + "2024-08-30T00:00:00", + "2024-08-31T00:00:00", + "2024-09-01T00:00:00" + ], + "y": [ + 1002.6656885456886, + 1029.320085562455, + 1031.6353596927408, + 1014.877157613626, + 1004.9262841780602, + 1010.5006139053265, + 1012.8869095027875 + ], + "yaxis": "y" + }, + { + "mode": "lines", + "name": "累计收益率", + "type": "scatter", + "x": [ + "2024-08-26T00:00:00", + "2024-08-27T00:00:00", + "2024-08-28T00:00:00", + "2024-08-29T00:00:00", + "2024-08-30T00:00:00", + "2024-08-31T00:00:00", + "2024-09-01T00:00:00" + ], + "y": [ + 0.17384925297968776, + 0.18095199109662177, + 0.05703148399117068, + 0.013933178120379116, + 0.0027913825488691288, + 0.01187018704065157, + 0.00683522830064485 + ], + "yaxis": "y2" + } + ], + "layout": { + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "日末余额(1.6天结算) 和 累计收益率 折线图" + }, + "xaxis": { + "title": { + "text": "日期" + } + }, + "yaxis": { + "title": { + "text": "金额" + } + }, + "yaxis2": { + "overlaying": "y", + "side": "right", + "tickformat": ".1%", + "title": { + "text": "收益率" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "import plotly.graph_objects as go\n", + "\n", + "res_df['日期'] = pd.to_datetime(res_df['date'])\n", + "\n", + "fig = go.Figure()\n", + "\n", + "# 添加日末余额(1.6天结算)的折线图\n", + "fig.add_trace(go.Scatter(\n", + " x=res_df['日期'],\n", + " y=res_df['日末余额(1.6天结算)'],\n", + " mode='lines',\n", + " name='日末余额',\n", + " yaxis='y1'\n", + "))\n", + "\n", + "# 添加日收益率的折线图\n", + "# fig.add_trace(go.Scatter(\n", + "# x=res_df['日期'],\n", + "# y=res_df['日收益率'],\n", + "# mode='lines',\n", + "# name='日收益率',\n", + "# yaxis='y2'\n", + "# ))\n", + "\n", + "# 添加累计收益率的折线图\n", + "fig.add_trace(go.Scatter(\n", + " x=res_df['日期'],\n", + " y=res_df['累计收益率'],\n", + " mode='lines',\n", + " name='累计收益率',\n", + " yaxis='y2'\n", + "))\n", + "\n", + "# 添加在途资金比例的折线图\n", + "# fig.add_trace(go.Scatter(\n", + "# x=res_df['日期'],\n", + "# y=res_df['在途资金比例'],\n", + "# mode='lines',\n", + "# name='在途资金比例',\n", + "# yaxis='y2'\n", + "# ))\n", + "\n", + "# 设置布局,添加第二个y轴\n", + "fig.update_layout(\n", + " title='日末余额(1.6天结算) 和 累计收益率 折线图',\n", + " xaxis=dict(title='日期'),\n", + " yaxis=dict(title='金额'),\n", + " yaxis2=dict(title='收益率', overlaying='y', side='right', tickformat='.1%')\n", + ")\n", + "\n", + "\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 基于market width 进行分层, 然后计算年华夏普率值, 进行资金配比" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
outcomecount
0-1.039838
41.037469
20.02804
1-0.5164
30.591
\n", + "
" + ], + "text/plain": [ + " outcome count\n", + "0 -1.0 39838\n", + "4 1.0 37469\n", + "2 0.0 2804\n", + "1 -0.5 164\n", + "3 0.5 91" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "raw_df.groupby('outcome').agg({'away_team': 'count'}).rename(columns={'away_team': 'count'}).reset_index().sort_values(by='count', ascending=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.1811803969722776" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "raw_df['odds EU'].mean()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}