1. 为什么我们需要一座“桥梁”?
很多朋友在用Backtrader做回测的时候感觉特别爽,策略写起来顺手,结果分析也清晰。但一到想把它用在模拟盘或者实盘上,立刻就卡住了,感觉无从下手。这感觉就像你花了大把时间在驾校的模拟器上练成了车神,结果发现真车的方向盘和油门你根本摸不着——因为驾校的模拟器没给你留插口。
Backtrader本身是一个极其强大的回测框架,但它设计之初的核心目标就是回测。它内部有一个完美的“虚拟世界”,有虚拟的资金、虚拟的订单簿、虚拟的成交。在这个世界里,你的策略可以天马行空。但这个世界是封闭的,它不知道外面的真实券商长什么样,不知道真实的股票行情怎么来,更不知道如何把“买入100股”这个指令,变成券商柜台里一条真实的委托。
所以,从回测到实盘,核心问题就是如何打通这个虚拟世界和现实世界。你需要一座坚固、可靠的“桥梁”。这座桥有两个关键的桥墩:一个是DataFeed(数据馈送),负责把现实世界的行情数据,源源不断地送进Backtrader的虚拟世界;另一个是Broker(经纪商),负责把虚拟世界里策略发出的交易指令,转化成现实世界中券商能理解的命令,并送出去执行,同时把真实的账户和持仓信息同步回来。
我刚开始折腾这个的时候,也以为会有个一蹴而就的官方方案,结果发现得自己动手“造桥”。这个过程虽然有点挑战,但一旦走通,你会发现你的策略真正“活”过来了,那种成就感是完全不一样的。下面,我就把我自己搭建这座桥的实战经验,包括踩过的坑和最终的解决方案,详细地分享给你。
2. 搭建前的准备:理解核心组件与选型
在动手写代码之前,我们得先把“造桥”的材料和图纸搞清楚。盲目开工,后面很容易返工。
2.1 Backtrader的扩展机制:Broker 与 DataFeed
Backtrader之所以能扩展,是因为它设计了一套清晰的接口。对于实盘交易,我们主要和两个抽象类打交道:
BrokerBase:这是经纪商的抽象。你的所有交易操作,比如getcash()(查现金)、getposition()(查持仓)、buy()/sell()(下单),最终都会调用这个类的具体实现。在回测时,Backtrader使用自带的BackBroker;在实盘时,我们就需要自己写一个类继承BrokerBase,然后在这些方法里,写上调用真实券商API的代码。DataBase或PandasData:这是数据源的抽象。它决定了Backtrader从哪里、以什么格式获取K线数据。回测时我们通常用PandasData读取本地CSV文件。实盘时,我们需要一个能动态获取最新行情(比如通过新浪、腾讯的接口,或者专业的行情源)的DataFeed。
理解了这两个核心,我们的任务就具体了:实现一个自定义Broker和一个自定义DataFeed。
2.2 中间件选型:为什么是easytrader?
这是针对A股市场的一个很实际的挑战。像同花顺、东方财富这类主流券商,并没有向普通个人开发者提供官方、稳定、免费的交易API。那我们的程序怎么下单呢?
目前社区里最成熟、最可行的方案,就是使用 easytrader 这类库。它的原理不是直接调用券商的底层接口(因为没有),而是通过自动化技术(类似按键精灵)去“模拟人工操作”PC客户端软件。它会自动填写代码、价格、数量,点击“买入”、“卖出”按钮。
它的优缺点非常明显:
- 优点:
- 几乎通用:只要你的券商有PC客户端,理论上都能适配。
- 门槛低:不需要申请机构接口,用个人账户就能玩。
- 社区活跃:遇到问题,比较容易找到相关的讨论和解决方案。
- 缺点:
- 不稳定:券商客户端一升级,界面元素一变,脚本就可能失效,需要维护。
- 有延迟:通过UI操作,速度比直接API慢,不适合高频或对速度要求极快的策略。
- 风控严格:有些券商的客户端会检测自动化工具,存在一定风险。
所以,在开始前你必须明确:这套方案适合中低频的量化策略实践和学习,让你策略的逻辑能够真实跑在市场里,感受实盘的冲击。但它不是用于生产级高频交易的方案。
除了easytrader,如果你对接的是期货(CTP接口)、数字货币交易所(有官方API),那就可以直接实现一个调用官方API的Broker,这才是更稳定和高效的方式。本文主要解决A股这个“特殊场景”。
3. 实战第一步:构建自定义Broker
这是整个桥梁的“主动力舱”,所有交易指令从这里出发。我们以对接同花顺(使用easytrader)为例,手把手实现。
3.1 初始化与连接
首先,我们创建一个TongHuaShunBroker类。初始化时,我们要做两件事:一是初始化父类,二是建立和同花顺客户端的连接。
import backtrader as bt
import easytrader
class TongHuaShunBroker(bt.BrokerBase):
def __init__(self, client_path=None):
super(TongHuaShunBroker, self).__init__()
# 初始化easytrader,指定使用同花顺
self.user = easytrader.use('ths')
# 存储活跃订单的列表,用于后续状态跟踪
self._orders = []
# 连接客户端。如果easytrader能自动找到路径,可以省略。
# 但建议显式指定,更稳定。路径是你电脑上同花顺下单客户端的exe文件位置。
if client_path:
self.user.connect(client_path)
print(f"已指定客户端路径: {client_path}")
else:
# 尝试自动连接,可能不稳定
print("尝试自动连接同花顺客户端...")
def start(self):
"""Broker启动时调用,用于验证连接"""
super(TongHuaShunBroker, self).start()
print("Broker启动,正在连接同花顺...")
try:
# 尝试获取一次账户余额,成功则说明连接正常
balance_info = self.user.balance
print(f"连接成功!账户资产信息: {balance_info}")
except Exception as e:
print(f"连接同花顺客户端失败!错误: {e}")
print("请确保:1. 同花顺客户端已登录。2. 客户端停留在交易界面。")
raise ConnectionError("无法连接到交易客户端,请检查配置。")
这里有个关键点:easytrader的balance属性在首次调用时会尝试与客户端交互并获取数据。如果客户端没开或者没登录,这里就会抛异常。我们把验证逻辑放在start()里,这样一旦连接失败,程序会尽早报错,而不是等到下单时才出问题。
3.2 实现账户信息查询
Backtrader在运行过程中,会不断调用getcash()和getvalue()来更新策略的现金和总资产显示。我们需要从easytrader获取真实数据。
def getcash(self):
"""返回可用资金"""


444

被折叠的 条评论
为什么被折叠?



