"""R638:显式往返身份配对,净利润与同币种五赢家门槛;不生成或认证策略。""" from collections import defaultdict,Counter from decimal import Decimal as D SUPPORTED_CURRENCIES={'USD','HKD'} def fill(ident,round_id,side,price,currency='USD',qty=1,time=0,account='demo'): return {'id':ident,'round_id':round_id,'side':side,'price':D(str(price)), 'currency':currency,'qty':qty,'fee':D('.5'),'time':time,'security':'S','account':account} def prepare(records): required={'id','round_id','side','price','currency','qty','fee','time','security','account'} if any(required-set(row) for row in records):raise ValueError('missing required fill field') for row in records: if any(not isinstance(row[field],str) or not row[field].strip() for field in ['id','round_id','account','security','currency']): raise ValueError('invalid identity field') if row['currency'] not in SUPPORTED_CURRENCIES:raise ValueError('unsupported fixture currency') if len({r['id'] for r in records})!=len(records):raise ValueError('duplicate fill') groups=defaultdict(list) for r in records: if type(r['time']) is not int or r['time']<0:raise ValueError('invalid fixture time') if not r['price'].is_finite() or r['price']<=0 or not r['fee'].is_finite() or r['fee']<0 or type(r['qty']) is not int or r['qty']<=0: raise ValueError('invalid amount') groups[r['round_id']].append(r) rows=[] for ident,group in groups.items(): buys=[r for r in group if r['side']=='buy'];sells=[r for r in group if r['side']=='sell'] if len(group)==1 and len(buys)==1: rows.append({'round_id':ident,'status':'open','net':None});continue if len(buys)!=1 or len(sells)!=1 or len(group)!=2: rows.append({'round_id':ident,'status':'ambiguous_pair','net':None});continue buy,sell=buys[0],sells[0] if buy['account']!=sell['account'] or buy['currency']!=sell['currency'] or buy['security']!=sell['security'] or buy['qty']!=sell['qty'] or sell['time']<=buy['time']: rows.append({'round_id':ident,'status':'identity_quantity_or_time_mismatch','net':None});continue net=buy['qty']*(sell['price']-buy['price'])-buy['fee']-sell['fee'] if not net.is_finite():raise ValueError('nonfinite net profit') rows.append({'round_id':ident,'status':'closed','currency':buy['currency'],'net':net,'fills':[buy['id'],sell['id']]}) winners=Counter(row['currency'] for row in rows if row['status']=='closed' and row['net']>0) return rows,winners records=[] for i in range(1,8): exit_price=103 if i<=5 else 99 if i==6 else 101 records += [fill(f'B{i}',f'R{i}','buy',100,time=2*i),fill(f'S{i}',f'R{i}','sell',exit_price,time=2*i+1)] records += [fill('B8','R8','buy',100,time=20)] records += [fill('B9a','R9','buy',100,time=21),fill('B9b','R9','buy',101,time=22),fill('S9','R9','sell',103,qty=2,time=23)] records += [fill('B10','R10','buy',100,time=24),fill('S10','R10','sell',103,currency='HKD',time=25)] records += [fill('B11','R11','buy',100,currency='HKD',time=26),fill('S11','R11','sell',103,currency='HKD',time=27)] rows,winners=prepare(records) assert winners=={'USD':5,'HKD':1} assert [r['net'] for r in rows if r['round_id'] in ['R1','R6','R7']]==[D(2),D(-2),D(0)] assert {r['round_id']:r['status'] for r in rows if r['round_id'] in ['R8','R9','R10']}=={'R8':'open','R9':'ambiguous_pair','R10':'identity_quantity_or_time_mismatch'} _,fewer=prepare([r for r in records if r['round_id']!='R5']) assert fewer['USD']==4 and fewer['USD']<5 and sum(fewer.values())==5 five_rows,five_winners=prepare(records[:5]) assert five_winners['USD']==2 and sum(row['status']=='open' for row in five_rows)==1 try:prepare(records+[records[0]]) except ValueError:pass else:raise AssertionError('duplicate execution counted twice') bad=[dict(records[0],price=D('NaN'))] try:prepare(bad) except ValueError:pass else:raise AssertionError('nonfinite price accepted') bad_account=[fill('X1','X','buy',100,time=1,account='one'),fill('X2','X','sell',103,time=2,account='two')] assert prepare(bad_account)[0][0]['status']=='identity_quantity_or_time_mismatch' try:prepare([{k:v for k,v in records[0].items() if k!='fee'}]) except ValueError:pass else:raise AssertionError('missing fee silently treated as zero') identity_failures=[] for field in ['id','round_id','account','security','currency']: for value in [None,'',' ','\t',0,False]: modified=[dict(r,**{field:value}) if field!='id' or index==0 else dict(r) for index,r in enumerate(records[:10])] try:prepare(modified) except ValueError:pass else:identity_failures.append((field,repr(value))) missing=[{k:v for k,v in records[0].items() if k!=field}] try:prepare(missing) except ValueError:pass else:identity_failures.append((field,'missing')) for currency in ['EUR','UNKNOWN','usd',' USD ']: try:prepare([dict(r,currency=currency) for r in records[:10]]) except ValueError:pass else:identity_failures.append(('unsupported_currency',currency)) assert not identity_failures,identity_failures print('profitable_closed_by_currency='+str(dict(winners))+' USD_input_eligible=True HKD_input_eligible=False') print('R1_net=2 R6_net=-2 R7_net=0 open_R8=excluded ambiguous_R9=excluded mixed_currency_R10=excluded') print('remove_R5=USD_4_not_eligible even_with_HKD_1 threshold_is_not_strategy_validation=True rules_generated=False')