我来详细介绍在Backtrader中使用Tushare数据的几种方法,从简单到复杂,包括示例代码。
方法1:使用PandasData(推荐,最灵活)
这是最常用的方法,将Tushare数据转换为Pandas DataFrame,然后用Backtrader的PandasData适配器加载。
基本示例
import backtrader as bt
import tushare as ts
import pandas as pd
from datetime import datetime
# 1. 从Tushare获取数据
def get_tushare_data(ts_code='000001.SZ', start_date='20200101', end_date='20231231'):
"""
获取Tushare股票数据并转换为Backtrader需要的格式
"""
# 初始化tushare(需要token)
ts.set_token('你的tushare_token') # 替换为你的token
pro = ts.pro_api()
# 获取日线数据
df = pro.daily(ts_code=ts_code,
start_date=start_date,
end_date=end_date)
# 数据清洗和格式化
df = df.sort_values('trade_date') # 按日期排序
df['trade_date'] = pd.to_datetime(df['trade_date']) # 转换为datetime
df.set_index('trade_date', inplace=True) # 设置日期为索引
# 重命名列以匹配Backtrader的PandasData
df.rename(columns={
'open': 'Open',
'high': 'High',
'low': 'Low',
'close': 'Close',
'vol': 'Volume'
}, inplace=True)
# 只保留需要的列
df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
return df
# 2. 创建自定义的PandasData类(可选,处理Tushare特有字段)
class TusharePandasData(bt.feeds.PandasData):
"""
自定义PandasData以处理Tushare数据
可以添加更多字段如换手率、涨跌幅等
"""
params = (
('datetime', None), # 使用索引作为datetime
('open', 'Open'),
('high', 'High'),
('low', 'Low'),
('close', 'Close'),
('volume', 'Volume'),
('openinterest', -1), # 无持仓量字段
# 可以添加额外字段
# ('pct_chg', 'pct_chg'), # 如果需要涨跌幅
)
# 3. 完整的回测示例
class SmaCrossStrategy(bt.Strategy):
params = (
('fast', 10),
('slow', 30),
)
def __init__(self):
self.fast_sma = bt.indicators.SMA(self.data.close, period=self.params.fast)
self.slow_sma = bt.indicators.SMA(self.data.close, period=self.params.slow)
self.crossover = bt.indicators.CrossOver(self.fast_sma, self.slow_sma)
def next(self):
if not self.position:
if self.crossover > 0: # 金叉
self.buy()
elif self.crossover < 0: # 死叉
self.close()
def run_backtest():
# 创建Cerebro引擎
cerebro = bt.Cerebro()
# 获取数据
print("正在获取数据...")
df = get_tushare_data('000001.SZ', '20200101', '20231231')
if df.empty:
print("数据获取失败,请检查网络或token")
return
print(f"数据量: {len(df)} 条")
print(f"日期范围: {df.index[0]} 到 {df.index[-1]}")
# 创建数据源
data = TusharePandasData(dataname=df)
# 添加到引擎
cerebro.adddata(data)
# 添加策略
cerebro.addstrategy(SmaCrossStrategy)
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 设置佣金
cerebro.broker.setcommission(commission=0.001) # 0.1%佣金
# 添加分析器
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
# 运行回测
print('初始资金: %.2f' % cerebro.broker.getvalue())
results = cerebro.run()
print('最终资金: %.2f' % cerebro.broker.getvalue())
# 打印分析结果
strat = results[0]
print("\n=== 策略绩效 ===")
print(f"总收益率: {strat.analyzers.returns.get_analysis()['rtot']:.2%}")
sharpe_ratio = strat.analyzers.sharpe.get_analysis()
print(f"夏普比率: {sharpe_ratio['sharperatio']:.3f}")
drawdown = strat.analyzers.drawdown.get_analysis()
print(f"最大回撤: {drawdown['max']['drawdown']:.2%}")
print(f"最长回撤周期: {drawdown['max']['len']} 天")
trades = strat.analyzers.trades.get_analysis()
if 'total' in trades:
print(f"总交易次数: {trades['total']['total']}")
print(f"盈利交易: {trades['won']['total']}")
print(f"亏损交易: {trades['lost']['total']}")
if trades['won']['total'] > 0:
print(f"胜率: {trades['won']['total']/trades['total']['total']:.2%}")
# 绘制图表
cerebro.plot(style='candlestick', volume=True)
if __name__ == '__main__':
run_backtest()
方法2:使用通用CSVData(保存为CSV再读取)
如果不想每次都从Tushare API获取,可以先保存到本地CSV。
import backtrader as bt
import tushare as ts
import pandas as pd
import os
class TushareCSVData(bt.feeds.GenericCSVData):
"""
自定义CSV数据加载器
"""
params = (
('datetime', 0), # 日期列索引
('open', 1), # 开盘价列索引
('high', 2), # 最高价列索引
('low', 3), # 最低价列索引
('close', 4), # 收盘价列索引
('volume', 5), # 成交量列索引
('openinterest', -1), # 无持仓量
('dtformat', '%Y-%m-%d'), # 日期格式
('nullvalue', 0.0),
)
def download_and_save_tushare_data(ts_code='000001.SZ',
start_date='20200101',
end_date='20231231',
save_path='data.csv'):
"""下载Tushare数据并保存为CSV"""
ts.set_token('你的tushare_token')
pro = ts.pro_api()
df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
# 格式化
df = df.sort_values('trade_date')
df['trade_date'] = pd.to_datetime(df['trade_date'])
# 保存为CSV
df.to_csv(save_path, index=False)
print(f"数据已保存到: {save_path}")
return save_path
# 使用示例
csv_file = download_and_save_tushare_data('000001.SZ')
data = TushareCSVData(dataname=csv_file)
方法3:使用自定义Data Feed(最灵活,但复杂)
对于需要特殊处理的场景,可以创建完全自定义的Data Feed。
class TushareDirectData(bt.feeds.DataBase):
"""
直接连接Tushare的Data Feed(示例)
注意:这需要处理实时数据连接,实际使用较少
"""
params = (
('ts_code', '000001.SZ'),
('start_date', '20200101'),
('end_date', '20231231'),
)
def __init__(self):
super().__init__()
# 初始化tushare连接
ts.set_token('你的tushare_token')
self.pro = ts.pro_api()
self.data_df = None
self.idx = 0
def start(self):
"""开始加载数据"""
self.data_df = self.pro.daily(
ts_code=self.p.ts_code,
start_date=self.p.start_date,
end_date=self.p.end_date
)
self.data_df = self.data_df.sort_values('trade_date')
self.idx = 0
def _load(self):
"""加载下一行数据"""
if self.idx >= len(self.data_df):
return False
row = self.data_df.iloc[self.idx]
# 设置当前bar的数据
self.lines.datetime[0] = bt.date2num(pd.to_datetime(row['trade_date']))
self.lines.open[0] = row['open']
self.lines.high[0] = row['high']
self.lines.low[0] = row['low']
self.lines.close[0] = row['close']
self.lines.volume[0] = row['vol']
self.lines.openinterest[0] = 0
self.idx += 1
return True
完整的多股票回测示例
def multi_stock_backtest():
"""多股票回测示例"""
cerebro = bt.Cerebro()
# 股票列表
stocks = ['000001.SZ', '000002.SZ', '000858.SZ']
for stock_code in stocks:
try:
# 获取数据
df = get_tushare_data(stock_code, '20200101', '20231231')
if not df.empty:
# 创建数据源
data = TusharePandasData(dataname=df, name=stock_code)
cerebro.adddata(data)
print(f"已添加: {stock_code}")
else:
print(f"跳过 {stock_code},无数据")
except Exception as e:
print(f"获取 {stock_code} 数据失败: {e}")
if len(cerebro.datas) == 0:
print("没有可用的数据")
return
# 添加策略
cerebro.addstrategy(SmaCrossStrategy)
# 设置初始资金和佣金
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
# 运行回测
print(f"初始资金: {cerebro.broker.getvalue():.2f}")
cerebro.run()
print(f"最终资金: {cerebro.broker.getvalue():.2f}")
# 绘图
cerebro.plot()
Tushare数据字段映射表
| Tushare字段 | Backtrader字段 | 说明 |
|---|---|---|
| trade_date | datetime | 交易日期 |
| open | open | 开盘价 |
| high | high | 最高价 |
| low | low | 最低价 |
| close | close | 收盘价 |
| vol | volume | 成交量 |
| amount | - | 成交额(需特殊处理) |
| pct_chg | - | 涨跌幅(可添加为额外字段) |
实用技巧和注意事项
1. 数据预处理
def preprocess_tushare_data(df):
"""数据预处理"""
# 1. 处理缺失值
df = df.fillna(method='ffill')
# 2. 处理异常值
df = df[(df['High'] >= df['Low']) & (df['Volume'] > 0)]
# 3. 添加额外指标
df['Returns'] = df['Close'].pct_change()
# 4. 确保索引是datetime
df.index = pd.to_datetime(df.index)
return df
2. 使用缓存提高效率
import pickle
import hashlib
from functools import lru_cache
@lru_cache(maxsize=10)
def get_cached_tushare_data(ts_code, start_date, end_date):
"""带缓存的数据获取"""
cache_key = f"{ts_code}_{start_date}_{end_date}"
cache_file = f"cache/{hashlib.md5(cache_key.encode()).hexdigest()}.pkl"
if os.path.exists(cache_file):
with open(cache_file, 'rb') as f:
return pickle.load(f)
else:
df = get_tushare_data(ts_code, start_date, end_date)
os.makedirs('cache', exist_ok=True)
with open(cache_file, 'wb') as f:
pickle.dump(df, f)
return df
3. 处理复权数据
def get_adj_data(ts_code, start_date, end_date):
"""获取复权数据"""
ts.set_token('你的token')
pro = ts.pro_api()
# 获取复权因子
df_adj = pro.adj_factor(ts_code=ts_code,
trade_date='')
# 获取前复权数据
df = pro.pro_bar(ts_code=ts_code,
adj='qfq', # qfq:前复权, hfq:后复权
start_date=start_date,
end_date=end_date)
# ... 后续处理
return df
4. 常见问题解决
问题1:数据时间范围不正确
# 确保数据包含回测所需的时间范围
start_date = '20190101' # 比回测开始时间早,用于计算指标
end_date = '20231231'
问题2:成交量单位不一致
# Tushare的成交量单位是"手",需要转换为"股"
df['Volume'] = df['Volume'] * 100 # 1手 = 100股
问题3:非交易日数据缺失
# 填充非交易日
df = df.asfreq('D') # 设置为每日频率
df = df.fillna(method='ffill') # 向前填充
5. 优化建议
- 使用Tushare Pro:免费版有频率限制,Pro版更稳定
- 批量下载数据:避免在循环中频繁调用API
- 本地缓存:减少API调用次数
- 错误处理:添加重试机制
- 数据验证:检查数据完整性和准确性
总结
使用Tushare数据配合Backtrader的最佳实践:
- 使用PandasData方法(方法1),灵活且高效
- 做好数据预处理和缓存
- 注意数据字段映射和单位转换
- 添加适当的错误处理和日志记录
这样就能充分利用 Tushare 丰富的数据和 Backtrader 强大的回测功能了。

382

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



