找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 股票 资源 源码
查看: 75|回复: 0

红利低波50策略回测

[复制链接]

719

主题

90

回帖

2万

积分

管理员

积分
20831
发表于 2024-12-8 04:12:11 | 显示全部楼层 |阅读模式
  • 1 获取市场中,近三年股息率最高的10%股票
  • 2 选取PEG在0.1~2之间的股票
  • 3 营收同比要大于1%, 净利润同比大于1%,ROE 大于1.5%
  • 4 在满足上面的条件中,按照股息率进行排序,选出排名最高的50只股票,组成股票池
  • 5 每月1日交易,每月进行轮动,卖出股息率不在排名前50的股票,买入新的股票
  • 6 每天检查昨日涨停,今日未涨停的股票,卖出

策略以上证50作为基准,, 有明显的超过收益,年化收益26%, 股息率确实对选股有效。

image.png




  1. # 标题:红利50策略

  2. # 导入函数库
  3. from jqdata import *
  4. from jqfactor import *
  5. import numpy as np
  6. import pandas as pd
  7. import pickle
  8. import talib
  9. import warnings
  10. import pandas as pd
  11. from jqdata import *
  12. from jqlib.technical_analysis import *




  13. # 初始化函数,设定基准等等
  14. def initialize(context):
  15.     # 设定上证50作为基准
  16.     set_benchmark('000016.XSHG')
  17.      # 用真实价格交易
  18.     set_option('use_real_price', True)
  19.     # 打开防未来函数
  20.     set_option("avoid_future_data", True)
  21.     # 将滑点设置为0
  22.     set_slippage(FixedSlippage(0.02))
  23.     # 设置交易成本万分之三,不同滑点影响可在归因分析中查看
  24.     set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003,
  25.                              close_today_commission=0, min_commission=5), type='stock')
  26.     # 过滤order中低于error级别的日志
  27.     log.set_level('order', 'error')

  28.    # 过滤order中低于error级别的日志
  29.     log.set_level('order', 'error')
  30.     # 初始化全局变量
  31.     g.no_trading_today_signal = False
  32.     g.stock_num = 50
  33.     g.hold_list = []  # 当前持仓的全部股票
  34.     g.yesterday_HL_list = []  # 记录持仓中昨日涨停的股票
  35.     # 设置交易运行时间
  36.     run_daily(prepare_stock_list, '9:05')

  37.     run_monthly(monthly_adjustment, 1, '9:30')

  38.     run_daily(check_limit_up, '14:00')  # 检查持仓中的涨停股是否需要卖出


  39. # 1-1 准备股票池
  40. def prepare_stock_list(context):
  41.     # 获取已持有列表
  42.     g.hold_list = []
  43.     for position in list(context.portfolio.positions.values()):
  44.         stock = position.security
  45.         g.hold_list.append(stock)
  46.     # 获取昨日涨停列表
  47.     if g.hold_list != []:
  48.         df = get_price(g.hold_list, end_date=context.previous_date, frequency='daily', fields=['close', 'high_limit'],
  49.                        count=1, panel=False, fill_paused=False)
  50.         df = df[df['close'] == df['high_limit']]
  51.         g.yesterday_HL_list = list(df.code)
  52.     else:
  53.         g.yesterday_HL_list = []
  54.         
  55.         
  56. def get_dividend_ratio_filter_list(context, stock_list, sort, p1, p2):
  57.     time1 = context.previous_date
  58.     time0 = time1 - datetime.timedelta(days=365*3)#最近3年分红
  59.     #获取分红数据,由于finance.run_query最多返回4000行,以防未来数据超限,最好把stock_list拆分后查询再组合
  60.     interval = 1000 #某只股票可能一年内多次分红,导致其所占行数大于1,所以interval不要取满4000
  61.     list_len = len(stock_list)
  62.     #截取不超过interval的列表并查询
  63.     q = query(finance.STK_XR_XD.code, finance.STK_XR_XD.a_registration_date, finance.STK_XR_XD.bonus_amount_rmb
  64.     ).filter(
  65.         finance.STK_XR_XD.a_registration_date >= time0,
  66.         finance.STK_XR_XD.a_registration_date <= time1,
  67.         finance.STK_XR_XD.code.in_(stock_list[:min(list_len, interval)]))
  68.     df = finance.run_query(q)
  69.     #对interval的部分分别查询并拼接
  70.     if list_len > interval:
  71.         df_num = list_len // interval
  72.         for i in range(df_num):
  73.             q = query(finance.STK_XR_XD.code, finance.STK_XR_XD.a_registration_date,  finance.STK_XR_XD.bonus_amount_rmb
  74.             ).filter(
  75.                 finance.STK_XR_XD.a_registration_date >= time0,
  76.                 finance.STK_XR_XD.a_registration_date <= time1,
  77.                 finance.STK_XR_XD.code.in_(stock_list[interval*(i+1):min(list_len,interval*(i+2))]))
  78.             temp_df = finance.run_query(q)
  79.             df = df.append(temp_df)
  80.     dividend = df.fillna(0)#df.fillna() 是一个 Pandas 数据处理库中的函数,它可以用来填充数据框中的空值
  81.     dividend = dividend.groupby('code').sum()
  82.     temp_list = list(dividend.index) #query查询不到无分红信息的股票,所以temp_list长度会小于stock_list
  83.     # #获取市值相关数据
  84.     q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(temp_list))
  85.     cap = get_fundamentals(q, date=time1)
  86.     cap = cap.set_index('code')
  87.     # #计算股息率
  88.     cap['dividend_ratio']=(dividend['bonus_amount_rmb']/10000)/cap['market_cap']
  89.     # #排序并筛选
  90.     cap = cap.sort_values(by=['dividend_ratio'], ascending=sort)
  91.     final_list = list(cap.index)[int(p1*len(cap)):int(p2*len(cap))]
  92.     print("近3年累计分红率排名前{0:.2%}的股有{1}只".format(p2,len(final_list)))
  93.     return final_list
  94.    


  95. def choice_hl(context, stocks):
  96.     yesterday = context.previous_date
  97.    
  98.     #高股息(全市场最大10%)
  99.     stocks = get_dividend_ratio_filter_list(context, stocks, False, 0, 0.1)
  100.    
  101.     # 1 选取 PEG 在 0.1 到 2之间
  102.     stocks =  peg_stock(context,stocks,0.1,2)
  103.    
  104.     #2 业绩筛选 营收增速同比大于1%,净利润增速同比大于1%,季度ROE大于1.5%
  105.     q = query(valuation.code).filter(
  106.                 valuation.code.in_(stocks),
  107.                 indicator.inc_total_revenue_year_on_year>1,
  108.                 indicator.inc_net_profit_year_on_year>1,
  109.                 indicator.inc_return >1.5)
  110.                
  111.     df=get_fundamentals(q)
  112.     stocks = list(df['code'])
  113.    
  114.     stock_list =  stocks[:g.stock_num] #选前前N只票
  115.    
  116.     return stock_list

  117.    
  118.    

  119. def peg_stock(context,stock_list,pegmin,pegmax):
  120.    
  121.     q = query(valuation.code).filter(
  122.                 valuation.code.in_(stock_list),
  123.                 valuation.pe_ratio / indicator.inc_net_profit_year_on_year>pegmin,
  124.                 valuation.pe_ratio / indicator.inc_net_profit_year_on_year<pegmax)
  125.     df=get_fundamentals(q)
  126.     stock_list = list(df['code'])
  127.     return stock_list


  128. # 获取因子值
  129. def get_jq_factors(stocks, end_date, factor_name):
  130.     factor_values = get_factor_values(stocks, factors=[factor_name], end_date=end_date, count=1)[factor_name].iloc[0].tolist()
  131.     df_factor = pd.DataFrame(columns=['code', factor_name])
  132.     df_factor['code'] = stocks
  133.     df_factor[factor_name] = factor_values
  134.     df_factor = df_factor.dropna()
  135.     return df_factor

  136. # 1-2 选股模块
  137. def get_stock_list(context):
  138.     # 指定日期防止未来数据
  139.     yesterday = context.previous_date
  140.     today = context.current_dt
  141.     initial_list = get_all_securities('stock', today).index.tolist()
  142.     # initial_list = get_stock(yesterday)
  143.     stocks = filter_kcbj_stock(initial_list)
  144.     choice = filter_st_stock(stocks)
  145.     choice = filter_paused_stock(choice)
  146.     choice = filter_new_stock(context, choice)
  147.     choice = filter_limitup_stock(context,choice)
  148.     initial_list = filter_limitdown_stock(context,choice)
  149.     stocks = choice_hl(context,initial_list)
  150.    
  151.     return stocks
  152.    

  153. # 1-3 整体调整持仓
  154. def monthly_adjustment(context):
  155.    
  156.     target_list = get_stock_list(context)[0 : g.stock_num]
  157.     # 调仓卖出
  158.     for stock in g.hold_list:
  159.         if (stock not in target_list) and (stock not in g.yesterday_HL_list):
  160.             log.info("卖出[%s]" % (stock))
  161.             position = context.portfolio.positions[stock]
  162.             close_position(position)
  163.         else:
  164.             log.info("已持有[%s]" % (stock))
  165.    
  166.     # 调仓买入
  167.     position_count = len(context.portfolio.positions)
  168.     target_num = len(target_list)
  169.     if target_num > position_count:
  170.             print(context.portfolio.cash)
  171.             value = context.portfolio.cash / (target_num - position_count)
  172.             for stock in target_list:
  173.                 if context.portfolio.positions[stock].total_amount == 0:
  174.                     if open_position(stock, value):
  175.                         if len(context.portfolio.positions) == target_num:
  176.                             break

  177.    


  178. # 1-4 调整昨日涨停股票
  179. def check_limit_up(context):
  180.     now_time = context.current_dt
  181.     if g.yesterday_HL_list != []:
  182.         # 对昨日涨停股票观察到尾盘如不涨停则提前卖出,如果涨停即使不在应买入列表仍暂时持有
  183.         for stock in g.yesterday_HL_list:
  184.             current_data = get_price(stock, end_date=now_time, frequency='1m', fields=['close', 'high_limit'],
  185.                                      skip_paused=False, fq='pre', count=1, panel=False, fill_paused=True)
  186.             if current_data.iloc[0, 0] < current_data.iloc[0, 1]:
  187.                 log.info("[%s]涨停打开,卖出" % (stock))
  188.                 position = context.portfolio.positions[stock]
  189.                 close_position(position)
  190.             else:
  191.                 log.info("[%s]涨停,继续持有" % (stock))

  192. # 2-1 过滤停牌股票
  193. def filter_paused_stock(stock_list):
  194.     current_data = get_current_data()
  195.     return [stock for stock in stock_list if not current_data[stock].paused]


  196. # 2-2 过滤ST及其他具有退市标签的股票
  197. def filter_st_stock(stock_list):
  198.     current_data = get_current_data()
  199.     return [stock for stock in stock_list
  200.             if not current_data[stock].is_st
  201.             and 'ST' not in current_data[stock].name
  202.             and '*' not in current_data[stock].name
  203.             and '退' not in current_data[stock].name]


  204. # 2-3 过滤科创北交股票
  205. def filter_kcbj_stock(stock_list):
  206.     for stock in stock_list[:]:
  207.         if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68' :
  208.             stock_list.remove(stock)
  209.     return stock_list


  210. # 2-4 过滤涨停的股票
  211. def filter_limitup_stock(context, stock_list):
  212.     last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  213.     current_data = get_current_data()
  214.     return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  215.             or last_prices[stock][-1] < current_data[stock].high_limit]


  216. # 2-5 过滤跌停的股票
  217. def filter_limitdown_stock(context, stock_list):
  218.     last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  219.     current_data = get_current_data()
  220.     return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  221.             or last_prices[stock][-1] > current_data[stock].low_limit]


  222. # 2-6 过滤次新股
  223. def filter_new_stock(context, stock_list):
  224.     yesterday = context.previous_date
  225.     return [stock for stock in stock_list if
  226.             not yesterday - get_security_info(stock).start_date < datetime.timedelta(days=375)]




  227. # 3-1 交易模块-自定义下单
  228. def order_target_value_(security, value):
  229.     if value == 0:
  230.         log.debug("Selling out %s" % (security))
  231.     else:
  232.         log.debug("Order %s to value %f" % (security, value))
  233.     return order_target_value(security, value)


  234. # 3-2 交易模块-开仓
  235. def open_position(security, value):
  236.     order = order_target_value_(security, value)
  237.     if order != None and order.filled > 0:
  238.         return True
  239.     return False


  240. # 3-3 交易模块-平仓
  241. def close_position(position):
  242.     security = position.security
  243.     order = order_target_value_(security, 0)  # 可能会因停牌失败
  244.     if order != None:
  245.         if order.status == OrderStatus.held and order.filled == order.amount:
  246.             return True
  247.     return False
复制代码

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋| 股指标网 ( 渝ICP备2024026571号-1 )

GMT+8, 2024-12-23 04:51 Powered by Discuz! X3.5

股指标