├── README.md └── 多行业协整策略.py /README.md: -------------------------------------------------------------------------------- 1 | # 基于Fama-French因子模型的配对交易策略 2 | ## 一、策略简介 3 | ### 1.配对交易 4 | 配对交易是一种统计套利策略,利用两只具有长期均衡定价关系的股票的短期价格偏离来进行套利。通过计算两只股票的相关性或者协整关系来找到股票对,本文通过协整检验来选择股票对(具有协整关系的股票对价差是均值回复的),如果两只股票具有长期均衡关系,短期的价差偏离会产生套利机会,当价差低于均衡价差时,买入价格较高的股票,同时卖出价格较高的股票;当价差高于均衡价差时,买入价格较低的股票,同时卖出价格较低的股票。但由于A股市场存在买空限制,所以本策略只进行买入平仓操作。 5 | ### 2.Fama-French五因子模型 6 | 1993年,Fama和French发表了三因子模型,认为股票的超额收益可以用以下三个因子来解释:市场风险(股票市场的期望超额收益)、市值、账面市值比来解释,随后他们发现除了上述风险,盈利因子和投资水平因子同样可以带来个股的超额收益,也就是对个股的超额收益具有解释能力。将这两个因子加入到三因子模型中,就有了后来的Fama-French五因子模型 7 | ### 3.策略思想 8 | 我们认为,同一市场内,能够具有长期均衡关系的股票对必然受相同因素的影响,我们借鉴Fama-French的思路,将其理论中的四个因子作为影响股票价格的重要因素,认为市值,公司估值,盈利水平和投资水平这四个方面相近的股票更有可能存在长期的均衡关系,这里不考虑市场因子是由于市场所有股票都会收到相同的市场因素的影响。我们在每个行业内,分别按照市值,PB(反应公司的估值水平),ROE,总资产增长率对股票进行分层筛选,选出股票池,此时股票池中有来自各个行业的一对股票对,然后对以上股票对进行协整检验,如果满足协整关系,则作为我们选定的股票对,进行交易。 9 | ## 二、模型介绍 10 | ### 1. 模型假设 11 | 在实际的价格波动构成中,训练期内具有协整关系的股票在样本外时间里,往往协整关系就被破坏了,从而造成价差无法在规定时间内收敛或者价差不收敛,并且可能会进一步扩大。因此,我们认为在一段较长时间内具有协整关系的证券可能在之后稍短的时期内,继续维持协整关系。 12 | 市值,PB(反应公司的估值水平),ROE和总资产增长率是影响公司股票价格的主要因素。 13 | ### 2. 模型框架 14 | 配对交易策略在实施过程中可以分为以下内容1)股票对的选定; 2)交易信号参数的设定;3)根据交易信号进行交易。 15 | 16 | ### 3.模型步骤 17 | 1)股票对定性与定量筛选 18 | 我们对股票组合进行了一系列的定性和定量筛选:1、剔除所有ST和*ST股票;2、提出上市时间未满1年的股票;3、按照申万三级行业分类,按照四因子筛选 19 | 20 | 2) 股票对协整检验 21 | 上面我们说明了配对交易策略是使用两只具有长期均衡关系的股票对价差短期内偏离均衡关系来进行套利的。那么我们用什么来衡量长期均衡关系是由为重要的。下面我们将介绍能够检验股票对具有长期均衡关系的协整检验。 22 | 股票价格序列一般是非平稳的时间序列,但是,当两个非平稳的时间序列之间存在协整关系,所谓两个非平稳时间序列的的协整关系是两个变量的回归后的残差是平稳的时间序列,这里平稳是假设股票价格序列和股票价格序列的长期均衡关系是:这里是协整系数,是A股票价格对B股票价格的溢价,是残差序列,若是平稳序列,则这对股票对的价格是具有协整关系,也就是具有长期均衡关系。我们首先对两只股票价格序列进行回归,得到残差序列;然后对残差序列进行ADF平稳性检验,若残差序列通过平稳性检验,证明了股票对存在长期均衡关系,可以进行配对交易策略;否则不成立。选出N对股票,每一对股票等权重分配初始资金的1/N. 23 | 24 | 3) 回归两只股票价格计算股票对价差 25 | 下面我们将股票池中每一对股票对中股票A的价格与股票B的价格进行回归,目的是算出这两个股票以怎样的线性组合构成平稳序列,计算得到的回归系数,计算价差序列:由上面的协整关系可以得知,价差序列是平稳系列,长期会存在均值回复的特征。将价差序列标准化,得到Z-score,具体计算方法为,即将价差减去价差序列的均值除以价差序列的标准差,需要说明的是,价差序列标准化以后不会影响其平稳性。当Z-score大于1,将该股票对的资金全仓买入价格较低的股票B,进行套利;当Z-score小于-1,全仓买入价格较低的股票A;当股票的价格介于-1和1之间,以p,q的比例持有A,B两只股票,在本策略中,p = q = 0.5。 26 | 27 | 4) 设置交易参数与交易信号 28 | 为了在最大程度接近真实的交易情况,我们使用日度数据来进行计算。由于经典的配对交易是通过一买一卖来进行套利的,但是A股市场存在买空限制,所以本策略只进行买入平仓操作:当股票价差向上突破阈值时,此时买入价格比较低的股票,即涨幅较小的股票,等待价差缩小,被低估的股票价格预期会上涨;当股票价差向下突破阈值时,此时买入价格比较低的股票,即跌幅较大的股票,等待价差缩小,同样预期被低估的股票价格会上涨。我们按照12个月为移动时间窗口,每隔1个月按照申万三级行业和Fama-French四因子筛选股票对,检验每一个行业筛选出的股票对的协整关系,选择协整关系显著的股票对进行交易;每一个月检验一次协整关系,如果协整关系被破坏,将原有的股票对从股票池中剔除,选择新的满足具有强协整关系并且显著的股票对进入股票池,否则继续持有。 29 | 30 | 5)止损条件 31 | 由于本策略在全行业内选股,所以我们以中证500指数为标准,设置止损线:10日内上证指数跌幅超过k%,选择平仓,这里分别设置参数k=5和8。 32 | -------------------------------------------------------------------------------- /多行业协整策略.py: -------------------------------------------------------------------------------- 1 | # 作者:heying 2 | # 策略基于joinquant平台实现 3 | # 策略基本思想:协整+多行业+止损 4 | import numpy as np 5 | import pandas as pd 6 | import statsmodels.api as sm 7 | import seaborn as sns 8 | from six import StringIO 9 | 10 | #=========================================== 11 | 12 | def initialize(context): 13 | #获取申万3级行业code 14 | g.SW3 = ['850111', 15 | '850112', 16 | '850113', 17 | '850122', 18 | '850131', 19 | '850151', 20 | '850152', 21 | '850154', 22 | '850161', 23 | '850171', 24 | '850211', 25 | '850221', 26 | '850231', 27 | '850241', 28 | '850311', 29 | '850313', 30 | '850322', 31 | '850323', 32 | '850331', 33 | '850332', 34 | '850334', 35 | '850336', 36 | '850338', 37 | '850342', 38 | '850343', 39 | '850344', 40 | '850345', 41 | '850352', 42 | '850361', 43 | '850363', 44 | '850372', 45 | '850373', 46 | '850381', 47 | '850382', 48 | '850383', 49 | '850412', 50 | '850521', 51 | '850522', 52 | '850531', 53 | '850543', 54 | '850544', 55 | '850553', 56 | '850611', 57 | '850615', 58 | '850616', 59 | '850711', 60 | '850712', 61 | '850714', 62 | '850715', 63 | '850716', 64 | '850723', 65 | '850724', 66 | '850725', 67 | '850726', 68 | '850729', 69 | '850751', 70 | '850811', 71 | '850812', 72 | '850813', 73 | '850822', 74 | '850831', 75 | '850841', 76 | '850851', 77 | '850852', 78 | '850911', 79 | '850912', 80 | '850913', 81 | '850921', 82 | '850936', 83 | '850941', 84 | '851012', 85 | '851021', 86 | '851112', 87 | '851113', 88 | '851114', 89 | '851115', 90 | '851121', 91 | '851231', 92 | '851233', 93 | '851234', 94 | '851235', 95 | '851236', 96 | '851243', 97 | '851244', 98 | '851311', 99 | '851312', 100 | '851313', 101 | '851314', 102 | '851315', 103 | '851322', 104 | '851323', 105 | '851324', 106 | '851325', 107 | '851326', 108 | '851411', 109 | '851432', 110 | '851433', 111 | '851511', 112 | '851561', 113 | '851611', 114 | '851612', 115 | '851615', 116 | '851621', 117 | '851631', 118 | '851641', 119 | '851711', 120 | '851751', 121 | '851761', 122 | '851771', 123 | '851811', 124 | '851821', 125 | '851911', 126 | '851921', 127 | '851931', 128 | '852021', 129 | '852031', 130 | '852033', 131 | '852051', 132 | '852052', 133 | '852111', 134 | '852121', 135 | '852131', 136 | '852141', 137 | '852211', 138 | '852223', 139 | '852224', 140 | '852241', 141 | '852242', 142 | '852244', 143 | '852311', 144 | '857231', 145 | '857232', 146 | '857235', 147 | '857244', 148 | '857251', 149 | '857322', 150 | '857331', 151 | '857334', 152 | '857335', 153 | '857336', 154 | '857342', 155 | '857411', 156 | '857421', 157 | '857431']#['850111','850112','850113','850121','850122','850131','850141','850151','850152','850154','850161','850171','850181','850211','850221','850222','850231','850241','850242','850311','850313','850321','850322','850323','850324','850331','850332','850333','850334','850335','850336','850337','850338','850339','850341','850342','850343','850344','850345','850351','850352','850353','850361','850362','850363','850372','850373','850381','850382','850383','850411','850412','850521','850522','850523','850531','850541','850542','850543','850544','850551','850552','850553','850611','850612','850614','850615','850616','850623','850711','850712','850713','850714','850715','850716','850721','850722','850723','850724','850725','850726','850727','850728','850729','850731','850741','850751','850811','850812','850813','850822','850823','850831','850832','850833','850841','850851','850852','850911','850912','850913','850921','850935','850936','850941','851012','851013','851014','851021','851111','851112','851113','851114','851115','851121','851122','851231','851232','851233','851234','851235','851236','851241','851242','851243','851244','851311','851312','851313','851314','851315','851316','851322','851323','851324','851325','851326','851327','851411','851421','851432','851433','851434','851435','851441','851511','851512','851521','851531','851541','851551','851561','851611','851612','851613','851614','851615','851621','851631','851641','851711','851721','851731','851741','851751','851761','851771','851781','851811','851821','851911','851921','851931','851941','852021','852031','852032','852033','852041','852051','852052','852111','852112','852121','852131','852141','852151','852211','852221','852222','852223','852224','852225','852226','852241','852242','852243','852244','852311','857221','857231','857232','857233','857234','857235','857241','857242','857243','857244','857251','857321','857322','857323','857331','857332','857333','857334','857335','857336','857341','857342','857343','857344','857411','857421','857431','858811'] 158 | g.days = 0 #记录当前交易天数 159 | set_params() 160 | set_variables() 161 | set_backtest() 162 | 163 | 164 | # ---代码块1. 设置参数 165 | def set_params(): 166 | # 基准 167 | g.benchmark = '000905.XSHG' 168 | # 调仓周期 169 | g.trade_freq = 20 170 | # 股票1默认仓位 171 | g.p = 0.5 172 | # 股票2默认仓位 173 | g.q = 0.5 174 | # 算z-score天数 175 | g.test_days = 250 176 | # 计算p值的历史天数窗口 177 | g.p_value_days = 500 178 | # 计算coef的历史天数窗口 179 | g.coef_days = 500 180 | # z_score 调仓上界 181 | g.z_score_up_bound = 1.5 182 | # z_score 调仓下界 183 | g.z_score_low_bound = -1 184 | # 是否开启止损方法 185 | g.open_stop_loss = True 186 | # 止损历史窗口天数 187 | g.stop_days = 10 188 | # 止损跌幅 189 | g.stop_percent = 0.08 190 | 191 | # ---代码块2. 设置变量 192 | def set_variables(): 193 | # 股票1 194 | g.security1 = '600720.XSHG'#'600192.XSHG' 195 | # 股票2 196 | g.security2 = '600449.XSHG'#'600088.XSHG' 197 | # 股票池1 198 | g.stock_list1 = [] 199 | # 股票池2 200 | g.stock_list2 = [] 201 | # 回归系数 202 | g.regression_ratio = 1.0036 203 | # 现在状态 204 | g.state = 'empty' 205 | 206 | # ---代码块3. 设置回测 207 | def set_backtest(): 208 | # 设置基准 209 | set_benchmark(g.benchmark) 210 | # 只报错 211 | log.set_level('order', 'error') 212 | # 真实价格 213 | set_option('use_real_price', True) 214 | # 无滑点 215 | set_slippage(FixedSlippage(0.)) 216 | 217 | 218 | # ---代码块4.计算z-score 219 | def z_test(): 220 | # 获取两支股票历史价格 221 | prices1 = np.array(attribute_history(g.security1, g.test_days, '1d', ['close']).close) 222 | prices2 = np.array(attribute_history(g.security2, g.test_days, '1d', ['close']).close) 223 | # 根据回归比例算它们的平稳序列 Y-a.X 224 | stable_series = prices2 - g.regression_ratio*prices1 225 | # 算均值 226 | series_mean = mean(stable_series) 227 | # 算标准差 228 | sigma = np.std(stable_series) 229 | # 算序列现值离均值差距多少 230 | diff = stable_series[-1] - series_mean 231 | # 返回z值 232 | return(diff/sigma) 233 | 234 | # ---代码块5.获取信号 235 | # 返回新的状态,是一个string 236 | def get_signal(): 237 | z_score = z_test() 238 | if z_score > g.z_score_up_bound: 239 | # 状态为全仓第一支 240 | return('buy1') 241 | # 如果小于负标准差 242 | if z_score < g.z_score_low_bound: 243 | # 状态为全仓第二支 244 | return('buy2') 245 | # 如果在正负标准差之间 246 | if g.z_score_low_bound <= z_score <= g.z_score_up_bound: 247 | # 如果差大于0 248 | if z_score >= 0: 249 | # 在均值上面 250 | return('side1') 251 | # 反之 252 | else: 253 | # 在均值下面 254 | return('side2') 255 | 256 | # ---代码块6.根据信号调换仓位 257 | # 输入是目标状态,输入为一个string 258 | def change_positions(new_state,context): 259 | # 总值产价值 260 | total_value = context.portfolio.portfolio_value*(1.0/len(g.stock_list1)) 261 | # print(total_value, (1.0/len(g.stock_list1))) 262 | # 如果新状态是全仓股票1 263 | if new_state == 'buy1': 264 | # 全卖股票2 265 | order_target(g.security2, 0) 266 | # 全买股票1 267 | order_value(g.security1, total_value) 268 | # 旧状态更改 269 | g.state = 'buy1' 270 | # 如果新状态是全仓股票2 271 | if new_state == 'buy2': 272 | # 全卖股票1 273 | order_target(g.security1, 0) 274 | # 全买股票2 275 | order_value(g.security2, total_value) 276 | # 旧状态更改 277 | g.state = 'buy2' 278 | # 如果处于全仓一股票状态,但是z-score交叉0点 279 | if (g.state == 'buy1' and new_state == 'side2') or (g.state == 'buy2' and new_state == 'side1'): 280 | # 按照p,q值将股票仓位调整为默认值 281 | order_target_value(g.security1, g.p * total_value) 282 | order_target_value(g.security2, g.q * total_value) 283 | # 代码里重复两遍因为要先卖后买,而我们没有特地确定哪个先哪个后 284 | order_target_value(g.security1, g.p * total_value) 285 | order_target_value(g.security2, g.q * total_value) 286 | # 状态改为‘平’ 287 | g.state = 'even' 288 | 289 | # ---代码块7,计算P值 290 | # 输入是待计算的p值的股票list 291 | 292 | def find_cointegrated_pairs(context, stock_list): 293 | delta = timedelta(days=g.p_value_days) 294 | starte_date = context.previous_date - delta 295 | prices_df = get_price(stock_list, start_date=starte_date, end_date=context.previous_date, frequency="daily", fields=["close"])["close"] 296 | #history(g.p_value_days, unit='1d', field='close', security_list=None, df=True, skip_paused=False, fq='pre') 297 | # 得到DataFrame长度 298 | n = prices_df.shape[1] 299 | # 初始化p值矩阵 300 | pvalue_matrix = np.ones((n, n)) 301 | # 抽取列的名称 302 | keys = prices_df.keys() 303 | # 初始化强协整组 304 | pairs = [] 305 | # 对于每一个i 306 | for i in range(n): 307 | # 对于大于i的j 308 | for j in range(i+1, n): 309 | # 获取相应的两只股票的价格Series 310 | stock1 = (prices_df[keys[i]]) 311 | stock2 = (prices_df[keys[j]]) 312 | # 分析它们的协整关系 313 | result = sm.tsa.stattools.coint(stock1, stock2) 314 | # 取出并记录p值 315 | pvalue = result[1] 316 | pvalue_matrix[i, j] = pvalue 317 | # 如果p值小于0.05 318 | if pvalue < 0.05: 319 | # 记录股票对和相应的p值 320 | pairs.append((keys[i], keys[j], pvalue)) 321 | else: 322 | continue 323 | # 返回结果 324 | return pvalue_matrix, pairs 325 | 326 | # ---代码块7,计算相关系数 327 | # 输入是待计算的股票对 328 | def get_coef_by_ols(context, pairs): 329 | delta = timedelta(days=g.coef_days) 330 | starte_date = context.previous_date - delta 331 | prices_df = get_price(pairs, start_date=starte_date, end_date=context.previous_date, frequency="daily", fields=["close"])["close"] 332 | stock_df1 = prices_df[pairs[0]] 333 | stock_df2 = prices_df[pairs[1]] 334 | x = stock_df1 335 | y = stock_df2 336 | X = sm.add_constant(x) 337 | result = (sm.OLS(y,X)).fit() 338 | return result.params[pairs[0]] 339 | 340 | 341 | # ---代码块7,选取行业最低p值股票对 342 | # 输入证监会行业代码 343 | def find_min_p(stocks): 344 | p,pairs = find_cointegrated_pairs(context,stocks) 345 | min_p = 100 346 | min_pair = [] 347 | for p in pairs: 348 | pv = p[2] 349 | if pv < min_p and pv >0.00001: 350 | min_p = pv 351 | min_pair = p 352 | print min_pair 353 | 354 | # ---代码块8,过滤st,停牌股 355 | # 输入待过滤的股票list 356 | def paused_filter(security_list): 357 | current_data = get_current_data() 358 | security_list = [stock for stock in security_list if not current_data[stock].paused] 359 | return security_list 360 | 361 | 362 | def delisted_filter(security_list): 363 | current_data = get_current_data() 364 | security_list = [stock for stock in security_list if not '退' in current_data[stock].name] 365 | return security_list 366 | 367 | def st_filter(security_list): 368 | current_data = get_current_data() 369 | security_list = [stock for stock in security_list if not current_data[stock].is_st] 370 | return security_list 371 | 372 | 373 | # ---代码块9,找出表现较好的行业 374 | # 输入需要选取的行业个数 375 | def get_all_low_pb_by_industry(context): 376 | industrys = g.SW3 377 | s_list1 = [] 378 | s_list2 = [] 379 | for industry in industrys: 380 | stocks = get_industry_stocks((industry), date=context.previous_date) 381 | # print(len(stocks)) 382 | stocks = st_filter(stocks) 383 | stocks = paused_filter(stocks) 384 | stocks = filter_new_stock(context,stocks) 385 | if len(stocks) < 10:#过滤股票数特别少的行业 386 | industrys.remove(industry) 387 | continue 388 | s1,s2 = get_low_pb_by_industry(context,stocks) 389 | p,pairs = find_cointegrated_pairs(context,[s1,s2]) 390 | # print(len(pairs)) 391 | if len(pairs) == 0: 392 | continue 393 | s_list1.append(s1) 394 | s_list2.append(s2) 395 | # print(len(industrys)) 396 | print(len(s_list2),len(s_list1)) 397 | return s_list1,s_list2 398 | 399 | # ---代码块10,行业中pb最低的两只 400 | # 输入需要选取的行业股票list 401 | def get_low_pb_by_industry(context,stocks): 402 | # if stocks: 403 | # # #找出最低市净率的两支股票 404 | # # df = get_fundamentals( 405 | # # query( 406 | # # valuation.code,valuation.pb_ratio,valuation.pe_ratio 407 | # # ).filter( 408 | # # valuation.code.in_(stocks), #查询股票池中的股票 409 | # # ).order_by( 410 | # # valuation.pb_ratio.asc() #ascent按升序排列 411 | # # ).limit( 412 | # # 2 #返回两支市净率最低的股票 413 | # # ) 414 | # # ) 415 | # # security1 = df['code'][0] #将市净率最低的股票号赋给security1 416 | # # security2 = df['code'][1] #将市净率次低的股票号赋给security2 417 | security1, security2 = fama_french5(stocks,context.previous_date,2) 418 | return security1, security2 419 | 420 | 421 | # ---代码块11,大盘指数止损 422 | # 输入止损点和选择方法 423 | def dp_stoploss(kernel=2, n=10, zs=0.08): 424 | if not g.open_stop_loss: 425 | return False 426 | ''' 427 | 方法1:当大盘N日均线(默认60日)与昨日收盘价构成“死叉”,则发出True信号 428 | 方法2:当大盘N日内跌幅超过zs,则发出True信号 429 | ''' 430 | # 止损方法1:根据大盘指数N日均线进行止损 431 | if kernel == 1: 432 | t = n+2 433 | hist = attribute_history('000001.XSHG', t, '1d', 'close', df=False) 434 | temp1 = sum(hist['close'][1:-1])/float(n) 435 | temp2 = sum(hist['close'][0:-2])/float(n) 436 | close1 = hist['close'][-1] 437 | close2 = hist['close'][-2] 438 | if (close2 > temp2) and (close1 < temp1): 439 | return True 440 | else: 441 | return False 442 | # 止损方法2:根据大盘指数跌幅进行止损 443 | elif kernel == 2: 444 | hist1 = attribute_history('000300.XSHG', n, '1d', 'close',df=False) 445 | #log.info("hist1['close'][-1] = " + str(hist1['close'][-1]) + " hist1['close'][0] = " + str(hist1['close'][0]) + " / = " + str(float(hist1['close'][-1]/hist1['close'][0]))) 446 | if ((1-float(hist1['close'][-1]/hist1['close'][0])) >= zs): 447 | return True 448 | else: 449 | return False 450 | 451 | 452 | # ---代码块12,清仓 453 | # 输入context账号信息 454 | def clear_all(context): 455 | for s in context.portfolio.positions: 456 | order_target(s,0) 457 | log.info("!!!stop loss " + s) 458 | 459 | # ---代码块13,famafrench5选股 460 | # 输入context账号信息 461 | def fama_french5(stock,current_date,n): 462 | last_year = pd.to_datetime(current_date)-datetime.timedelta(days=365) 463 | #获取上一年资产 464 | q = query( 465 | balance.code,balance.total_assets 466 | ).filter( 467 | valuation.code.in_(stock) 468 | ) 469 | total_assets_lag = get_fundamentals(q, date=current_date) 470 | #获取今年的财务数据 471 | q = query( 472 | balance.code,valuation.market_cap,valuation.pb_ratio,indicator.roe,balance.total_assets 473 | ).filter( 474 | valuation.code.in_(stock) 475 | ) 476 | data = get_fundamentals(q, date=last_year) 477 | #计算资产增长率 478 | data.loc[:,'total_assets'] = (data.loc[:,'total_assets']-total_assets_lag.loc[:,'total_assets'])/total_assets_lag.loc[:,'total_assets'] 479 | #删除pb小于0的 480 | data = data[data.loc[:,'pb_ratio']>=0] 481 | #fama-French排序 482 | 483 | #市账比最小的三分之一 484 | if ceil(len(data.index)/3)=2: 516 | stock1 = data[0] 517 | stock2 = data[1] 518 | else: 519 | stock1 = data[0] 520 | stock2 = data[0] 521 | return stock1,stock2 522 | 523 | # ---代码块14,过滤新股 524 | # 输入context账号信息,及待过滤的股票list 525 | def filter_new_stock(context, stock_list, n_day=360): 526 | current_data=get_all_securities(['stock']) 527 | return [stock for stock in stock_list if (context.current_dt.date() - get_security_info(stock).start_date) >= datetime.timedelta(n_day)] 528 | 529 | #============================================== 530 | # 每天开盘前调用一次 531 | def before_trading_start(context): 532 | g.days += 1 533 | if g.days % g.trade_freq != 1: 534 | return 535 | print(g.days) 536 | g.stock_list1,g.stock_list2 = get_all_low_pb_by_industry(context) 537 | # g.security1 = min_pair[0] 538 | # g.security2 = min_pair[1] 539 | # print p,pairs 540 | 541 | 542 | 543 | # 每个单位时间(如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次)调用一次 544 | def handle_data(context, data): 545 | stop = dp_stoploss(2,g.stop_days,g.stop_percent) 546 | if stop: 547 | clear_all(context) 548 | if g.days % g.trade_freq != 1: 549 | return 550 | for i in range(len(g.stock_list1)): 551 | g.security1 = g.stock_list1[i] 552 | g.security2 = g.stock_list2[i] 553 | new_state = get_signal() 554 | change_positions(new_state,context) 555 | 556 | 557 | def after_trading_end(context): 558 | g.regression_ratio = get_coef_by_ols(context,[g.security1, g.security2]) 559 | 560 | --------------------------------------------------------------------------------