欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 锐评 > 【PA交易】BackTrader的交易管理

【PA交易】BackTrader的交易管理

2024/10/24 10:14:18 来源:https://blog.csdn.net/u012677852/article/details/139891865  浏览:    关键词:【PA交易】BackTrader的交易管理

前言

本主要讨论BackTrader中的Broker定制化。如果你已经对于BackTrader的交易管理非常熟悉,并且自己有了成熟的适配方案,那么并不需要看这篇文章。但是如果你还没有深入研究过,那么这篇文章可能会给到你启发。

背景与需求

网上现存大量资源详尽介绍了BackTrader的订单和交易模块。笔者无意再去额外写一篇冗余的基础教程来污染中文网络。如果读者对于交易管理相关模块尚未不熟悉,可以参考对应的官网教程Broker - Backtrader、Orders - General - Backtrader以及搜索包括CSDN在内的大量中文资源讲解。

笼统的讲,BackTrader提供了一个Broker模块来模拟处理资金、账户、买卖等等操作。同时BackTrader也提供了Live Trading - Oanda v1.0 - Backtrader等其他对接一些国外外汇交易商的Broker接口。

但目前BackTrader提供的bt.BackBroker的实现直接使用和适配起来却略显复杂和缺乏灵活性。

另一方面,如果我们想要对接CTP的模拟或者实盘接口,显然目前BT并没有直接提供一个合适的Broker来提供这个功能。

更进一步的,BackTrader提供的Order类型包含了大量和外汇交易紧密相关的概念和逻辑,这对于我们的需求属实有些多余,也十分容易引起混淆。强行去将一些概念与我们的内盘期货交易习惯相对应也非常多余。

定制化Broker

出于打造自己的回测和交易框架系统的目的,有必要定制一个独立的Broker。这个定制化的Broker可以接管包括订单、TPSL、资金管理、基础统计等全部功能。当然我们不会从头书写这个类,因为这将脱离框架的一些自动化流程,而是采用继承BackTrader的bt.BrokerBase类的方式

虽然我们的Broker主要专注于回测,但是掌握了改写方法,也很容易实现一个针对CTP模拟或者实盘交易的Broker适配器。

定制化的根本的原因在于,实现起来非常简单且清晰,高度可控。毕竟交易的事情,自己全拿在自己手里才是最稳妥的!而回测,越是按照市场规则的定制,越准确!

本文仅分析框架的类层次和如何继承和定义一个定制化的Broker,不会讨论Broker实现细节。


BackTrader的交易管理

首先,关于交易管理BackTrader提供了Order用于表示订单数据,Broker处理交易的各种操作,一个简单的类层次关系如图所示:

上图左侧的几个Broker是BackTrader用于交易的几个实现,其中IBBroker更像一个通用的交互式可用于实盘交易的实现。如果感兴趣直接使用这个Broker可以参考:Live Trading - Interactive Brokers - Backtrader

但是即使直接使用这个Broker,灵活性依然不足,因为市场差异本身较大,仍然需要大量的定制化工作。而这些定制化工作所需要的时间,并不会比直接继承bt.BrokerBase,进而自行实现一些逻辑来的轻松。

首先简单地了解一下目前框架所提供的实体,并分析我们需要哪些功能,不需要哪些功能。

订单实体Orders

如上图所示,订单的类层次机构结构如图所示,从下往上:

  • SellOrder和BuyOrder:指明了Order的多空类型,并直接继承了Order类
  • Order类:处理了拆单、超时和移动止损三个部分。
  • OrderBase类:
    • 核心实现,实际数据的存储封装在了OrderData类中,
    • 实现中使用created(OrderData)和executed(OrderData)两个属性分别作为挂单和成交单。

其中Order类在BackTrader的回测Broker中是需要的,因为这个类中模拟了执行订单的行为。后面会提到,我们更希望将订单的状态、行为、操作等切换成为i更类似于内盘期货市场的风格,这样更加符合我们一般在交易软件中的操作习惯,比如文华。并且把交易的所有操作都提升到Broker层面去执行,Orders仅做为数据载体。这样的好处在于:

  • 回测过程中,更方便去利用Tick中的挂单数量信息实现真实模拟;
  • 在模拟和实盘中,可以利用到CTP返回的成交结果。

我们将继承OrderBase类,在本文中会定制一个非常简单的MyOrder类型。

  • 注意,完全可以在生产中舍弃框架的Order类。实例中继承OrderBase类仅仅是为了少写一点代码。

经纪商Broker

BackTrader提供的Broker接口会在整个策略运行的生命周期中被使用。BrokerBase类也包含了start、stop、next等生命周期方法,他们在策略运行的对应生命周期时被调用。

交易的核心方法是buy和sell,同时为了保留扩展性,BrokerBase类的实现中为这两个接口预设了可变参数**kwargs。可见BackTrader最初的设计是鼓励这种继承BrokerBase类来实现定制化的Broker的魔改行为的。

我们可以覆盖BrokerBase类的绝大多数方法。显然,包括与手续费相关的计算也会被完全重写。

另外,BackTrader糟糕的将一些没有在BrokerBase类中定义的接口也在其他地方进行了调用。这很糟糕。我们已经初步整理了这些接口,并且也会实现这些接口。下文中使用一个空的MyBroker定义代码来展示所需要实现的接口。


MyBroker

示例策略

