量化学习平台
文章
市场宽度
背离图
登录
注册
ffscore选股加rsrs择时
策略
作者: 水滴
```python # 风险及免责提示:该策略由聚宽用户在聚宽社区分享,仅供学习交流使用。 # 原文一般包含策略说明,如有疑问请到原文和作者交流讨论。 # 原文网址:https://www.joinquant.com/post/38552 # 标题:ffscore选股加rsrs择时 # 作者:美吉姆优秀毕业代表 # 克隆自聚宽文章:https://www.joinquant.com/post/35511 # 标题:FSCORE选股与RSRS择时 # 作者:量化菜鸟鸟 # 克隆自聚宽文章:https://www.joinquant.com/post/31359 # 标题:F_Score 选股,年化80%+ # 作者:回测时间长一点 # 回测资金需要 1000000 # 导入函数库 from jqdata import * from jqfactor import get_factor_values import numpy as np import pandas as pd # 初始化函数,设定基准等等 def initialize(context): # 设定沪深300作为基准 # set_benchmark('161005.XSHE') # 开启动态复权模式(真实价格) set_option('use_real_price', True) log.set_level('order', 'error') log.set_level('system', 'error') # log.set_level('history', 'error') # log.set_level('strategy', 'error') g.stock_index = '000300.XSHG' g.stock_list = [] g.hold_num = 8 g.score = 6 #几分以上,不含 g.singl = [] g.sellrank = g.hold_num * 2 # 排名多少位之后(不含)卖出 g.buyrank = g.sellrank -1 # 排名多少位之前(含)可以买入 g.roa_cgh = 0.002 g.mincent = 0.6 #仓位偏差多少重置偏差 g.maxcent = 1.4 g.stocknum = [] #########add for rsrs ################## g.ref_stock = '000300.XSHG' #用ref_stock做择时计算的基础数据 g.N = 18 # 计算最新斜率slope,拟合度r2参考最近N天 g.M = 600 # 计算最新标准分zscore,rsrs_score参考最近M天 g.score_threshold = 0.7 # rsrs标准分指标阈值 g.threshold = 0.03 #避免指标过于灵敏反复打脸 g.mean_day = 30 #计算结束ma收盘价,参考最近mean_day g.mean_diff_day = 2 #计算初始ma收盘价,参考(mean_day + mean_diff_day)天前,窗口为mean_diff_day的一段时间 g.slope_series = initial_slope_series()[:-1] # 除去回测第一天的slope,避免运行时重复加入 ########## add end ##################### # 开盘前运行 run_monthly(before_market_open, 1,time='before_open') # run_daily(before_market_open, time='before_open') # 开盘时运行 run_daily(market_open, time='open') # 收盘后运行 # run_daily(after_market_close, time='after_close') #2-1 选股模块 def get_factor_filter_list(context,stock_list,jqfactor,sort,p1,p2): yesterday = context.previous_date score_list = get_factor_values(stock_list, jqfactor, end_date=yesterday, count=1)[jqfactor].iloc[0].tolist() df = pd.DataFrame(columns=['code','score']) df['code'] = stock_list df['score'] = score_list df.dropna(inplace=True) df.sort_values(by='score', ascending=sort, inplace=True) filter_list = list(df.code)[int(p1*len(stock_list)):int(p2*len(stock_list))] return filter_list ## 开盘前运行函数 def before_market_open(context): # g.stock_list = get_PEG(context,get_index_stocks('000300.XSHG')) # g.stock_list = g.stock_list[:g.buyrank] g.stock_list = get_index_stocks(g.stock_index) g.stock_list = kickout(context,g.stock_list) #去除不要的行业,申万一级 g.stock_list = get_factor_filter_list(context, g.stock_list, 'residual_volatility', False, 0, 0.8) #残差波动因子 g.stock_list = check_stock(context,g.stock_list) g.stock_list = roa_chg(context,g.stock_list) # print('g.stock_list',g.stock_list) # record(total = len(g.stock_list),hold_num = len(context.portfolio.positions.keys())) # g.stock_list = g.stock_list[:g.hold_num] # g.stock_list = g.stock_list[:min(g.sellrank,len(g.stock_list))] #15和结果,选小的 g.stock_list = g.stock_list[:max(g.sellrank,int(len(g.stock_list)* 0.5))] #15和结果,选小的 g.stock_list = g.stock_list #+ list('161005.XSHE') # print('初始g.stock_list',len(g.stock_list)) ## 开盘时运行函数 def market_open(context): hold_stock = context.portfolio.positions.keys() pool = g.stock_list hold_stocks = g.stock_list g.stocknum = g.hold_num current_data = get_current_data() #获取日期 total_value = context.portfolio.total_value #总共市值 maxpercent = g.maxcent / g.stocknum #允许最大占比 buycash = total_value / g.stocknum #买入金额:总市值除以持股数 # for s in hold_stock: # if s not in pool: # order_target(s,0) ###############根据择时信号看看是否需要买入############ g.timing_signal = get_timing_signal(context,g.ref_stock) # print('g.timing_signal',g.timing_signal) if g.timing_signal == 'SELL': #择时信号为空卖出所有股票 if context.portfolio.positions: for stock in context.portfolio.positions: position = context.portfolio.positions[stock] close_position(position) elif g.timing_signal == 'BUY' or g.timing_signal == 'KEEP': # 持有的股票如果不在选股池,没有涨停就卖出;如果仓位比重大于最大占比限制,就降到正常仓位比重 # if context.portfolio.available_cash > 10000: for stock in context.portfolio.positions.keys(): current_data = get_current_data() #获取日期 price1d = get_close_price(stock, 1) #获取昨天收盘价 nosell_1 = context.portfolio.positions[stock].price >= current_data[stock].high_limit #如果涨停了 sell_2 = stock not in hold_stocks #不在传入的列表里 if sell_2 and not nosell_1: #如果不传入的列表里and没有涨停 _close_position(stock) # g.selllist.append(stock) log.info('交易:清仓股票 %s '%current_data[stock].name) else: current_percent = context.portfolio.positions[stock].value / context.portfolio.total_value #个股市值除以账户总市值 if current_percent > maxpercent:# or context.portfolio.available_cash > 10000 : if order_target_value(stock, buycash): #如果超过允许的最大比值,就重置 log.info('交易:平衡降低 %s 到 %s'%(current_data[stock].name, buycash)) if len(pool) > 0 and len(context.portfolio.positions.keys()) < g.hold_num: # log.info("待买股票列表:%s" %(hold_stocks)) total_value = context.portfolio.total_value minpercent = g.mincent / g.stocknum #0.6/4 = 0.15 buycash = total_value / g.stocknum #1/4 = 0.25 for i in range(min(g.buyrank, len(hold_stocks))): free_cash = context.portfolio.available_cash # if hold_stocks[i] not in get_blacklist() and free_cash > context.portfolio.total_value / (g.stocknum * 10): # 黑名单里的股票不买,并且闲钱有这么多 if hold_stocks[i] not in get_blacklist() and free_cash > current_data[hold_stocks[i]].last_price * 110: # 黑名单里的股票不买,并且闲钱够买1手以上 if hold_stocks[i] in context.portfolio.positions.keys(): #已经持仓的 log.info("交易:已有股票 [%s]" %(current_data[hold_stocks[i]].name)) current_percent = context.portfolio.positions[hold_stocks[i]].value / context.portfolio.total_value #持仓占比 if current_percent >= minpercent:continue #如果大于最低分界,就继续 tobuy = min(free_cash, buycash - context.portfolio.positions[hold_stocks[i]].value) #取小的:可用闲钱,或者total_value / g.stocknum 减去 持有的vaule就是要补充的 g.tobuyhold = tobuy #用于调整持仓,等会rebuy用 else: #如果不在持仓的 tobuy = min(buycash, free_cash) #取小的:total_value / g.stocknum,或者 可用闲钱 # g.tobuynew = tobuy #用于新买股票,等会rebuy用 if len(context.portfolio.positions.keys()) < g.hold_num: if order_value(hold_stocks[i], tobuy) != None: # # g.buylist.append(hold_stocks[i]) #新买的票都写进list log.info("交易:新买股票 [%s] %s " %((current_data[hold_stocks[i]].name),tobuy)) else: log.info('持股数已满,停止买入') # cash = context.portfolio.total_value/g.hold_num # pool = pool[:min(g.buyrank, len(pool))] # # print('pool 二次',len(pool)) # for s in pool: # if len(context.portfolio.positions.keys()) < g.hold_num: # if s not in context.portfolio.positions.keys(): # order_target_value(s,cash) else: pass def roa_chg(context,stocklist): security_list = stocklist my_watch_date = context.current_dt one_year_ago = my_watch_date - datetime.timedelta(days=365) h = get_history_fundamentals(security_list, [indicator.adjusted_profit, # balance.total_current_assets, balance.total_assets, # balance.total_current_liability, # balance.total_non_current_liability, # cash_flow.net_operate_cash_flow, # income.operating_revenue, # income.operating_cost, ], watch_date=my_watch_date, count=5).dropna() # 连续的5个季度 def ttm_sum(x): return x.iloc[1:].sum() def ttm_avg(x): return x.iloc[1:].mean() def pre_ttm_sum(x): return x.iloc[:-1].sum() def pre_ttm_avg(x): return x.iloc[:-1].mean() def val_1(x): return x.iloc[-1] def val_2(x): if len(x.index) > 1: return x.iloc[-2] else: return nan # 扣非利润 adjusted_profit_ttm = h.groupby('code')['adjusted_profit'].apply(ttm_sum) adjusted_profit_ttm_pre = h.groupby('code')['adjusted_profit'].apply(pre_ttm_sum) # 总资产平均 total_assets_avg = h.groupby('code')['total_assets'].apply(ttm_avg) total_assets_avg_pre = h.groupby('code')['total_assets'].apply(pre_ttm_avg) # # 经营活动产生的现金流量净额 # net_operate_cash_flow_ttm = h.groupby('code')['net_operate_cash_flow'].apply(ttm_sum) # # 长期负债率: 长期负债/总资产 # long_term_debt_ratio = h.groupby('code')['total_non_current_liability'].apply(val_1) / h.groupby('code')['total_assets'].apply(val_1) # long_term_debt_ratio_pre = h.groupby('code')['total_non_current_liability'].apply(val_2) / h.groupby('code')['total_assets'].apply(val_2) # # 流动比率:流动资产/流动负债 # current_ratio = h.groupby('code')['total_current_assets'].apply(val_1) / h.groupby('code')['total_current_liability'].apply(val_1) # current_ratio_pre = h.groupby('code')['total_current_assets'].apply(val_2) / h.groupby('code')['total_current_liability'].apply(val_2) # # 营业收入 # operating_revenue_ttm = h.groupby('code')['operating_revenue'].apply(ttm_sum) # operating_revenue_ttm_pre = h.groupby('code')['operating_revenue'].apply(pre_ttm_sum) # # 营业成本 # operating_cost_ttm = h.groupby('code')['operating_cost'].apply(ttm_sum) # operating_cost_ttm_pre = h.groupby('code')['operating_cost'].apply(pre_ttm_sum) # 1. ROA 资产收益率 roa = adjusted_profit_ttm / total_assets_avg roa_pre = adjusted_profit_ttm_pre / total_assets_avg_pre # # 2. OCFOA 经营活动产生的现金流量净额/总资产 # ocfoa = net_operate_cash_flow_ttm / total_assets_avg # 3. ROA_CHG 资产收益率变化 roa_chg = roa - roa_pre # # 4. OCFOA_ROA 应计收益率: 经营活动产生的现金流量净额/总资产 -资产收益率 # ocfoa_roa = ocfoa - roa # # 5. LTDR_CHG 长期负债率变化 (长期负债率=长期负债/总资产) # ltdr_chg = long_term_debt_ratio - long_term_debt_ratio_pre # # 6. CR_CHG 流动比率变化 (流动比率=流动资产/流动负债) # cr_chg = current_ratio - current_ratio_pre # # 8. GPM_CHG 毛利率变化 (毛利率=1-营业成本/营业收入) # gpm_chg = operating_cost_ttm_pre/operating_revenue_ttm_pre - operating_cost_ttm/operating_revenue_ttm # # 9. TAT_CHG 资产周转率变化(资产周转率=营业收入/总资产) # tat_chg = operating_revenue_ttm/total_assets_avg - operating_revenue_ttm_pre/total_assets_avg_pre spo_list = list(set(finance.run_query( query( finance.STK_CAPITAL_CHANGE.code ).filter( finance.STK_CAPITAL_CHANGE.code.in_(security_list), finance.STK_CAPITAL_CHANGE.pub_date.between(one_year_ago, my_watch_date), finance.STK_CAPITAL_CHANGE.change_reason_id == 306004) )['code'])) spo_score = pd.Series(True, index = security_list) if spo_list: spo_score[spo_list] = False df_scores = pd.DataFrame(index=security_list) df_scores['roa_chg'] = roa_chg res = df_scores.loc[lambda df_scores: df_scores['roa_chg'] > g.roa_cgh].sort_values(by = 'roa_chg',ascending=False).index return res #ffscore选股 def check_stock(context,security_list): my_watch_date = context.current_dt one_year_ago = my_watch_date - datetime.timedelta(days=365) h = get_history_fundamentals(security_list, [indicator.adjusted_profit, balance.total_current_assets, balance.total_assets, balance.total_current_liability, balance.total_non_current_liability, cash_flow.net_operate_cash_flow, income.operating_revenue, income.operating_cost, ], watch_date=my_watch_date, count=5).dropna() # 连续的5个季度 def ttm_sum(x): return x.iloc[1:].sum() def ttm_avg(x): return x.iloc[1:].mean() def pre_ttm_sum(x): return x.iloc[:-1].sum() def pre_ttm_avg(x): return x.iloc[:-1].mean() def val_1(x): return x.iloc[-1] def val_2(x): if len(x.index) > 1: return x.iloc[-2] else: return nan # 扣非利润 adjusted_profit_ttm = h.groupby('code')['adjusted_profit'].apply(ttm_sum) adjusted_profit_ttm_pre = h.groupby('code')['adjusted_profit'].apply(pre_ttm_sum) # 总资产平均 total_assets_avg = h.groupby('code')['total_assets'].apply(ttm_avg) total_assets_avg_pre = h.groupby('code')['total_assets'].apply(pre_ttm_avg) # 经营活动产生的现金流量净额 net_operate_cash_flow_ttm = h.groupby('code')['net_operate_cash_flow'].apply(ttm_sum) # 长期负债率: 长期负债/总资产 long_term_debt_ratio = h.groupby('code')['total_non_current_liability'].apply(val_1) / h.groupby('code')['total_assets'].apply(val_1) long_term_debt_ratio_pre = h.groupby('code')['total_non_current_liability'].apply(val_2) / h.groupby('code')['total_assets'].apply(val_2) # 流动比率:流动资产/流动负债 current_ratio = h.groupby('code')['total_current_assets'].apply(val_1) / h.groupby('code')['total_current_liability'].apply(val_1) current_ratio_pre = h.groupby('code')['total_current_assets'].apply(val_2) / h.groupby('code')['total_current_liability'].apply(val_2) # 营业收入 operating_revenue_ttm = h.groupby('code')['operating_revenue'].apply(ttm_sum) operating_revenue_ttm_pre = h.groupby('code')['operating_revenue'].apply(pre_ttm_sum) # 营业成本 operating_cost_ttm = h.groupby('code')['operating_cost'].apply(ttm_sum) operating_cost_ttm_pre = h.groupby('code')['operating_cost'].apply(pre_ttm_sum) # 1. ROA 资产收益率 roa = adjusted_profit_ttm / total_assets_avg roa_pre = adjusted_profit_ttm_pre / total_assets_avg_pre # 2. OCFOA 经营活动产生的现金流量净额/总资产 ocfoa = net_operate_cash_flow_ttm / total_assets_avg # 3. ROA_CHG 资产收益率变化 roa_chg = roa - roa_pre # 4. OCFOA_ROA 应计收益率: 经营活动产生的现金流量净额/总资产 -资产收益率 ocfoa_roa = ocfoa - roa # 5. LTDR_CHG 长期负债率变化 (长期负债率=长期负债/总资产) ltdr_chg = long_term_debt_ratio - long_term_debt_ratio_pre # 6. CR_CHG 流动比率变化 (流动比率=流动资产/流动负债) cr_chg = current_ratio - current_ratio_pre # 8. GPM_CHG 毛利率变化 (毛利率=1-营业成本/营业收入) gpm_chg = operating_cost_ttm_pre/operating_revenue_ttm_pre - operating_cost_ttm/operating_revenue_ttm # 9. TAT_CHG 资产周转率变化(资产周转率=营业收入/总资产) tat_chg = operating_revenue_ttm/total_assets_avg - operating_revenue_ttm_pre/total_assets_avg_pre spo_list = list(set(finance.run_query( query( finance.STK_CAPITAL_CHANGE.code ).filter( finance.STK_CAPITAL_CHANGE.code.in_(security_list), finance.STK_CAPITAL_CHANGE.pub_date.between(one_year_ago, my_watch_date), finance.STK_CAPITAL_CHANGE.change_reason_id == 306004) )['code'])) spo_score = pd.Series(True, index = security_list) if spo_list: spo_score[spo_list] = False df_scores = pd.DataFrame(index=security_list) # 1 df_scores['roa'] = roa>0 # df_scores['roa'] = roa>0.024 #不是拟合不是玄学,是国债收益率 # 2 df_scores['ocfoa'] = ocfoa>0 # 3 df_scores['roa_chg'] = roa_chg>0 # df_scores['roa_chg'] = roa_chg>0.024 #不是拟合不是玄学,是国债收益率 # 4 df_scores['ocfoa_roa'] = ocfoa_roa>0 # 5 df_scores['ltdr_chg'] = ltdr_chg<=0 # 6 df_scores['cr_chg'] = cr_chg>0 # 7 df_scores['spo'] = spo_score > 0 # 8 df_scores['gpm_chg'] = gpm_chg>0 # 9 df_scores['tat_chg'] = tat_chg>0 # 合计 df_scores = df_scores.dropna() df_scores['total'] = df_scores['roa'] + df_scores['ocfoa'] + df_scores['roa_chg'] + \ df_scores['ocfoa_roa'] + df_scores['ltdr_chg'] + df_scores['cr_chg'] + \ df_scores['spo'] + df_scores['gpm_chg'] + df_scores['tat_chg'] res = df_scores.loc[lambda df_scores: df_scores['total'] > g.score].sort_values(by = 'total',ascending=False).index # 升序 asc() # 降序 desc() # 市值排序: # q = get_fundamentals(query( # valuation.code, valuation.market_cap, valuation.circulating_market_cap,valuation.pb_ratio,valuation.pe_ratio # ).filter(valuation.code.in_(res) # ).order_by(valuation.market_cap.asc()).limit(len(res))) # roe 排序: q = get_fundamentals(query( indicator.code,indicator.roe ).filter( indicator.code.in_(res) ).order_by(indicator.roe.desc()).limit(len(res))) res = list(q['code']) # record(total = len(res),hold_num = len(context.portfolio.positions.keys())) return res def kickout(context,stocklist): #钢铁 有色金属I 房地产 #金融服务 国防军工I 传媒I z_list = [] # stock_sw1 = get_industry_stocks('801040') + get_industry_stocks('801050') + get_industry_stocks('801180') + \ # get_industry_stocks('801190') + get_industry_stocks('801740') + get_industry_stocks('801760') + \ # get_industry_stocks('801950') + get_industry_stocks('801960') + get_industry_stocks('801790') stock_sw1 = get_industry_stocks('801180') for s in stocklist: if s not in stock_sw1: z_list.append(s) else: pass return z_list ############################# rsrs函数 ############################# # 这里求R2的式子有点问题,但是这个效果更好,原因未找到! def get_ols(x, y): slope, intercept = np.polyfit(x, y, 1) r2 = 1 - (sum((y - (slope * x + intercept))**2) / ((len(y) - 1) * np.var(y, ddof=1))) return (intercept, slope, r2) def initial_slope_series(): data = attribute_history(g.ref_stock, g.N + g.M, '1d', ['high', 'low']) return [get_ols(data.low[i:i+g.N], data.high[i:i+g.N])[1] for i in range(g.M)] # 因子标准化 def get_zscore(slope_series): mean = np.mean(slope_series) std = np.std(slope_series) return (slope_series[-1] - mean) / std # 只看RSRS因子值作为买入、持有和清仓依据,前版本还加入了移动均线的上行作为条件 def get_timing_signal(context,stock): g.mean_diff_day = 5 # 30+5 天,避免指标过于灵敏反复打脸 close_data = attribute_history(g.ref_stock, g.mean_day + g.mean_diff_day, '1d', ['close']) high_low_data = attribute_history(g.ref_stock, g.N, '1d', ['high', 'low']) # 这两句同上面的功能相同,愿意测试的可以试试,与avoid_future_data相互矛盾,只能取二者中的一个 # close_data = get_price(g.ref_stock, end_date=context.current_dt-datetime.timedelta(1),count=g.mean_day + g.mean_diff_day,fields=['close']) # high_low_data = get_price(g.ref_stock, end_date=context.current_dt-datetime.timedelta(1),count=g.N, fields=['high', 'low']) intercept, slope, r2 = get_ols(high_low_data.low, high_low_data.high) g.slope_series.append(slope) rsrs_score = get_zscore(g.slope_series[-g.M:]) * r2 # g.score_threshold = g.score_threshold * (1 + g.threshold) # record(rsrs分数 = rsrs_score,low = -g.score_threshold * (1 - g.threshold),high = g.score_threshold * (1 - g.threshold)) # if rsrs_score > g.score_threshold * (1 + g.threshold): return "BUY" #0.7乘以1.03 0.721 # if rsrs_score > g.score_threshold: return "BUY" #0.7乘以1.03 0.721 # elif rsrs_score < -g.score_threshold: return "SELL" print('rsrs_score %s'%rsrs_score) if g.singl == [] or g.singl == "KEEP": if rsrs_score > g.score_threshold: g.singl = "BUY" #0.7乘以1.03 0.721 elif rsrs_score < -g.score_threshold: g.singl = "SELL" else: g.singl = "KEEP" elif g.singl == "SELL": if rsrs_score <= -g.score_threshold * (1 - g.threshold): # 0.7 * 1-0.001 g.singl = "SELL" print('selling %s'%rsrs_score) else: g.singl = "KEEP" elif g.singl == "BUY": if rsrs_score >= g.score_threshold * (1 - g.threshold): #0.7 + 1.03 = 0.721 #0.679 g.singl = "BUY" else: g.singl = "KEEP" return g.singl # elif rsrs_score < -g.score_threshold * (1 - g.threshold): return "SELL" ##0.7乘以0.97 -0.679 ############################# rsrs函数 ############################# ## 收盘后运行函数 def after_market_close(context): # record(hold_num = len(context.portfolio.positions.keys())) record(total = len(g.stock_list),hold_num = len(context.portfolio.positions.keys())) # 交易函数 def order_target_value_(security, value): if value == 0: # log.debug("Selling out %s" % (security)) print('') # print('rsrs清仓') else: log.debug("Order %s to value %f" % (security, value)) # 如果股票停牌,创建报单会失败,order_target_value 返回None # 如果股票涨跌停,创建报单会成功,order_target_value 返回Order,但是报单会取消 # 部成部撤的报单,聚宽状态是已撤,此时成交量>0,可通过成交量判断是否有成交 return order_target_value(security, value) def close_position(position): security = position.security order = order_target_value_(security, 0) # 可能会因停牌失败 if order != None: if order.status == OrderStatus.held and order.filled == order.amount: return True return False # 获取前n个单位时间当时的收盘价 def get_close_price(code, n, unit='1d'): return attribute_history(code, n, unit, 'close', df=False)['close'][0] def get_blacklist(): blacklist = ['600519.XSHG'] return blacklist # 平仓,卖出指定持仓 def _close_position(code): order = order_target_value(code, 0) # 可能会因停牌或跌停失败 # if order != None and order.status == OrderStatus.held: # g.sold_stock[code] = 0 ```
文章分类
关于作者
水滴
注册时间: