量化分析经典策略总结
菲阿里四价(期货)
原理
菲阿里四价同 R Breaker 一样,也是一种 日内 策略交易,适合短线投资者。
菲阿里四价指的是:昨日高点、昨日低点、昨天收盘、今天开盘四个价格。
菲阿里四价上下轨的计算非常简单。昨日高点为上轨,昨日低点为下轨。当价格突破上轨时,买入开仓;当价格突破下轨时,卖出开仓。
策略逻辑
第一步:获取昨日最高价、最低价、收盘价、开盘价四个数据。
第二步:计算上轨和下轨。当价格上穿上轨时,买入开仓;当价格下穿下轨时,卖出开仓。
第三步:当日平仓。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 from __future__ import print_function, absolute_importfrom gm.api import *""" 上轨=昨日最高点; 下轨=昨日最低点; 止损=今日开盘价; 如果没有持仓,且现价大于了昨天最高价做多,小于昨天最低价做空。 如果有多头持仓,当价格跌破了开盘价止损。 如果有空头持仓,当价格上涨超过开盘价止损。 选取 进行了回测。 注意: 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。 """ def init (context ): context.symbol = 'SHFE.rb2010' subscribe(symbols = context.symbol,frequency = '60s' ,count = 1 ) context.count = 0 time = context.now.strftime('%H:%M:%S' ) if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00' : algo(context) schedule(schedule_func=algo, date_rule='1d' , time_rule='09:00:00' ) schedule(schedule_func=algo, date_rule='1d' , time_rule='21:00:00' ) def algo (context ): context.history_data = history_n(symbol=context.symbol, frequency='1d' , end_time=context.now, fields='symbol,open,high,low' , count=2 , df=True ) def on_bar (context,bars ): bar = bars[0 ] data = context.history_data position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long) position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short) if context.mode == 2 : open = data.loc[1 , 'open' ] high = data.loc[0 , 'high' ] low = data.loc[0 , 'low' ] else : open = current(context.symbol)[0 ]['open' ] high = data.loc[-1 , 'high' ] low = data.loc[-1 , 'low' ] if position_long: if bar.close < open : order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('以市价单平多仓' ) elif position_short: if bar.close > open : order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('以市价单平空仓' ) else : if bar.close > high and not context.count: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('以市价单开多仓' ) context.count = 1 elif bar.close < low and not context.count: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('以市价单开空仓' ) context.count = 1 if context.now.hour == 14 and context.now.minute == 59 : order_close_all() print ('全部平仓' ) context.count = 0 if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2020-01-01 15:00:00' , backtest_end_time='2020-09-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=100000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
双均线策略(期货)
原理
从统计角度来说,均线就是历史价格的平均值,可以代表过去 N 日股价的平均走势。
Granville 八大法则其中有四条是用于判断买进时机,另外四条是用于判断卖出时机。买进和卖出法则一一对应,分布在高点的左右两侧(除买 4 和卖 4 以外)。法则内容如下所示:
买 1:均线整体上行,股价由下至上上穿均线,此为黄金交叉,形成第一个买点。
买 2:股价出现下跌迹象,但尚未跌破均线,此时均线变成支撑线,形成第二个买点。
买 3:股价仍处于均线上方,但呈现急剧下跌趋势。当跌破均线时,出现第三个买点。
买 4:(右侧)股价和均线都处于下降通道,且股价处于均线下方,严重远离均线,出现第四个买点。
卖 1:均线由上升状态变为缓慢下降的状态,股价也开始下降。当股价跌破均线时,此为死亡交叉,形成第一个卖点。
卖 2:股价仍处于均线之下,但股价开始呈现上涨趋势,当股价无限接近均线但尚未突破时,此时均线变成阻力线,形成第二个卖点。
卖 3:股价终于突破均线,处于均线上方。但持续时间不长,股价开始下跌,直至再一次跌破均线,此为第三个卖点。
卖 4:(左侧)股价和均线都在上涨,股价上涨的速度远快于均线上涨的速度。当股价严重偏离均线时,出现第四个卖点。
均线理论为什么有效?
Shiller(1981)在研究中发现,资产的长期价格呈现均值回复的特征,即从长期来看,资产的价格会回归均值。这也是均线理论被广泛应用的前提。
均线理论的缺陷
均线归根到底是一种平均值,平均值在应用过程中存在最大的问题就是其滞后性。当出现买入卖出信号时,最佳时机早已过去。
均线理论的改进
对均线的计算方法进行改正。
加权移动平均线是在移动平均线的基础上按照时间进行加权。越靠近当前日期的价格对未来价格的影响越大,赋予更大的权重;越远离当前日期价格,赋予越小的权重。
调整均线周期
利用不同周期均线得到的结果也不同。
策略逻辑
第一步:获取数据,计算长短期均线
第二步:设置交易信号
当短期均线由上向下穿越长期均线时做空
当短期均线由下向上穿越长期均线时做多
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 from __future__ import print_function, absolute_importfrom gm.api import *import talib''' 本策略以SHFE.rb2101为交易标的,根据其一分钟(即60s频度)bar数据建立双均线模型, 短周期为20,长周期为60,当短期均线由上向下穿越长期均线时做空, 当短期均线由下向上穿越长期均线时做多,每次开仓前先平掉所持仓位,再开仓。 注:为了适用于仿真和实盘,在策略中增加了一个“先判断是否平仓成功再开仓”的判断逻辑,以避免出现未平仓成功,可用资金不足的情况。 回测数据为:SHFE.rb2101的60s频度bar数据 回测时间为:2020-04-01 09:00:00到2020-05-31 15:00:00 ''' def init (context ): context.short = 20 context.long = 60 context.symbol = 'SHFE.rb2101' context.period = context.long + 1 context.open_long = False context.open_short = False subscribe(context.symbol, '60s' , count=context.period) def on_bar (context, bars ): prices = context.data(context.symbol, '60s' , context.period, fields='close' ) short_avg = talib.SMA(prices.values.reshape(context.period), context.short) long_avg = talib.SMA(prices.values.reshape(context.period), context.long) position_long = context.account().position(symbol=context.symbol, side=1 ) position_short = context.account().position(symbol=context.symbol, side=2 ) if long_avg[-2 ] < short_avg[-2 ] and long_avg[-1 ] >= short_avg[-1 ]: if not position_long: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, position_effect=PositionEffect_Open, order_type=OrderType_Market) print (context.symbol, '以市价单调空仓到仓位' ) else : context.open_short = True order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, position_effect=PositionEffect_Close, order_type=OrderType_Market) print (context.symbol, '以市价单平多仓' ) if short_avg[-2 ] < long_avg[-2 ] and short_avg[-1 ] >= long_avg[-1 ]: if not position_short: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, position_effect=PositionEffect_Open, order_type=OrderType_Market) print (context.symbol, '以市价单调多仓到仓位' ) else : context.open_long = True order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, position_effect=PositionEffect_Close, order_type=OrderType_Market) print (context.symbol, '以市价单平空仓' ) def on_order_status (context, order ): status = order['status' ] side = order['side' ] effect = order['position_effect' ] if status == 3 : if effect == 2 and side == 2 and context.open_short: context.open_short = False order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, position_effect=PositionEffect_Open, order_type=OrderType_Market) print (context.symbol, '以市价单调空仓到仓位' ) if effect == 2 and side == 1 and context.open_long: context.open_long = False order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, position_effect=PositionEffect_Open, order_type=OrderType_Market) print (context.symbol, '以市价单调多仓到仓位' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2020-04-01 09:00:00' , backtest_end_time='2020-05-31 15:00:00' , backtest_adjust=ADJUST_NONE, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
Dual Thrust(期货)
原理
其核心思想是定义一个区间,区间的上界和下界分别为支撑线和阻力线。当价格超过上界时,如果持有空仓,先平再开多;如果没有仓位,直接开多。当价格跌破下界时,如果持有多仓,则先平仓,再开空仓;如果没有仓位,直接开空仓。
上下界的设定是交易策略的核心部分。在计算上下界时共用到:最高价、最低价、收盘价、开盘价四个参数。
公式如下:
Range = Max(HH - LC, HC - LL)
max(最大最高价(HH) - 最小收盘价(LC),最大收盘价(HC) - 最小最低价(LL))
上限:Open + K1 * Range
下限:Open - k2 * Range
K1 和 K2 一般根据自己经验以及回测结果进行优化。
策略逻辑
第一步:设置参数 N、k1、k2
第二步:计算 HH、LC、HC、LL
第三步:计算 range
第四步:设定做多和做空信号
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 from __future__ import print_function, absolute_importfrom gm.api import *""" Dual Thrust是一个趋势跟踪系统 计算前N天的最高价-收盘价和收盘价-最低价。然后取这2N个价差的最大值,乘以k值。把结果称为触发值。 在今天的开盘,记录开盘价,然后在价格超过上轨(开盘+触发值)时马上买入,或者价格低于下轨(开盘-触发值)时马上卖空。 没有明确止损。这个系统是反转系统,也就是说,如果在价格超过(开盘+触发值)时手头有空单,则平空开多。 同理,如果在价格低于(开盘-触发值)时手上有多单,则平多开空。 选用了SHFE的rb2010 在2020-02-07 15:00:00 到 2020-04-15 15:00:00' 进行回测。 注意: 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交 """ def init (context ): context.symbol = 'SHFE.rb2010' context.N = 5 context.k1 = 0.2 context.k2 = 0.2 time = context.now.strftime('%H:%M:%S' ) if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00' : algo(context) schedule(schedule_func=algo, date_rule='1d' , time_rule='09:00:00' ) schedule(schedule_func=algo, date_rule='1d' , time_rule='21:00:00' ) subscribe(symbols=context.symbol, frequency='60s' , count=1 ) def algo (context ): data = history_n(symbol=context.symbol, frequency='1d' , end_time=context.now, fields='symbol,open,high,low,close' , count=context.N + 1 , df=True ) if context.mode == 2 : current_open = data.open .loc[0 ] data.drop(context.N, inplace=True ) else : current_open = current(context.symbol)[0 ]['open' ] HH = data['high' ].max () HC = data['close' ].max () LC = data['close' ].min () LL = data['low' ].min () range = max (HH - LC, HC - LL) context.buy_line = current_open + range * context.k1 context.sell_line = current_open - range * context.k2 def on_bar (context, bars ): bar = bars[0 ] buy_line = context.buy_line sell_line = context.sell_line position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long) position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short) if bar.close > buy_line: if position_long: return elif position_short: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('市价单平空仓' , context.symbol) order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('市价单开多仓' , context.symbol) else : order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('市价单开多仓' , context.symbol) elif bar.close < sell_line: if position_long: order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('市价单平多仓' , context.symbol) order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('市价单开空仓' , context.symbol) elif position_short: return else : order_volume(symbol=context.symbol, volume=1 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('市价单开空仓' , context.symbol) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2020-02-07 15:00:00' , backtest_end_time='2020-04-15 15:00:00' , backtest_initial_cash=30000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
R-Breaker(期货)
原理
原理
R Breaker 是一种 日内 回转交易策略,属于短线交易。日内回转交易是指当天买入或卖出标的后于当日再卖出或买入标的。日内回转交易通过标的短期波动盈利,低买高卖,时间短、投机性强,适合短线投资者。
R Breaker 主要分为分为反转和趋势两部分。空仓时进行趋势跟随,持仓时等待反转信号反向开仓。
由于我国 A 股采用的是“T+1”交易制度,为了方便起见,以期货为例演示 R Breaker 策略。
反转和趋势突破的价位点根据前一交易日的收盘价、最高价和最低价数据计算得出,分别为:突破买入价、观察卖出价、反转卖出价、反转买入价、观察买入价和突破卖出价。计算方法如下:
指标计算方法:
中心价位 P = (H + C + L)/3
突破买入价 = H + 2P -2L
观察卖出价 = P + H - L
反转卖出价 = 2P - L
反转买入价 = 2P - H
观察买入价 = P - (H - L)
突破卖出价 = L - 2(H - P)
(H: 最高价, C: 收盘价, L: 最低价)
触发条件
空仓时:突破策略
空仓时,当盘中价格>突破买入价,则认为上涨的趋势还会继续,开仓做多;
空仓时,当盘中价格<突破卖出价,则认为下跌的趋势还会继续,开仓做空。
持仓时:反转策略
持多单时:当日内最高价>观察卖出价后,盘中价格回落,跌破反转卖出价构成的支撑线时,采取反转策略,即做空;
持空单时:当日内最低价<观察买入价后,盘中价格反弹,超过反转买入价构成的阻力线时,采取反转策略,即做多。
背后逻辑解析
前一交易日 K 线越长,下影线越长,突破买入价越高。
前一交易日 K 线越长,上影线越长,突破卖入价越高。
当今日的价格突破前一交易日的最高点,形态上来看会是上涨趋势,具备一定的开多仓条件,但还不够。若前一交易日的下影线越长,说明多空方博弈激烈,多方力量强大。因此可以设置更高的突破买入价,一旦突破说明多方力量稳稳地占据了上风,那么就有理由相信未来会继续上涨。同理可解释突破卖出价背后的逻辑。
持有多仓时,若标的价格持续走高,则在当天收盘之前平仓获利离场。若价格不升反降,跌破观察卖出价时,此时价格仍处于前一交易日最高价之上,继续观望。若继续下跌,直到跌破反转卖出价时,平仓止损。
持有空仓时,若标的价格持续走低,则在当天收盘之前平仓获利离场。若价格不降反升,升至观察买入价时,此时价格仍处于前一交易日最低价之下,继续观望。若继续上涨,直到升至反转买入价时,平仓止损。
策略逻辑
第一步:根据收盘价、最高价和最低价数据计算六个价位。
第二步:如果是空仓条件下,如果价格超过突破买入价,则开多仓;如果价格跌破突破卖出价,则开空仓。
第三步:在持仓条件下:
持多单时,当最高价超过观察卖出价,盘中价格进一步跌破反转卖出价,反手做空;
持空单时,当最低价低于观察买入价,盘中价格进一步超过反转买入价,反手做多。
第四步:接近收盘时,全部平仓。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 from __future__ import print_function, absolute_importimport pandas as pdfrom gm.api import *from datetime import datetime, timedelta""" R-Breaker是一种短线日内交易策略 根据前一个交易日的收盘价、最高价和最低价数据通过一定方式计算出六个价位,从大到小依次为: 突破买入价、观察卖出价、反转卖出价、反转买入、观察买入价、突破卖出价。以此来形成当前交易 日盘中交易的触发条件。 追踪盘中价格走势,实时判断触发条件。具体条件如下: 突破 在空仓条件下,如果盘中价格超过突破买入价,则采取趋势策略,即在该点位开仓做多。 在空仓条件下,如果盘中价格跌破突破卖出价,则采取趋势策略,即在该点位开仓做空。 反转 持多单,当日内最高价超过观察卖出价后,盘中价格出现回落,且进一步跌破反转卖出价构成的支撑线时,采取反转策略,即在该点位反手做空。 持空单,当日内最低价低于观察买入价后,盘中价格出现反弹,且进一步超过反转买入价构成的阻力线时,采取反转策略,即在该点位反手做多。 设定止损条件。当亏损达到设定值后,平仓。 注意: 1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。 2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。 3:本策略使用在15点收盘时全平的方式来处理不持有隔夜单的情况,实际使用中15点是无法平仓的。 """ def init (context ): context.symbol = 'SHFE.ag' context.stopLossPrice = 50 startDate = get_previous_trading_date(exchange='SHFE' , date=context.now.date()) continuous_contract = get_continuous_contracts(context.symbol, startDate, startDate) context.mainContract = continuous_contract[0 ]['symbol' ] time = context.now.strftime('%H:%M:%S' ) if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00' : algo(context) schedule(schedule_func=algo, date_rule='1d' , time_rule='09:00:00' ) schedule(schedule_func=algo, date_rule='1d' , time_rule='21:00:00' ) subscribe(continuous_contract[0 ]['symbol' ], frequency='60s' , count=1 ) def algo (context ): startDate = get_previous_trading_date(exchange='SHFE' , date=context.now.date()) contractInfo = get_continuous_contracts(context.symbol, startDate, startDate) if context.mainContract != contractInfo[0 ]['symbol' ]: context.mainContract = contractInfo[0 ]['symbol' ] subscribe(context.mainContract, frequency='60s' , count=1 , unsubscribe_previous=True ) data = history_n(symbol=context.mainContract, frequency='1d' , end_time=context.now, fields='high,low,open,symbol,close' , count=2 , df=True ) high = data['high' ].iloc[0 ] low = data['low' ].iloc[0 ] close = data['close' ].iloc[0 ] pivot = (high + low + close) / 3 context.bBreak = high + 2 * (pivot - low) context.sSetup = pivot + (high - low) context.sEnter = 2 * pivot - low context.bEnter = 2 * pivot - high context.bSetup = pivot - (high - low) context.sBreak = low - 2 * (high - pivot) context.data = data def on_bar (context, bars ): STOP_LOSS_PRICE = context.stopLossPrice bBreak = context.bBreak sSetup = context.sSetup sEnter = context.sEnter bEnter = context.bEnter bSetup = context.bSetup sBreak = context.sBreak data = context.data position_long = context.account().position(symbol=context.mainContract, side=PositionSide_Long) position_short = context.account().position(symbol=context.mainContract, side=PositionSide_Short) if not position_long and not position_short: if bars[0 ].close > bBreak: order_volume(symbol=context.mainContract, volume=10 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ("空仓,盘中价格超过突破买入价: 开仓做多" ) context.open_position_price = bars[0 ].close elif bars[0 ].close < sBreak: order_volume(symbol=context.mainContract, volume=10 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ("空仓,盘中价格跌破突破卖出价: 开仓做空" ) context.open_position_price = bars[0 ].close else : if (position_long and context.open_position_price - bars[0 ].close >= STOP_LOSS_PRICE) or \ (position_short and bars[0 ].close - context.open_position_price >= STOP_LOSS_PRICE): print ('达到止损点,全部平仓' ) order_close_all() if position_long: if data.high.iloc[1 ] > sSetup and bars[0 ].close < sEnter: order_close_all() order_volume(symbol=context.mainContract, volume=10 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ("多头持仓,当日内最高价超过观察卖出价后跌破反转卖出价: 反手做空" ) context.open_position_price = bars[0 ].close elif position_short: if data.low.iloc[1 ] < bSetup and bars[0 ].close > bEnter: order_close_all() order_volume(symbol=context.mainContract, volume=10 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ("空头持仓,当日最低价低于观察买入价后超过反转买入价: 反手做多" ) context.open_position_price = bars[0 ].close if context.now.hour == 14 and context.now.minute == 59 : order_close_all() print ('全部平仓' ) context.count = 0 if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2019-10-1 15:00:00' , backtest_end_time='2020-04-16 15:00:00' , backtest_initial_cash=1000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
布林线均值回归(股票)
原理
提起布林线均值回归策略,就不得不提布林带这个概念。布林带是利用统计学中的均值和标准差联合计算得出的,分为均线,上轨线和下轨线。布林线均值回归策略认为,标的价格在上轨线和下轨线围成的范围内浮动,即使短期内突破上下轨,但长期内仍然会回归到布林带之中。因此,一旦突破上下轨,即形成买卖信号。
当股价向上突破上界时,为卖出信号,当股价向下突破下界时,为买入信号。
BOLL 线的计算公式:
中轨线 = N 日移动平均线
上轨线 = 中轨线 + k 标准差
下轨线 = 中轨线 - k 标准差
策略思路
第一步:根据数据计算 BOLL 线的上下界
第二步:获得持仓信号
第三步:回测分析
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 from __future__ import print_function, absolute_importfrom gm.api import *""" 本策略采用布林线进行均值回归交易。当价格触及布林线上轨的时候进行卖出,当触及下轨的时候,进行买入。 使用600004在 2009-09-17 13:00:00 到 2020-03-21 15:00:00 进行了回测。 注意: 1:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。 """ def init (context ): context.maPeriod = 26 context.stdPeriod = 26 context.stdRange = 1 context.symbol = 'SHSE.600004' context.period = max (context.maPeriod, context.stdPeriod, context.stdRange) + 1 subscribe(symbols= context.symbol, frequency='1d' , count=context.period) def on_bar (context, bars ): data = context.data(symbol=context.symbol, frequency='1d' , count=context.period, fields='close' ) bollUpper = data['close' ].rolling(context.maPeriod).mean() \ + context.stdRange * data['close' ].rolling(context.stdPeriod).std() bollBottom = data['close' ].rolling(context.maPeriod).mean() \ - context.stdRange * data['close' ].rolling(context.stdPeriod).std() pos = context.account().position(symbol=context.symbol, side=PositionSide_Long) if data.close.values[-1 ] > bollUpper.values[-1 ] and data.close.values[-2 ] < bollUpper.values[-2 ]: if pos: order_volume(symbol=context.symbol, volume=100 , side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('以市价单卖出一手' ) elif data.close.values[-1 ] < bollBottom.values[-1 ] and data.close.values[-2 ] > bollBottom.values[-2 ]: if not pos: order_volume(symbol=context.symbol, volume=100 , side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('以市价单买入一手' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2009-09-17 13:00:00' , backtest_end_time='2020-03-21 15:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=1000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
alpha 对冲(股票+期货)
原理
何为 alpha?
提到 Alpha 策略,首先要理解什么是 CAPM 模型。
CAPM 模型于 1964 年被 Willian Sharpe 等人提出。Sharpe 等人认为,假设市场是均衡的,资产的预期超额收益率就由市场收益超额收益和风险暴露决定的。如下式所示。
E ( r p ) = r f + β p ( r m − r f ) E(r_p) = r_f + \beta_p(r_m - r_f)
E ( r p ) = r f + β p ( r m − r f )
其中 r m r_m r m 为市场组合,r f r_f r f 为无风险收益率。
根据 CAPM 模型可知,投资组合的预期收益由两部分组成,一部分为无风险收益率 r f r_f r f ,另一部分为风险收益率。
CAPM 模型一经推出就受到了市场的追捧。但在应用过程中发现,CAPM 模型表示的是在均衡状态下市场的情况,但市场并不总是处于均衡状态,个股总会获得超出市场基准水平的收益,即在 CAPM 模型的右端总是存在一个 alpha 项。
为了解决这个问题,1968 年,美国经济学家迈克·詹森(Michael Jensen)提出了詹森指数来描述这个 alpha,因此又称 alpha 指数。计算方式如式 2 所示。
α p = r p − [ r f + β p ( r m − r f ) ] \alpha_p = r_p - [r_f + \beta_p(r_m - r_f)]
α p = r p − [ r f + β p ( r m − r f )]
因此,投资组合的收益可以改写成
r p = α + β p ( r m − r f ) r_p = \alpha + \beta_p(r_m - r_f)
r p = α + β p ( r m − r f )
可将投资组合的收益拆分为 alpha 收益和 beta 收益。其中 beta 的计算公式为
β = c o v ( r p , r m ) σ p σ m \beta = \frac{cov(r_p, r_m)}{\sigma_p \sigma_m}
β = σ p σ m co v ( r p , r m )
β 是由市场决定的,属于系统性风险,与投资者管理能力无关,只与投资组合与市场的关系有关。当市场整体下跌时,β 对应的收益也会随着下跌(假设 beta 为正)。alpha 收益与市场无关,是投资者自身能力的体现。投资者通过自身的经验进行选股择时,得到超过市场的收益。
什么是 alpha 对冲策略?
所谓的 alpha 对冲不是将 alpha 收益对冲掉,恰恰相反,alpha 对冲策略是将 β 收益对冲掉,只获取 alpha 收益
alpha 对冲策略将市场性风险对冲掉,只剩下 alpha 收益,整体收益完全取决于投资者自身的能力水平,与市场无关。
怎么对冲?
alpha 对冲策略常采用股指期货(指以股票价格指数 作为标的物的金融期货合约)做对冲。在股票市场上做多头,在期货市场上做股指期货空头。当股票现货市场亏损时,可以通过期货市场弥补亏损;当期货市场亏损时,可以通过股票现货市场弥补亏损。
策略要点
alpha 策略能否成功,主要包括以下几个要点:
获取到的 alpha 收益是否足够高,能否超过无风险利率以及指数.
期货和现货之间的基差变化.
期货合约的选择.
alpha 对冲只是一种对冲市场风险的方法,在创建策略时需要结合其他理论一起使用,怎样获取到较高的 alpha 收益才是决定策略整体收益的关键。
策略实现
第一步:制定一个选股策略,构建投资组合,使其同时拥有 alpha 和 beta 收益。
(本策略选取过去一天 EV/EBITDA 值并选取 30 只 EV/EBITDA 值最小且大于零的股票)
第二步:做空股指期货,将投资组合的 beta 抵消,只剩 alpha 部分。
第三步:进行回测。
企业价值倍数 = EV/EBITDA
EV 为公司价值,EBITDA 为息税折旧摊销前利润
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 from __future__ import print_function, absolute_import, unicode_literalsfrom gm.api import *''' 本策略每隔1个月定时触发计算SHSE.000300成份股的过去一天EV/EBITDA值并选取30只EV/EBITDA值最小且大于零的股票 对不在股票池的股票平仓并等权配置股票池的标的 并用相应的CFFEX.IF对应的真实合约等额对冲 回测数据为:SHSE.000300和他们的成份股和CFFEX.IF对应的真实合约 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00 注意:本策略仅供参考,实际使用中要考虑到期货和股票处于两个不同的账户,需要人为的保证两个账户的资金相同。 ''' def init (context ): schedule(schedule_func=algo, date_rule='1m' , time_rule='09:40:00' ) context.percentage_stock = 0.4 context.percentage_futures = 0.4 def algo (context ): now = context.now last_day = get_previous_trading_date(exchange='SHSE' , date=now) stock300 = get_history_constituents(index='SHSE.000300' , start_date=last_day, end_date=last_day)[0 ]['constituents' ].keys() index_futures = get_continuous_contracts(csymbol='CFFEX.IF' , start_date=last_day, end_date=last_day)[-1 ]['symbol' ] not_suspended_info = get_history_instruments(symbols=stock300, start_date=now, end_date=now) not_suspended_symbols = [item['symbol' ] for item in not_suspended_info if not item['is_suspended' ]] fin = get_fundamentals(table='trading_derivative_indicator' , symbols=not_suspended_symbols, start_date=now, end_date=now, fields='EVEBITDA' , filter ='EVEBITDA>0' , order_by='EVEBITDA' , limit=30 , df=True ) fin.index = fin.symbol positions = context.account().positions() for position in positions: symbol = position['symbol' ] sec_type = get_instrumentinfos(symbols=symbol)[0 ]['sec_type' ] if sec_type == SEC_TYPE_FUTURE and symbol != index_futures: order_target_percent(symbol=symbol, percent=0 , order_type=OrderType_Market, position_side=PositionSide_Short) print ('市价单平不在标的池的' , symbol) elif symbol not in fin.index: order_target_percent(symbol=symbol, percent=0 , order_type=OrderType_Market, position_side=PositionSide_Long) print ('市价单平不在标的池的' , symbol) percent = context.percentage_stock / len (fin.index) for symbol in fin.index: order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market, position_side=PositionSide_Long) print (symbol, '以市价单调多仓到仓位' , percent) ratio = get_history_instruments(symbols=index_futures, start_date=last_day, end_date=last_day)[0 ]['margin_ratio' ] percent = context.percentage_futures * ratio order_target_percent(symbol=index_futures, percent=percent, order_type=OrderType_Market, position_side=PositionSide_Short) print (index_futures, '以市价单调空仓到仓位' , percent) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 08:00:00' , backtest_end_time='2017-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
网格交易(期货)
原理
什么是网格交易法?
网格交易法是一种利用行情震荡进行获利的策略。在标的价格不断震荡的过程中,对标的价格绘制网格,在市场价格触碰到某个网格线时进行加减仓操作尽可能获利。
网格交易法属于左侧交易的一种。与右侧交易不同,网格交易法并非跟随行情,追涨杀跌,而是逆势而为,在价格下跌时买入,价格上涨时卖出。
怎样设计网格?
投资者可以随意设置网格的宽度和数量。既可以设置为等宽度,也可以设置为不等宽度的。设置等宽度网格可能会导致买点卖点过早,收益率较低。设置不等宽度网格能够避免这个问题,但如果行情出现不利变动,可能会错失买卖机会。
核心
网格交易主要包括以下几个核心要点:
挑选的标的最好是价格变化较大,交易较为活跃
网格交易是基于行情震荡进行获利的策略,如果标的不活跃,价格波动不大,很难触发交易。
选出网格的压力位和阻力位
确定适当的压力位和阻力位,使价格大部分时间能够在压力位和阻力位之间波动。如果压力位和阻力位设置范围过大,会导致难以触发交易;如果压力位和阻力位设置范围过小,则会频繁触发交易。
设置网格的宽度和数量
设定多少个网格以及网格的宽度可根据投资者自身喜好自行确定。
策略思路
第一步:确定价格中枢、压力位和阻力位
第二步:确定网格的数量和间隔
第三步:当价格触碰到网格线时,若高于买入价,则每上升一格卖出 m 手;若低于买入价,则每下跌一格买入 m 手。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npimport pandas as pdfrom gm.api import *''' 本策略标的为:SHFE.rb1901 价格中枢设定为:前一交易日的收盘价 从阻力位到压力位分别为:1.03 * open、1.02 * open、1.01 * open、open、0.99 * open、0.98 * open、0.97 * open 每变动一个网格,交易量变化100个单位 回测数据为:SHFE.rb1901的1min数据 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00 ''' def init (context ): context.symbol = 'SHFE.rb1901' subscribe(symbols=context.symbol, frequency='60s' ) context.volume = 1 context.last_grid = 0 context.center = history_n(symbol=context.symbol, frequency='1d' , end_time=context.now, count=1 , fields='close' )[0 ]['close' ] context.grid_change_last = [0 , 0 ] def on_bar (context, bars ): bar = bars[0 ] position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long) position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short) context.band = np.array([0.97 , 0.98 , 0.99 , 1 , 1.01 , 1.02 , 1.03 ]) * context.center grid = pd.cut([bar.close], context.band, labels=[1 , 2 , 3 , 4 , 5 , 6 ])[0 ] if np.isnan(grid): print ('价格波动超过网格范围,可适当调节网格宽度和数量' ) if context.last_grid < grid: grid_change_new = [context.last_grid,grid] if context.last_grid == 0 : context.last_grid = grid return if context.last_grid != 0 : if grid_change_new != context.grid_change_last: context.last_grid = grid context.grid_change_last = grid_change_new if position_long: order_volume(symbol=context.symbol, volume=context.volume, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('以市价单平多仓{}手' .format (context.volume)) if not position_long: order_volume(symbol=context.symbol, volume=context.volume, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('以市价单开空{}手' .format (context.volume)) if context.last_grid > grid: grid_change_new = [grid, context.last_grid] if context.last_grid == 0 : context.last_grid = grid return if context.last_grid != 0 : if grid_change_new != context.grid_change_last: context.last_grid = grid context.grid_change_last = grid_change_new if position_short: order_volume(symbol=context.symbol, volume=context.volume, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close) print ('以市价单平空仓{}手' .format (context.volume)) if not position_short: order_volume(symbol=context.symbol, volume=context.volume, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('以市价单开多{}手' .format (context.volume)) if position_short == 10 or position_long == 10 : order_close_all() print ('触发止损,全部平仓' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2018-07-01 08:00:00' , backtest_end_time='2018-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=100000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
多因子选股(股票)
原理
多因子策略是最广泛应用的策略之一。CAPM 模型的提出为股票的收益提供了解释,但随着各种市场异象的出现,使得人们发现股票存在超额收益,这种收益不能为市场因子所解释,因此,出现了多因子模型。
多因子模型最早是由 Fama-French 提出,包括三因子和五因子模型。Fama 认为,股票的超额收益可以由市场因子、市值因子和账面价值比因子共同解释。随着市场的发展,出现许多三因子模型难以解释的现象。因此,Fama 又提出了五因子模型,加入了盈利水平、投资水平因子。
此后,陆续出现了六因子模型、八因子模型等,目前多少个因子是合适的尚无定论。
Fama-French 三因子模型
Fama 等人在 CAPM 的基础上,Fama 加入了 HML 和 SMB 两个因子,提出了三因子模型,也是多因子模型的基础。
E [ R i ] − R f = β i , M K T ( E [ R M ] − R f ) + β i , S M B E [ R S M B ] + β i , H M L [ R H M L ] E[R_i] - R_f = \beta_{i,MKT}(E[R_M] - R_f) + \beta_{i,SMB}E[R_{SMB}] + \beta_{i,HML}[R_{HML}]
E [ R i ] − R f = β i , M K T ( E [ R M ] − R f ) + β i , SMB E [ R SMB ] + β i , H M L [ R H M L ]
其中 E [ R i ] E[R_i] E [ R i ] 代表股票 i 的预期收益率,R f R_f R f 代表无风险收益率,E [ R M ] E[R_M] E [ R M ] 为市场组合预期收益率,E [ R S M B ] E[R_{SMB}] E [ R SMB ] 和 E [ R H M L ] E[R_{HML}] E [ R H M L ] 分别为规模因子收益率和价值因子预期收益率
为构建价值因子和规模因子,Fama 选择 BM(账面市值比) 和市值两个指标进行双重排序,将股票分为大市值组 B 和小市值组 S;按照账面市值比将股票分为 BM 高于 70%分位数的 H 组,BM 低于 30%分位数的 L 组,BM 处于二者之间的记为 M 组。如表所示。
H
M
L
市值分组
S
S/H
S/M
S/L
市值分组
B
B/H
B/M
B/L
得到上述分组以后,就可以构建规模和价值两个因子。
S M B = 1 3 ( S / H + S / M + S / L ) − 1 3 ( B / H + B / M + B / L ) SMB = \frac{1}{3}(S/H + S/M + S/L) - \frac{1}{3}(B/H + B/M + B/L)
SMB = 3 1 ( S / H + S / M + S / L ) − 3 1 ( B / H + B / M + B / L )
H M L = 1 2 ( S / H + B / H ) − 1 2 ( S / L + B / L ) HML = \frac{1}{2}(S/H + B/H) - \frac{1}{2}(S/L + B/L)
H M L = 2 1 ( S / H + B / H ) − 2 1 ( S / L + B / L )
上述式子解释一下可以发现,规模因子是三个小市值组合的等权平均减去三个大市值组合的等权平均;价值因子是两个高 BM 组合的等权平均减去两个低 BM 组合的等权平均。
策略设计思路
在用三因子模型估算股票预期收益率时,经常会发现并非每只股票都能严格吻合式 1,大部分股票都会存在一个 alpha 截距项。当存在 alpha 截距项时,说明股票当前价格偏离均衡价格。基于此,可以设计套利策略。
alpha < 0 时,说明股票收益率低于均衡水平,股票价格被低估,应该买入。
alpha > 0 时,说明股票收益率高于均衡水平,股票价格被高估,应该卖出。
因此,可以获取 alpha 最小并且小于 0 的 10 只的股票买入开仓。
策略步骤
第一步:获取股票市值以及账面市值比数据。
第二步:将股票按照各个因子进行排序分组,分组方法如上表所示。
第三步:依据式 2 式 3,计算 SMB、HML 因子。
第四步:因子回归,计算 alpha 值。获取 alpha 最小并且小于 0 的 10 只的股票买入开仓。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npfrom gm.api import *from pandas import DataFrame''' 本策略每隔1个月定时触发,根据Fama-French三因子模型对每只股票进行回归,得到其alpha值。 假设Fama-French三因子模型可以完全解释市场,则alpha为负表明市场低估该股,因此应该买入。 策略思路: 计算市场收益率、个股的账面市值比和市值,并对后两个进行了分类, 根据分类得到的组合分别计算其市值加权收益率、SMB和HML. 对各个股票进行回归(假设无风险收益率等于0)得到alpha值. 选取alpha值小于0并为最小的10只股票进入标的池 平掉不在标的池的股票并等权买入在标的池的股票 回测数据:SHSE.000300的成份股 回测时间:2017-07-01 08:00:00到2017-10-01 16:00:00 ''' def init (context ): schedule(schedule_func=algo, date_rule='1m' , time_rule='09:40:00' ) context.date = 20 context.ratio = 0.8 context.BM_BIG = 3.0 context.BM_MID = 2.0 context.BM_SMA = 1.0 context.MV_BIG = 2.0 context.MV_SMA = 1.0 def market_value_weighted (stocks, MV, BM ): select = stocks[(stocks['NEGOTIABLEMV' ] == MV) & (stocks['BM' ] == BM)] market_value = select['mv' ].values mv_total = np.sum (market_value) mv_weighted = [mv / mv_total for mv in market_value] stock_return = select['return' ].values return_total = [] for i in range (len (mv_weighted)): return_total.append(mv_weighted[i] * stock_return[i]) return_total = np.sum (return_total) return return_total def algo (context ): last_day = get_previous_trading_date(exchange='SHSE' , date=context.now) context.stock300 = get_history_constituents(index='SHSE.000300' , start_date=last_day, end_date=last_day)[0 ]['constituents' ].keys() not_suspended = get_history_instruments(symbols=context.stock300, start_date=last_day, end_date=last_day) not_suspended = [item['symbol' ] for item in not_suspended if not item['is_suspended' ] and item['symbol' ]!= 'SHSE.601313' ] fin = get_fundamentals(table='trading_derivative_indicator' , symbols=not_suspended, start_date=last_day, end_date=last_day,fields='PB,NEGOTIABLEMV' , df=True ) fin['PB' ] = (fin['PB' ] ** -1 ) size_gate = fin['NEGOTIABLEMV' ].quantile(0.50 ) bm_gate = [fin['PB' ].quantile(0.30 ), fin['PB' ].quantile(0.70 )] fin.index = fin.symbol x_return = [] for symbol in not_suspended: close = history_n(symbol=symbol, frequency='1d' , count=context.date + 1 , end_time=last_day, fields='close' , skip_suspended=True , fill_missing='Last' , adjust=ADJUST_PREV, df=True )['close' ].values stock_return = close[-1 ] / close[0 ] - 1 pb = fin['PB' ][symbol] market_value = fin['NEGOTIABLEMV' ][symbol] if pb < bm_gate[0 ]: if market_value < size_gate: label = [symbol, stock_return, context.BM_SMA, context.MV_SMA, market_value] else : label = [symbol, stock_return, context.BM_SMA, context.MV_BIG, market_value] elif pb < bm_gate[1 ]: if market_value < size_gate: label = [symbol, stock_return, context.BM_MID, context.MV_SMA, market_value] else : label = [symbol, stock_return, context.BM_MID, context.MV_BIG, market_value] elif market_value < size_gate: label = [symbol, stock_return, context.BM_BIG, context.MV_SMA, market_value] else : label = [symbol, stock_return, context.BM_BIG, context.MV_BIG, market_value] if len (x_return) == 0 : x_return = label else : x_return = np.vstack([x_return, label]) stocks = DataFrame(data=x_return, columns=['symbol' , 'return' , 'BM' , 'NEGOTIABLEMV' , 'mv' ]) stocks.index = stocks.symbol columns = ['return' , 'BM' , 'NEGOTIABLEMV' , 'mv' ] for column in columns: stocks[column] = stocks[column].astype(np.float64) smb_s = (market_value_weighted(stocks, context.MV_SMA, context.BM_SMA) + market_value_weighted(stocks, context.MV_SMA, context.BM_MID) + market_value_weighted(stocks, context.MV_SMA, context.BM_BIG)) / 3 smb_b = (market_value_weighted(stocks, context.MV_BIG, context.BM_SMA) + market_value_weighted(stocks, context.MV_BIG, context.BM_MID) + market_value_weighted(stocks, context.MV_BIG, context.BM_BIG)) / 3 smb = smb_s - smb_b hml_b = (market_value_weighted(stocks, context.MV_SMA, 3 ) + market_value_weighted(stocks, context.MV_BIG, context.BM_BIG)) / 2 hml_s = (market_value_weighted(stocks, context.MV_SMA, context.BM_SMA) + market_value_weighted(stocks, context.MV_BIG, context.BM_SMA)) / 2 hml = hml_b - hml_s close = history_n(symbol='SHSE.000300' , frequency='1d' , count=context.date + 1 , end_time=last_day, fields='close' , skip_suspended=True , fill_missing='Last' , adjust=ADJUST_PREV, df=True )['close' ].values market_return = close[-1 ] / close[0 ] - 1 coff_pool = [] for stock in stocks.index: x_value = np.array([[market_return], [smb], [hml], [1.0 ]]) y_value = np.array([stocks['return' ][stock]]) coff = np.linalg.lstsq(x_value.T, y_value)[0 ][3 ] coff_pool.append(coff) stocks['alpha' ] = coff_pool stocks = stocks[stocks.alpha < 0 ].sort_values(by='alpha' ).head(10 ) symbols_pool = stocks.index.tolist() positions = context.account().positions() for position in positions: symbol = position['symbol' ] if symbol not in symbols_pool: order_target_percent(symbol=symbol, percent=0 , order_type=OrderType_Market, position_side=PositionSide_Long) print ('市价单平不在标的池的' , symbol) percent = context.ratio / len (symbols_pool) for symbol in symbols_pool: order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market, position_side=PositionSide_Long) print (symbol, '以市价单调多仓到仓位' , percent) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 08:00:00' , backtest_end_time='2017-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
指数增强(股票)
原理
说到指数增强,就不得不说指数。
在进行股票投资时,有一种分类方式是将投资分为主动型投资 和被动型投资 。被动型投资是指完全复制指数,跟随指数的投资方式。与被动型投资相反,主动型投资是根据投资者的知识结合经验进行主动选股,不是被动跟随指数。主动型投资者期望获得超越市场的收益,被动型投资者满足于市场平均收益率水平。
指数增强是指在跟踪指数的基础上,采用一些判断基准,将不看好的股票权重调低或平仓,将看好的股票加大仓位,以提高收益率的方法。
既然如此,我已经判断出来哪只是“好股票”,哪只是“一般”的股票,为什么不直接买入?而是要买入指数呢?
指数增强不同于其他主动投资方式,除了注重获取超越市场的收益,还要兼顾降低组合风险,注重收益的稳定性。如果判断失误,只买入选中股票而非指数会导致投资者承受巨大亏损。
和 alpha 对冲策略类似,指数增强仅仅是一个思路,怎样选择“好股”还需投资者结合自身经验判断。
本策略利用“动量”这一概念,认为过去 5 天连续上涨的股票具备继续上涨的潜力,属于强势股;过去 5 天连续下跌的股票未来会继续下跌,属于弱势股。
策略步骤
第一步:选择跟踪指数,以权重大于 0.35%的成分股为股票池。
第二步:根据个股价格动量来判断是否属于优质股,即连续上涨 5 天则为优势股;间隔连续下跌 5 天则为劣质股。
第三步:将优质股权重调高 0.2,劣质股权重调低 0.2。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npfrom gm.api import *from pandas import DataFrame''' 本策略以0.8为初始权重跟踪指数标的沪深300中权重大于0.35%的成份股. 个股所占的百分比为(0.8*成份股权重)*100%.然后根据个股是否: 1.连续上涨5天 2.连续下跌5天 来判定个股是否为强势股/弱势股,并对其把权重由0.8调至1.0或0.6 回测时间为:2017-07-01 08:50:00到2017-10-01 17:00:00 ''' def init (context ): context.ratio = 0.8 stock300 = get_history_constituents(index='SHSE.000300' , start_date='2017-06-30' , end_date='2017-06-30' )[0 ]['constituents' ] stock300_symbol = [] stock300_weight = [] for key in stock300: if (stock300[key] / 100 ) > 0.0035 : stock300_symbol.append(key) stock300_weight.append(stock300[key] / 100 ) context.stock300 = DataFrame([stock300_weight], columns=stock300_symbol, index=['weight' ]).T print ('选择的成分股权重总和为: ' , np.sum (stock300_weight)) subscribe(symbols=stock300_symbol, frequency='1d' , count=5 , wait_group=True ) def on_bar (context, bars ): for bar in bars: symbol = bar['symbol' ] position = context.account().position(symbol=symbol, side=PositionSide_Long) if not position: buy_percent = context.stock300['weight' ][symbol] * context.ratio order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Market, position_side=PositionSide_Long) print (symbol, '以市价单开多仓至仓位:' , buy_percent) else : recent_data = context.data(symbol=symbol, frequency='1d' , count=5 , fields='close' )['close' ].tolist() if all (np.diff(recent_data) > 0 ): buy_percent = context.stock300['weight' ][symbol] * (context.ratio + 0.2 ) order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Market, position_side=PositionSide_Long) print ('强势股' , symbol, '以市价单调多仓至仓位:' , buy_percent) elif all (np.diff(recent_data) < 0 ): buy_percent = context.stock300['weight' ][symbol] * (context.ratio - 0.2 ) order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Market, position_side=PositionSide_Long) print ('弱势股' , symbol, '以市价单调多仓至仓位:' , buy_percent) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 08:00:00' , backtest_end_time='2017-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
跨品种套利(期货)
原理
什么是跨品种套利?
当两个合约有很强的相关性时,可能存在相似的变动关系,两种合约之间的价差会维持在一定的水平上。当市场出现变化时,两种合约之间的价差会偏离均衡水平。此时,可以买入其中一份合约同时卖出其中一份合约,当价差恢复到正常水平时平仓,获取收益。
跨品种套利有以下几个特点:
套利的两种资产必须有一定的相关性。
两种合约标的不同,到期时间相同。
两种资产之间的价差呈现一定规律。
怎样确定合约之间有相关性?
最常用的方法是利用 EG 两步法对两个序列做协整检验,判断两个序列是否平稳。只有单整阶数相同,二者才有可能存在一定的关系。
策略设计
传统利用价差进行跨品种套利的方法是计算出均值和方差,设定开仓、平仓和止损阈值。当新的价格达到阈值时,进行相应的开仓和平仓操作。
应该怎样确定均值?
均值的选取主要有两种方法,第一种方法是固定均值。先历史价格计算相应的阈值(比如利用 2017 年 2 月-2017 年 6 月的数据计算阈值,在 2019 年 7 月进行套利),再用最新价差进行比较,会发现前后均值差异很大。
因此,常用变动的均值设定阈值。即用过去 N 天两个标的之间差值的均值和方差。
策略思路
第一步:选择相关性较高的两个合约,本例选择大商所的焦炭和焦煤。
第二步:以过去 30 个的 1d 频率 bar 的均值正负 0.75 个标准差作为开仓阈值,以正负 2 个标准差作为止损阈值。
第三步:最新价差上穿上界时做空价差,回归到均值附近平仓;下穿下界时做多价差,回归到均值附近平仓。设定止损点,触发止损点则全部平仓。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 from __future__ import print_function, absolute_import, unicode_literalsfrom gm.api import *import numpy as np''' 本策略首先滚动计算过去30个1min收盘价的均值,然后用均值加减2个标准差得到布林线. 若无仓位,在最新价差上穿上轨时做空价差;下穿下轨时做多价差 若有仓位则在最新价差回归至上下轨水平内时平仓 回测数据为:DCE.j1901和DCE.jm1901的1min数据 回测时间为:2018-02-01 08:00:00到2018-12-31 08:00:00 ''' def init (context ): context.symbol = ['DCE.j1901' , 'DCE.jm1901' ] subscribe(symbols=context.symbol, frequency='1d' , count=11 , wait_group=True ) def on_bar (context, bars ): j_close = context.data(symbol=context.symbol[0 ],frequency='1d' ,fields='close' ,count=31 ).values jm_close = context.data(symbol=context.symbol[1 ],frequency='1d' ,fields='close' ,count=31 ).values new_price = j_close[-1 ] - jm_close[-1 ] spread_history = j_close[:-2 ] - jm_close[:-2 ] context.spread_history_mean = np.mean(spread_history) context.spread_history_std = np.std(spread_history) context.up = context.spread_history_mean + 0.75 * context.spread_history_std context.down = context.spread_history_mean - 0.75 * context.spread_history_std context.up_stoppoint = context.spread_history_mean + 2 * context.spread_history_std context.down_stoppoint = context.spread_history_mean - 2 * context.spread_history_std position_jm_long = context.account().position(symbol=context.symbol[0 ], side=1 ) position_jm_short = context.account().position(symbol=context.symbol[0 ], side=2 ) if not position_jm_short and not position_jm_long: if new_price > context.up: print ('做空价差组合' ) order_volume(symbol=context.symbol[0 ],side=OrderSide_Sell,volume=1 ,order_type=OrderType_Market, position_effect=1 ) order_volume(symbol=context.symbol[1 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Open) if new_price < context.down: print ('做多价差组合' ) order_volume(symbol=context.symbol[0 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Open) order_volume(symbol=context.symbol[1 ], side=OrderSide_Sell, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Open) if position_jm_long: if new_price >= context.spread_history_mean: print ('价差回归到均衡水平,平仓' ) order_volume(symbol=context.symbol[0 ], side=OrderSide_Sell, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) order_volume(symbol=context.symbol[1 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) if new_price < context.down_stoppoint: print ('价差超过止损点,平仓止损' ) order_volume(symbol=context.symbol[0 ], side=OrderSide_Sell, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) order_volume(symbol=context.symbol[1 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) if position_jm_short: if new_price <= context.spread_history_mean: print ('价差回归到均衡水平,平仓' ) order_volume(symbol=context.symbol[0 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) order_volume(symbol=context.symbol[1 ], side=OrderSide_Sell, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) if new_price > context.up_stoppoint: print ('价差超过止损点,平仓止损' ) order_volume(symbol=context.symbol[0 ], side=OrderSide_Buy, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) order_volume(symbol=context.symbol[1 ], side=OrderSide_Sell, volume=1 , order_type=OrderType_Market, position_effect=PositionEffect_Close) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2018-02-01 08:00:00' , backtest_end_time='2018-12-31 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=2000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
日内回转交易(股票)
原理
日内回转交易
日内回转交易,顾名思义就是在一天内完成“买”和“卖”两个相反方向的操作(可一次也可多次),也就是“T+0”交易。
日内回转可用于股票和期货。其中期货采用“T+0”交易制度,可以直接进行日内回转交易。由于 A 股采用的是“T+1”交易制度,无法直接进行日内回转交易,需要先配置一定的底仓再进行回转交易。
股票的日内回转交易
怎样对股票进行日内回转交易?
首先,在正式交易的前一个交易日配置一定的底仓。以 500 股为例,记做 total = 500。
然后开始正式的日内回转交易。
配置底仓的作用是利用替代法实现“T+0”。由于当天买入的股票当天不能卖出,但底仓是可以卖出的,用底仓替代新买入的股票进行卖出操作。假设在第二个交易日发生了 1 次买入,5 次卖出交易,每次交易买卖数量为 100 股。利用 turnaround = [0,0]变量记录每次交易的数量,也是当天收盘时需要回转的记录。其中第一个数据表示当日买入数量,第二个数据表示当日卖出数量。下表为单个交易日的买卖信号。
信号方向
数量
交易记录
剩余可回转的数量
总仓位
买
100
[100,0]
500
600
卖
100
[100,100]
400
500
卖
100
[100,200]
300
400
卖
100
[100,300]
200
300
卖
100
[100,400]
100
200
假设在表的最后再加一个卖出信号是否可行?
答案是不可行 。
因为如果再加一个卖出信号,需要回转的股票数量变为[100,500],即开多 100 股,开空 500 股。这就意味着在当天收盘之前,需要卖出 100 股,再买入 500 股进行回转。这个交易日内已经出现 5 次卖出信号,底仓的 500 股已经全部卖出,仅有 100 股今日买入的仓位,这部分股票是不能当日卖出的。所以,不能再添加卖出信号。
因此,在判断买入或卖出信号是否能执行时,隐含一个判断条件。即:
每次交易的数量 + 当日买入的数量(turnaround 的第一位)< 底仓数量(以卖出信号为例)。
MACD 指标简介
MACD 又称“异移动平均线”,是根据双指数移动平均线发展而来。由快的指数(常 12)减去慢的指数(常 26)得到 DIF,再用 2×(快线 DIF-DIF 的 9 日加权移动均线 DEA)得到 MACD 柱。
DIF 的计算方法为: DIF = 当天的 12 日指数移动平均值 - 当天的 26 日指数应对平均值。
策略思路
第一步:设置变量
context.first:底仓配置信号,0 表示未配置底仓;1 表示配置底仓。
context.trade_n:每次交易数量。
context.day:用来获取前一交易日的时间和最新交易日的时间,第一位是最新交易日,第二位是前一交易日。当二者不同时,意味着新的一天,需要初始化其他变量。
context.ending:开始回转信号,0 表示未触发;1 表示已触发。
context.turnaround:当日买卖股票操作记录,也是回转记录。第一位代表买入股数,第二位代表卖出股数。
第二步:计算 MACD 指标,设计交易信号
当 MACD 小于 0 时,买入对应股票 100 手;
当 MACD 大于 0 时,卖出对应股票 100 手;
第三步:接近收盘时,全部回转
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 from __future__ import print_function, absolute_import, unicode_literalsfrom gm.api import *''' 本策略首先买入SHSE.600000股票10000股 随后根据60s的数据计算MACD(12,26,9), 在MACD>0的时候买入100股;在MACD<0的时候卖出100股 但每日操作的股票数不超过原有仓位,并于收盘前把仓位调整至开盘前的仓位 回测数据为:SHSE.600000的60s数据 回测时间为:2017-09-01 08:00:00到2017-10-01 16:00:00 ''' def init (context ): context.symbol = 'SHSE.600000' context.first = 0 subscribe(symbols=context.symbol, frequency='60s' , count=35 ) context.trade_n = 100 context.day = [0 , 0 ] context.ending = 1 def on_bar (context, bars ): bar = bars[0 ] if context.first == 0 : context.total = 10000 order_volume(symbol=context.symbol, volume=context.total, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print (context.symbol, '以市价单开多仓10000股' ) context.first = 1. day = bar.bob.strftime('%Y-%m-%d' ) context.day[-1 ] = int (day[-2 :]) context.turnaround = [0 , 0 ] return day = bar.bob.strftime('%Y-%m-%d %H:%M:%S' ) context.day[0 ] = bar.bob.day if context.day[0 ] != context.day[-1 ]: context.ending = 0 context.turnaround = [0 , 0 ] if context.ending == 1 : return if context.total >= 0 : symbol = bar['symbol' ] recent_data = context.data(symbol=symbol, frequency='60s' , count=35 , fields='close' ) macd = talib.MACD(recent_data['close' ].values)[0 ][-1 ] if macd > 0 : if context.turnaround[0 ] + context.trade_n < context.total: context.turnaround[0 ] += context.trade_n order_volume(symbol=context.symbol, volume=context.trade_n, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print (symbol, '市价单开多仓' , context.trade_n, '股' ) elif macd < 0 : if context.turnaround[1 ] + context.trade_n < context.total: context.turnaround[1 ] += context.trade_n order_volume(symbol=context.symbol, volume=context.trade_n, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print (symbol, '市价单开空仓' , context.trade_n, '股' ) if day[11 :16 ] == '14:55' or day[11 :16 ] == '14:57' : position = context.account().position(symbol=context.symbol, side=PositionSide_Long) if position['volume' ] != context.total: order_target_volume(symbol=context.symbol, volume=context.total, order_type=OrderType_Market, position_side=PositionSide_Long) print ('市价单回转仓位操作...' ) context.ending = 1 context.day[-1 ] = context.day[0 ] if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-09-01 08:00:00' , backtest_end_time='2017-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=2000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
跨期套利(期货)
原理
什么是跨期套利?
跨期套利是指在同益市场利用标的相同、交割月份不同的商品期货合约进行长短期套利的策略。跨期套利本质上是一种风险对冲,当价格出现单方向变动时,单边投机者要承担价格反向变动的风险,而跨期套利过滤了大部分的价格波动风险,只承担价差反向变动的风险。
跨期套利相较于跨品种套利而言更复杂一些。跨期套利分为牛市套利、熊市套利、牛熊交换套利。每种套利方式下还有正向套利和反向套利。不管是哪种套利方式,其核心都是认为“价差会向均值回归”。因此,在价差偏离均值水平时,按照判断买入被低估的合约,卖出被高估的合约。
套利方法
价差(近-远)
未来价
原理
操作
偏大
上涨/下跌
近月增长 > 远月增长
买近卖远
偏大
上涨/下跌
近月下跌 < 远月下跌
买近卖远
偏小
上涨/下跌
近月增长 < 远月增长
卖近买远
偏小
上涨/下跌
近月下跌 > 远月下跌
卖近买远
协整检验
要想判断两个序列之间是否存在关系,需要对序列进行协整检验。
两个序列都为单整序列,残差序列也平稳,说明二者之间存在长期稳定的均衡关系。
策略步骤
第一步:选择同一标的不同月份的合约,本策略以豆粕为例。
第二步:计算价差的上下轨。
第三步:设计信号。价差上穿上轨,买近卖远;价差下穿下轨,卖近买远。
价差达到止损点时平仓,价差回归到均值附近时平仓。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npfrom gm.api import *''' 通过计算两个真实价格序列回归残差的0.9个标准差上下轨,并在价差突破上轨的时候做空价差,价差突破下轨的时候做多价差 并在回归至标准差水平内的时候平仓 回测数据为:DCE.m1801和DCE.m1805的1min数据 回测时间为:2017-09-25 08:00:00到2017-10-01 15:00:00 ''' def init (context ): context.goods = ['DCE.m1801' , 'DCE.m1805' ] subscribe(symbols=context.goods, frequency='1d' , count=31 , wait_group=True ) def on_bar (context, bars ): close_1801 = context.data(symbol=context.goods[0 ], frequency='1d' , count=31 , fields='close' )['close' ].values close_1805 = context.data(symbol=context.goods[1 ], frequency='1d' , count=31 , fields='close' )['close' ].values spread = close_1801[:-2 ] - close_1805[:-2 ] spread_new = close_1801[-1 ] - close_1805[-1 ] up = np.mean(spread) + 0.75 * np.std(spread) down = np.mean(spread) - 0.75 * np.std(spread) up_stop = np.mean(spread) + 2 * np.std(spread) down_stop = np.mean(spread) - 2 * np.std(spread) position1801_long = context.account().position(symbol = context.goods[0 ],side =PositionSide_Long) position1801_short = context.account().position(symbol = context.goods[0 ],side =PositionSide_Short) if not position1801_short and not position1801_long: if spread_new > up: order_volume(symbol=context.goods[0 ], volume=1 , order_type=OrderType_Market, side=OrderSide_Buy, position_effect=PositionEffect_Open) order_volume(symbol=context.goods[1 ], volume=1 , order_type=OrderType_Market, side=OrderSide_Sell, position_effect=PositionEffect_Open) print ('上穿上轨,买近卖远' ) if spread_new < down: order_volume(symbol=context.goods[0 ], volume=1 , order_type=OrderType_Market, side=OrderSide_Sell, position_effect=PositionEffect_Open) order_volume(symbol=context.goods[1 ], volume=1 , order_type=OrderType_Market, side=OrderSide_Buy, position_effect=PositionEffect_Open) print ('下穿下轨,卖近买远' ) if position1801_long: if spread_new <= np.mean(spread): order_close_all() print ('价差回归,平仓' ) if spread_new > up_stop: order_close_all() print ('达到止损点,全部平仓' ) if position1801_short: if spread_new >= np.mean(spread): order_close_all() print ('价差回归,平全部仓' ) if spread_new < down_stop: order_close_all() print ('达到止损点,全部平仓' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 08:00:00' , backtest_end_time='2017-12-31 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=2000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
海龟交易法(期货)
原理
起源
海龟交易思想起源于上世纪八十年代的美国。理查德丹尼斯与好友比尔打赌,主题是一个成功的交易员是天生的还是后天的。理查德用十年时间证明了通过日常系统培训,交易员可以通过后天培训成为一名优秀的交易者。这套培训系统就是海龟交易系统。
海龟交易系统是一个完整的、机械的交易思想,可以系统地完成整个交易过程。它包括了买卖什么、头寸规模、何时买卖、何时退出等一系列交易策略,是一个趋势交易策略。它最显著的特点是捕捉中长期趋势,力求在短期内获得最大的收益。
建仓资金
海龟交易法将建仓资金按照一定比例划分为若干个小部分,每次建仓头寸和加仓规模都与波动量 N(又称平均真实波动振幅 average true range ATR)有关。ATR 是日内指数最大波动的平均振幅,由当日最高、最低价和上一交易日的收盘价决定。
ATR
T R = m a x ( H − L , H − P D C , P D C − L ) TR = max(H - L, H - PDC, PDC - L)
TR = ma x ( H − L , H − P D C , P D C − L )
其中 PDC 是前一交易日的收盘价,ATR 就是 TR 在 N 天内的均值。
价值波动量
利用 N 值来体现价值波动量 DV:DV = N * 合约每点价值
其中每点代表的价值量是指每一个指数点数所代表的价格。
每一次开仓交易合约数 unit 的确定是将总资产的 1%除以 DV 得到。
U n i t = 总资产的 1 % D V Unit = \frac{总资产的1\%}{DV}
U ni t = D V 总资产的 1%
入市信号
海龟交易法使用的是以一个理查德唐奇安的通道突破系统为基础的入市系统。唐奇安通道分为系统一和系统二,对应短期突破和中长期突破。其中,短期突破系统是以 20 日(最高价或最低价)突破为基础,当价格突破 20 日价格即为入市信号;中长期系统是当盘中价格突破过去 55 日价格为入市信号。
加仓和止损
海龟交易法的加仓规则是当捕捉到入市信号后建立第一个交易单位的头寸,市价继续向盈利方向突破 1/2N 时加仓。
止损位为 2N,同加仓一样采用平均真实振幅 N 值为止损单位。每加仓一次,止损位就提高 1/2N。
止盈
短期:多头头寸在突破过去 10 日最低价处止盈离市,空头头寸在突破过去 10 日最高价处止盈离市。
中长期:多头头寸在突破过去 20 日最低价处止盈离市,空头头寸在突破过去 20 日最高价处止盈离市。
策略思路
第一步:获取历史数据,计算唐奇安通道和 ATR
第二步:当突破唐奇安通道时,开仓。
第三步:计算加仓和止损信号。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npimport pandas as pdfrom gm.api import *''' 以短期为例:20日线 第一步:获取历史数据,计算唐奇安通道和ATR 第二步:当突破唐奇安通道时,开仓。 第三步:计算加仓和止损信号。 ''' def init (context ): context.n = 20 context.symbol = 'DCE.i2012' context.ratio = 0.8 subscribe(symbols=context.symbol, frequency='60s' , count=2 ) time = context.now.strftime('%H:%M:%S' ) if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00' : algo(context) schedule(schedule_func=algo, date_rule='1d' , time_rule='09:00:00' ) schedule(schedule_func=algo, date_rule='1d' , time_rule='21:00:00' ) def algo (context ): if context.mode == 2 : data = history_n(symbol=context.symbol, frequency='1d' , count=context.n+1 , end_time=context.now, fields='close,high,low,bob' , df=True ) tr_list = [] for i in range (0 , len (data)-1 ): tr = max ((data['high' ].iloc[i] - data['low' ].iloc[i]), data['close' ].shift(-1 ).iloc[i] - data['high' ].iloc[i], data['close' ].shift(-1 ).iloc[i] - data['low' ].iloc[i]) tr_list.append(tr) context.atr = int (np.floor(np.mean(tr_list))) context.atr_half = int (np.floor(0.5 * context.atr)) context.don_open = np.max (data['high' ].values[-context.n:]) context.don_close = np.min (data['low' ].values[-context.n:]) if context.mode == 1 : data = history_n(symbol=context.symbol, frequency='1d' , count=context.n, end_time=context.now, fields='close,high,low' , df=True ) current_data = current(symbols=context.symbol) tr_list = [] for i in range (1 , len (data)): tr = max ((data['high' ].iloc[i] - data['low' ].iloc[i]), data['close' ].shift(-1 ).iloc[i] - data['high' ].iloc[i], data['close' ].shift(-1 ).iloc[i] - data['low' ].iloc[i]) tr_list.append(tr) tr_new = max ((current_data[0 ]['high' ] - current_data[0 ]['low' ]), data['close' ].iloc[-1 ] - current_data[0 ]['high' ], data['close' ].iloc[-1 ] - current_data[0 ]['low' ]) tr_list.append(tr_new) context.atr = int (np.floor(np.mean(tr_list))) context.atr_half = int (np.floor(0.5 * context.atr)) context.don_open = np.max (data['high' ].values[-context.n:]) context.don_close = np.min (data['low' ].values[-context.n:]) context.long_add_point = context.don_open + context.atr_half context.long_stop_loss = context.don_open - context.atr_half context.short_add_point = context.don_close - context.atr_half context.short_stop_loss = context.don_close + context.atr_half def on_bar (context, bars ): symbol = bars[0 ]['symbol' ] recent_data = context.data(symbol=context.symbol, frequency='60s' , count=2 , fields='close,high,low' ) close = recent_data['close' ].values[-1 ] position_long = context.account().position(symbol=symbol, side=PositionSide_Long) position_short = context.account().position(symbol=symbol, side=PositionSide_Short) if not position_long and not position_short: if close > context.don_open: order_volume(symbol=symbol, side=OrderSide_Buy, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('开多仓atr' ) if close < context.don_close: order_volume(symbol=symbol, side=OrderSide_Sell, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('开空仓atr' ) if position_long: if close > context.long_add_point: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Open) print ('继续加仓0.5atr' ) context.long_add_point += context.atr_half context.long_stop_loss += context.atr_half if close < context.long_stop_loss: volume_hold = position_long['volume' ] if volume_hold >= context.atr_half: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) else : order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Sell, order_type=OrderType_Market,position_effect=PositionEffect_Close) print ('平多仓0.5atr' ) context.long_add_point -= context.atr_half context.long_stop_loss -= context.atr_half if position_short: if close < context.short_add_point: order_volume(symbol = symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open) print ('继续加仓0.5atr' ) context.short_add_point -= context.atr_half context.short_stop_loss -= context.atr_half if close > context.short_stop_loss: volume_hold = position_short['volume' ] if volume_hold >= context.atr_half: order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close) else : order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Close) print ('平空仓0.5atr' ) context.short_add_point += context.atr_half context.short_stop_loss += context.atr_half if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2020-02-15 09:15:00' , backtest_end_time='2020-09-01 15:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=1000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
做市商交易(期货)
原理
做市商制度
做市商制度是一种报价驱动制度。做市商根据自己的判断,不断地报出买入报价和卖出报价,以自有资金与投资者进行交易。做市商获取的收益就是买入价和卖出价的价差。
假设做市商以 6344 卖出一手合约,同时以 6333 买入一手合约。如果都成交,做市商可净获利 11 个点。但如果当时合约价格持续走高或走低,做市商没有对手方能够成交,这时就不得不提高自己的买价或降低自己的卖价进行交易,做市商就会亏损。因此,做市商并不是稳赚不赔的。
策略思路
第一步:订阅 tick 数据(只有最近 3 个月数据)
第二步:获取 tick 数据中的卖一和买一价格。
第三步:以买一价格开多,以卖一价格开空。以卖一价格平多,以买一价格平空。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 from __future__ import print_function, absolute_import, unicode_literalsfrom gm.api import *''' 本策略通过不断对DCE.y2105进行: 买(卖)一价现价单开多(空)仓和卖(买)一价平多(空)仓来做市 并以此赚取差价 回测数据为:DCE.y2105的tick数据 回测时间为:2021-01-29 11:25:00到2021-01-29 11:30:00 需要特别注意的是:本平台对于回测对限价单固定完全成交,本例子 仅供参考. 敬请通过适当调整回测参数 1.backtest_commission_ratio回测佣金比例 2.backtest_slippage_ratio回测滑点比例 3.backtest_transaction_ratio回测成交比例 以及优化策略逻辑来达到更贴近实际的回测效果 目前只支持最近三个月的tick数据,回测时间和标的需要修改 ''' def init (context ): context.symbol = 'DCE.y2105' subscribe(symbols=context.symbol, frequency='tick' ) def on_tick (context, tick ): quotes = tick['quotes' ][0 ] position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long) position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short) if not position_long: price = quotes['bid_p' ] print ('买一价为: ' , price) order_target_volume(symbol=context.symbol, volume=1 , price=price, order_type=OrderType_Limit, position_side=PositionSide_Long) print ('DCE.y2105开限价单多仓1手' ) else : price = quotes['ask_p' ] print ('卖一价为: ' , price) order_target_volume(symbol=context.symbol, volume=0 , price=price, order_type=OrderType_Limit, position_side=PositionSide_Long) print ('DCE.y2105平限价单多仓1手' ) if not position_short: price = quotes['ask_p' ] print ('卖一价为: ' , price) order_target_volume(symbol=context.symbol, volume=1 , price=price, order_type=OrderType_Limit, position_side=PositionSide_Short) print ('DCE.y2105卖一价开限价单空仓' ) else : price = quotes['bid_p' ] print ('买一价为: ' , price) order_target_volume(symbol=context.symbol, volume=0 , price=price, order_type=OrderType_Limit, position_side=PositionSide_Short) print ('DCE.y2105买一价平限价单空仓' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 backtest_transaction_ratio回测成交比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2021-01-20 11:25:00' , backtest_end_time='2021-01-20 11:30:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=500000 , backtest_commission_ratio=0.00006 , backtest_slippage_ratio=0.0001 , backtest_transaction_ratio=0.5 )
行业轮动(股票)
原理
行业轮动现象
在某一段时间内,某一行业或某几个行业组内股票价格共同上涨或下降的现象。
行业轮动策略是根据行业轮动现象做成的策略,利用行业趋势进行获利的方法,属于主动交易策略。其本质是通过一段时期的市场表现,力求抓住表现较好的行业以及投资品种,选择不同时期的强势行业进行获利。
行业轮动的原因
原因 1:行业周期
行业的成长周期可以分为初创期、成长期、成熟期和衰退期,一般行业会按照这个周期运行。初创期属于行业刚刚起步阶段,风险高、收益小。成长期内风险高、收益高。处于成熟期的企业风险低、收益高。处于衰退期的企业风险低、收益低。在一段时间内,不同的行业会处于不同的行业周期,在时间维度上看会呈现行业轮动现象。
原因 2:国家政策
国家政策对我国资本市场有重大影响。我国每年的财政政策和货币政策都是市场关注的热点,货币政策和财政政策会释放出影响市场的信息,如利率。当政策释放出下调利率的信号,就为资金需求量大、项目周期长的行业缓解了压力,如房地产行业,这时对于这类行业利好,相应的股价就会上涨。
原因 3:重大事件
资本市场对于消息的反应是迅速的。根据有效市场理论,在半强式有效市场下,一切已公开的信息都会反映在股价当中。以疫情为例,消息一出迅速拉动医疗行业股价水平,带动行业增长。
行业轮动下资产配置
策略设计
部分研究表明,行业在日、月频率上会存在动量现象,在周频率上会存在反转现象,也就是行业间轮动。因此,在日和月频率上可以利用行业动量设计策略,如果是在周频率上可以利用反转效应设计策略。
(引自:武文超. 中国 A 股市场的行业轮动现象分析——基于动量和反转交易策略的检验[J]. 金融理论与实践, 2014, 000(009):111-114.)
将行业变量作为一个因子放入多因子模型中,利用多因子模型预测各个行业的周期收益率,采用滚动预测方法每次得到一个样本外预测值,根据这些预测值判断该买入哪些行业,卖出哪些行业。
(引自:高波, 任若恩. 基于主成分回归模型的行业轮动策略及其业绩评价[J]. 数学的实践与认识, 2016, 46(019):82-92.)
策略思路
策略示例采用第一种策略构建方法,利用行业动量设计策略。为了提高策略速度,以 6 个行业为例进行演示。
第一步:确定行业指数,获取行业指数收益率。
第二步:根据行业动量获取最佳行业指数。
第三步:在最佳行业中,选择最大市值的 5 支股票买入。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 from __future__ import print_function, absolute_import, unicode_literalsimport numpy as npfrom gm.api import *''' 本策略每隔1个月定时触发计算SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914 (300工业.300材料.300可选.300消费.300医药.300金融)这几个行业指数过去 20个交易日的收益率并选取了收益率最高的指数的成份股获取并获取了他们的市值数据 随后把仓位调整至市值最大的5只股票上 回测数据为:SHSE.000910.SHSE.000909.SHSE.000911.SHSE.000912.SHSE.000913.SHSE.000914和他们的成份股 回测时间为:2017-07-01 08:00:00到2017-10-01 16:00:00 ''' def init (context ): schedule(schedule_func=algo, date_rule='1m' , time_rule='09:40:00' ) context.index = ['SHSE.000910' , 'SHSE.000909' , 'SHSE.000911' , 'SHSE.000912' , 'SHSE.000913' , 'SHSE.000914' ] context.date = 20 context.ratio = 0.8 def algo (context ): today = context.now last_day = get_previous_trading_date(exchange='SHSE' , date=today) return_index = [] for i in context.index: return_index_his = history_n(symbol=i, frequency='1d' , count=context.date, fields='close,bob' , fill_missing='Last' , adjust=ADJUST_PREV, end_time=last_day, df=True ) return_index_his = return_index_his['close' ].values return_index.append(return_index_his[-1 ] / return_index_his[0 ] - 1 ) sector = context.index[np.argmax(return_index)] print ('最佳行业指数是: ' , sector) symbols = get_history_constituents(index=sector, start_date=last_day, end_date=last_day)[0 ]['constituents' ].keys() not_suspended_info = get_history_instruments(symbols=symbols, start_date=today, end_date=today) not_suspended_symbols = [item['symbol' ] for item in not_suspended_info if not item['is_suspended' ]] fin = get_fundamentals(table='tq_sk_finindic' , symbols=not_suspended_symbols, start_date=last_day, end_date=last_day, limit=5 , fields='NEGOTIABLEMV' , order_by='-NEGOTIABLEMV' , df=True ) fin.index = fin['symbol' ] percent = 1.0 / len (fin.index) * context.ratio positions = context.account().positions() for position in positions: symbol = position['symbol' ] if symbol not in fin.index: order_target_percent(symbol=symbol, percent=0 , order_type=OrderType_Market, position_side=PositionSide_Long) print ('市价单平不在标的池的' , symbol) for symbol in fin.index: order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market, position_side=PositionSide_Long) print (symbol, '以市价单调整至仓位' , percent) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 08:00:00' , backtest_end_time='2017-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
机器学习(股票)
原理
什么是机器学习?
随着计算机技术的发展,投资者不再只局限于传统投资策略,机器学习在资本市场得到广泛应用。机器学习的核心是通过机器模仿人类的思考过程以及思维习惯,通过对现有数据的学习,对问题进行预测和决策。目前,机器学习已在人脸识别、智能投顾、自然语言处理等方面得到广泛应用。
机器学习可以分为两类,一类是无监督学习,另一类是监督学习。监督学习是指按照已有的标记进行学习,即已经有准确的分类信息。比如二分类问题,一类是“好”,另一类是“不好”,这种明确地指出分类基准的问题。这类模型包括:神经网络、决策树、支持向量机等。
无监督学习是指针对未标记过的数据集进行学习。比如聚类问题,没有准确的标准说明应该聚成几类,只有相对概念。这类模型包括:K_means 聚类、层次聚类法等。
什么是支持向量机?
支持向量机是最典型的一类机器学习模型,常用于解决二分类问题。支持向量机的原理是在一个样本空间内,找到一个平面,将样本数据分为两个部分,即两个分类,这个平面就叫做超平面。
利用支持向量机预测股票涨跌
在利用支持向量机进行预测之前,先将数据集分为训练集和测试集。常用的分类方法是将数据及进行 8:2 分解,0.8 部分是训练集,0.2 部分是测试集。用训练集训练模型,再用测试集评价模型的准确率等指标。
在利用支持向量机预测时,还有很重要的一步是进行参数优化。SVM 的参数包括以下几个。
参数符号
参数说明
C
罚函数,错误项的惩罚系数,默认为 1。C 越大,对错误样本的惩罚力度越大,准确度越高但泛化能力越低(泛化能力是指拓展到测试集中的准确率)。C 越小,允许样本增加一点错误,使泛化能力提高。
Kernel
核函数,包括 linear(线型核函数)、poly(多项式核函数)、rbf(高斯核函数)、sigmod(sigmod 核函数)。
degree
当核函数选成多项式核函数时对应的阶数。
Gamma
核函数系数。
策略思路
第一步:获取原始数据,这里获取 2016-04-01 到 2017-07-30 的数据。
第二步:计算 SVM 模型的输入变量。
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 from __future__ import print_function, absolute_import, unicode_literalsfrom datetime import datetimeimport numpy as npfrom gm.api import *import systry : from sklearn import svm except : print ('请安装scikit-learn库和带mkl的numpy' ) sys.exit(-1 ) ''' 本策略选取了七个特征变量组成了滑动窗口长度为15天的训练集,随后训练了一个二分类(上涨/下跌)的支持向量机模型. 若没有仓位则在每个星期一的时候输入标的股票近15个交易日的特征变量进行预测,并在预测结果为上涨的时候购买标的. 若已经持有仓位则在盈利大于10%的时候止盈,在星期五损失大于2%的时候止损. 特征变量为:1.收盘价/均值2.现量/均量3.最高价/均价4.最低价/均价5.现量6.区间收益率7.区间标准差 训练数据为:SHSE.600000浦发银行,时间从2016-03-01到2017-06-30 回测时间为:2017-07-01 09:00:00到2017-10-01 09:00:00 ''' def init (context ): context.symbol = 'SHSE.600000' subscribe(symbols=context.symbol, frequency='60s' ) start_date = '2016-03-01' end_date = '2017-06-30' recent_data = history(context.symbol, frequency='1d' , start_time=start_date, end_time=end_date, fill_missing='last' , df=True ) days_value = recent_data['bob' ].values days_close = recent_data['close' ].values days = [] print ('准备数据训练SVM' ) for i in range (len (days_value)): days.append(str (days_value[i])[0 :10 ]) x_all = [] y_all = [] for index in range (15 , (len (days) - 5 )): start_day = days[index - 15 ] end_day = days[index] data = history(context.symbol, frequency='1d' , start_time=start_day, end_time=end_day, fill_missing='last' , df=True ) close = data['close' ].values max_x = data['high' ].values min_n = data['low' ].values amount = data['amount' ].values volume = [] for i in range (len (close)): volume_temp = amount[i] / close[i] volume.append(volume_temp) close_mean = close[-1 ] / np.mean(close) volume_mean = volume[-1 ] / np.mean(volume) max_mean = max_x[-1 ] / np.mean(max_x) min_mean = min_n[-1 ] / np.mean(min_n) vol = volume[-1 ] return_now = close[-1 ] / close[0 ] std = np.std(np.array(close), axis=0 ) features = [close_mean, volume_mean, max_mean, min_mean, vol, return_now, std] x_all.append(features) for i in range (len (days_close) - 20 ): if days_close[i + 20 ] > days_close[i + 15 ]: label = 1 else : label = 0 y_all.append(label) x_train = x_all[: -1 ] y_train = y_all[: -1 ] context.clf = svm.SVC(C=1.0 , kernel='rbf' , degree=3 , gamma='auto' , coef0=0.0 , shrinking=True , probability=False , tol=0.001 , cache_size=200 , verbose=False , max_iter=-1 , decision_function_shape='ovr' , random_state=None ) context.clf.fit(x_train, y_train) print ('训练完成!' ) def on_bar (context, bars ): bar = bars[0 ] today = bar.bob.strftime('%Y-%m-%d' ) weekday = datetime.strptime(today, '%Y-%m-%d' ).isoweekday() position = context.account().position(symbol=context.symbol, side=PositionSide_Long) if not position and weekday == 1 : data = history_n(symbol=context.symbol, frequency='1d' , end_time=today, count=15 , fill_missing='last' , df=True ) close = data['close' ].values train_max_x = data['high' ].values train_min_n = data['low' ].values train_amount = data['amount' ].values volume = [] for i in range (len (close)): volume_temp = train_amount[i] / close[i] volume.append(volume_temp) close_mean = close[-1 ] / np.mean(close) volume_mean = volume[-1 ] / np.mean(volume) max_mean = train_max_x[-1 ] / np.mean(train_max_x) min_mean = train_min_n[-1 ] / np.mean(train_min_n) vol = volume[-1 ] return_now = close[-1 ] / close[0 ] std = np.std(np.array(close), axis=0 ) features = [close_mean, volume_mean, max_mean, min_mean, vol, return_now, std] features = np.array(features).reshape(1 , -1 ) prediction = context.clf.predict(features)[0 ] if prediction == 1 : context.price = close[-1 ] order_target_percent(symbol=context.symbol, percent=0.95 , order_type=OrderType_Market, position_side=PositionSide_Long) print ('SHSE.600000以市价单开多仓到仓位0.95' ) elif position and bar.close / context.price >= 1.10 : order_close_all() print ('SHSE.600000以市价单全平多仓止盈' ) elif position and bar.close / context.price < 1.02 and weekday == 5 : order_close_all() print ('SHSE.600000以市价单全平多仓止损' ) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2017-07-01 09:00:00' , backtest_end_time='2017-10-01 09:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=10000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )
小市值(股票)
原理
因子投资
提到量化策略,总是绕不开因子投资。自 CAPM 模型提出以来,因子投资不断发展壮大,成为市场广泛关注的领域。市面上的策略很大一部分都是基于各种各样的因子创造的,因子在股票和债券市场上的应用也取得了不小的成就。
到底什么是因子投资?
20 世纪 60 年代,资本资产定价模型(Capital Asset Pricing Model)提出,揭示了资产的预期收益率(预期超额收益率)与风险之间的关系,第一次给出了资本资产定价的直观表达式。
E [ R i ] − R f = β i ( E [ R M ] − R f ) E[R_i] - R_f = \beta_i(E[R_M] - R_f)
E [ R i ] − R f = β i ( E [ R M ] − R f )
其中 R m R_m R m 表示市场预期收益率,β 表示市场的风险暴露。
该公式指出资产的预期超额收益率由资产对市场风险的暴露大小决定,也就是说,资产的超额预期收益率可以完全由市场因子解释。
随着市场异象不断出现,人们发现资产的收益率并非只由市场因子决定,还受到其他因子的影响。1976 年,Ross 提出了无套利定价理论,构建了多因子定价模型,表达式为:
E [ R i ε ] = β i λ + α i E[R_i^{\varepsilon}] = \beta_i \lambda + \alpha_i
E [ R i ε ] = β i λ + α i
其中 λ 是因子预期收益率,β 是对应的因子暴露,α 表示误差项。
该公式指出资产的预期收益率是由一系列因子及其因子暴露加上误差项决定的。而因子投资的目的就是寻找到能够解释资产收益率的因子。
既然资产预期收益率是由众多因子决定的,什么是因子?怎么寻找因子?
根据《因子投资方法与实践》一书中的定义,“一个因子描述了众多资产共同暴露的某种系统性风险,该风险是资产收益率背后的驱动力,因子收益率正式这种系统性风险的风险溢价或风险补偿,它是这些资产的共性收益。”翻译过来就是,想要作为因子,必须能够解释多个资产的收益情况,并且能够带来正收益。
再来看式 2,可以发现,资产预期收益率是由两部分组成的,除了 λ 以外,还有一个 α 项,称之为误差项,表示资产预期收益率中 λ 无法解释的部分。α 的出现可能有以下两个原因,一种是因为模型设定错误,右侧遗漏了重要的因子;一种是由于选用样本数据可能存在一定偏差,导致在该样本数据下出现了 α 项。
为了确定 α 的出现是哪种原因,需要利用统计检验进行判断。
如果 α 显著为零,说明 α 的出现只是偶然,并不能说明定价模型存在错误;
如果 α 显著不为零,说明资产预期收益里尚存在定价模型未能完全解释的部分。这种情况,就成为异象。
因此,因子大体上可分为两种,一种是定价因子,也就是 λ 部分;一种是异象因子,也就是 α 部分。
规模因子
1981 年 Banz 基于纽交所长达 40 年的数据发现,小市值股票月均收益率比其他股票高 0.4%。其背后的原因可能是投资者普遍不愿意持有小公司股票,使得这些小公司价格普遍偏低,甚至低于成本价,因此会有较高的预期收益率。由此产生了小市值策略,即投资于市值较小的股票。市值因子也被纳入进大名鼎鼎的 Fama 三因子模型和五因子模型之中。
A 股市场上规模因子是否有效?研究发现,2016 年以前,A 股市场上规模因子的显著性甚至超过了欧美等发达国家市场。但到了 2017-2018 年期间,大市值股票的表现明显优于小市值股票,使得规模因子在 A 股市场上的有效性存疑。
策略逻辑
第一步:确定调仓频率,以每月第一天调仓为例
第二步:确定股票池股票数量,这里假设有 30 支
第三步:调仓日当天获取前一个月的历史数据,并按照市值由小到大排序
第四步:买入前 30 支股票
策略代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 from __future__ import print_function, absolute_import, unicode_literalsfrom gm.api import *from datetime import timedelta""" 小市值策略 本策略每个月触发一次,计算当前沪深市场上市值最小的前30只股票,并且等权重方式进行买入。 对于不在前30的有持仓的股票直接平仓。 回测时间为:2018-07-01 08:00:00 到 2020-10-01 16:00:00 """ def init (context ): schedule(schedule_func=algo, date_rule='1m' , time_rule='09:40:00' ) context.ratio = 0.8 context.num = 30 def algo (context ): date1 = (context.now - timedelta(days=100 )).strftime("%Y-%m-%d %H:%M:%S" ) date2 = context.now.strftime("%Y-%m-%d %H:%M:%S" ) all_stock = get_instruments(exchanges='SHSE, SZSE' , sec_types=[1 ], fields='symbol, listed_date, delisted_date' , df=True ) code = all_stock[(all_stock['listed_date' ] < date1) & (all_stock['delisted_date' ] > date2) & (all_stock['symbol' ].str [5 ] != '9' ) & (all_stock['symbol' ].str [5 ] != '2' )] fundamental = get_fundamentals_n('trading_derivative_indicator' , code['symbol' ].to_list(), context.now, fields='TOTMKTCAP' , order_by='TOTMKTCAP' , count=1 , df=True ) trade_symbols = fundamental.reset_index(drop=True ).loc[:context.num - 1 , 'symbol' ].to_list() print ('本次股票池有股票数目: ' , len (trade_symbols)) percent = 1.0 / len (trade_symbols) * context.ratio positions = context.account().positions() for position in positions: symbol = position['symbol' ] if symbol not in trade_symbols: order_target_percent(symbol=symbol, percent=0 , order_type=OrderType_Market, position_side=PositionSide_Long) print ('市价单平不在标的池的' , symbol) for symbol in trade_symbols: order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market, position_side=PositionSide_Long) print (symbol, '以市价单调整至权重' , percent) if __name__ == '__main__' : ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id' , filename='main.py' , mode=MODE_BACKTEST, token='{{token}}' , backtest_start_time='2005-01-01 08:00:00' , backtest_end_time='2020-10-01 16:00:00' , backtest_adjust=ADJUST_PREV, backtest_initial_cash=1000000 , backtest_commission_ratio=0.0001 , backtest_slippage_ratio=0.0001 )