"""R643:显式机会引用匹配合成人工成交,不用靠近的时间强配。""" from collections import defaultdict from decimal import Decimal as D def text(x):return isinstance(x,str) and bool(x.strip()) def compare(opportunities,actual): if any(not all(text(row[k]) for k in ['id','security','version','currency']) for row in opportunities+actual):raise ValueError('invalid identity') for rows in [opportunities,actual]: if len({r['id'] for r in rows})!=len(rows):raise ValueError('duplicate identity') for row in opportunities+actual: if row['side'] not in {'buy','sell'} or type(row['time']) is not int or type(row['qty']) is not int or row['qty']<=0:raise ValueError('invalid event') if row['currency']!='USD' or not D(row['price']).is_finite() or D(row['price'])<=0:raise ValueError('invalid scope or money') by_ref=defaultdict(list) for row in actual:by_ref[row['reference']].append(row) rows=[];known={r['id'] for r in opportunities} for signal in opportunities: candidates=by_ref[signal['id']] if not candidates:status='no_actual_execution';detail={} elif len(candidates)>1:status='ambiguous_actual_group';detail={} else: event=candidates[0];detail={} if any(event[k]!=signal[k] for k in ['security','currency','version']):status='identity_or_version_mismatch' elif event['side']!=signal['side']:status='direction_mismatch' elif not 0<=event['time']-signal['time']<=3:status='timing_mismatch' else: status='matched';detail={'qty_difference':event['qty']-signal['qty'],'time_lag':event['time']-signal['time'],'price_difference':D(event['price'])-D(signal['price'])} rows.append({'opportunity':signal['id'],'status':status,**detail}) for event in actual: if event['reference'] not in known:rows.append({'execution':event['id'],'status':'coverage_gap' if event['security'] not in {'A','B'} else 'unlinked_actual'}) return rows def row(ident,s,side,t,qty=1,price='100',version='v1',reference=None): return {'id':ident,'security':s,'side':side,'time':t,'qty':qty,'price':price,'version':version,'currency':'USD','reference':reference} opportunities=[row('S1','A','buy',10,3),row('S2','A','sell',20),row('S3','B','buy',30),row('S4','B','sell',40)] actual=[row('E1','A','buy',12,2,'101',reference='S1'),row('E2','A','buy',21,reference='S2'),row('E3','C','buy',31,reference='OUTSIDE'),row('E4','B','sell',41,version='v2',reference='S4')] result=compare(opportunities,actual) assert [r['status'] for r in result]==['matched','direction_mismatch','no_actual_execution','identity_or_version_mismatch','coverage_gap'] assert result[0]['qty_difference']==-1 and result[0]['time_lag']==2 and result[0]['price_difference']==D(1) late=[dict(actual[0],time=14)]+actual[1:] assert compare(opportunities,late)[0]['status']=='timing_mismatch' multiple=actual+[dict(actual[0],id='E5')] assert compare(opportunities,multiple)[0]['status']=='ambiguous_actual_group' try:compare(opportunities,actual+[actual[0]]) except ValueError:pass else:raise AssertionError('duplicate execution accepted') print('rows='+str(result)) print('late=timing_mismatch multiple_fills=ambiguous_actual_group duplicate_id=rejected actual_account_access=False')