快速上手#

下载数据#

股票列表、股票日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股或科创板。

: 使用TradePy内置的指标函数可简化该策略的实现,可查阅策略实现教程
⚠️ 回测中假定以收盘价买入其实是犯了典型的"偷价"错误,因为收盘价已是集合竞价后的价格,盘中不可能保证以该价位成交。因而,回测结果只是提供一个参考,再考虑滑点等因素的话,不可能完全复现实盘的表现。
[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()

[ ]:

运行回测#

⚠️ 使用日K回测时,如果日内价格运行区间同时包含止盈和止损价,此时无法判断哪个先发生,如果策略指定的止损止盈幅度较小,这个问题的影响可能会更加突出! 默认配置下,TradePy采用偏保守策略,此时会一律止损,因而回测结果应该接近策略实际表现的下限区间。您也可以提供不同的BacktestConf.sl_tf_order值来决定先止损、先止盈还是随机,并观察不同设定下的收益曲线变化。使用分钟K线做回测可以基本解决此问题(但回测用时会大大延长),可查阅使用分时数据教程
[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()
[ ]: