订单类型不是新引擎,是引擎的旋钮
第五章结尾埋了个伏笔:绝大多数"订单类型"只是撮合循环的参数。这一章把真实交易所的下单方式过一遍。每讲一种,我们都问同一个工程问题:它改了循环的哪一部分?看完你会发现,一张看起来吓人的下单表单,拆开来不过四五组旋钮。
第一组旋钮:剩余量怎么办(时效指令)
第五章说过,循环停下来时的剩余处理分出了流派。这组指令叫时效指令(time-in-force),回答"这张单活多久、剩了怎么办":
- GTC(good-till-cancelled,撤前有效):默认款。剩余挂进簿子一直排,直到你撤掉。有的交易所给GTC加个期限变体GTD(到某日有效)。
- IOC(immediate-or-cancel,即刻成交剩余撤销):进撮合循环,能成交多少算多少,剩余立刻撤掉,一秒都不排。用法:我想要这个深度里的量,但不想要排队的不确定性。
- FOK(fill-or-kill,全成或全退):进循环之前先算一遍——对面可成交量够不够我全吃?不够,整张拒绝,一股不成交;够,才进循环。用法:大额换仓,宁愿不做也不愿做一半。
工程实现:GTC什么都不用加;IOC是循环结束后把剩余丢弃而非入簿;FOK是循环前加一次"干跑"(只算不成交),量够再真跑。FOK有个坑:干跑和真跑之间不能有别的订单插进来改变簿子——这逼你接受一个原则,撮合必须串行,这是第十一章单线程架构的第一块基石。
第二组旋钮:只许挂,不许吃
Post-only(只做挂单方):订单进门时检查——如果它此刻会成交,直接拒单(有的交易所选择把价格自动挪开一格)。为什么有人要这个?因为交易所给挂单方(maker)手续费返佣、向吃单方(taker)收费(第八章详谈),大玩家绝不允许自己"一不小心成了taker"——那等于本来收钱变成交钱。Post-only是把"我一定排队"写成了硬约束。
第三组旋钮:先藏着,等信号——条件单
前面所有单都是进门就进撮合。止损单(stop order)不一样:它进门后先睡一觉,不进簿子,只登记一个触发价。等市场最新成交价碰到触发价,它才醒来,变成一张普通订单冲进撮合循环。醒来的姿势又分两派:
- 止损市价单:触发后变市价单,保证成交,不保证价。跌到止损位时往往很拥挤,实际成交价可能比触发价差不少。
- 止损限价单:触发后变限价单,保证价格上限,不保证成交——价格一泻千里时你的限价单可能根本追不上,止损没止住。这个风险要自己想清楚。
工程实现:撮合引擎旁边多一个触发器——每笔成交产生后,拿最新价去扫一遍沉睡的止损单列表,把被触发的唤醒成普通订单重新进门。注意触发源通常是"本所最新成交价",这就有一个微妙的顺序问题:一笔成交既改变簿子,又可能唤醒新订单,新订单再改变簿子——事件链条必须严格按序处理,又是串行的理由。
第四组旋钮:别让人看见我的底牌——冰山单
大单挂出来会吓坏市场:墙上突然出现10万股的买单,所有人都会调整行为。冰山单(iceberg order)的办法是:总量10万股,但墙上只露1000股(叫"峰值量");露出来的1000股被吃掉,引擎自动从水下再补1000股上来,直到总量耗尽。露出来的部分正常排队,水下部分别人看不见。
工程实现上有两个点要想清楚。一是补单时的队位:新补的1000股是一张"新单",按第三章的规则应该重新排到这个价位的队尾——所以冰山单在排队上是吃亏的,这是隐藏自己的代价。二是成交优先级:同一价位上,完全显露的单和冰山的显露部分怎么排,各交易所规则不同,写之前要定死。
一张表收拢全章
GTC → 剩余入簿;IOC → 剩余丢弃;FOK → 进门前先干跑验量;Post-only → 进门前检查是否成交,成交则拒;止损 → 先睡,被成交价唤醒再进门;冰山 → 入簿只入峰值,吃光自动补。
六种旋钮,没有一种动了第五章那个核心循环。它们改的是:进门前的检查(FOK、post-only)、进门的时间(止损)、入簿的量(冰山)、剩余的去向(IOC)。这就是设计订单类型的方法论:新类型必须能表达成对现有流程的一个明确修改点,说不清楚的类型不做。
真实交易所的表单里还有更花哨的——移动止损、OCO(二选一单)、TWAP切片——但它们要么是上面旋钮的组合,要么是交易所之外的策略层(客户端或券商系统)做的事,不属于撮合引擎本体。分清"引擎该管的"和"策略层该管的",是交易所架构里最重要的边界之一。
下一章换个场景:当市场还没开门、没有买一卖一的时候,第一笔价格是怎么产生的?