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 = ['won', 'lost'] 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)