期权做市商系统的软件描述(4/5)—多进程与多线程的选择

论坛 期权论坛 期权     
微信公众号   2016-6-1 08:41   24494   1

元旦前开始,期粉儿推出“期权做市商系统”的系列文章,作为送给各位读者的新年礼物。这一系列文章均出自本平台签约作者,中粮祈德丰首席期权量化及架构师袁煜华先生之笔。袁老师学富五车不止,且乐于助人,江湖人称“期权相关问题的百科全书”——百全老师,昵称百泉。

 

关注本公众号在历史消息中可以查看《期权做市商系统的选择》、《期权做市商系统的硬件架构》以及《期权做市商系统的软件描述》的1-3篇。

 

本质上来说,期权做市商系统是一套自动交易系统,一定具备以下的标准模块。任何一套自动交易系统的基本流程总是由接受行情数据开始,由策略生成报单提交至报单网关,报单网关负责收发交易所的信息并且反馈给策略和风控模块,风控模块根据事先设定的参数在一定条件下停止策略关闭报单网关。

 

在开发一套期权做市商系统之前,架构师需要决定是选择在将不同的模块用多线程运行在一个进程中,还是将它们运行在不同的进程中。通常操作系统需要耗费更多的资源来管理多进程,而多线程的额外开销比较小,因此从效率的角度来说我们似乎应该选择单进程多线程。对于一套仅交易标的资产的自动交易系统来说,可能确实如此,但是从期权做市商系统的角度来说,一方面单进程多线程的优势被夸大了,另一方面多进程有一项对期权做市商来说异常重要的优势——低耦合性。当我们在一个进程里运行多个线程,任何一个线程发生了问题都可能导致整个进程崩溃。假如我们只交易标的资产,我们可以电话联系交易所或者经纪商,先撤掉所有的报单,然后从其他渠道获取行情管理我们的仓位,最坏的结果无非是平掉全部的头寸提前结束当天的交易。但是对期权做市商来说,并非那么轻松。

 

首先,期权的流动性远远低于标的资产,做市商想平掉全部的头寸即便不是完全不可能也必须付出昂贵的代价。其次,做市商的持仓会相当复杂,并非如教科书上列出的仅有两三个合约构成的期权组合策略那样简单,而且即使是那些简单的组合策略,在市场波动的情况下,其价值变化也是非线性的,而不是标的资产本身那样简单的线性变化,在没有电脑系统的帮助下,靠人工基本是不可能计算清楚做市商的市场风险的,这意味着风控模块里计算风险的功能必须能够独立运行,不应该受到其他任何线程的影响。第三,期权做市商是要承担报价义务的,交易所对做市商的报价合约数量、持续报价时间、回应询价比例等都有严格的要求,在全市场正常运行时某做市商由于自身系统的原因而导致义务不能完成的,当然是不能列入豁免义务的范围。如果做市商不能满足报价义务,会受到处罚甚至被剥夺继续做市的资格。因此,期权做市商系统必定有一个宽报价模式保证在策略模块出现问题的时候,能够不依赖任何理论价格计算,报较宽的价差来满足做市的义务。这意味着策略模块里的宽报价功能必须能够独立运行,不应该受到其他任何线程的影响。

 

这样,四个基本模块里我们已经有两个必须有独立的进程了。剩下的两个,行情馈送和报单网关,我们看到和风控与策略都有连接,同样的原因,我们既不想将它们和风控放在同一个进程里增加风控崩溃的风险,也不想将他们和策略模块放在同一个进程里增加策略崩溃的风险。同样的理由适用于图形界面程序,我们不希望发生由于一个图形界面程序的错误导致所有图形界面都不可用。总而言之,我们希望各个模块都运行在自己进程里,一旦出现问题,可以把对整个系统的负面影响减小到最低,最坏的结果我们主动停止所有其他的进程,即使这样也胜过因为某个环节的错误而导致整个系统的完全崩溃。

 

最后说明一下为什么单进程多线程对多进程的优势被夸大了。这个优势主要体现在数据共享方面,在同一个进程中,同一个数据的地址对于所有的线程来说都是相同的,我们无需将数据从一个线程传送到另一个线程,不同的线程可以对同一地址的数据直接进行操作,而在多进程之间共享数据需要经过操作系统。这是由现代操作系统虚拟内存的机制决定的,同一个物理内存地址在不同的进程中被映射到了不同的虚拟内存空间的地址,因此进程间的数据共享比线程间的数据共享要复杂的多。顺便一提的是,C++标准只针对线程间数据共享做出了规范,并不支持进程间数据共享,如何实现进程间的数据共享必须依赖于操作系统实现。既然多线程可以直接操作共享数据,为什么我们说它的优势被夸大了呢?原因在于共享数据需要同步,当有线程在对共享数据进行操作时,其他线程必须等待它完成之后才能进行下一步操作。以期权做市场系统为例,行情馈送模块收到新的行情正要去更新,而策略模块也正要去读最新的行情数据,要么读的线程先等写的线程写完再去读,要么写的线程先等读的线程读完再去写。在编程中,这通常是用互斥锁实现的,先到的线程给共享数据加上锁,使用完毕之后打开锁,后到的线程在发现共享数据被锁上了以后需要等待到锁被打开再进行操作。后到的线程在等待期间,操作系统内核可能会将它的状态保存起来以后让其进入休眠,然后将它使用的CPU交给其它需要执行的线程,等到锁被打开之后,操作系统内核再将休眠的线程唤醒、恢复之前保存的状态并分配一个可用的CPU让它继续执行,这种情况我们称为上下文切换,由于休眠的线程离开了CPU导致其在CPU缓存中的数据被其它线程的数据所覆盖,当它被唤醒时,需要重新从内存载入数据进缓存,造成延迟。解决这种延迟的办法,一种是使用旋转锁,使用一个原子变量记录共享数据的状态,让等待的线程不停地检查原子变量的状态,这样可以避免内核把等待的线程切换掉,但是旋转锁并不彻底解决问题。只要有锁,我们就必须考虑一种被称为饥饿的情形,即有些优先级高的线程一直锁住共享数据,导致其它线程永远没有机会抢到那把锁然后对共享数据进行操作,从而使得这些线程一直处于等待的状态,或者说被饿死了。所以,在多线程编程中,我们追求的是一种被称为无锁无等待的数据共享方式,即每个线程都能保证在有限的步数内完成操作而不会无限制的等待下去。当我们使用这种数据共享方式以后,多进程和多线程的区别就模糊了,而多线程下简单的数据共享优势也不再存在了。(下篇预告:期权做市商系统数据库和日志记录)

分享到 :
0 人收藏

1 个回复

倒序浏览
真是好东西!!!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1337
帖子:252
精华:4
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP