3. 一括入札戦略
参照元:docs/_joinquant_migration_source/Example_03_集集竞价选股.ipynb 最初のMarkdownセル。
この戦略は、SHSE.000300 上海と深セン 300 の構成銘柄データを取得し、30 日以内に始値が前回の終値を上回った日数をカウントします。この数がしきい値の 10 を超えると、ストック プールに追加されます。次に、株式プールにない株式のポジションを決済し、株式プール内の株式を均等に割り当てます。取引間隔は1ヶ月です。
バックテストデータ:SHSE.000300 上海および深セン300指数構成銘柄 バックテスト期間:2016-04-05~2021-02-01
>>> import qteasy as qt
>>> import pandas as pd
>>> import numpy as np
>>> htypes = 'open, close'
>>> shares = qt.filter_stock_codes(index='000300.SH', date='20220131')
>>> print(shares[0:10])
>>> dt = qt.get_history_data(htypes, shares=shares, asset_type='any', freq='m')
['000001.SZ', '000002.SZ', '000063.SZ', '000066.SZ', '000069.SZ', '000100.SZ', '000157.SZ', '000166.SZ', '000301.SZ', '000333.SZ']
3.1. カスタム戦略を設定する最初の方法は、ポジション データと銘柄選択データを使用して比例取引シグナル PS シグナルを直接生成することです。
GeneralStrategy 戦略クラスを使用して銘柄選択係数を計算し、ゼロ未満のすべての係数を削除し、上位 30 銘柄を並べ替えて抽出します。次のロジックに従って取引シグナルを生成します。 1. 現在のポジションを確認します。ポジション内の株式が選択されなかった場合は、すべて売却します。 2. 現在位置を確認します。新しく選択した銘柄がポジションにない場合は、新しく選択した銘柄を均等に購入します。
取引シグナルのタイプをPSに設定し、取引シグナルを生成します。売買シグナルの生成にはポジションデータを使用する必要があるため、バッチ生成モードは使用できず、stepwiseモードのみが使用可能です。
>>> import numpy as np
>>> class GroupPS(qt.GeneralStg):
...
... def realize(self):
...
... # 读取策略参数(开盘价大于收盘价的天数)
... n_day = self.get_pars('n_day')
...
... # 从历史数据编码中读取四种历史数据的最新数值
... opens, closes = self.get_data('open_E_d', 'close_E_d')
... opens = opens[-30:] # 从前一交易日起前30天内开盘价
... closes = closes[-30:] #
...
... # 从持仓数据中读取当前的持仓数量,并找到持仓股序号
... own_amounts = self.get_data('proc.own_amounts')
... owned = np.where(own_amounts > 0)[1] # 所有持仓股的序号
... not_owned = np.where(own_amounts == 0)[1] # 所有未持仓的股票序号
...
... # 选股因子为开盘价大于收盘价的天数,使用astype将True/False结果改为1/0,便于加总
... factors = ((opens - closes) > 0).astype('float')
... # 所有开盘价-收盘价>0的结果会被转化为1,其余结果转化为0,因此可以用sum得到开盘价大于收盘价的天数
... factors = factors.sum(axis=0)
... # 选出开盘价大于收盘价天数大于十天的所有股票的序号
... all_args = np.arange(len(factors))
... selected = np.where(factors > n_day)[0]
... not_selected = np.setdiff1d(all_args, selected)
... # 计算选出的股票的数量
... selected_count = len(selected)
...
... # 开始生成交易信号
... signal = np.zeros_like(factors)
... # 如果持仓为正,且未被选中,生成全仓卖出交易信号
... own_but_not_selected = np.intersect1d(owned, not_selected)
... signal[own_but_not_selected] = -1 # 在PS信号模式下 -1 代表全仓卖出
...
... if selected_count == 0:
... # 如果选中的数量为0,则不需要生成买入信号,可以直接返回只有卖出的信号
... return signal
...
... # 如果持仓为零,且被选中,生成全仓买入交易信号
... selected_but_not_own = np.intersect1d(not_owned, selected)
... signal[selected_but_not_own] = 1. / selected_count # 在PS信号模式下,+1 代表全仓买进 (如果多只股票均同时全仓买进,则会根据资金总量平均分配资金)
...
... return signal
Operator オブジェクトを作成し、取引戦略をバックテストする
>>> from qteasy import Parameter, StgData
>>> alpha = GroupPS(name='GroupPS',
... description='本策略每隔1个月定时触发, 从SHSE.000300成份股中选择过去30天内开盘价大于前收盘价的天数大于10天的股票买入',
... pars=[Parameter((3, 25), par_type='int', name='n_day', value=10)],
... data_types=[StgData('open', freq='d', asset_type='E', window_length=32),
... StgData('close', freq='d', asset_type='E', window_length=32)],
... )
>>> op = qt.Operator(alpha, signal_type='PS', run_freq='ME')
>>> op.op_type = 'stepwise'
>>> op.set_parameter(0, par_values=(20,))
>>> qt.run(op=op, mode=1,
... asset_type='E',
... asset_pool=shares,
... invest_start='20160405',
... invest_end='20210201',
... trade_batch_size=100,
... sell_batch_size=1,
... trade_log=True)
結果は次のとおりです。
====================================
| |
| BACK TESTING RESULT |
| |
====================================
qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 0.0ms
time consumption for operation back looping: 12s 766.3ms
investment starts on 2016-04-05 00:00:00
ends on 2021-02-01 00:00:00
Total looped periods: 4.8 years.
-------------operation summary:------------
Only non-empty shares are displayed, call
"loop_result["oper_count"]" for complete operation summary
Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
000001.SZ 1 1 2 1.7% 0.0% 98.3%
000069.SZ 2 2 4 3.4% 0.0% 96.6%
000301.SZ 3 3 6 13.6% 0.0% 86.4%
000333.SZ 1 1 2 1.7% 0.0% 98.3%
000338.SZ 1 1 2 3.5% 0.0% 96.5%
000596.SZ 1 1 2 1.8% 0.0% 98.2%
000651.SZ 1 1 2 1.7% 0.0% 98.3%
000776.SZ 1 1 2 1.7% 0.0% 98.3%
000786.SZ 1 1 2 1.7% 0.0% 98.3%
000800.SZ 2 2 4 5.4% 0.0% 94.6%
... ... ... ... ... ... ...
603806.SH 2 2 4 3.6% 0.0% 96.4%
603939.SH 0 1 1 3.7% 0.0% 96.3%
688599.SH 0 1 1 3.7% 0.0% 96.3%
000408.SZ 1 1 2 1.7% 0.0% 98.3%
002648.SZ 2 2 4 3.4% 0.0% 96.6%
300751.SZ 1 1 2 1.7% 0.0% 98.3%
688065.SH 1 1 2 1.7% 0.0% 98.3%
600674.SH 2 2 4 3.7% 0.0% 96.3%
600803.SH 1 1 2 1.7% 0.0% 98.3%
601615.SH 1 1 2 1.7% 0.0% 98.3%
Total operation fee: ¥ 1,290.37
total investment amount: ¥ 100,000.00
final value: ¥ 216,271.60
Total return: 116.27%
Avg Yearly return: 17.32%
Skewness: 0.29
Kurtosis: 7.52
Benchmark return: 65.96%
Benchmark Yearly return: 11.06%
------strategy loop_results indicators------
alpha: 0.115
Beta: 0.525
Sharp ratio: 0.956
Info ratio: 0.017
250 day volatility: 0.149
Max drawdown: 18.93%
peak / valley: 2018-05-25 / 2018-09-11
recovered on: 2019-04-18
===========END OF REPORT=============

3.2. カスタム戦略を設定する 2 番目の方法では、PT 取引シグナルを使用してポジション目標を設定します。
銘柄選択要素の計算が完了したら、各銘柄のポジション目標を直接設定します。これにより、位置データを知る必要がなく、位置目標信号を直接出力することができます。バックテストプロセス中に、実際のポジションに基づいて取引シグナルが生成されます。
>>> class GroupPT(qt.GeneralStg):
...
... def realize(self):
...
... # 读取策略参数(开盘价大于收盘价的天数)
...
... # 读取策略参数(开盘价大于收盘价的天数)
... n_day = self.get_pars('n_day')
...
... # 从历史数据编码中读取四种历史数据的最新数值
... opens, closes = self.get_data('open_E_d', 'close_E_d') # 从前一交易日起前30天内开盘价
... opens = opens[-30:]
... closes = closes[-30:]
...
... # 选股因子为开盘价大于收盘价的天数,使用astype将True/False结果改为1/0,便于加总
... factors = ((opens - closes) > 0).astype('float')
... # 所有开盘价-收盘价>0的结果会被转化为1,其余结果转化为0,因此可以用sum得到开盘价大于收盘价的天数
... factors = factors.sum(axis=0)
... # import pdb; pdb.set_trace()
... # 选出开盘价大于收盘价天数大于十天的所有股票的序号
... all_args = np.arange(len(factors))
... selected = np.where(factors > n_day)[0]
... not_selected = np.setdiff1d(all_args, selected)
... # 计算选出的股票的数量
... selected_count = len(selected)
... print(f'now {selected_count} shares are selected!')
...
... # 开始生成交易信号
... signal = np.zeros_like(factors)
... if selected_count == 0:
... return signal
... # 所有被选中的股票均设置为正持仓目标
... signal[selected] = 1. / selected_count
... # 未被选中的股票持仓目标被设置为0
... signal[not_selected] = 0
...
... return signal
Operator オブジェクトを作成し、取引戦略のバックテストを開始します
>>> alpha = GroupPT(name='GroupPS',
... description='本策略每隔1个月定时触发, 从SHSE.000300成份股中选择过去30天内开盘价大于前收盘价的天数大于10天的股票买入',
... pars=[Parameter((3, 25), par_type='int', name='n_day', value=10)],
... data_types=[StgData('open', freq='d', asset_type='E', window_length=32),
... StgData('close', freq='d', asset_type='E', window_length=32)],
... )
>>> op = qt.Operator(alpha, signal_type='PT', run_freq='ME')
>>> op.set_parameter(0, par_values=(20,))
>>> qt.run(op=op, mode=1,
... asset_type='E',
... asset_pool=shares,
... invest_start='20160405',
... invest_end='20210201',
... trade_batch_size=100,
... sell_batch_size=1,
... trade_log=True)
取引バックテストの結果は以下の通りです。
====================================
| |
| BACK TESTING RESULT |
| |
====================================
qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 399.1ms
time consumption for operation back looping: 8s 621.5ms
investment starts on 2016-04-05 00:00:00
ends on 2021-02-01 00:00:00
Total looped periods: 4.8 years.
-------------operation summary:------------
Only non-empty shares are displayed, call
"loop_result["oper_count"]" for complete operation summary
Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
000001.SZ 1 1 2 1.7% 0.0% 98.3%
000069.SZ 2 2 4 3.4% 0.0% 96.6%
000301.SZ 6 5 11 13.6% 0.0% 86.4%
000338.SZ 1 1 2 1.7% 0.0% 98.3%
000596.SZ 1 1 2 1.8% 0.0% 98.2%
000625.SZ 1 1 2 1.7% 0.0% 98.3%
000661.SZ 1 1 2 1.7% 0.0% 98.3%
000776.SZ 1 1 2 1.7% 0.0% 98.3%
000786.SZ 1 1 2 1.7% 0.0% 98.3%
000800.SZ 2 3 5 5.4% 0.0% 94.6%
... ... ... ... ... ... ...
603806.SH 2 2 4 3.6% 0.0% 96.4%
603939.SH 0 2 2 3.7% 0.0% 96.3%
688599.SH 0 2 2 3.7% 0.0% 96.3%
000408.SZ 1 1 2 1.7% 0.0% 98.3%
002648.SZ 1 1 2 1.7% 0.0% 98.3%
300751.SZ 1 1 2 1.7% 0.0% 98.3%
688065.SH 1 1 2 1.7% 0.0% 98.3%
600674.SH 2 2 4 3.7% 0.0% 96.3%
600803.SH 1 1 2 1.7% 0.0% 98.3%
601615.SH 1 1 2 1.7% 0.0% 98.3%
Total operation fee: ¥ 1,375.35
total investment amount: ¥ 100,000.00
final value: ¥ 216,215.03
Total return: 116.22%
Avg Yearly return: 17.31%
Skewness: 0.23
Kurtosis: 6.63
Benchmark return: 65.96%
Benchmark Yearly return: 11.06%
------strategy loop_results indicators------
alpha: 0.110
Beta: 0.542
Sharp ratio: 1.015
Info ratio: 0.017
250 day volatility: 0.139
Max drawdown: 18.58%
peak / valley: 2016-04-14 / 2017-01-16
recovered on: 2017-09-27
===========END OF REPORT=============