我们首先结合我们之前的tick数据源写一个基于五分钟均线的示例策略程序,策略逻辑很简单,5bar均上穿20bar均则买入,下传则卖出(为了方便调试,不同于官网示例是,没有使用框架的CrossOver函数)。这是非常简单的操作。部分代码如下:


class TestStrategy(bt.Strategy):def __init__(self):"""为管线数据取别名"""# ...略def next(self):dt = self.get_dt_str()if self.sma5_5[-1] is None:returnif dt == self.last_dt5:returnmean_val = np.mean([self.bid[0], self.sma5_5[0], self.sma5_20[0]])delta = abs(mean_val - min(self.ask[0], self.sma5_5[0], self.sma5_20[0]))# 在5分钟级别上执行双均线系统if self.sma5_5[-1] < self.sma5_20[-1] and self.sma5_5[0] > self.sma5_20[0]:print('5 cross over 20, buy')tp = self.ask[0] + 10 * deltasl = self.ask[0] - 5 * deltaorders = self.buy_bracket(price=self.ask[0],stopprice=sl,limitprice=tp)self.orders.append(order.to_dict())elif self.sma5_5[-1] > self.sma5_20[-1] and self.sma5_5[0] < self.sma5_20[0]:print('5 cross over 20, sell')tp = self.bid[0] - 10 * deltasl = self.bid[0] + 5 * deltaorders = self.sell_bracket(price=self.bid[0],stopprice=sl,limitprice=tp)

这是一个非常naive且直白的双均线策略。忽略五分钟KBar的没有完成的情况。在五分钟K线完成后,如果M5均线向上穿M20则买入,如果M5向下穿M20则卖出,同时每笔交易都携带了止盈止损。相信写过策略的朋友都非常熟悉这种入门策略了。

这段程序运行很正常。但是有一些需要调整的地方:

  • 止盈止损创建额外订单

我们在代码中设置了止盈止损,但是似乎是出于一致性和简化API的原因,这两个订单被当框架做Stop和Limit订单创建了实际的Order。无论对于回测的Broker、还是模拟或者实盘,这种实现方式都过于粗放,并且不符合我们“条件单”的习惯,而对于交易分析也会比较容易混淆和混乱。

在MyBroker中,我们会将止盈止损作为订单的一部分,同时交易过程完全由Broker接管并统一调度。因为使用了这个改动,我们也不会在使用*_bracket的两个API,而是直接使用简单的buy和sell接口。之所以这样做的原因可以从框架源码中strategy.*_bracket看到。

  • 杠杆和倍数

虽然示例中没有用到,但是杠杆和倍数是需要的。我们有过外汇交易经验的朋友都知道,乘数和我们熟悉的期货一样,代表一手多少倍,同时交易商还可以支持一个杠杆率,可能是100~400。这个我们内盘商品是没有的。

我们应在MyBroker中定义不同品种对应的倍数,然后将倍数计算后结果添加到资金计算中。

  • 爆仓

虽然不希望看到,但是保证金不足时停止策略运行有助于我们提高回测效率,所以我们需要这个检查,不然的话,如果明明已经爆仓了,还在继续运行策略,也和现实有些出入。

这就需要我们为MyBroker添加针对保证金和总资金的额度检测,当爆仓后,立刻停止运行回测。

  • 安全锁

为了整个软件系统最终能够快速无缝切换进入实盘,我们可以考虑可以在Broker中增加额外的安全锁,如果亏损到达一定数量,则强制锁定所有交易接口。这可以在我们的程序出现BUG时,及时阻止任何可能得损失。产品级别的实现中,安全锁的添加应该独立于交易框架,这样可以避免程序崩溃后或者某个地方跑入死循环,能够及时停止程序阻止交易继续进行。

继承BrokerBase

本系列文章中的继承自BrokerBase的MyBroker主要目的只是回测。篇幅和其他一些很好猜到的原因,此处不给出具体实现,所有实际的内容则会留空,如下:


class MyBroker(bt.BrokerBase):def __init__(self, **kwargs):super(MyBroker, self).__init__()passdef set_fund_history(self, fund):passdef setcash(self, cash):passdef getcash(self):passdef get_notification(self):return Nonedef getvalue(self, datas=None):passdef getposition(self, data):passdef add_order_history(self, orders, notify=False):passdef submit(self, order):passdef cancel(self, order):passdef buy(self, owner, data, size, price=None,plimit=None, exectype=None, valid=None, tradeid=0,  # 不使用这些参数oco=None, trailamount=None, trailpercent=None,  # 不使用这些参数**kwargs):passdef sell(self, owner, data, size, price=None,plimit=None, exectype=None, valid=None, tradeid=0,  # 不使用这些参数oco=None, trailamount=None, trailpercent=None,  # 不使用这些参数**kwargs):pass# =========================================# 新增方法def start(self):passdef stop(self):passdef next(self):# 刷新并计算Value和cash# 检查挂单成交情况# 根据tick值触发订单TPSLpass

注意next函数的注释解释了这个函数需要做的工作。订单的管理可以完全在自定义的Broker中实现,从而实现定制化交易管理的功能。

结语

本文简略介绍了BT中Broker的框架,同时介绍了应该定制化自己的Broker类。文中没有给出具体实现方式,这里的具体实现并不复杂,主要是订单的管理以及资金的刷新。注意,实现时候一个能改关注周期级别,同时为了支持更灵活的分析器使用,一些分析器会用到的接口必须要实现。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com