和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 = []
嗯,之前写的什么鬼……
总之:
- 很多想法一开始是好的,比如说booking dataframe的形式,但实现起来往往没什么用。
- 对基础数据结构的理解很弱,经常是没有思考,写到哪算哪,能work就好了(虽然这样也行)
- 如果不work了就进行重构,也算是进步。 (总能找到很多方法安慰自己
- 最后,这个bug的原因和这些代码都没有一毛钱的关系,是因为tws的python的api是从java转过来的,如果size的小数位很长,数据就无法传递,所以事先要round一下小数位(摊手
- 前几天遇到的另外一个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