"""R631:相同固定排名的有状态缓冲成员政策,空位留现金。""" from math import isfinite RANKINGS=[['A','B','C','D'],['A','C','B','D'],['A','B','C','D'],['A','C','B','D'],['C','D','A','B'],['C','D','B','A']] def select(ranking,old,buffered,blocked=()): if len(set(ranking))!=len(ranking):raise ValueError('duplicate security') ranks={s:i+1 for i,s in enumerate(ranking)} retained=[s for s in old if ranks.get(s,999)<=3] if buffered else [] desired=list(retained) for s in ranking[:2]: if s not in desired and len(desired)<2:desired.append(s) if any(s in blocked for s in set(old)-set(desired)): return {'status':'blocked_exit','actual':list(old),'desired':desired,'retained':retained} return {'status':'applied','actual':desired,'desired':desired,'retained':retained} def run(buffered): held=[];cash=1000.;qty={s:0. for s in 'ABCD'};notional=0.;rows=[];exits=0 for t,ranking in enumerate(RANKINGS): result=select(ranking,held,buffered);new=result['actual'] added=sorted(set(new)-set(held));removed=sorted(set(held)-set(new));exits+=len(removed) target={s:5. if s in new else 0. for s in qty} for side in ['sell','buy']: for s in qty: delta=target[s]-qty[s] if (side=='sell' and delta>=0) or (side=='buy' and delta<=0):continue cash-=delta*100;notional+=abs(delta)*100;qty[s]=target[s] assert cash>=0 qty=target;held=new assert cash>=0 and cash+sum(q*100 for q in qty.values())==1000 rows.append({'session':t,'members':sorted(new),'added':added,'removed':removed,'cash':cash}) return exits,notional/1000,rows a=run(False);b=run(True) assert a[0]==4 and b[0]==2 and a[1]==5 and b[1]==3 assert b[2][1]['members']==['A','B'] and b[2][4]['members']==['A','C'] assert select(['A'],[],True)['actual']==['A'] assert 1-len(select(['A'],[],True)['actual'])*.5==.5 blocked=select(['A','C','D','B'],['A','B'],True,blocked=['B']) assert blocked['status']=='blocked_exit' and blocked['actual']==['A','B'] scores={'B':1.,'A':1.,'C':0.} assert all(isfinite(v) for v in scores.values()) assert sorted(scores,key=lambda s:(-scores[s],s))==['A','B','C'] print('top2_exits=4 buffer_exits=2 gross_turnover_including_formation=5.0,3.0 ending=1000,1000') print('buffer_members='+str([row['members'] for row in b[2]])) print('vacancy_cash_weight=0.5 suspended_exit=blocked_not_approved_retention ties=score_then_security')