from pathlib import Path from datetime import datetime from zoneinfo import ZoneInfo import json,hashlib import numpy as np import pandas as pd OUT=Path(__file__).resolve().parent def read(p):return json.loads(p.read_text(encoding='utf-8-sig')) def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest() cfg=read(OUT/'价格对照实验设定.json');risk=read(OUT/'价格与组合风险诊断-r2.json') src=Path(risk['source_path']);supp=Path(risk['supplement_path']) assert sha(src)==risk['source_sha256'] and sha(supp)==risk['supplement_sha256'] cols=['SPY','IEF','GLD','BIL','XLK','XLI','XLE','XLV','XLP','XLU'];universe=cols[4:] p=pd.read_csv(src).pivot(index='date',columns='symbol',values='adjusted_close').sort_index().loc['2023-10-30':'2026-11-04'] new=pd.read_csv(supp).pivot(index='date',columns='symbol',values='adjusted_close') for s in ['XLV','XLP']:p[s]=new[s] p=p[cols];assert len(p)==757 and p.notna().all().all() and np.isfinite(p.values).all() end_month=[i for i in range(len(p)-1) if p.index[i][:7]!=p.index[i+1][:7] and p.index[i]>='2024-12-01'] assert p.index[end_month[0]]=='2024-12-31' selection_log=[];targets={};selected=None for i in end_month: if p.index[i][5:7]=='12': moment=(p.iloc[i-21][universe]/p.iloc[i-252][universe]-1).to_dict() selected=sorted(universe,key=lambda s:(-moment[s],s))[:2] selection_log.append({'signal':p.index[i],'execution':p.index[i+1],'ranking':sorted(moment.items(),key=lambda x:(-x[1],x[0])),'selected':selected,'latest_price_used':p.index[i-21],'lookback_start':p.index[i-252]}) assert selected is not None basic={'SPY':.30,'IEF':.20,'GLD':.25,'BIL':.25} target={'base_AI8_static':basic,'add_SPY_10pct':basic|{'SPY':.40,'BIL':.15}, 'equal_sector_10pct':basic|{'BIL':.15,**{s:.10/6 for s in universe}}, 'annual_price_top2_10pct':basic|{'BIL':.15,**{s:.05 for s in selected}}} for w in target.values():assert abs(sum(w.values())-1)<1e-12 targets[i+1]={'signal':p.index[i],'weights':target} assert len(selection_log)==2 def net_trade_nav(holdings,cash,w,c): nav=float(holdings.sum()+cash);low,high=nav/(1+2*c),nav for _ in range(60): mid=(low+high)/2 if mid+c*np.abs(mid*w-holdings).sum()>nav:high=mid else:low=mid after=(low+high)/2;notional=float(np.abs(after*w-holdings).sum());fee=notional*c assert abs(after+fee-nav)<1e-7 return after,notional,fee # Independent closed-form cases: initial purchase and a 100/25 portfolio rebalanced 50/50. assert abs(net_trade_nav(np.array([0.,0.]),100.,np.array([.5,.5]),.0005)[0]-100/1.0005)<1e-10 assert abs(net_trade_nav(np.array([100.,25.]),0.,np.array([.5,.5]),.001)[0]-124.925)<1e-10 # Future prices must not change the annual selection made at the signal date. for log in selection_log: i=p.index.get_loc(log['signal']);perturbed=p.copy();perturbed.iloc[i+1:]*=7 score=perturbed.iloc[i-21][universe]/perturbed.iloc[i-252][universe]-1 assert sorted(universe,key=lambda s:(-score[s],s))[:2]==log['selected'] start=min(targets);results=[];curves={};trades=[] for bps in cfg['costs_one_way_bps']: c=bps/10000 for strategy in list(next(iter(targets.values()))['weights']): holdings=np.zeros(len(cols));cash=100000.;navs=[cash];fees=0.;turnover=0.;orders=[] for i in range(start,len(p)): if i>start:holdings*=p.iloc[i].values/p.iloc[i-1].values nav=float(holdings.sum()+cash) if i in targets: signal=targets[i]['signal'];assert signal