"""港股每股lot生效元数据对照统一100股简化,含费用和数量现金桥。""" from decimal import Decimal as D import json lots=[{'symbol':'HK:A','from':'2026-10-01','to':'2026-10-31','lot':500,'source':'synthetic_issuer_table_v1'}, {'symbol':'HK:B','from':'2026-10-01','to':'2026-10-31','lot':1000,'source':'synthetic_issuer_table_v1'}, {'symbol':'HK:B','from':'2026-11-01','to':'2026-12-31','lot':500,'source':'synthetic_issuer_table_v2'}] def lot_at(symbol,day): rows=[r for r in lots if r['symbol']==symbol and r['from']<=day<=r['to']] if len(rows)!=1: raise ValueError('missing_or_ambiguous_lot_metadata') return rows[0]['lot'] def size(budget,price,lot): budget,price=D(str(budget)),D(str(price)) if type(lot) is not int or lot<=0 or not budget.is_finite() or not price.is_finite() or budget<0 or price<=0: raise ValueError('invalid_sizing_input') quantity=max(0,int((budget-1)//(price*lot)))*lot fee=D(1) if quantity else D(0) return {'quantity':quantity,'fee':str(fee),'cash_left':str(budget-quantity*price-fee)} def run(simplified): cash,fees,holdings,orders=D(20000),D(0),{},[] for symbol,price in [('HK:A',D('12.5')),('HK:B',D('7.2'))]: lot=100 if simplified else lot_at(symbol,'2026-10-26') plan=size(10000,price,lot);q=plan['quantity'];fee=D(plan['fee']) cash-=q*price+fee;fees+=fee;holdings[symbol]=q orders.append([symbol,'buy',q,str(price),str(fee),lot]) opened_cash=cash assert cash+holdings['HK:A']*D('12.5')+holdings['HK:B']*D('7.2')==D(20000)-fees for symbol,price in [('HK:A',D(13)),('HK:B',D(7))]: q=holdings.pop(symbol);cash+=q*price-1;fees+=1 orders.append([symbol,'sell',q,str(price),'1']) return {'entry_cash':str(opened_cash),'orders':orders,'ending_cash':str(cash), 'fees':str(fees),'holdings':holdings} actual,reference=run(False),run(True) assert actual['orders'][:2]==[['HK:A','buy',500,'12.5','1',500],['HK:B','buy',1000,'7.2','1',1000]] assert reference['orders'][:2]==[['HK:A','buy',700,'12.5','1',100],['HK:B','buy',1300,'7.2','1',100]] assert actual['orders'][2:]==[['HK:A','sell',500,'13','1'],['HK:B','sell',1000,'7','1']] assert reference['orders'][2:]==[['HK:A','sell',700,'13','1'],['HK:B','sell',1300,'7','1']] assert D(actual['entry_cash'])==6548 and D(reference['entry_cash'])==1888 assert D(actual['ending_cash'])==20046 and D(reference['ending_cash'])==20086 assert actual['fees']==reference['fees']=='4' and not actual['holdings'] and not reference['holdings'] assert lot_at('HK:B','2026-11-02')==500 and lot_at('HK:B','2026-10-26')==1000 assert size(1000,12.5,500)=={'quantity':0,'fee':'0','cash_left':'1000.0'} try: lot_at('HK:UNKNOWN','2026-10-26') except ValueError: pass else: raise AssertionError('没有逐股元数据不得默默回退100股') lots.append(dict(lots[0],source='synthetic_conflicting_version')) try: lot_at('HK:A','2026-10-26') except ValueError: pass else: raise AssertionError('重叠生效版本必须拒绝') finally: lots.pop() print(json.dumps({'lot_metadata':lots,'per_stock_model':actual,'uniform_100_reference':reference, 'scope':'synthetic HKD stocks and issuer metadata; no odd-lot or actual broker matching claim'}))