"""C07构造交易账本。仅标准库;各函数接收输入并计算,非真实撮合或收费制度。""" from decimal import Decimal, localcontext, ROUND_HALF_UP from pathlib import Path import json import math D = Decimal def money(x, digits=6): return format(D(x).quantize(D(1).scaleb(-digits), rounding=ROUND_HALF_UP), 'f') def shortfall(p): q, p0, pa, pt, fee = (D(str(p[k])) for k in ('quantity', 'decision', 'arrival', 'terminal', 'fee')) fills = [{'quantity': n, 'price': str(px), 'notional': money(D(n)*D(str(px)))} for n, px in p['fills']] done = sum(D(n) for n, _ in p['fills']) gross = sum(D(n)*D(str(px)) for n, px in p['fills']) rows = [] for s in (1, -1): delay = s*done*(pa-p0) execution = s*(gross-done*pa) opportunity = s*(q-done)*(pt-p0) paper = s*q*(pt-p0) actual = s*(done*pt-gross)-fee total = delay+execution+opportunity+fee assert total == paper-actual rows.append(dict(side=s, target=int(q), decision=money(p0), arrival=money(pa), terminal=money(pt), filled=int(done), unfilled=int(q-done), delay=money(delay), execution=money(execution), fee=money(fee), opportunity=money(opportunity), shortfall=money(total), paper_pnl=money(paper), actual_pnl=money(actual))) alternate = dict(p, terminal='99') changed = sum(D(n)*(D(str(px))-p0) for n, px in p['fills']) + (q-done)*(D(alternate['terminal'])-p0)+fee return dict(fills=fills, ledger=rows, lower_terminal_buy_shortfall=money(changed)) def spreads(p): rows=[] for r in p['trades']: side,qty=r['side'],r['quantity'] bid,ask,px=(D(r[k]) for k in ('bid','ask','price')) mid=(bid+ask)/2 half=side*(px-mid) improvement=(ask-px) if side==1 else (px-bid) rows.append(dict(r, mid=money(mid), half=money(half), effective=money(2*half), cost=money(qty*half), half_bps=money(half/mid*10000), effective_bps=money(2*half/mid*10000), improvement=money(improvement))) return dict(trades=rows, cost=money(sum(D(r['cost']) for r in rows)), wrong_absolute_cost=money(sum(abs(D(r['cost'])) for r in rows))) def vwap(p): own=sum(D(n)*D(px) for n,px in p['fills'])/sum(D(n) for n,_ in p['fills']) market=[dict(slot=i,price=px,volume=n,notional=money(D(px)*n)) for i,(px,n) in enumerate(p['market'])] rows=[] for name,lo,hi in p['windows']: selected=[r for r in market if lo<=r['slot']target-used: status='拒绝超量' else: children[cid]=dict(qty=value,filled=0,state='live') elif action=='cum': c=children[cid] if valuec['qty']: status='拒绝非法累计' elif value==c['filled']: status='忽略重复累计' else: delta=value-c['filled'];c['filled']=value if value==c['qty']:c['state']='filled' elif action=='cancel_request':children[cid]['state']='cancel_pending' elif action=='cancel_ack':children[cid]['state']='cancelled' filled=sum(c['filled'] for c in children.values()) live=sum(c['qty']-c['filled'] for c in children.values() if c['state'] in ('live','cancel_pending')) free=target-filled-live assert free>=0 and filled+live+free==target rows.append(dict(action=action,child=cid,value=value,status=status,delta=delta,child_cumulative=children.get(cid,{}).get('filled',0),filled=filled,live=live,available=free)) return dict(events=rows, children=children) def queue_bounds(p): q=p['quantity'];low,high=p['ahead'];confirmed=p['confirmed_cancel'];unknown=p['possible_extra_cancel'];vlow,vhigh=p['active_volume'] ahead_low=max(0,low-confirmed-unknown);ahead_high=max(0,high-confirmed) lower=min(q,max(0,vlow-ahead_high));upper=min(q,max(0,vhigh-ahead_low)) scenario=[] for name,ahead,cancel,volume in p['scenarios']: fill=min(q,max(0,volume-max(0,ahead-cancel))) cash=D(p['cash'])-fill*D(p['limit']) scenario.append(dict(scenario=name,ahead=ahead,cancel=cancel,volume=volume,fill=fill,unfilled=q-fill,cash=money(cash),shares=fill,marked_assets=money(cash+fill*D(p['terminal'])))) return dict(ahead_after=[ahead_low,ahead_high],fill_bounds=[lower,upper],scenarios=scenario) def sweep(book,quantity,limit,cash): remaining=quantity;spent=D(0);rows=[] for px,available in book: price=D(px) fill=min(remaining,available,int((D(cash)-spent)/price)) if price<=D(limit) else 0 amount=price*fill;spent+=amount;remaining-=fill rows.append(dict(price=px,available=available,eligible=price<=D(limit),fill=fill,amount=money(amount),remaining=remaining,spent=money(spent),cash=money(D(cash)-spent))) filled=quantity-remaining return dict(levels=rows,filled=filled,remaining=remaining,spent=money(spent),cash=money(D(cash)-spent),average=None if not filled else money(spent/filled)) def impact(p): # 本函数只处理非负、有限的固定比例成本;初始上界不是交易规模硬上限。 def number(value,label,positive=False): try: value=D(str(value)) except (ValueError,TypeError,ArithmeticError) as error: raise ValueError(label+'必须是有限数值') from error if not value.is_finite() or value<0 or (positive and value==0): raise ValueError(label+('必须为正的有限数值' if positive else '必须为非负有限数值')) return value base=number(p['base'],'基准交易金额') budget=number(p['budget'],'成本预算') initial_upper=number(p['search_max'],'初始搜索上界',positive=True) scales=[number(scale,'候选规模') for scale in p['scales']] if not p['assets']: raise ValueError('资产参数不能为空') increasing=False for asset in p['assets']: weight=number(asset['weight'],'交易比例') a=number(asset['a'],'线性系数') b=number(asset['b'],'冲击系数') sigma=number(asset['sigma'],'收益波动率') number(asset['volume'],'市场成交额',positive=True) increasing=increasing or (base*weight>0 and (a>0 or b*sigma>0)) rows=[];totals=[] def one(asset,x): a,b,sigma,volume=(D(str(asset[k])) for k in ('a','b','sigma','volume')) participation=x/volume linear=a*x;nonlinear=b*sigma*x*participation.sqrt() marginal=a+D('1.5')*b*sigma*participation.sqrt() return participation,linear,nonlinear,marginal def total(scale): return sum(sum(one(asset,base*D(str(asset['weight']))*scale)[1:3]) for asset in p['assets']) for original_scale,scale in zip(p['scales'],scales): for asset in p['assets']: x=base*D(str(asset['weight']))*scale pr,lin,non,mar=one(asset,x) rows.append(dict(asset=asset['name'],scale=original_scale,amount=money(x),participation=money(pr),linear=money(lin),impact=money(non),total=money(lin+non),marginal_bps=money(mar*10000))) cost=total(scale);totals.append(dict(scale=original_scale,cost=money(cost),feasible=cost<=budget)) lo,hi=D(0),initial_upper expansions=0 if not increasing: # 实际交易金额或全部实际成本系数为零:所有规模都满足非负预算,没有有限边界。 status='unbounded' continuous_scale=continuous_amount=None upper=None elif budget==0: status='zero_budget' continuous_scale=money(0);continuous_amount=money(0) upper=money(0) else: # 仅在上界成本达到预算后二分;极端输入扩展仍失败时不伪报一个有限根。 while total(hi)=budget and contractions<2048: hi/=2 contractions+=1 if total(hi/2)>=budget: raise ValueError('搜索区间数值跨度过大,无法可靠求根') upper=format(hi,'f') status='finite' for _ in range(160): mid=(lo+hi)/2 if total(mid)<=budget:lo=mid else:hi=mid continuous_scale=money(lo);continuous_amount=money(base*lo) return dict(assets=rows,scales=totals,continuous_scale=continuous_scale,continuous_amount=continuous_amount,budget=p['budget'],continuous_status=status,initial_upper=p['search_max'],bracket_upper=upper,bracket_expansions=expansions) def fees(p): rate,minimum=D(p['rate']),D(p['minimum']) def fee(x):return max(minimum,D(x)*rate) if D(x)>0 else D(0) rows=[dict(parent=p['parent'],child=f'C{i:02}',amount=str(amount),proportional=money(D(amount)*rate),fee=money(fee(amount))) for i,amount in enumerate(p['children'],1)] total=sum(D(x) for x in p['children']);child=sum(D(r['fee']) for r in rows) compare=[] for n,amount in p['comparisons']: value=D(amount)*n compare.append(dict(count=n,each=str(amount),total=money(value),parent_fee=money(fee(value)),child_fee=money(n*fee(amount)),difference=money(n*fee(amount)-fee(value)))) return dict(children=rows,total=money(total),parent_fee=money(fee(total)),child_fee=money(child),difference=money(child-fee(total)),threshold=money(minimum/rate),comparisons=compare) def replay(p,delays,tolerance): rows=[];costs=[];opportunity=D(0) for i,(event,delay) in enumerate(zip(p['events'],delays),1): t=event['signal']+delay quotes=[r for r in event['quotes'] if D(str(r[0]))<=D(str(t))] qt,ask=max(quotes,key=lambda r:D(str(r[0]))) age=D(str(t))-D(str(qt));valid=age<=D(str(tolerance)) cost=D(event['quantity'])*(D(ask)-D(event['decision'])) if valid else None opp=D(0) if valid else D(event['quantity'])*(D(event['terminal'])-D(event['decision'])) opportunity+=opp if cost is not None:costs.append(cost) rows.append(dict(event=i,signal=event['signal'],delay=delay,arrival=t,quote_time=qt,ask=ask,age=money(age),status='成交' if valid else '报价过期拒单',cost=None if cost is None else money(cost),opportunity=money(opp))) ordered=sorted(costs);n=len(ordered) median=(ordered[(n-1)//2]+ordered[n//2])/2 if n else None p95=ordered[math.ceil(D('0.95')*n)-1] if n else None return dict(events=rows,mean_delay=money(sum(D(str(d)) for d in delays)/len(delays)),filled=n,total=len(delays),median=None if median is None else money(median),p95=None if p95 is None else money(p95),execution_cost=money(sum(costs)),opportunity=money(opportunity),total_shortfall=money(sum(costs)+opportunity)) def residual(p): pairs=[r for r in p['orders'] if r['actual'] is not None] grouped={} for r in pairs: if r['split']=='train':grouped.setdefault(r['group'],[]).append(D(r['actual'])-D(r['predicted'])) offsets={g:sum(v)/len(v) for g,v in grouped.items()} rows=[] for r in p['orders']: offset=offsets.get(r['group']);actual=None if r['actual'] is None else D(r['actual']) predicted=D(r['predicted']);adjusted=None if offset is None else predicted+offset rows.append(dict(r,offset=None if offset is None else money(offset),adjusted=None if adjusted is None else money(adjusted),residual=None if actual is None else money(actual-predicted),after_residual=None if actual is None or adjusted is None else money(actual-adjusted),actual_amount=None if actual is None else money(D(r['notional'])*actual/10000))) test=[r for r in rows if r['split']=='holdout' and r['actual'] is not None] groups=[dict(group=g,n=len(v),mean_residual=money(offsets[g])) for g,v in grouped.items()] return dict(orders=rows,groups=groups,paired=len(pairs),submitted=len(p['orders']),holdout_paired=len(test),holdout_submitted=sum(r['split']=='holdout' for r in rows),before_mae=money(sum(abs(D(r['residual'])) for r in test)/len(test)),after_mae=money(sum(abs(D(r['after_residual'])) for r in test)/len(test))) def build(): result={} def put(i,p,o):result[f'DJ4-{i:03}']=dict(inputs=p,outputs=o) p=dict(quantity=1000,decision='100',arrival='100.2',terminal='101',fee='7',fills=[[300,'100.3'],[400,'100.5']]);put(67,p,shortfall(p)) trades=[] for s,prices in ((1,['100.02','100','99.99']),(-1,['99.98','100','100.01'])): for i,(px,q) in enumerate(zip(prices,[100,200,100]),1):trades.append(dict(id=f'{s}:{i}',side=s,quantity=q,bid='99.98',ask='100.02',price=px)) p=dict(trades=trades);put(68,p,spreads(p)) p=dict(market=[['101',1000],['99',2000],['98',0],['99',2000],['102',1000],['104',2000]],fills=[[100,'99.5'],[200,'99.7']],windows=[['预设',1,4],['事后',4,6],['全天',0,6],['零量',2,3]]);put(69,p,vwap(p)) p=dict(fraction='0.05',events=[[1000,0,0,50],[600,30,0,50],[800,40,10,80],[600,40,0,50]]);put(70,p,dict(base=participation(p),double_rate=participation(p,'0.10'))) p=dict(target=1000,events=[['new','A',600],['cum','A',200],['cum','A',200],['cancel_request','A',0],['new','X',800],['cum','A',250],['cancel_ack','A',0],['new','B',750],['cum','B',300],['cum','B',750]]);put(71,p,remainder(p)) p=dict(quantity=300,ahead=[100,300],confirmed_cancel=50,possible_extra_cancel=50,active_volume=[200,350],limit='10',cash='4000',terminal='10.2',scenarios=[['零成交',300,50,200],['部分成交',200,50,300],['足额成交',100,100,350]]);put(72,p,queue_bounds(p)) p=dict(book=[['10',100],['10.01',200],['10.02',150],['10.04',400],['10.06',500]],quantity=600,limit='10.02',cash='7000',next_book=[['10.03',150]],new_limit='10.04') first=sweep(p['book'],p['quantity'],p['limit'],p['cash']);put(73,p,dict(first=first,next_unchanged=sweep(p['next_book'],first['remaining'],p['limit'],first['cash']),next_raised=sweep(p['next_book'],first['remaining'],p['new_limit'],first['cash']))) p=dict(base='100000',scales=[1,2,4,7,10],budget='1000',search_max='10',assets=[dict(name='甲',weight='0.5',a='0.0005',b='0.1',sigma='0.02',volume='1000000'),dict(name='乙',weight='0.3',a='0.0005',b='0.1',sigma='0.03',volume='500000'),dict(name='丙',weight='0.2',a='0.0005',b='0.1',sigma='0.015',volume='2000000')]);put(74,p,impact(p)) p=dict(parent='M1',children=[1000,2000,3000,4000,5000,6000,7000,8000,14000,50000],rate='0.0003',minimum='5',comparisons=[[10,10000],[10,20000],[1,100000],[0,10000]]);put(75,p,fees(p)) events=[] for i in range(6): t=i*10 quotes=[[t,'100.01'],[t+0.8,str(D('100.02')+D(i)*D('.01'))]] if i==4:quotes.append([t+1.8,'100.4']) events.append(dict(signal=t,quantity=100,decision='100',terminal='100.8',quotes=quotes)) p=dict(events=events,stable=[1]*6,tail=[0,0,0,0,2,4],tolerance='1.5',bid_offset='0.02',quote_depth=100) quote_ledger=[dict(event=i,time=t,bid=money(D(ask)-D(p['bid_offset'])),ask=ask,depth=p['quote_depth']) for i,e in enumerate(events,1) for t,ask in e['quotes']] put(76,p,dict(quotes=quote_ledger,stable=replay(p,p['stable'],p['tolerance']),tail=replay(p,p['tail'],p['tolerance']),loose_tail=replay(p,p['tail'],'4'))) orders=[] for i,(group,pred,acts,notional) in enumerate([('小额常规','2',['3','5','4'],10000),('小额开端','4',['5','7','7'],10000),('大额常规','6',['10','12','13'],50000),('大额开端','8',['15','17','19'],50000)]): for j,actual in enumerate(acts):orders.append(dict(id=f'{i+1}-{j+1}',group=group,notional=str(notional),predicted=pred,actual=actual,split='train' if j<2 else 'holdout')) orders.sort(key=lambda r:(r['split']=='holdout',r['id'])) orders.append(dict(id='缺失',group='大额开端',notional='50000',predicted='8',actual=None,split='holdout')) p=dict(orders=orders,known_at_prediction=['母单规模','预设时段','预测成本','订单ID'],label='决策价有向已成交价格差,不含费用和未成交机会项');put(77,p,residual(p)) assert len(result)==11 return result def main(): with localcontext() as context: context.prec=42 data=build() Path(__file__).with_suffix('.json').write_text(json.dumps(data,ensure_ascii=False,indent=2,allow_nan=False)+'\n',encoding='utf-8',newline='\n') if __name__=='__main__':main()