diff --git a/data/PEV 3.11-10.26.xlsx b/data/PEV 3.11-10.26.xlsx new file mode 100644 index 0000000..d7fae57 Binary files /dev/null and b/data/PEV 3.11-10.26.xlsx differ diff --git a/pev_eda.ipynb b/pev_eda.ipynb index 477f159..f6dbde9 100644 --- a/pev_eda.ipynb +++ b/pev_eda.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 30, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -14,87 +14,34 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 3, "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]" + "def get_oddsjam_order_data_from_db()->List:\n", + " config_file_path = 'config\\mysql_config.json'\n", + " mysql_config = MysqlConfig.parse_file(config_file_path)\n", + " dao = Database(mysql_config)\n", + "\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]\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "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" - } - ], + "outputs": [], "source": [ - "order_data_list[1]" + "simulation_data = get_oddsjam_order_data_from_db()" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -103,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -124,14 +71,13 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 7, "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", @@ -140,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -149,16 +95,16 @@ }, { "cell_type": "code", - "execution_count": 144, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "3.4551825216755248" + "3.5232254542910955" ] }, - "execution_count": 144, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -169,19 +115,19 @@ }, { "cell_type": "code", - "execution_count": 133, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "bet_status\n", - "lost 13439\n", - "won 5902\n", + "lost 19316\n", + "won 8249\n", "dtype: int64" ] }, - "execution_count": 133, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -267,19 +213,19 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "bet_status\n", - "lost 932\n", - "won 848\n", + "lost 1231\n", + "won 1080\n", "dtype: int64" ] }, - "execution_count": 168, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -384,71 +330,6 @@ "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, @@ -460,7 +341,1030 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 20, + "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": 57, + "metadata": {}, + "outputs": [], + "source": [ + "odds_df = data_df.groupby('date').agg({'odds2': 'mean'}).reset_index()" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "name": "lost", + "type": "bar", + "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", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" + ], + "y": [ + 11, + 96, + 138, + 109, + 190, + 99, + 289, + 62, + 89, + 98, + 50 + ], + "yaxis": "y" + }, + { + "name": "won", + "type": "bar", + "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", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" + ], + "y": [ + 12, + 113, + 94, + 79, + 178, + 88, + 284, + 59, + 56, + 80, + 37 + ], + "yaxis": "y" + }, + { + "mode": "markers+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", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" + ], + "y": [ + 1.1965734109952426, + 1.2294773511597243, + 1.3513840696836157, + 1.209601147300343, + 1.1063211834713325, + 1.2904563185996685, + 1.0492181007177963, + 1.2070565768132386, + 1.154097538043856, + 1.1587947147909168, + 1.2470557464128864 + ], + "yaxis": "y2" + } + ], + "layout": { + "barmode": "group", + "legend": { + "title": { + "text": "Variables" + } + }, + "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": "Comparison of Two Variables Across Categories" + }, + "xaxis": { + "title": { + "text": "日期" + } + }, + "yaxis": { + "title": { + "text": "金额" + } + }, + "yaxis2": { + "overlaying": "y", + "side": "right", + "title": { + "text": "收益率" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import plotly.graph_objects as go\n", + "\n", + "\n", + "\n", + "df = pd.pivot_table(data_df, index=['date'], columns=['bet_status'], aggfunc='size', fill_value=0).reset_index()\n", + "df = df.sort_values(by='date')\n", + "date_x = df['date'].tolist()\n", + "# 创建Plotly图形对象\n", + "fig = go.Figure()\n", + "\n", + "cols = df.columns[1:]\n", + "for col in cols:\n", + " y_data = df[col].tolist()\n", + " # 添加第一个变量的数据\n", + " fig.add_trace(go.Bar(\n", + " x=date_x,\n", + " y=y_data,\n", + " name=col,\n", + " # marker_color='indianred' # 设置颜色\n", + " yaxis='y1'\n", + " ))\n", + "\n", + "fig.add_trace(go.Scatter(\n", + " x=odds_df['date'],\n", + " y=odds_df['odds2'],\n", + " mode='markers+lines',\n", + " name='平均赔率',\n", + " yaxis='y2'\n", + "))\n", + "\n", + "# 更新布局\n", + "fig.update_layout(\n", + " barmode='group', # 将柱状图设置为并排显示\n", + " title='Comparison of Two Variables Across Categories',\n", + " xaxis=dict(title='日期'),\n", + " yaxis=dict(title='金额'),\n", + " yaxis2=dict(title='收益率', overlaying='y', side='right'),\n", + " legend_title='Variables'\n", + ")\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, "metadata": {}, "outputs": [ { @@ -484,414 +1388,540 @@ " \n", " \n", " \n", - " away_team\n", - " best_price_away_name\n", - " best_price_away_odd\n", - " best_price_away_odd_books_new\n", - " best_price_home_name\n", - " best_price_home_odd\n", - " best_price_home_odd_books_new\n", - " home_team\n", - " league\n", - " market\n", - " sport\n", - " date\n", - " weighted_price_away_odd\n", - " weighted_price_home_odd\n", - " handicap to go\n", + " id\n", + " bet_id\n", + " game_id\n", + " away_bet_name\n", + " home_bet_name\n", + " away_no_vig_price\n", + " home_no_vig_price\n", + " away_price\n", + " home_price\n", + " away_sportsbooks\n", + " ...\n", + " sportsbook_orderid\n", + " home_or_away\n", + " start_timestamp\n", + " selected_sportsbook\n", + " bet_status\n", " outcome\n", - " odds US\n", - " odds EU\n", " benefit\n", + " date\n", + " odds2\n", + " investment\n", " \n", " \n", " \n", " \n", - " 0\n", - " FC Augsburg\n", - " Under 4.5\n", - " -150\n", - " OddsJam,William Hill,Caesars\n", - " Over 4.5\n", - " 162\n", - " FanDuel\n", - " FC Bayern Munich\n", - " Germany - Bundesliga\n", - " Total Goals\n", - " soccer\n", - " 2023-03-11\n", - " -141.153\n", - " 142.032\n", - " Over 4.5\n", - " 1.0\n", - " 162\n", - " 1.620000\n", - " 1.620000\n", - " \n", - " \n", " 1\n", - " FC Augsburg\n", - " FC Bayern Munich Under 3.5\n", - " -137\n", - " OddsJam\n", - " FC Bayern Munich Over 3.5\n", - " 146\n", - " FanDuel\n", - " FC Bayern Munich\n", - " Germany - Bundesliga\n", - " Team Total\n", - " soccer\n", - " 2023-03-11\n", - " -126.848\n", - " 127.536\n", - " FC Bayern Munich Over 3.5\n", - " 1.0\n", - " 146\n", - " 1.460000\n", - " 1.460000\n", + " edge:10012-40335-2024-08-31:1st_half_team_tota...\n", + " edge:10012-40335-2024-08-31:1st_half_team_tota...\n", + " 10012-40335-2024-08-31\n", + " Oud-Heverlee Leuven Under 0.5\n", + " Oud-Heverlee Leuven Over 0.5\n", + " 102.85\n", + " -102.85\n", + " -110.0\n", + " -102.0\n", + " [FanDuel, DraftKings]\n", + " ...\n", + " None\n", + " home\n", + " 1725129900000\n", + " Bovada\n", + " won\n", + " 1\n", + " 0.980392\n", + " 2024-09-01\n", + " 0.980392\n", + " 1\n", " \n", " \n", - " 2\n", - " FC Augsburg\n", - " FC Bayern Munich Under 1.5\n", - " -135\n", - " DraftKings,10bet,DraftKings (Tennessee)\n", - " FC Bayern Munich Over 1.5\n", - " 128\n", - " FanDuel\n", - " FC Bayern Munich\n", - " Germany - Bundesliga\n", - " 1st Half Team Total\n", - " soccer\n", - " 2023-03-11\n", - " -123.407\n", - " 123.898\n", - " FC Bayern Munich Over 1.5\n", - " 1.0\n", - " 128\n", - " 1.280000\n", - " 1.280000\n", + " 9\n", + " edge:10012-40335-2024-08-31:total_goals:over_2...\n", + " edge:10012-40335-2024-08-31:total_goals:over_2...\n", + " 10012-40335-2024-08-31\n", + " Under 2.5\n", + " Over 2.5\n", + " -101.35\n", + " 101.35\n", + " -102.0\n", + " 105.0\n", + " [Bodog, Bovada]\n", + " ...\n", + " None\n", + " home\n", + " 1725129900000\n", + " BetOnline\n", + " lost\n", + " -1\n", + " -1.000000\n", + " 2024-09-01\n", + " 1.050000\n", + " 1\n", " \n", " \n", - " 6\n", - " Liverpool\n", - " Bournemouth Under 0.5\n", - " 132\n", - " FanDuel\n", - " Bournemouth Over 0.5\n", - " -137\n", - " Barstool\n", - " Bournemouth\n", - " England - Premier League\n", - " Team Total\n", - " soccer\n", - " 2023-03-11\n", - " 141.875\n", - " -141.519\n", - " Bournemouth Over 0.5\n", - " 1.0\n", - " -137\n", - " 0.729927\n", - " 0.729927\n", + " 15\n", + " edge:10035-41288-2024-08-31:total_goals:over_2...\n", + " edge:10035-41288-2024-08-31:total_goals:over_2...\n", + " 10035-41288-2024-08-31\n", + " Under 2.5\n", + " Over 2.5\n", + " -121.90\n", + " 121.90\n", + " -134.0\n", + " 124.0\n", + " [FanDuel]\n", + " ...\n", + " None\n", + " home\n", + " 1725129000000\n", + " BetOnline\n", + " won\n", + " 1\n", + " 1.240000\n", + " 2024-09-01\n", + " 1.240000\n", + " 1\n", " \n", " \n", - " 8\n", - " FC Augsburg\n", - " Under 3.5\n", - " 141\n", - " OddsJam\n", - " Over 3.5\n", - " -138\n", - " FanDuel\n", - " FC Bayern Munich\n", - " Germany - Bundesliga\n", - " Total Goals\n", - " soccer\n", - " 2023-03-11\n", - " 147.467\n", - " -146.976\n", - " Over 3.5\n", - " 1.0\n", - " -138\n", - " 0.724638\n", - " 0.724638\n", + " 19\n", + " edge:10068-27381-24-35:point_spread:northweste...\n", + " edge:10068-27381-24-35:point_spread:northweste...\n", + " 10068-27381-24-35\n", + " Tulsa -37.5\n", + " Northwestern State +37.5\n", + " 100.40\n", + " -100.40\n", + " 105.0\n", + " -110.0\n", + " [betPARX, BetRivers]\n", + " ...\n", + " None\n", + " away\n", + " 1724976000000\n", + " betPARX\n", + " lost\n", + " -1\n", + " -1.000000\n", + " 2024-08-30\n", + " 1.050000\n", + " 1\n", " \n", " \n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", - " ...\n", + " 25\n", + " edge:10068-27381-24-35:total_points:over_53_5:...\n", + " edge:10068-27381-24-35:total_points:over_53_5:...\n", + " 10068-27381-24-35\n", + " Under 53.5\n", + " Over 53.5\n", + " 132.39\n", + " -132.39\n", + " 122.0\n", + " -130.0\n", + " [DraftKings]\n", " ...\n", + " None\n", + " home\n", + " 1724976000000\n", + " betPARX\n", + " won\n", + " 1\n", + " 0.769231\n", + " 2024-08-30\n", + " 0.769231\n", + " 1\n", " \n", " \n", - " 80358\n", - " New Orleans Pelicans\n", - " Memphis Grizzlies +1.5\n", - " -143\n", - " TonyBet\n", - " New Orleans Pelicans -1.5\n", - " 135\n", - " BetMGM,partypoker,Borgata\n", - " Memphis Grizzlies\n", - " NBA\n", - " 1st Half Point Spread\n", - " Basketball\n", - " 2023-10-26\n", - " -133.490\n", - " 133.546\n", - " New Orleans Pelicans -1.5\n", - " 1.0\n", - " 135\n", - " 1.350000\n", - " 1.350000\n", - " \n", - " \n", - " 80360\n", - " Dallas Mavericks\n", - " San Antonio Spurs -0.5\n", - " 148\n", - " FanDuel\n", - " Dallas Mavericks +0.5\n", - " -155\n", - " Pinny,Pinnacle\n", - " San Antonio Spurs\n", - " NBA\n", - " 1st Half Point Spread\n", - " Basketball\n", - " 2023-10-26\n", - " 146.604\n", - " -146.127\n", - " San Antonio Spurs -0.5\n", - " 1.0\n", - " 148\n", - " 1.480000\n", - " 1.480000\n", - " \n", - " \n", - " 80361\n", - " Dallas Mavericks\n", - " San Antonio Spurs +0.5\n", - " 128\n", - " FanDuel\n", - " Dallas Mavericks -0.5\n", - " -132\n", - " Pinny,Pinnacle\n", - " San Antonio Spurs\n", - " NBA\n", - " 1st Half Point Spread\n", - " Basketball\n", - " 2023-10-26\n", - " 126.875\n", - " -126.419\n", - " San Antonio Spurs +0.5\n", - " 1.0\n", - " 128\n", - " 1.280000\n", - " 1.280000\n", - " \n", - " \n", - " 80363\n", - " Portland Trail Blazers\n", - " Los Angeles Clippers Under 60.5\n", - " -116\n", - " Pinny,Pinnacle\n", - " Los Angeles Clippers Over 60.5\n", - " 110\n", - " partypoker,BetMGM,Borgata\n", - " Los Angeles Clippers\n", - " NBA\n", - " 1st Half Team Total\n", - " Basketball\n", - " 2023-10-26\n", - " -106.549\n", - " 106.565\n", - " Los Angeles Clippers Over 60.5\n", - " 1.0\n", - " 110\n", - " 1.100000\n", + " 26\n", + " edge:10068-27381-24-35:total_points:over_53:un...\n", + " edge:10068-27381-24-35:total_points:over_53:un...\n", + " 10068-27381-24-35\n", + " Under 53\n", + " Over 53\n", + " 109.70\n", + " -109.70\n", + " 110.0\n", + " -110.0\n", + " [betPARX, BetRivers]\n", + " ...\n", + " None\n", + " away\n", + " 1724976000000\n", + " betPARX\n", + " lost\n", + " -1\n", + " -1.000000\n", + " 2024-08-30\n", " 1.100000\n", + " 1\n", " \n", " \n", - " 80365\n", - " Portland Trail Blazers\n", - " Los Angeles Clippers -3.5\n", - " 104\n", - " Unibet,BetRivers,betPARX\n", - " Portland Trail Blazers +3.5\n", - " -110\n", - " BetAnySports,LowVig,BetOnline,SuperBook\n", - " Los Angeles Clippers\n", - " NBA\n", - " 1st Quarter Point Spread\n", - " Basketball\n", - " 2023-10-26\n", - " 103.013\n", - " -102.757\n", - " Los Angeles Clippers -3.5\n", - " 1.0\n", - " 104\n", - " 1.040000\n", - " 1.040000\n", + " 27\n", + " edge:10068-27381-24-35:total_points:over_54_5:...\n", + " edge:10068-27381-24-35:total_points:over_54_5:...\n", + " 10068-27381-24-35\n", + " Under 54.5\n", + " Over 54.5\n", + " 114.29\n", + " -114.29\n", + " 105.0\n", + " -114.0\n", + " [Fanatics]\n", + " ...\n", + " None\n", + " home\n", + " 1724976000000\n", + " betPARX\n", + " won\n", + " 1\n", + " 0.877193\n", + " 2024-08-30\n", + " 0.877193\n", + " 1\n", + " \n", + " \n", + " 28\n", + " edge:10068-27381-24-35:total_points:over_54:un...\n", + " edge:10068-27381-24-35:total_points:over_54:un...\n", + " 10068-27381-24-35\n", + " Under 54\n", + " Over 54\n", + " 122.41\n", + " -122.41\n", + " 112.0\n", + " -122.0\n", + " [DraftKings]\n", + " ...\n", + " None\n", + " home\n", + " 1724976000000\n", + " BetRivers\n", + " won\n", + " 1\n", + " 0.819672\n", + " 2024-08-30\n", + " 0.819672\n", + " 1\n", + " \n", + " \n", + " 30\n", + " edge:10068-27381-24-35:total_points:over_55:un...\n", + " edge:10068-27381-24-35:total_points:over_55:un...\n", + " 10068-27381-24-35\n", + " Under 55\n", + " Over 55\n", + " -112.29\n", + " 112.29\n", + " -110.0\n", + " 110.0\n", + " [BookMaker]\n", + " ...\n", + " None\n", + " away\n", + " 1724976000000\n", + " BookMaker\n", + " lost\n", + " -1\n", + " -1.000000\n", + " 2024-08-30\n", + " 0.909091\n", + " 1\n", + " \n", + " \n", + " 32\n", + " edge:10068-27381-24-35:total_points:over_56:un...\n", + " edge:10068-27381-24-35:total_points:over_56:un...\n", + " 10068-27381-24-35\n", + " Under 56\n", + " Over 56\n", + " -104.24\n", + " 104.24\n", + " -112.0\n", + " 105.0\n", + " [DraftKings]\n", + " ...\n", + " None\n", + " home\n", + " 1724976000000\n", + " betPARX\n", + " won\n", + " 1\n", + " 1.050000\n", + " 2024-08-30\n", + " 1.050000\n", + " 1\n", " \n", " \n", "\n", - "

37469 rows × 19 columns

\n", + "

10 rows × 50 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", + " id \\\n", + "1 edge:10012-40335-2024-08-31:1st_half_team_tota... \n", + "9 edge:10012-40335-2024-08-31:total_goals:over_2... \n", + "15 edge:10035-41288-2024-08-31:total_goals:over_2... \n", + "19 edge:10068-27381-24-35:point_spread:northweste... \n", + "25 edge:10068-27381-24-35:total_points:over_53_5:... \n", + "26 edge:10068-27381-24-35:total_points:over_53:un... \n", + "27 edge:10068-27381-24-35:total_points:over_54_5:... \n", + "28 edge:10068-27381-24-35:total_points:over_54:un... \n", + "30 edge:10068-27381-24-35:total_points:over_55:un... \n", + "32 edge:10068-27381-24-35:total_points:over_56:un... \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", + " bet_id game_id \\\n", + "1 edge:10012-40335-2024-08-31:1st_half_team_tota... 10012-40335-2024-08-31 \n", + "9 edge:10012-40335-2024-08-31:total_goals:over_2... 10012-40335-2024-08-31 \n", + "15 edge:10035-41288-2024-08-31:total_goals:over_2... 10035-41288-2024-08-31 \n", + "19 edge:10068-27381-24-35:point_spread:northweste... 10068-27381-24-35 \n", + "25 edge:10068-27381-24-35:total_points:over_53_5:... 10068-27381-24-35 \n", + "26 edge:10068-27381-24-35:total_points:over_53:un... 10068-27381-24-35 \n", + "27 edge:10068-27381-24-35:total_points:over_54_5:... 10068-27381-24-35 \n", + "28 edge:10068-27381-24-35:total_points:over_54:un... 10068-27381-24-35 \n", + "30 edge:10068-27381-24-35:total_points:over_55:un... 10068-27381-24-35 \n", + "32 edge:10068-27381-24-35:total_points:over_56:un... 10068-27381-24-35 \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", + " away_bet_name home_bet_name \\\n", + "1 Oud-Heverlee Leuven Under 0.5 Oud-Heverlee Leuven Over 0.5 \n", + "9 Under 2.5 Over 2.5 \n", + "15 Under 2.5 Over 2.5 \n", + "19 Tulsa -37.5 Northwestern State +37.5 \n", + "25 Under 53.5 Over 53.5 \n", + "26 Under 53 Over 53 \n", + "27 Under 54.5 Over 54.5 \n", + "28 Under 54 Over 54 \n", + "30 Under 55 Over 55 \n", + "32 Under 56 Over 56 \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", + " away_no_vig_price home_no_vig_price away_price home_price \\\n", + "1 102.85 -102.85 -110.0 -102.0 \n", + "9 -101.35 101.35 -102.0 105.0 \n", + "15 -121.90 121.90 -134.0 124.0 \n", + "19 100.40 -100.40 105.0 -110.0 \n", + "25 132.39 -132.39 122.0 -130.0 \n", + "26 109.70 -109.70 110.0 -110.0 \n", + "27 114.29 -114.29 105.0 -114.0 \n", + "28 122.41 -122.41 112.0 -122.0 \n", + "30 -112.29 112.29 -110.0 110.0 \n", + "32 -104.24 104.24 -112.0 105.0 \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", + " away_sportsbooks ... sportsbook_orderid home_or_away \\\n", + "1 [FanDuel, DraftKings] ... None home \n", + "9 [Bodog, Bovada] ... None home \n", + "15 [FanDuel] ... None home \n", + "19 [betPARX, BetRivers] ... None away \n", + "25 [DraftKings] ... None home \n", + "26 [betPARX, BetRivers] ... None away \n", + "27 [Fanatics] ... None home \n", + "28 [DraftKings] ... None home \n", + "30 [BookMaker] ... None away \n", + "32 [DraftKings] ... None home \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", + " start_timestamp selected_sportsbook bet_status outcome benefit \\\n", + "1 1725129900000 Bovada won 1 0.980392 \n", + "9 1725129900000 BetOnline lost -1 -1.000000 \n", + "15 1725129000000 BetOnline won 1 1.240000 \n", + "19 1724976000000 betPARX lost -1 -1.000000 \n", + "25 1724976000000 betPARX won 1 0.769231 \n", + "26 1724976000000 betPARX lost -1 -1.000000 \n", + "27 1724976000000 betPARX won 1 0.877193 \n", + "28 1724976000000 BetRivers won 1 0.819672 \n", + "30 1724976000000 BookMaker lost -1 -1.000000 \n", + "32 1724976000000 betPARX won 1 1.050000 \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", + " date odds2 investment \n", + "1 2024-09-01 0.980392 1 \n", + "9 2024-09-01 1.050000 1 \n", + "15 2024-09-01 1.240000 1 \n", + "19 2024-08-30 1.050000 1 \n", + "25 2024-08-30 0.769231 1 \n", + "26 2024-08-30 1.100000 1 \n", + "27 2024-08-30 0.877193 1 \n", + "28 2024-08-30 0.819672 1 \n", + "30 2024-08-30 0.909091 1 \n", + "32 2024-08-30 1.050000 1 \n", "\n", - "[37469 rows x 19 columns]" + "[10 rows x 50 columns]" ] }, - "execution_count": 23, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "raw_df[raw_df['outcome'] == 1]" + "data_df.head(10)" ] }, { "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, + "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "年化夏普率: 0.8002309699976565\n", - "ROI: 0.00683522830064485\n" + "status date active inactive\n", + "0 2024-01-01 2 1\n", + "1 2024-01-02 1 1\n", + "2 2024-01-03 0 1\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "# 示例数据\n", + "data = {\n", + " 'date': ['2024-01-01', '2024-01-01', '2024-01-01', '2024-01-02', '2024-01-02', '2024-01-03'],\n", + " 'status': ['active', 'inactive', 'active', 'active', 'inactive', 'inactive']\n", + "}\n", + "\n", + "df = pd.DataFrame(data)\n", + "\n", + "# 将日期列转换为日期类型\n", + "df['date'] = pd.to_datetime(df['date'])\n", + "\n", + "# 使用 pivot_table 进行聚合\n", + "result = pd.pivot_table(df, values='status', index=['date'], columns=['status'], aggfunc='size', fill_value=0).reset_index()\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bet_status date lost won\n", + "0 2024-08-26 11 12\n", + "1 2024-08-27 96 113\n", + "2 2024-08-28 138 94\n", + "3 2024-08-29 109 79\n", + "4 2024-08-30 190 178\n", + "5 2024-08-31 99 88\n", + "6 2024-09-01 289 284\n", + "7 2024-09-02 62 59\n", + "8 2024-09-03 89 56\n", + "9 2024-09-04 98 80\n", + "10 2024-09-05 50 37\n", + "Index(['date', 'lost', 'won'], dtype='object', name='bet_status')\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "\n", + "\n", + "# 使用 pivot_table 进行聚合\n", + "result = pd.pivot_table(data_df, index=['date'], columns=['bet_status'], aggfunc='size', fill_value=0).reset_index()\n", + "\n", + "print(result)\n", + "print(result.columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "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", + "
datestatus
02024-01-01active
12024-01-01inactive
22024-01-01active
32024-01-02active
42024-01-02inactive
52024-01-03inactive
\n", + "
" + ], + "text/plain": [ + " date status\n", + "0 2024-01-01 active\n", + "1 2024-01-01 inactive\n", + "2 2024-01-01 active\n", + "3 2024-01-02 active\n", + "4 2024-01-02 inactive\n", + "5 2024-01-03 inactive" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "年化夏普率: -1.4002737338136018\n", + "ROI: -0.011875712320100193\n" ] } ], @@ -938,7 +1968,7 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": 70, "metadata": {}, "outputs": [ { @@ -949,9 +1979,8 @@ }, "data": [ { - "mode": "lines", "name": "日末余额", - "type": "scatter", + "type": "bar", "x": [ "2024-08-26T00:00:00", "2024-08-27T00:00:00", @@ -959,7 +1988,11 @@ "2024-08-29T00:00:00", "2024-08-30T00:00:00", "2024-08-31T00:00:00", - "2024-09-01T00:00:00" + "2024-09-01T00:00:00", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" ], "y": [ 1002.6656885456886, @@ -968,12 +2001,48 @@ 1014.877157613626, 1004.9262841780602, 1010.5006139053265, - 1012.8869095027875 + 1012.8869095027875, + 1015.040231362553, + 997.2052265455578, + 980.6588565360646, + 974.1046903384964 ], "yaxis": "y" }, { - "mode": "lines", + "mode": "markers+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", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" + ], + "y": [ + 0.17384925297968776, + 0.1817336321334136, + -0.06688902311428041, + -0.09243710870965964, + -0.01694897264848009, + 0.06139093881401034, + -0.0037706970033483623, + 0.035622210587666514, + -0.19936311011066554, + -0.058234435568632774, + -0.053429707249929806 + ], + "yaxis": "y2" + }, + { + "mode": "markers+lines", "name": "累计收益率", "type": "scatter", "x": [ @@ -983,7 +2052,11 @@ "2024-08-29T00:00:00", "2024-08-30T00:00:00", "2024-08-31T00:00:00", - "2024-09-01T00:00:00" + "2024-09-01T00:00:00", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" ], "y": [ 0.17384925297968776, @@ -992,7 +2065,43 @@ 0.013933178120379116, 0.0027913825488691288, 0.01187018704065157, - 0.00683522830064485 + 0.00683522830064485, + 0.008667540166362694, + -0.006075589985235103, + -0.010250173849373946, + -0.011875712320100193 + ], + "yaxis": "y2" + }, + { + "mode": "markers+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", + "2024-09-02T00:00:00", + "2024-09-03T00:00:00", + "2024-09-04T00:00:00", + "2024-09-05T00:00:00" + ], + "y": [ + 0.023, + 0.20844435227771982, + 0.22539150187983306, + 0.1822349323660187, + 0.3626054613992025, + 0.1860833007795684, + 0.5670456723281954, + 0.11946052305029518, + 0.1428514806800882, + 0.1784988638864379, + 0.08871586629758899 ], "yaxis": "y2" } @@ -1843,61 +2952,47 @@ } ], "source": [ - "import pandas as pd\n", "import plotly.graph_objects as go\n", + "from plotly.subplots import make_subplots\n", + "\n", + "\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", + "fig.add_trace(go.Bar(\n", " x=res_df['日期'],\n", " y=res_df['日末余额(1.6天结算)'],\n", - " mode='lines',\n", " name='日末余额',\n", - " yaxis='y1'\n", - "))\n", + " yaxis='y1'))\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", + "for col in ['日收益率', '累计收益率', '在途资金比例']:\n", + " fig.add_trace(go.Scatter(\n", + " x=res_df['日期'],\n", + " y=res_df[col],\n", + " mode='markers+lines',\n", + " name=col,\n", + " yaxis='y2'))\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", + " yaxis2=dict(title='收益率', overlaying='y', side='right', tickformat='.1%'),\n", ")\n", "\n", - "\n", "fig.show()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, @@ -2004,6 +3099,26 @@ "source": [ "raw_df['odds EU'].mean()" ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "37.78343433288728" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1.01**365" + ] } ], "metadata": { diff --git a/profit_simulation.py b/profit_simulation.py new file mode 100644 index 0000000..446a217 --- /dev/null +++ b/profit_simulation.py @@ -0,0 +1,183 @@ +import pandas as pd +from datetime import datetime +from dao.Database import Database +from data_model import MysqlConfig, OddsJamOrder, OddsjamBet +from typing import List +import json +import os +import plotly.graph_objects as go + + +def get_oddsjam_order_data_from_db(load_from_local: bool = False) -> pd.DataFrame: + current_date_str = datetime.now().strftime('%Y%m%d') + file_path = os.path.join('data', f'oddsjam_order_data_{current_date_str}.csv') + if load_from_local and os.path.exists(file_path): + return pd.read_csv(file_path, low_memory=False) + + config_file_path = 'config\mysql_config.json' + mysql_config = MysqlConfig.parse_file(config_file_path) + dao = Database(mysql_config) + + select_query = "SELECT * FROM bet.oddsjam_order where bet_status in ('won', 'lost');" + raw_data_list = dao.fetchall(query=select_query) + order_data_list = [OddsJamOrder(**data).model_dump() + for data in raw_data_list] + order_df = pd.DataFrame(order_data_list) + order_df.to_csv(file_path, index=False, encoding='utf-8-sig') + return order_df + + +def calc_benefit_by_order_info(order_info: dict) -> float: + home_or_away = order_info['home_or_away'] + price = order_info[f'{home_or_away}_price'] / 100 + if order_info['outcome'] == -1: + return -1 + if price >= 0: + return price + else: + return 1 / abs(price) + + +def calc_odds(row): + home_or_away = row['home_or_away'] + price = row[f'{home_or_away}_price'] / 100 + if price >= 0: + return price + else: + return 1 / abs(price) + + +def clac_closing_balance(day_benefit_list: List, pre_balance: float = 1000, pre_benefit: float = 0) -> List: + closing_balance_list = [] + for i, benefit in enumerate(day_benefit_list): + closing_balance = pre_balance + pre_benefit / 3 + benefit * 2 / 3 + closing_balance_list.append(closing_balance) + pre_balance = closing_balance + pre_benefit = benefit + return closing_balance_list + + +def calc_in_transit_funds_ratio(daily_investment_list: List, closing_balance_list: List, start_closing_balance: float = 1000) -> List: + assert len(daily_investment_list) == len(closing_balance_list) + ratio_list = [] + for i, daily_investment in enumerate(daily_investment_list): + if i == 0: + ratio = daily_investment / start_closing_balance + else: + ratio = daily_investment / closing_balance_list[i-1] + ratio_list.append(ratio) + return ratio_list + + +def simulate_profit(data_df: pd.DataFrame, init_balance=1000) -> pd.DataFrame: + res_df = data_df.groupby('date').agg({'investment': 'sum', 'benefit': 'sum'}).reset_index() + # res_df['日期'] = pd.to_datetime(res_df['date']) + res_df = res_df.rename(columns={'investment': '当日投入', 'benefit': '日收益'}) + + res_df['日收益率'] = res_df['日收益'] / res_df['当日投入'] + res_df['累计收益'] = res_df['日收益'].cumsum() + res_df['累计投入'] = res_df['当日投入'].cumsum() + res_df['累计收益率'] = res_df['累计收益'] / res_df['累计投入'] + + day_benefit_list = res_df['日收益'].tolist() + closing_balance_list = clac_closing_balance(day_benefit_list=day_benefit_list, + pre_balance=init_balance) + res_df['日末余额(1.6天结算)'] = closing_balance_list + daily_investment_list = res_df['当日投入'].tolist() + res_df['在途资金比例'] = calc_in_transit_funds_ratio(daily_investment_list=daily_investment_list, + closing_balance_list=closing_balance_list, + start_closing_balance=init_balance) + annualized_sharpe_ratio = res_df['日收益'].sum() / init_balance / res_df['日收益率'].std() * ((365 / len(res_df))**0.5) + roi = res_df['日收益'].sum() / res_df['当日投入'].sum() + return res_df, annualized_sharpe_ratio, roi + + +def plot_won_lost_mean_odds(data_df: pd.DataFrame): + + data_df = data_df.sort_values(by='date') + date_x = data_df['date'].tolist() + fig = go.Figure() + cols = data_df.columns[1:] + for col in cols: + y_data = data_df[col].tolist() + fig.add_trace(go.Bar(x=date_x, y=y_data, name=col, yaxis='y1')) + + fig.add_trace(go.Scatter(x=data_df['date'], y=data_df['odds'], mode='markers+lines', name='平均赔率', yaxis='y2')) + fig.update_layout( + barmode='group', + font=dict(family="Times New Roman"), + title='每天胜负数量以及平均赔率', + xaxis=dict(title='日期'), + yaxis=dict(title='数量'), + yaxis2=dict(title='赔率', overlaying='y', side='right'), + ) + fig.write_html('data/won_lost_mean_odds.html') + + +def plot_profit_simulation(data_df: pd.DataFrame, title: str = None): + fig = go.Figure() + fig.add_trace(go.Bar(x=data_df['date'], y=data_df['日末余额(1.6天结算)'], name='日末余额', yaxis='y1')) + for col in ['日收益率', '累计收益率', '在途资金比例']: + fig.add_trace(go.Scatter( + x=data_df['date'], + y=data_df[col], + mode='markers+lines', + name=col, + yaxis='y2')) + + if title is None: + title = '收益模拟' + fig.update_layout( + title=title, + font=dict(family="Times New Roman"), + xaxis=dict(title='日期'), + yaxis=dict(title='金额'), + yaxis2=dict(title='收益率', overlaying='y', side='right', tickformat='.1%')) + fig.write_html('data/profit_simulation.html') + + +if __name__ == '__main__': + + order_df = get_oddsjam_order_data_from_db(load_from_local=True) + # order_df = pd.read_excel('data/PEV 3.11-10.26.xlsx', sheet_name='原始数据') + + order_df['outcome'] = order_df['bet_status'].apply(lambda x: 1 if x == 'won' else -1) + + order_df['benefit'] = order_df.apply(lambda row: calc_benefit_by_order_info(row.to_dict()), axis=1) + order_df['date'] = order_df['start_timestamp'].apply( + lambda x: datetime.fromtimestamp(x // 1000).strftime("%Y-%m-%d")) + + data_df = order_df.copy() + + data_df = data_df[data_df['market_width'] <= 25] + data_df = data_df[data_df['market_width'] >= 20] + market_width_df = data_df.groupby('date').agg({'market_width': 'mean'}).reset_index() + + data_df['investment'] = 1 + res_df, annualized_sharpe_ratio, roi = simulate_profit(data_df) + print(f'年化夏普率: {annualized_sharpe_ratio}') + print(f'ROI: {roi}') + + data_df['odds'] = data_df.apply(calc_odds, axis=1) + total_mean_odds = data_df['odds'].mean() + print(f'{len(data_df)} 场比赛平均赔率: {total_mean_odds}') + won_rate = len(data_df[data_df['outcome'] == 1]) / len(data_df) + print(f'{len(data_df)} 场比赛胜率: {won_rate}') + + odds_df = data_df.groupby('date').agg({'odds': 'mean'}).reset_index() + res_df = pd.merge(res_df, odds_df, on='date', how='left') + + bet_status_df = pd.pivot_table(data_df, index=['date'], columns=[ + 'bet_status'], aggfunc='size', fill_value=0).reset_index() + res_df = pd.merge(res_df, bet_status_df, on='date', how='left') + res_df['won rate'] = res_df['won'] / (res_df['won'] + res_df['lost']) + + if 'market_width' in data_df.columns: + res_df = pd.merge(res_df, market_width_df, on='date', how='left') + + res_df.to_csv('data/profit_simulation.csv', index=False, encoding='utf-8-sig') + title = f'收益模拟,年化夏普率: {annualized_sharpe_ratio}, ROI: {roi}' + plot_profit_simulation(data_df=res_df, title=title) + plot_won_lost_mean_odds(data_df=res_df) + + diff --git a/requirements.txt b/requirements.txt index 8168c6e..70cfb7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pymysql==1.1.1 pydantic==2.8.2 -simplejson==3.19.2 \ No newline at end of file +playwright==1.45.1 \ No newline at end of file