"""盈利能力与现金覆盖双门槛,季度可得快照和完整20股交易账。""" from decimal import Decimal as D import json def make_facts(): facts=[] for i in range(1,26): for end,available in [('2025-12-31','2026-02-15'),('2026-03-31','2026-05-15')]: gross = [5,5,5,5+i] if end=='2025-12-31' else [5,5,5+i,5] net = [2,2,3,3] if end=='2025-12-31' else [2,3,3,2] cfo, assets = [3]*4,100 quarters = (['2025Q1','2025Q2','2025Q3','2025Q4'] if end=='2025-12-31' else ['2025Q2','2025Q3','2025Q4','2026Q1']) if i == 21: gross=[25]*4; cfo=[1]*4 if end=='2025-12-31' else [1,1,1,9] if i == 23: net=[-1]*4 if i == 24: gross=[5,5,5,None] if i == 25: assets=0 facts.append({'symbol':f'S{i:02}','end':end,'assets_date':end,'available':available, 'quarters':quarters,'gross':gross,'net':net,'cfo':cfo, 'assets':assets,'financial':i==22}) return facts def score(fact): if fact['financial']: return None,'financial_excluded' if fact['assets_date'] != fact['end']: return None,'period_mismatch' ids=[int(q[:4])*4+int(q[-1])-1 for q in fact['quarters']] if (len(ids)!=4 or any(b-a!=1 for a,b in zip(ids,ids[1:])) or any(len(fact[field])!=4 for field in ['gross','net','cfo'])): return None,'flow_periods' fields=fact['gross']+fact['net']+fact['cfo']+[fact['assets']] if any(x is None for x in fields): return None,'missing' values=[D(str(x)) for x in fields] if any(not x.is_finite() for x in values): return None,'nonfinite' gross=sum(D(str(x)) for x in fact['gross']) net=sum(D(str(x)) for x in fact['net']) cfo=sum(D(str(x)) for x in fact['cfo']) assets=D(str(fact['assets'])) if assets <= 0: return None,'assets' if net <= 0: return None,'nonpositive_income' if cfo < net: return None,'cash_conversion' return gross/assets,'eligible' def select(facts,cutoff): scores,reasons={},{} for symbol in sorted({f['symbol'] for f in facts}): known=[f for f in facts if f['symbol']==symbol and f['available']<=cutoff] if not known: continue fact=max(known,key=lambda f:(f['end'],f['available'])) value,reason=score(fact); reasons[symbol]=reason if value is not None: scores[symbol]=value return sorted(scores,key=lambda s:(-scores[s],s))[:20],scores,reasons facts=make_facts() execution_days=['2026-04-01','2026-07-01','2026-07-02'] assert execution_days[0]>'2026-03-31' and execution_days[1]>'2026-06-30' first,scores,reasons=select(facts,'2026-03-31') second,_,_=select(facts,'2026-06-30') assert first==[f'S{i:02}' for i in range(20,0,-1)] assert second==['S21']+[f'S{i:02}' for i in range(20,1,-1)] assert reasons['S21']=='cash_conversion' and scores['S20']==D('.4') assert {s:reasons[s] for s in ['S22','S23','S24','S25']}=={ 'S22':'financial_excluded','S23':'nonpositive_income','S24':'missing','S25':'assets'} assert score(dict(facts[0],assets_date='2026-12-31'))[1]=='period_mismatch' assert score(dict(facts[0],quarters=['2025Q1']*4))[1]=='flow_periods' assert score(dict(facts[0],cfo=[2,2,3,3]))[1]=='eligible' assert score(dict(facts[0],cfo=[2,2,3,2]))[1]=='cash_conversion' assert select(facts[:25],'2026-01-01')[0]==[] cash,holdings,fees,orders=D(10000),set(),D(0),[] for stage,target in enumerate([first,second,[]]): for symbol in sorted(holdings-set(target)): price=D(19) if stage==1 else D(21) cash+=20*price-1;fees+=1;orders.append([stage,symbol,'sell',20,str(price),1]) for symbol in sorted(set(target)-holdings): cash-=D(401);fees+=1;orders.append([stage,symbol,'buy',20,'20',1]) holdings=set(target) if stage==0: assert cash==1980 and cash+len(holdings)*400==10000-fees if stage==1: assert cash==1958 and fees==22 and len(holdings)==20 assert cash==10338 and fees==42 and not holdings assert orders[:20]==[[0,f'S{i:02}','buy',20,'20',1] for i in range(1,21)] assert orders[20:22]==[[1,'S01','sell',20,'19',1],[1,'S21','buy',20,'20',1]] assert orders[22:]==[[2,s,'sell',20,'21',1] for s in sorted(second)] print(json.dumps({'first':first,'second':second,'first_quality_S20':str(scores['S20']), 'execution_days_by_stage':execution_days, 'first_exclusions':{s:r for s,r in reasons.items() if r!='eligible'}, 'orders':orders,'ending_cash':str(cash),'fees':str(fees), 'holdings':sorted(holdings),'scope':'synthetic point-in-time snapshots only'}))