Files
bet/profit_simulation.py
2024-09-06 23:20:42 +08:00

184 lines
7.4 KiB
Python

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)