连接tws接口的一个小bug

和tws没什么关系,是数据结构的基础没打好留下的坑

目前用的交易框架是backtrader,策略模块是component-based的思路设计的,简单来说,假设有一个策略是黄金反转系统,主文件是gold_reverse.py,开仓规则写在class entry里,平仓规则写在class exit里,不同策略需要的component不同,有些需要单边市判断,就要多一个class one-sided。

但也有一些component是所有策略都需要的,比如说当前的持仓情况,就有/components/common.py 来记录这些函数,目前还拥有一些模块,money(开仓头寸的计算),booking(记录所有拥有的交易单和止损单),addunit(加仓模块),execute(执行买卖单,止损单,平仓的method)。

最近开始连经纪商实盘测试,backtest一点问题也没有,live trading就各种bug。前两三天遇到的一个bug是:下交易单时带着的止损单不能执行,是下个next(如果周期是M10,就是十分钟后)backtrader看市场当年价格是否满足止损条件决定平不平仓(那要止损单何用!)一开始以为是execute的问题,但测试时把booking去掉就行了。

讲一下当时写execute和booking的思路。一开始小伙伴A(程序员)提供的sample里self.order是一个单独的object(因为只考虑了不加仓的情况)。后来小伙伴B提出应该拥有一个booking模块记录所有的交易单和止损单,我们设想的是表格的形式,比如说column_name为单子的方向,大小,状态,市场,等等等。所以一开始想的数据格式就是pandas的dataframe。

这时候不得不提backtrader每个交易单执行后返回的是个拥有很多attributes的object。attributes有ref(特定的编码)、size、stage、alive……

而dataframe是不能存储这样的obejct,而且如果是

self.order = self.strategy.buy( data= self.data, size = size) #执行买单
self.a = self.order

self.a保存的object和self.order保存的object不是同一个(这个部分我也不明白是怎么回事 但测试是这样) 所以到时候self.strategy.cancel(self.a)并不能cancel到你想要的那个order

我那时候的思路还是在self.order, self.stoplossorder是单个的object,于是我写出了非常丑陋的代码……

##### initial orderbook #####

if self.execute.order:
    orderrecord = {
        'Market':self.market,
        'OrderID':self.execute.order.ref,
        'Price':self.data.close,
        'Size':self.execute.order.size,
        'Position':len(self),
        'Status':self.execute.order.status,
        'Alive': self.execute.order.alive,
        }

##### initial stoploss orderbook #####

if self.execute.stoplossorder and self.execute.order:
    stoporderrecord = {
        'Market':self.market,
        'StopOrderID':self.execute.stoplossorder.ref,
        'StopPrice':self.execute.stoplossorder.price,
        'Size':self.execute.stoplossorder.size,
        'OrderID':self.execute.order.ref,#####
        'Status':self.execute.stoplossorder.status,
        'Alive': self.execute.stoplossorder.alive,
        }

而在execute的关闭止损单就更丑了…放在了文章最后

其实写完我发现我写的booking几乎没啥用,唯一的用处好像是知道当前场上加了多少次仓,然后获取最后一次加仓的止损单的状态,如果止损单被平了就退场,但当时能用就这么用着吧……

但live trading的时候就不能用了…… 我至今不知道具体的原因。

因为自己解决不了bug,求助小伙伴A时,被喷了。大致就是“这代码写得什么鬼,重写!”然后被教育重读/打了一遍《think python》,被建议用List。

完了,我就把booking和execute全删了,把功能写在了common里,把self.order和self.stoplossorder调成为List Object。

代码如下(以止损和关闭仓位的函数作为对比:

def stop_loss(self, side, size, stoplossprice):
    logger.warning('[{}] stop loss, side = {}, size = {}, price = {}'.format(len(self), side, size, stoplossprice))

    if side == -1:
        self.stoplossorder.append(self.strategy.sell(
        data = self.data,
        size = size,
        price = stoplossprice,
        exectype = bt.Order.Stop,
        ))
    elif side == 1:
        self.stoplossorder.append(self.strategy.buy(
        data = self.data,
        size = size,
        price = stoplossprice,
        exectype = bt.Order.Stop,
        ))

def close_pos(self):
    logger.warning('[{}] close positions'.format(len(self)))

    if self.common.side != 0:
        order = self.order_target_size(
        target = 0,
        )

    for i in range(len(self.stoplossorder)):
        if self.stoplossorder[i].alive():
            self.strategy.cancel(self.stoplossorder[i])

    self.stoplossorder = []
    self.order = []

嗯,之前写的什么鬼……

总之:

  1. 很多想法一开始是好的,比如说booking dataframe的形式,但实现起来往往没什么用。
  2. 对基础数据结构的理解很弱,经常是没有思考,写到哪算哪,能work就好了(虽然这样也行)
  3. 如果不work了就进行重构,也算是进步。 (总能找到很多方法安慰自己
  4. 最后,这个bug的原因和这些代码都没有一毛钱的关系,是因为tws的python的api是从java转过来的,如果size的小数位很长,数据就无法传递,所以事先要round一下小数位(摊手
  5. 前几天遇到的另外一个bug是ibapi只接受int格式的size指令,如果是float就不执行(继续摊手

一段蜜汁代码

def stop_loss(self, side, size, stoplossprice):
    logger.warning('[{}] stop loss, side = {}, size = {}, price = {}'.format(len(self), side, size, stoplossprice))

    size = int(size)

    if not self.stop1:

        if side == -1:
            self.stop1 = self.strategy.sell(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )
            self.stoplossorder = self.stop1
        elif side == 1:
            self.stop1 = self.strategy.buy(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )
            self.stoplossorder = self.stop1

    elif self.stop1 and not self.stop2:
        if side == -1:
            self.stop2 = self.strategy.sell(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop2
        elif side == 1:
            self.stop2 = self.strategy.buy(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop2

    elif self.stop1 and self.stop2 and not self.stop3:

        if side == -1:
            self.stop3 = self.strategy.sell(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop3
        elif side == 1:
            self.stop3 = self.strategy.buy(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop3


    elif self.stop1 and self.stop2 and self.stop3 and not self.stop4:

        if side == -1:
            self.stop4 = self.strategy.sell(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop4
        elif side == 1:
            self.stop4 = self.strategy.buy(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop4

    elif self.stop1 and self.stop2 and self.stop3 and self.stop4 and not self.stop5:
        if side == -1:
            self.stop5 = self.strategy.sell(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop5
        elif side == 1:
            self.stop5 = self.strategy.buy(
                data = self.data,
                size = size,
                price = stoplossprice,
                exectype = bt.Order.Stop,
            )

            self.stoplossorder = self.stop5

def close_pos(self):
    logger.warning('[{}] close positions'.format(len(self)))


    if self.common.side != 0:
       order = self.order_target_size(
       target = 0,
       )

    if self.stop1 and self.stop1.alive():
    	self.strategy.cancel(self.stop1)
   	if self.stop2 and self.stop2.alive():
        self.strategy.cancel(self.stop2)
    if self.stop3 and self.stop3.alive():
        self.strategy.cancel(self.stop3)
    if self.stop4 and self.stop4.alive():
        self.strategy.cancel(self.stop4)
    if self.stop5 and self.stop5.alive():
        self.strategy.cancel(self.stop5)

    self.stop1 = None
    self.stop2 = None
    self.stop3 = None
    self.stop4 = None
    self.stop5 = None
    self.order = None