"""只多头的滞后20期OLS残差反转,五期累计、恢复/十期退出及现金账。""" from fractions import Fraction as F from decimal import Decimal as D from math import isfinite import json def indicators(market,stock): if len(market)!=len(stock): raise ValueError('returns_alignment') residuals,output=[],[] for i in range(len(stock)): alpha=beta=residual=None;reason='warmup' if i>=20: xs,ys=market[i-20:i],stock[i-20:i] if any(v is None or not isfinite(float(v)) for v in xs+ys+[market[i],stock[i]]): reason='invalid_input' else: xm,ym=sum(xs)/20,sum(ys)/20 variance=sum((v-xm)**2 for v in xs) if variance==0: reason='unidentified_market_variance' else: beta=sum((x-xm)*(y-ym) for x,y in zip(xs,ys))/variance alpha=ym-beta*xm residual=stock[i]-alpha-beta*market[i];reason='ready' residuals.append(residual) tail=residuals[max(0,i-4):i+1] cumulative=sum(tail) if len(tail)==5 and all(r is not None for r in tail) else None output.append({'i':i,'alpha':str(alpha) if alpha is not None else None, 'beta':str(beta) if beta is not None else None, 'residual':str(residual) if residual is not None else None, 'sum5':str(cumulative) if cumulative is not None else None,'reason':reason}) return output def run(market,stock,opens): rows=indicators(market,stock) if len(opens)!=len(rows): raise ValueError('open_alignment') cash,quantity,fees,entry=D(1000),0,0,None pending_buy,pending_exit=False,None ledger=[] for i,row in enumerate(rows): price=D(str(opens[i])) if not price.is_finite() or price<=0: raise ValueError('invalid_open') orders=[] reason=pending_exit or ('max_holding' if quantity and i>=entry+10 else None) if quantity and reason: cash+=quantity*price-1;fees+=1 orders.append(['sell',quantity,str(price),1,reason]);quantity=0;entry=None;pending_exit=None if pending_buy and not quantity: if 40*price+1>cash: raise ValueError('cash') cash-=40*price+1;quantity=40;entry=i;fees+=1 orders.append(['buy',40,str(price),1,'negative_residual_long']) pending_buy=False value=F(row['sum5']) if row['sum5'] is not None else None if not quantity and value is not None and value=0: pending_exit='residual_recovered' ledger.append({**row,'orders':orders,'quantity':quantity,'cash':str(cash), 'fees':fees,'equity':str(cash+quantity*price), 'entry_requested':pending_buy,'exit_requested':pending_exit}) return ledger market=[F(1 if i%2 else -1,100) for i in range(40)] stock=[F(3,2)*m+(F(-1,50) if 20<=i<=24 else F(3,100) if 25<=i<=29 else 0) for i,m in enumerate(market)] opens=[10]*40;opens[27]=10.5;opens[35]=9.5 rows=run(market,stock,opens) assert rows[20]['alpha']=='0' and rows[20]['beta']=='3/2' and rows[20]['residual']=='-1/50' assert rows[24]['sum5']=='-23/250' and rows[24]['entry_requested'] assert rows[25]['orders']==[['buy',40,'10',1,'negative_residual_long']] assert rows[26]['sum5']=='9/500' and rows[26]['exit_requested']=='residual_recovered' assert rows[27]['orders']==[['sell',40,'10.5',1,'residual_recovered']] assert D(rows[-1]['cash'])==D(rows[-1]['equity'])==1018 and rows[-1]['quantity']==0 and rows[-1]['fees']==2 common_market=[F(-1,100) if 20<=i<=24 else m for i,m in enumerate(market)] common_stock=[F(3,2)*m for m in common_market] common=run(common_market,common_stock,opens) assert all(not row['orders'] for row in common) and common[-1]['cash']=='1000' cold_stock=[F(3,2)*m+(F(-1,50) if i>=20 else 0) for i,m in enumerate(market)] cold=run(market,cold_stock,opens) assert cold[35]['orders']==[['sell',40,'9.5',1,'max_holding']] assert D(cold[-1]['cash'])==978 and cold[-1]['fees']==2 and cold[-1]['quantity']==0 constant=indicators([F(0)]*40,[F(0)]*40) assert constant[20]['reason']=='unidentified_market_variance' and constant[24]['sum5'] is None for invalid in [None,float('nan'),float('inf'),float('-inf')]: sparse=list(stock);sparse[24]=invalid check=indicators(market,sparse) assert check[24]['reason']=='invalid_input' and check[24]['residual'] is None and check[24]['sum5'] is None assert run(market[:26],stock[:26],opens[:26])==rows[:26] future=list(stock);future[30]=F(100) assert indicators(market,future)[:26]==indicators(market,stock)[:26] print(json.dumps({'first_fit':rows[20],'trigger':rows[24],'entry':rows[25], 'recovery_exit':rows[27],'ending_cash':rows[-1]['cash'], 'maximum_exit':cold[35],'market_only_final':common[-1], 'constant_market_status':constant[20]['reason'], 'scope':'synthetic lagged OLS; market input never traded or shorted'}))