Multi-factor.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # 克隆自聚宽文章:https://www.joinquant.com/post/65807
  2. # 标题:11年26倍,简单的多因子策略
  3. # 作者:foolmouse
  4. # https://www.joinquant.com/view/community/detail/625cc5268324506f6746fc0c6c605c44
  5. # 很简单的几个随手可得的因子:
  6. # ROA高。
  7. # 归母净利润增长快。
  8. # PB排名小且小于2。
  9. # 就这些,没有其他花里胡哨诘屈聱牙的高频因子,没有复杂计算,只是简单的排序,我觉得比较满意的几点:
  10. # 没有现在满社区的小市值暴露(虽然我也有小市值仓位,但是不会是重仓)。
  11. # 所有因子都是理论基础很扎实的几个基础因子,即使暂时会失效长期看应该有效的。
  12. # 没有太多参数(只有一个PB小于2,这个存在后视镜,是根据结果优化的。去掉也行但是2015年股灾回撤比较大,如果实盘我会考虑指数估值什么的大盘择时,但是聚宽没有这个因子因此没用)
  13. # 在牛市后半程会跑输,但是大部分熊市后会赚回来的。
  14. '''
  15. 1.市净率小于2;
  16. 2.负债比例高于市场平均值;
  17. 3.企业的流动资产至少是流动负债的1.2倍;
  18. 4.每年四次调仓,即在1/4/7/10月调仓;
  19. 5.可加入止损(十天HS300跌幅达10%清仓);
  20. '''
  21. ## 初始化函数,设定要操作的股票、基准等等
  22. def initialize(context):
  23. # 设定指数
  24. g.stockindex = '000300.XSHG'
  25. # 设定沪深300作为基准
  26. set_benchmark('000300.XSHG')
  27. # True为开启动态复权模式,使用真实价格交易
  28. set_option('use_real_price', True)
  29. # 设定成交量比例
  30. set_option('order_volume_ratio', 1)
  31. # 股票类交易手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
  32. set_order_cost(OrderCost(open_tax=0, close_tax=0.001, \
  33. open_commission=0.0003, close_commission=0.0003,\
  34. close_today_commission=0, min_commission=5), type='stock')
  35. # 最大持仓数量
  36. g.stocknum = 10
  37. ## 自动设定调仓月份(如需使用自动,注销下段)
  38. """
  39. f = 4 # 调仓频率
  40. log.info(range(1,13,12/f))
  41. g.Transfer_date = range(1,13,12/f)
  42. """
  43. ## 手动设定调仓月份(如需使用手动,注释掉上段)
  44. # g.Transfer_date = (3,9)
  45. #根据大盘止损,如不想加入大盘止损,注释下句即可
  46. # run_daily(dapan_stoploss, time='open')
  47. ## 按月调用程序
  48. run_weekly(trade, weekday=1, time='open')
  49. ## 交易函数
  50. def trade(context):
  51. ## 获得Buylist
  52. Buylist = check_stocks(context)
  53. value = context.portfolio.total_value
  54. hold_count = min(len(Buylist),g.stocknum)
  55. per_value = value/hold_count if hold_count>0 else 0
  56. ## 卖出
  57. if len(context.portfolio.positions) > 0:
  58. for stock in context.portfolio.positions.keys():
  59. if stock not in Buylist:
  60. order_target_value(stock, 0)
  61. else:
  62. order_target_value(stock,per_value)
  63. ## 买入
  64. if len(Buylist) > 0:
  65. for stock in Buylist:
  66. if stock not in context.portfolio.positions.keys():
  67. order_target_value(stock,per_value)
  68. ## 选股函数
  69. def check_stocks(context):
  70. # 获取所有股票
  71. yesterday = context.previous_date
  72. security = get_all_securities("stock", yesterday).index.tolist()
  73. # 排除st,科创,新股
  74. security = filter_st_stock(security)
  75. security = filter_kcbj_stock(security)
  76. security = filter_new_stock(context,security)
  77. # 获取因子值
  78. Stocks = get_fundamentals(query(
  79. valuation.code,
  80. valuation.pb_ratio,
  81. indicator.roa,
  82. indicator.inc_net_profit_to_shareholders_year_on_year
  83. ).filter(
  84. valuation.code.in_(security),
  85. valuation.pb_ratio<2
  86. ))
  87. stock_count = len(Stocks)
  88. # 计算roa靠前的
  89. Stocks = Stocks.sort_values(by='roa',ascending=False).iloc[:int(stock_count/10)]
  90. # 计算增长率考前的
  91. stock_count = len(Stocks)
  92. Stocks = Stocks.sort_values(by='inc_net_profit_to_shareholders_year_on_year',ascending=False).iloc[:int(stock_count/10)]
  93. # 计算PB最小的
  94. Stocks = Stocks.sort_values(by='pb_ratio',ascending=True).iloc[:10]
  95. Codes = Stocks.code
  96. return list(Codes)
  97. # 过滤停牌股票
  98. def filter_paused_stock(stock_list):
  99. current_data = get_current_data()
  100. return [stock for stock in stock_list if not current_data[stock].paused]
  101. # 过滤ST及其他具有退市标签的股票
  102. def filter_st_stock(stock_list):
  103. current_data = get_current_data()
  104. return [stock for stock in stock_list
  105. if not current_data[stock].is_st
  106. and 'ST' not in current_data[stock].name
  107. and '*' not in current_data[stock].name
  108. and '退' not in current_data[stock].name]
  109. # 过滤科创北交股票
  110. def filter_kcbj_stock(stock_list):
  111. for stock in stock_list[:]:
  112. if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68':
  113. stock_list.remove(stock)
  114. return stock_list
  115. # 过滤涨停的股票
  116. def filter_limitup_stock(context, stock_list):
  117. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  118. current_data = get_current_data()
  119. return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
  120. or last_prices[stock][-1] < current_data[stock].high_limit]
  121. # 过滤跌停的股票
  122. def filter_limitdown_stock(context, stock_list):
  123. last_prices = history(1, unit='1m', field='close', security_list=stock_list)
  124. current_data = get_current_data()
  125. return [stock for stock in stock_list if (stock in context.portfolio.positions.keys()
  126. or last_prices[stock][-1] > current_data[stock].low_limit)]
  127. # 过滤次新股
  128. def filter_new_stock(context, stock_list):
  129. yesterday = context.previous_date
  130. return [stock for stock in stock_list if not yesterday - get_security_info(stock).start_date < datetime.timedelta(days=375)]