将 Backtrader 从回测框架扩展到模拟或实盘交易
这是最核心的一步。我们需要创建一个类,继承 backtrader.BrokerBase,并实现其中的关键方法,让这些方法的内部逻辑调用 easytrader 来完成。ths_broker.py (示例代码)"""一个连接同花顺和 Backtrader 的自定义 Broker"""self.user = easytrader.use('ths') # 初始化 easytrader,使用同花顺# 这里
这是一个非常棒的实践性问题!将 Backtrader 从回测框架扩展到模拟或实盘交易,是每个量化交易者都会遇到的关键一步。
你无法直接将 Backtrader 连接到同花顺的模拟交易。原因在于:
-
没有官方API:同花顺、东方财富等国内券商软件,没有为个人开发者提供公开、稳定、免费的交易API。它们的软件是封闭的系统。
-
Backtrader的机制:Backtrader的实时交易模式需要一个实现了其 Broker 和 DataFeed 接口的“桥梁”来与外部券商进行通信。
那么,如何实现这个流程呢?我们需要一个**中间件(或称为“桥梁”)**来模拟人在操作同花顺客户端。目前最流行和可行的方案是使用 easytrader 这个库。
核心思路:
Backtrader (你的策略) -> 自定义Broker -> easytrader -> 同花顺客户端 -> 同花顺模拟交易服务器
easytrader 是一个通过自动化操作PC客户端(如点击、输入等)来实现程序化交易的Python库。
下面,我将为你详细讲解实现这个交易流程的步骤,并提供关键代码示例。
交易流程实现步骤
第1步:环境准备
-
安装 Backtrader:
Generated bash
Use code with caution.Bashpip install backtrader
-
安装 easytrader:
Generated bash
Use code with caution.Bashpip install easytrader
-
安装同花顺PC客户端:
-
去同花顺官网下载并安装最新版的PC客户端。
-
登录你的账户,并确保你已经开通了模拟交易功能。
-
关键:在你的策略运行时,同花顺客户端必须处于登录状态,并停留在交易主界面。
-
-
准备一个实时数据源:
-
easytrader 只负责交易,不负责提供行情数据。Backtrader在实时模式下需要一个数据源。
-
你可以使用 Tushare, AkShare, baostock 等免费数据接口来获取实时或分钟级的K线数据。这里我们以 AkShare 为例。
Use code with caution.Bashpip install akshare
-
第2步:创建自定义的 Broker
这是最核心的一步。我们需要创建一个类,继承 backtrader.BrokerBase,并实现其中的关键方法,让这些方法的内部逻辑调用 easytrader 来完成。
ths_broker.py (示例代码)
Generated python
import backtrader as bt
import easytrader
class TongHuaShunBroker(bt.BrokerBase):
"""
一个连接同花顺和 Backtrader 的自定义 Broker
"""
def __init__(self, **kwargs):
super(TongHuaShunBroker, self).__init__()
self.user = easytrader.use('ths') # 初始化 easytrader,使用同花顺
# 这里需要根据你同花顺客户端的路径进行配置
# 例如: self.user.connect(r'C:\ths\xiadan.exe')
# 如果 easytrader 能够自动找到,则无需 connect
self.orders = [] # 存储 Backtrader 的订单对象
def start(self):
super(TongHuaShunBroker, self).start()
print("Broker started. Connecting to TongHuaShun...")
# 尝试获取余额,以验证连接
try:
balance = self.user.balance
print("Successfully connected to THS. Balance info:", balance)
except Exception as e:
print(f"Failed to connect to THS: {e}")
raise ConnectionError("Could not connect to TongHuaShun client.")
def getcash(self):
# 获取可用现金
cash = self.user.balance.get('enable_balance', 0.0)
return cash
def getvalue(self, datas=None):
# 获取总资产
value = self.user.balance.get('asset_balance', 0.0)
return value
def getposition(self, data, clone=True):
# 获取指定标的的持仓
# easytrader 的持仓返回一个列表,需要查找
code = data._name # data._name 通常是股票代码
positions = self.user.position
for pos in positions:
if pos['stock_code'] == code:
# Backtrader 需要一个 Position 对象
position = bt.position.Position(size=pos['current_amount'], price=pos['cost_price'])
return position
# 如果没有找到,返回空仓位
return bt.position.Position(size=0, price=0.0)
def _submit(self, owner, data, side, exectype, size, price):
# 实际的下单逻辑
code = data._name
try:
if side == bt.Order.BUY:
result = self.user.buy(stock_code=code, price=price, amount=size)
print(f"BUY Order Submitted to THS: {result}")
elif side == bt.Order.SELL:
result = self.user.sell(stock_code=code, price=price, amount=size)
print(f"SELL Order Submitted to THS: {result}")
else:
return None # 不支持其他订单类型
# 创建一个 Backtrader 的 Order 对象并返回
order = bt.Order(
data=data,
owner=owner,
# ... 其他参数 ...
)
order.submit(self)
self.orders.append(order)
return order
except Exception as e:
print(f"Order submission failed for {code}: {e}")
return None
# 你需要实现 buy, sell, cancel 等方法
# Backtrader 会调用这些公共方法,它们内部应调用 _submit 或 _cancel
def buy(self, owner, data, size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None, **kwargs):
exectype = exectype or bt.Order.Market # 简化处理,默认为市价单
price = price or data.close[0] # 如果是市价单,价格不重要,但 easytrader 需要一个价格
return self._submit(owner, data, bt.Order.BUY, exectype, size, price)
def sell(self, owner, data, size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None, **kwargs):
exectype = exectype or bt.Order.Market
price = price or data.close[0]
return self._submit(owner, data, bt.Order.SELL, exectype, size, price)
# 注意:这是一个简化的 Broker。一个完整的 Broker 还需要处理订单状态更新、
# 部分成交、撤单(cancel)等复杂情况。这需要轮询 `user.entrust` 来获取订单状态并更新。
Use code with caution.Python
第3步:创建自定义的 DataFeed
我们需要一个能从 AkShare 获取数据并喂给 Backtrader 的数据源。
akshare_data.py (示例代码)
Generated python
import backtrader as bt
import akshare as ak
import pandas as pd
from datetime import datetime, time
class AkShareData(bt.feeds.PandasData):
"""
一个从 AkShare 获取实时/历史数据的 Data Feed
"""
params = (
('fromdate', datetime(2023, 1, 1)),
('todate', datetime.now()),
('timeframe', bt.TimeFrame.Minutes),
('compression', 1), # 1分钟线
('stock_code', ''),
)
def __init__(self):
super(AkShareData, self).__init__()
self.get_data()
def get_data(self):
print(f"Fetching data for {self.p.stock_code}...")
# AkShare 获取分钟级数据接口示例
df = ak.stock_zh_a_hist_min_em(
symbol=self.p.stock_code,
period=str(self.p.compression),
start_date=self.p.fromdate.strftime('%Y-%m-%d %H:%M:%S'),
end_date=self.p.todate.strftime('%Y-%m-%d %H:%M:%S')
)
# 数据清洗和格式化以符合 backtrader 要求
df.rename(columns={
'时间': 'datetime',
'开盘': 'open',
'收盘': 'close',
'最高': 'high',
'最低': 'low',
'成交量': 'volume'
}, inplace=True)
df['datetime'] = pd.to_datetime(df['datetime'])
df.set_index('datetime', inplace=True)
df['openinterest'] = 0 # backtrader 需要这一列
# 将数据加载到 PandasData 中
self.p.dataname = df
# 在实时模式下,你需要实现 _load() 方法来动态获取新数据
# 这里我们先用历史数据模拟
Use code with caution.Python
第4步:组装并运行 Cerebro
现在,我们将自定义的 Broker、DataFeed 和你的策略组合在一起。
run_ths.py
Generated python
import backtrader as bt
from datetime import datetime
from ths_broker import TongHuaShunBroker # 导入自定义 Broker
from akshare_data import AkShareData # 导入自定义 DataFeed
# 1. 你的交易策略
class MyStrategy(bt.Strategy):
def next(self):
# 这是一个非常简单的示例策略:如果当前时间是 14:55,就买入100股
current_time = self.data.datetime.time()
if current_time == time(14, 55):
print(f"{self.data.datetime.datetime(0)}: It's 14:55, buying 100 shares...")
self.buy(size=100) # easytrader 买入单位是“股”
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
print("Order Submitted/Accepted")
return
if order.status in [order.Completed]:
if order.isbuy():
print(f"BUY EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm: {order.executed.comm:.2f}")
elif order.issell():
print(f"SELL EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm: {order.executed.comm:.2f}")
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
print("Order Canceled/Margin/Rejected")
# 2. 主程序
if __name__ == '__main__':
# 确保同花顺客户端已经登录并打开
cerebro = bt.Cerebro()
# 3. 设置自定义 Broker
my_broker = TongHuaShunBroker()
cerebro.setbroker(my_broker)
# 4. 添加数据
# 注意:这里我们用历史分钟数据来模拟。
# 在真实的实时场景中,你需要让 DataFeed 动态加载数据,
# 并在 cerebro.run() 中设置 qlive=True
stock_code = '600519' # 以贵州茅台为例
data = AkShareData(
dataname=stock_code,
fromdate=datetime(2023, 11, 1),
todate=datetime(2023, 11, 30),
timeframe=bt.TimeFrame.Minutes,
compression=1,
stock_code=stock_code # 将股票代码传入
)
cerebro.adddata(data, name=stock_code) # name 参数很重要,Broker会用它
# 5. 添加策略
cerebro.addstrategy(MyStrategy)
# 6. 设置初始资金(这只是给 backtrader 一个参考,实际资金以来 THS Broker)
cerebro.broker.setcash(100000.0)
# 7. 运行
print("Starting strategy...")
cerebro.run()
print("Final Portfolio Value: %.2f" % cerebro.broker.getvalue())
Use code with caution.Python
运行和注意事项
-
先运行同花顺:在运行 Python 脚本之前,必须手动打开同花顺PC客户端,登录你的模拟交易账户。
-
easytrader的连接:easytrader 可能需要你指定同花顺下单程序的路径,这可以在 TongHuaShunBroker 的 __init__ 中通过 self.user.connect(...) 来设置。
-
简化与现实的差距:
-
订单状态:上面的 Broker 示例非常简化。现实中,订单提交后不会立即成交,你需要轮询查询订单状态,并据此更新 Backtrader 内部的订单对象。这是实现一个健壮 Broker 最复杂的部分。
-
错误处理:网络中断、交易被拒、资金不足等情况都需要完善的错误处理逻辑。
-
实时数据:真正的实时交易需要一个能推送或轮询最新K线数据的 DataFeed。你需要修改 AkShareData 的 _load 方法来实现这个功能,并使用 cerebro.run(qlive=True)。
-
风险:easytrader 这种方式依赖于客户端UI,如果同花顺软件更新,可能会导致脚本失效。它存在一定的稳定性和延迟问题,不适用于高频交易。
-
总结:
通过 easytrader 作为桥梁,将 Backtrader 连接到同花顺模拟交易是完全可行的。核心工作是编写一个自定义的 Broker 类,将 Backtrader 的交易指令(如 buy, sell, getcash)翻译成 easytrader 的函数调用。虽然过程比较复杂,但这是目前在A股市场进行个人程序化交易最主流的方案之一。
更多推荐
所有评论(0)