快速上手#
下载数据#
股票列表、股票日K以及宽基指数日K数据。其中宽基指数包括:上证指数、深证成指、创业板指、科创指数、上证50、沪深300、中证500、中证1000
[1]:
import tradepy
from tradepy.collectors.stock_listing import StocksListingCollector
from tradepy.collectors.stock_day_bars import StockDayBarsCollector
from tradepy.collectors.market_index import BroadBasedIndexCollector
from tradepy.collectors.etf_day_bars import ETFDayBarsCollector
from tradepy.collectors.etf_listing import ETFListingCollector
from tradepy.collectors.adjust_factor import AdjustFactorCollector
from tradepy.depot.stocks import StocksDailyBarsDepot
[2]:
# 本地数据将保存到此目录。该可在 ~/.tradepy/config.yaml 中修改
tradepy.config.common.database_dir
[2]:
PosixPath('/Users/dilu/Desktop/Software/Stock/database2')
下载股票列表、复权因子#
需要分别获取个股的行业分类和市值数据,耗时需10-20分钟。 如果运行失败,可降低batch_size
后重试
[6]:
StocksListingCollector().run(batch_size=25)
[stock_listing] [2023-08-17 22:57:40,439] [INFO]: =============== 开始更新A股上市公司列表 ===============
[stock_listing] [2023-08-17 22:57:51,807] [INFO]: 获取个股的东财行业分类信息
[base] [2023-08-17 22:57:51,809] [INFO]:
[StocksListingCollector]:
5042 下载任务
批大小 = 25
批数量 = 202
每批间隔(s) = 3
临时下载目录: /var/folders/w2/yctzhkrd5wbdg8p7tsf9205w0000gn/T/tmp6ljdlfs9
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 202/202 [11:53<00:00, 3.53s/it]
[stock_listing] [2023-08-17 23:09:45,390] [INFO]: 已下载至 /Users/dilu/Desktop/Software/Stock/database2/listing.csv
[3]:
AdjustFactorCollector().run(batch_size=25)
[adjust_factor] [2023-08-18 15:14:28,294] [INFO]: =============== 开始更新个股复权因子 ===============
[adjust_factor] [2023-08-18 15:14:28,296] [INFO]: 下载中
[base] [2023-08-18 15:14:28,297] [INFO]:
[AdjustFactorCollector]:
5042 下载任务
批大小 = 25
批数量 = 202
每批间隔(s) = 2
临时下载目录: /var/folders/w2/yctzhkrd5wbdg8p7tsf9205w0000gn/T/tmp_as7d1ay
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 202/202 [10:56<00:00, 3.25s/it]
[adjust_factor] [2023-08-18 15:25:25,407] [INFO]: 添加头尾时间边界1900, 3000
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5042/5042 [00:15<00:00, 328.15it/s]
[adjust_factor] [2023-08-18 15:25:41,330] [INFO]: 已下载至 /Users/dilu/Desktop/Software/Stock/database2/adjust_factors.csv
[3]:
timestamp | hfq_factor | |
---|---|---|
code | ||
000001 | 1900-01-01 | 1.0000000000000000 |
000001 | 1991-04-03 | 1.0000000000000000 |
000001 | 1991-08-19 | 1.3089531680441000 |
000001 | 1992-03-23 | 1.9756060606061000 |
000001 | 1993-05-24 | 3.7625186877754000 |
... | ... | ... |
688819 | 2023-05-19 | 1.0497824990630000 |
688819 | 3000-01-01 | NaN |
688981 | 1900-01-01 | 1.0000000000000000 |
688981 | 2020-07-16 | 1.0000000000000000 |
688981 | 3000-01-01 | NaN |
58961 rows × 2 columns
下载宽基指数日K#
[5]:
BroadBasedIndexCollector().run()
[market_index] [2023-08-17 22:56:52,451] [INFO]: =============== 开始更新宽基指数 ===============
[market_index] [2023-08-17 22:56:54,569] [INFO]: 下载 SSE
[market_index] [2023-08-17 22:56:55,128] [INFO]: 下载 SZSE
[market_index] [2023-08-17 22:56:55,719] [INFO]: 下载 ChiNext
[market_index] [2023-08-17 22:56:56,167] [INFO]: 下载 STAR
[market_index] [2023-08-17 22:56:56,553] [INFO]: 下载 CSI-300
[market_index] [2023-08-17 22:56:57,089] [INFO]: 下载 CSI-500
[market_index] [2023-08-17 22:56:57,652] [INFO]: 下载 CSI-1000
[market_index] [2023-08-17 22:56:57,975] [INFO]: 下载 SSE-50
下载ETF基金日K#
[3]:
ETFListingCollector().run()
ETFDayBarsCollector().run()
[etf_listing] [2023-08-18 11:44:45,274] [INFO]: =============== 开始更新ETF列表 ===============
[etf_listing] [2023-08-18 11:44:45,635] [INFO]: 已下载至 /Users/dilu/Desktop/Software/Stock/database2/etf-listing.csv
[etf_day_bars] [2023-08-18 11:44:45,636] [INFO]: =============== 开始更新ETF日K数据 ===============
[base] [2023-08-18 11:44:45,642] [INFO]: 检查本地数据是否需要更新
140it [00:00, 252.39it/s]
[base] [2023-08-18 11:44:46,216] [INFO]: 添加新标的, 起始日期 2010-01-01
[base] [2023-08-18 11:44:46,218] [INFO]:
[ETFDayBarsCollector]:
791 下载任务
批大小 = 50
批数量 = 16
每批间隔(s) = 5
临时下载目录: /var/folders/w2/yctzhkrd5wbdg8p7tsf9205w0000gn/T/tmpgptedd41
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 16/16 [01:43<00:00, 6.47s/it]
下载股票日K#
默认起始日期为
2016-01-01
,可传入since_date
以设定起始日期。同样,可调低batch_size
以防止被数据接口方限流。由于网络不稳定或接口限流,调用偶尔会失败,此时会自动重试(至多三次)。如果本地已下载了股票日K数据,
StockDayBarsCollector
则会先检查是否需要更新,并下载、添加最新的日K数据。
[3]:
StockDayBarsCollector(since_date='2018-06-01').run(batch_size=35)
查看股票日K数据#
部分数据列含义如下:
market: 股票所在主板
turnover: 换手率
vol: 成交量
mkt_cap: 总市值(亿)
mkt_cap_rank: 总市值在本交易日的全市场分位。e.g., “0.5”即”超过全市场50%的股票”
company: 该交易日的公司名称,因而可能有不同的值, e.g., ST摘帽
[2]:
df = StocksDailyBarsDepot.load()
df
5042it [00:21, 236.78it/s]
[2]:
timestamp | code | company | market | open | high | low | close | turnover | vol | chg | pct_chg | mkt_cap | mkt_cap_rank | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
code | ||||||||||||||
300268 | 2019-01-02 | 300268 | 佳沃股份 | 创业板 | 11.17 | 11.24 | 11.03 | 11.09 | 1.15 | 11291 | -0.13 | -1.16 | 14.86 | 0.03 |
000610 | 2019-01-02 | 000610 | 西安旅游 | 深证主板 | 7.47 | 7.72 | 7.45 | 7.47 | 8.45 | 198811 | -0.02 | -0.27 | 17.69 | 0.08 |
000541 | 2019-01-02 | 000541 | 佛山照明 | 深证主板 | 5.18 | 5.23 | 5.10 | 5.13 | 0.18 | 19642 | -0.05 | -0.97 | 71.79 | 0.70 |
002356 | 2019-01-02 | 002356 | 赫美集团 | 深证主板 | 7.28 | 7.36 | 7.19 | 7.30 | 1.05 | 50630 | -0.06 | -0.82 | 38.53 | 0.47 |
603156 | 2019-01-02 | 603156 | 养元饮品 | 上证主板 | 41.71 | 41.92 | 41.55 | 41.73 | 0.75 | 4499 | 0.15 | 0.36 | 314.34 | 0.95 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
300390 | 2023-08-29 | 300390 | 天华新能 | 创业板 | 26.61 | 27.56 | 26.41 | 27.42 | 1.73 | 105571 | 0.66 | 2.47 | 229.65 | 0.87 |
600615 | 2023-08-29 | 600615 | 丰华圆珠 | 上证主板 | 8.56 | 9.00 | 8.50 | 8.93 | 1.07 | 20030 | 0.42 | 4.94 | 16.79 | 0.02 |
002259 | 2023-08-29 | 002259 | ST升达 | 深证主板 | 3.22 | 3.24 | 3.18 | 3.23 | 0.19 | 14405 | 0.00 | 0.00 | 24.30 | 0.11 |
300409 | 2023-08-29 | 300409 | 道氏技术 | 创业板 | 10.84 | 11.17 | 10.79 | 11.13 | 1.17 | 55746 | 0.30 | 2.77 | 64.74 | 0.57 |
300271 | 2023-08-29 | 300271 | 华宇软件 | 创业板 | 8.40 | 8.76 | 8.39 | 8.69 | 1.65 | 132354 | 0.25 | 2.96 | 71.22 | 0.60 |
4738370 rows × 14 columns
[ ]:
实现策略#
实现一个基于10均和30均的金叉买进、死叉卖出的简单策略。但此处我们对短周期均线使用指数移动平均,这比简单移动平均更能反应近期价格变化的趋势,且只当股价在120日均线上方时才买入,不买入ST股或科创板。
[3]:
import talib
import pandas as pd
from tradepy.strategy.base import BacktestStrategy, BuyOption
from tradepy.decorators import tag
[4]:
class MovingAverageCrossoverStrategy(BacktestStrategy):
@tag(notna=True)
def ma10(self, close: pd.Series) -> pd.Series:
"""
这是一个技术指标计算函数
- 输入参数可以为其他指标,称为"上游指标"
- "上游指标"必须已在回测数据的DataFrame中,或有自己的计算函数
- 您只需要关注指标的计算函数的实现,TradePy会自动推导指标的依赖关系,保证正确的计算顺序
- TA-Lib集成了很多常用技术指标的计算方法,推荐使用。
"""
return talib.EMA(close, 10)
# notna=True:意为该指标值不能为空,因而回测将从第120日开始进行,请注意预留足够的回测天数
@tag(notna=True)
def ma120(self, close):
return talib.SMA(close, 120)
@tag(notna=True)
def ma30(self, close):
return talib.SMA(close, 30)
@tag(notna=True)
def ma10_ref1(self, ma10):
return ma10.shift(1)
@tag(notna=True)
def ma30_ref1(self, ma30):
return ma30.shift(1)
def should_buy(self,
company,
ma120: float, ma10: float, ma30: float,
ma10_ref1: float, ma30_ref1: float,
close: float) -> BuyOption | None:
"""
买入信号(必须实现)
- 回测数据DataFrame中的列都可作为输入参数
- 如果触发买入,则返回 买入价 以及 权重值
- 制定回测配置时,如果设置了"每日最大开仓数量"或“最低开仓金额”,则有可能触发买入的股票数量大于可买入数量。
此时,可通过给予不同的权重值,以优先选中预期收益最大的标的。如果希望随机选择标的,可一律返回权重值"1"
"""
if "ST" not in company:
if (ma10 > ma120) and (ma10_ref1 < ma30_ref1) and (ma10 > ma30):
return close, 1
def should_sell(self, ma10, ma30, ma10_ref1, ma30_ref1):
"""
尾盘平仓信号(可选实现)
- 除了使用止盈/止损点位平仓,还可根据一定的策略逻辑,判断是否应该在尾盘时提前平仓
- 返回True / False
"""
return (ma10_ref1 > ma30_ref1) and (ma10 < ma30)
def pre_process(self, df: pd.DataFrame) -> pd.DataFrame:
"""
数据预处理 (可选实现)
- 输入为原始的回测数据DataFrame
- 此处可执行任意逻辑,如过滤掉无法交易的板块、加载外部数据(如大盘指数)并添加到DataFrame等等
"""
return df.query('market != "科创板"').copy()
[ ]:
运行回测#
[5]:
from tradepy.core.conf import BacktestConf, StrategyConf, SlippageConf
conf = BacktestConf(
cash_amount=1e6, # 初始资金
broker_commission_rate=0.01, # 券商费率,此处为万一
min_broker_commission_fee=0, # 券商最低佣金,此处为免五
use_minute_k=False, # 是否使用分时K线做日内买卖点判断, 需要下载分钟K线
sl_tf_orde='stop loss first', # 日内价格同时达到止损/止盈价时,先止损还是先止盈
strategy=StrategyConf(
stop_loss=4.5, # 静态止损点位 %
take_profit=5, # 静态止盈点位 %
take_profit_slip=SlippageConf(
method='max_jump', # 最大滑点价位
params=1 # 一跳
),
stop_loss_slip=SlippageConf(
method='max_pct', # 最大滑点百分比
params=0.1 # 0.1%
),
max_position_opens=10, # 每日最大可开仓数量
max_position_size=0.25, # 个股最仓位
min_trade_amount=8000, # 开仓最低买入额
)
)
res, trade_book = MovingAverageCrossoverStrategy.backtest(df, conf)
# res, trade_book = MovingAverageCrossoverStrategy.backtest(res, conf) # 首次计算后,可直接将带指标的回测数据作为输入
[base] [2023-09-20 09:05:12,511] [INFO]: >>> 获取待计算因子
[base] [2023-09-20 09:05:12,512] [INFO]: - 待计算: [ma30, ma10, ma120, ma30_ref1, ma10_ref1]
[base] [2023-09-20 09:05:12,513] [INFO]: >>> 计算每支个股的后复权价格以及技术因子
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5042/5042 [01:00<00:00, 83.20it/s]
[backtester] [2023-09-20 09:06:16,542] [INFO]: >>> 重建索引 [timestamp, code]
[backtester] [2023-09-20 09:06:19,539] [INFO]: >>> 交易中 ...
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1013/1013 [00:21<00:00, 48.01it/s]
[ ]:
backtest
方法返回添加了技术指标后的回测K线数据,以及TradeBook
实例,记录了交易过程以及每日资产金额变化,该实例也可用于生成结果分析报告。添加技术指标后的数据,可再作为
backtest
的输入数据执行多次回测,此时将不再重新计算指标
[ ]:
[6]:
trade_book.trade_logs_df[:5]
[6]:
action | id | code | vol | price | total_value | chg | pct_chg | total_return | |
---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||
2019-07-02 | 开仓 | 000985-7e2f | 000985 | 5400 | 25.318006 | 136717.233745 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 603538-1c91 | 603538 | 3600 | 27.173121 | 97823.234427 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 600136-49a5 | 600136 | 3500 | 27.899347 | 97647.715470 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 002099-3cc7 | 002099 | 1800 | 55.319792 | 99575.624984 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 600728-dc57 | 600728 | 700 | 131.042719 | 91729.903044 | 0.0 | 0.0 | 0.0 |
[7]:
trade_book.trade_logs_df[:5]
[7]:
action | id | code | vol | price | total_value | chg | pct_chg | total_return | |
---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||
2019-07-02 | 开仓 | 000985-7e2f | 000985 | 5400 | 25.318006 | 136717.233745 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 603538-1c91 | 603538 | 3600 | 27.173121 | 97823.234427 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 600136-49a5 | 600136 | 3500 | 27.899347 | 97647.715470 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 002099-3cc7 | 002099 | 1800 | 55.319792 | 99575.624984 | 0.0 | 0.0 | 0.0 |
2019-07-02 | 开仓 | 600728-dc57 | 600728 | 700 | 131.042719 | 91729.903044 | 0.0 | 0.0 | 0.0 |
[8]:
trade_book.cap_logs_df[:5]
[8]:
frozen_cash_amount | market_value | free_cash_amount | capital | pct_chg | |
---|---|---|---|---|---|
timestamp | |||||
2019-07-02 | 0 | 999772.62 | 127.40 | 999900.02 | 0.000000 |
2019-07-03 | 0 | 994719.17 | 71.22 | 994790.39 | -0.005110 |
2019-07-04 | 0 | 993782.65 | 247.79 | 994030.44 | -0.000764 |
2019-07-05 | 0 | 990946.48 | 842.40 | 991788.88 | -0.002255 |
2019-07-08 | 0 | 965505.87 | 1453.10 | 966958.97 | -0.025035 |
[ ]:
分析结果#
与上证指数对比,可直观地看到该策略获得了超额收益(4年半间大约70%回报),但是20年年中开始表现不甚理想,尤其到21年10月之前,经历了较长的回撤期。
使用
BasicEvaluator.basic_report
可查看主要指标。使用
BasicEvaluator.html_report
可下载详细的结果报告,该报告由quantstats生成,详情请查阅其文档。
[9]:
from tradepy.backtest import BasicEvaluator
from tradepy.analysis.plots import plot_equity_curve
[10]:
plot_equity_curve(trade_book)
8it [00:00, 138.17it/s]
[11]:
e = BasicEvaluator(trade_book)
e.basic_report()
===========
开仓 = 7718
止损 = 3395
止盈 = 3639
提前平仓 = 657
胜率 47.42%
最大回撤 = -20.35%
期末资金 = 165.35%
平均开仓收益: 0.11% (标准差: 3.43%)
夏普比率: 0.78
===========
[1]:
e.html_report()
[ ]: