"""Deterministic teaching model. No APIs, accounts or real orders. Run: python order-model.py /new/output/dir Prices, quantities, fees, queue and later events are explicitly synthetic. """ from decimal import Decimal as D from pathlib import Path import csv,json,sys ASKS=[(D('60.00'),D('0.50')),(D('60.10'),D('1.00')),(D('60.40'),D('1.50'))] MAKER=D('.001');TAKER=D('.002') def serialize(x): if isinstance(x,D):return format(x,'f') if isinstance(x,dict):return {k:serialize(v) for k,v in x.items()} if isinstance(x,list):return [serialize(v) for v in x] return x def candidate(case,limit=None,tif='GTC',post=False,quantity=D('2')): if limit is not None:limit=D(limit) eligible=[(p,q) for p,q in ASKS if limit is None or p<=limit] available=sum((q for p,q in eligible),D(0));remaining=quantity;fills=[] if post and available>0:status='rejected_post_only' elif tif=='FOK' and available0:fills.append({'price':price,'quantity':got,'role':'taker','fee':price*got*TAKER});remaining-=got if remaining==0:status='filled' elif limit is None:status='depth_exhausted' elif tif in ['IOC','FOK']:status='cancelled_remainder' else:status='open_remainder' filled=sum((f['quantity'] for f in fills),D(0));notional=sum((f['price']*f['quantity'] for f in fills),D(0)) fee=sum((f['fee'] for f in fills),D(0));open_qty=remaining if status=='open_remainder' else D(0) cancelled=quantity-filled-open_qty return {'case':case,'quantity_requested':quantity,'limit':limit,'time_in_force':tif,'post_only':post,'status':status,'fills':fills, 'quantity_filled':filled,'quantity_open':open_qty,'quantity_not_filled_or_open':cancelled,'notional_eur':notional,'fee_eur':fee, 'cash_debit_eur':notional+fee,'average_fill_price':notional/filled if filled else None, 'illustrative_reserve_eur':open_qty*limit*(1+TAKER) if open_qty else D(0)} def model(): cases=[candidate('market_two'),candidate('limit_60.10_gtc','60.10'),candidate('limit_60.10_ioc','60.10','IOC'), candidate('limit_60.10_fok','60.10','FOK'),candidate('post_only_60.10','60.10',post=True),candidate('post_only_59.90','59.90',post=True), candidate('limit_59.90_gtc','59.90'),candidate('limit_60.40_gtc','60.40'),candidate('limit_60.40_fok','60.40','FOK'), candidate('limit_59.90_ioc','59.90','IOC'),candidate('market_five_depth_limit',quantity=D('5'))] mixed=candidate('gtc_then_assumed_later_maker_fill','60.10');later_qty=mixed['quantity_open'];price=D('60.10') mixed['fills'].append({'price':price,'quantity':later_qty,'role':'maker','fee':price*later_qty*MAKER}) mixed.update(status='filled_after_assumed_event',quantity_filled=D('2'),quantity_open=D(0),quantity_not_filled_or_open=D(0),illustrative_reserve_eur=D(0)) mixed['notional_eur']=sum(f['price']*f['quantity'] for f in mixed['fills']);mixed['fee_eur']=sum(f['fee'] for f in mixed['fills']) mixed['cash_debit_eur']=mixed['notional_eur']+mixed['fee_eur'];mixed['average_fill_price']=mixed['notional_eur']/D(2) mixed['assumption']='A later incoming market seller fills the entire 0.5 LTC remainder at its resting 60.10 price. No competing order ahead at that price.';cases.append(mixed) prior=D('2');ours=D('2');queue=[] for sell in [D('1'),D('1.5')]: consumed_prior=min(prior,sell);prior-=consumed_prior;ours_fill=min(ours,sell-consumed_prior);ours-=ours_fill queue.append({'incoming_sell_ltc':sell,'prior_order_fill_ltc':consumed_prior,'our_fill_ltc':ours_fill,'prior_remaining_ltc':prior,'our_remaining_ltc':ours}) checks=[] def check(name,value):assert value,name;checks.append({'name':name,'passed':True}) for x in cases: check(x['case']+' quantity conservation',x['quantity_requested']==x['quantity_filled']+x['quantity_open']+x['quantity_not_filled_or_open']) check(x['case']+' fills reconcile',sum((f['quantity'] for f in x['fills']),D(0))==x['quantity_filled']) check(x['case']+' notional reconciles',sum((f['price']*f['quantity'] for f in x['fills']),D(0))==x['notional_eur']) check(x['case']+' fees reconcile',sum((f['price']*f['quantity']*(MAKER if f['role']=='maker' else TAKER) for f in x['fills']),D(0))==x['fee_eur']) check(x['case']+' debit reconciles',x['cash_debit_eur']==x['notional_eur']+x['fee_eur']) check(x['case']+' limit respected',x['limit'] is None or all(f['price']<=x['limit'] for f in x['fills'])) check('Hand-calculated market debit',cases[0]['cash_debit_eur']==D('120.54060')) check('GTC and IOC same immediate fills, different remainder',cases[1]['fills']==cases[2]['fills'] and cases[1]['quantity_open']==D('.5') and cases[2]['quantity_open']==0) check('Partial FOK does not execute',cases[3]['quantity_filled']==0) check('Crossing post-only does not execute',cases[4]['quantity_filled']==0 and cases[4]['quantity_open']==0) check('Mixed roles fee hand calculation',mixed['fee_eur']==D('.21025')) check('FIFO queue example does not infer touch as fill',queue[0]['our_fill_ltc']==0 and queue[1]['our_fill_ltc']==D('.5') and ours==D('1.5')) return serialize({'schema':1,'release':'2026-10-03-v1','evidence':'synthetic_model','asks':[{'price_eur':p,'quantity_ltc':q} for p,q in ASKS], 'maker_fee_fraction':MAKER,'taker_fee_fraction':TAKER,'scenarios':cases,'fifo_queue_events':queue,'assertions':checks, 'scope':'Synthetic three-level book, declared maker/taker fees, no exchange engine, order precision, hidden liquidity, latency, funding or withdrawal. Quote-currency fees and a conservative illustrative reserve. Market-five row only walks bounded depth; not an exchange order outcome.'}) def main(): out=Path(sys.argv[1]).resolve();out.mkdir(parents=True,exist_ok=True);data=model() (out/'order-results.json').write_text(json.dumps(data,indent=2)+'\n',encoding='utf-8') fields=['case','quantity_requested','limit','time_in_force','post_only','status','quantity_filled','quantity_open','quantity_not_filled_or_open','notional_eur','fee_eur','cash_debit_eur','average_fill_price','illustrative_reserve_eur'] with (out/'order-cases.csv').open('w',newline='',encoding='utf-8') as f: w=csv.DictWriter(f,fieldnames=fields,extrasaction='ignore');w.writeheader();w.writerows(data['scenarios']) print(json.dumps({'scenarios':len(data['scenarios']),'queue_events':len(data['fifo_queue_events']),'assertions':len(data['assertions'])})) if __name__=='__main__':main()