POLS / guozu /tools /simulate.py
Ethscriptions's picture
G9: 国足数据更新至2026-10-07(加时赛:8场非正式比赛)+ 对白标签改为系统 (part 2)
efe6f90 verified
Raw History Blame Contribute Delete
5.48 kB
import json,collections
TEAMS={}
for l in open('teams.tsv').read().strip().split('\n'):
p=l.split('\t'); TEAMS[p[0]]=p[1]
lines=[l.split('\t') for l in open('China.tsv').read().strip().split('\n')]
COMP={'WC':'世界杯','WQ':'世预赛','WQS':'世预赛','AC':'亚洲杯','SQ':'亚预赛','EAH':'东亚杯'}
MARGIN=1.07
def num(s): return int(s.replace('−','-'))
CUTOFF='2026-10-07' # 数据截止日
FCOMP={'F':'友谊赛','FT':'邀请赛','FFS':'国际足联系列赛'}
def make(l, comp):
y,m,d,t1,t2,s1,s2,tc,loc,ch,r1a,r2a=l[:12]
ch=num(ch); r1=num(r1a)-ch; r2=num(r2a)+ch
cn_first = t1=='CN'
opp = t2 if cn_first else t1
rc, ro = (r1,r2) if cn_first else (r2,r1)
gc, go = (int(s1),int(s2)) if cn_first else (int(s2),int(s1))
# 主场判定
if loc=='' : home = 'CN' if cn_first else opp
elif loc=='CN': home='CN'
elif loc==opp: home=opp
else: home=None
ha = 100 if home=='CN' else (-100 if home==opp else 0)
dr = rc - ro + ha
We = 1/(10**(-dr/400)+1)
pD = max(0.05, 0.28 - 0.25*(2*We-1)**2)
pW = max(0.01, We*(1-pD)); pL=max(0.01,(1-We)*(1-pD))
s=pW+pD+pL; pW,pD,pL=pW/s,pD/s,pL/s
oW = max(1.01, round(1/(pW*MARGIN),2)); oNW = max(1.01, round(1/((pD+pL)*MARGIN),2)); oD=round(1/(pD*MARGIN),2); oL=max(1.01,round(1/(pL*MARGIN),2))
res = 'W' if gc>go else 'D' if gc==go else 'L'
return dict(date=f'{y}-{m}-{d}',opp=opp,oppName=TEAMS.get(opp,opp),comp=comp[tc],code=tc,gf=gc,ga=go,res=res,home='H' if home=='CN' else ('A' if home==opp else 'N'),eloC=rc,eloO=ro,pW=round(pW,3),pD=round(pD,3),pL=round(pL,3),oW=oW,oD=oD,oL=oL,oNW=oNW)
out=[make(l,COMP) for l in lines if '2002'<=l[0] and l[7] in COMP and f'{l[0]}-{l[1]}-{l[2]}'<=CUTOFF]
LAST=out[-1]['date']
# 最后一场正式比赛之后(截至 CUTOFF)的非正式比赛:单独展示,不计入正式结算
extra=[make(l,FCOMP) for l in lines if l[7] in FCOMP and LAST<f'{l[0]}-{l[1]}-{l[2]}'<=CUTOFF]
bank=0;nw=0;peak=(0,'');trough=(0,'');streak=0;maxl=0
for o in out:
pnl = 100*(o['oW']-1) if o['res']=='W' else -100
bank+=pnl; o['pnl']=round(pnl,2); o['bank']=round(bank,2)
nw += 100*(o['oNW']-1) if o['res']!='W' else -100; o['bankNW']=round(nw,2)
if bank>peak[0]: peak=(bank,o['date'])
if bank<trough[0]: trough=(bank,o['date'])
c=collections.Counter(o['res'] for o in out)
print('matches',len(out),c,'first',out[0]['date'],'last',out[-1]['date'])
print('stake',100*len(out),'final',round(bank,2),'ROI',round(bank/(100*len(out))*100,2),'%','peak',peak,'trough',trough,'reverse',round(nw,2))
print('avg odds win', round(sum(o['oW'] for o in out)/len(out),2))
yr=collections.defaultdict(float)
for o in out: yr[o['date'][:4]]+=o['pnl']
print({k:round(v) for k,v in sorted(yr.items())})
best=max(out,key=lambda o:o['pnl']); print('best',best)
wins=[o for o in out if o['res']=='W']; print('top wins', sorted([(o['oW'],o['date'],o['oppName'],o['gf'],o['ga']) for o in wins],reverse=True)[:6])
cmp=collections.defaultdict(lambda:[0,0])
for o in out: cmp[o['comp']][0]+=1; cmp[o['comp']][1]+=o['pnl']
print({k:(v[0],round(v[1])) for k,v in cmp.items()})
json.dump(out,open('matches.json','w'),ensure_ascii=False)
# ---------- 导出给网页 ----------
ZH={'CW':'库拉索','CM':'喀麦隆','NZ':'新西兰','AU':'澳大利亚','BH':'巴林','BR':'巴西','BT':'不丹','CR':'哥斯达黎加','GU':'关岛','HK':'中国香港','ID':'印度尼西亚','IQ':'伊拉克','IR':'伊朗','JO':'约旦','JP':'日本','KG':'吉尔吉斯斯坦','KP':'朝鲜','KR':'韩国','KW':'科威特','LA':'老挝','LB':'黎巴嫩','MM':'缅甸','MV':'马尔代夫','MY':'马来西亚','OM':'阿曼','PH':'菲律宾','PS':'巴勒斯坦','QA':'卡塔尔','SA':'沙特阿拉伯','SG':'新加坡','SY':'叙利亚','TH':'泰国','TJ':'塔吉克斯坦','TR':'土耳其','UZ':'乌兹别克斯坦','VN':'越南'}
import os
summary=dict(n=len(out),stake=100*len(out),final=round(bank,2),W=c['W'],D=c['D'],L=c['L'],
back=round(sum(o['pnl']+100 for o in out if o['res']=='W'),2),maxBank=max(o['bank'] for o in out),
minBank=min(o['bank'] for o in out),minDate=min(out,key=lambda o:o['bank'])['date'],
ev=round(sum(100*(o['pW']*o['oW']-1) for o in out),2),margin=MARGIN)
for k,lab in [('oL','opp'),('oD','draw')]:
b=0;series=[]
for o in out:
hit=(o['res']=='L' and k=='oL') or (o['res']=='D' and k=='oD')
b+=100*(o[k]-1) if hit else -100; series.append(round(b,2))
summary[lab]=round(b,2)
for o,v in zip(out,series): o['bank_'+lab]=v
summary['ret']=round(summary['final']/summary['stake']*100,2)
for o in out: o['oppZh']=ZH.get(o['opp'],o['oppName'])
eb=0
for o in extra:
o['oppZh']=ZH.get(o['opp'],o['oppName']); pnl=100*(o['oW']-1) if o['res']=='W' else -100; eb+=pnl; o['pnl']=round(pnl,2); o['bank']=round(eb,2)
ec=collections.Counter(o['res'] for o in extra)
summary.update(cutoff=CUTOFF,last=LAST,extraN=len(extra),extraW=ec['W'],extraD=ec['D'],extraL=ec['L'],extraPnl=round(eb,2))
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
js='// 自动生成:tools/simulate.py —— 数据来源 eloratings.net(比赛、赛前评分),赔率为模型估算\nwindow.GZ='+json.dumps(dict(summary=summary,matches=out,extra=extra),ensure_ascii=False,separators=(',',':'))+';\n'
open(os.path.join(ROOT,'data','matches.js'),'w',encoding='utf-8').write(js)
print("summary",summary)
for o in extra: print(o['date'],o['oppZh'],o['comp'],o['home'],o['gf'],o['ga'],o['oW'],o['pnl'],o['bank'])