- 1 获取市场中,近三年股息率最高的10%股票
- 2 选取PEG在0.1~2之间的股票
- 3 营收同比要大于1%, 净利润同比大于1%,ROE 大于1.5%
- 4 在满足上面的条件中,按照股息率进行排序,选出排名最高的50只股票,组成股票池
- 5 每月1日交易,每月进行轮动,卖出股息率不在排名前50的股票,买入新的股票
- 6 每天检查昨日涨停,今日未涨停的股票,卖出
策略以上证50作为基准,, 有明显的超过收益,年化收益26%, 股息率确实对选股有效。
- # 标题:红利50策略
- # 导入函数库
- from jqdata import *
- from jqfactor import *
- import numpy as np
- import pandas as pd
- import pickle
- import talib
- import warnings
- import pandas as pd
- from jqdata import *
- from jqlib.technical_analysis import *
- # 初始化函数,设定基准等等
- def initialize(context):
- # 设定上证50作为基准
- set_benchmark('000016.XSHG')
- # 用真实价格交易
- set_option('use_real_price', True)
- # 打开防未来函数
- set_option("avoid_future_data", True)
- # 将滑点设置为0
- set_slippage(FixedSlippage(0.02))
- # 设置交易成本万分之三,不同滑点影响可在归因分析中查看
- set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003,
- close_today_commission=0, min_commission=5), type='stock')
- # 过滤order中低于error级别的日志
- log.set_level('order', 'error')
- # 过滤order中低于error级别的日志
- log.set_level('order', 'error')
- # 初始化全局变量
- g.no_trading_today_signal = False
- g.stock_num = 50
- g.hold_list = [] # 当前持仓的全部股票
- g.yesterday_HL_list = [] # 记录持仓中昨日涨停的股票
- # 设置交易运行时间
- run_daily(prepare_stock_list, '9:05')
- run_monthly(monthly_adjustment, 1, '9:30')
- run_daily(check_limit_up, '14:00') # 检查持仓中的涨停股是否需要卖出
- # 1-1 准备股票池
- def prepare_stock_list(context):
- # 获取已持有列表
- g.hold_list = []
- for position in list(context.portfolio.positions.values()):
- stock = position.security
- g.hold_list.append(stock)
- # 获取昨日涨停列表
- if g.hold_list != []:
- df = get_price(g.hold_list, end_date=context.previous_date, frequency='daily', fields=['close', 'high_limit'],
- count=1, panel=False, fill_paused=False)
- df = df[df['close'] == df['high_limit']]
- g.yesterday_HL_list = list(df.code)
- else:
- g.yesterday_HL_list = []
-
-
- def get_dividend_ratio_filter_list(context, stock_list, sort, p1, p2):
- time1 = context.previous_date
- time0 = time1 - datetime.timedelta(days=365*3)#最近3年分红
- #获取分红数据,由于finance.run_query最多返回4000行,以防未来数据超限,最好把stock_list拆分后查询再组合
- interval = 1000 #某只股票可能一年内多次分红,导致其所占行数大于1,所以interval不要取满4000
- list_len = len(stock_list)
- #截取不超过interval的列表并查询
- q = query(finance.STK_XR_XD.code, finance.STK_XR_XD.a_registration_date, finance.STK_XR_XD.bonus_amount_rmb
- ).filter(
- finance.STK_XR_XD.a_registration_date >= time0,
- finance.STK_XR_XD.a_registration_date <= time1,
- finance.STK_XR_XD.code.in_(stock_list[:min(list_len, interval)]))
- df = finance.run_query(q)
- #对interval的部分分别查询并拼接
- if list_len > interval:
- df_num = list_len // interval
- for i in range(df_num):
- q = query(finance.STK_XR_XD.code, finance.STK_XR_XD.a_registration_date, finance.STK_XR_XD.bonus_amount_rmb
- ).filter(
- finance.STK_XR_XD.a_registration_date >= time0,
- finance.STK_XR_XD.a_registration_date <= time1,
- finance.STK_XR_XD.code.in_(stock_list[interval*(i+1):min(list_len,interval*(i+2))]))
- temp_df = finance.run_query(q)
- df = df.append(temp_df)
- dividend = df.fillna(0)#df.fillna() 是一个 Pandas 数据处理库中的函数,它可以用来填充数据框中的空值
- dividend = dividend.groupby('code').sum()
- temp_list = list(dividend.index) #query查询不到无分红信息的股票,所以temp_list长度会小于stock_list
- # #获取市值相关数据
- q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(temp_list))
- cap = get_fundamentals(q, date=time1)
- cap = cap.set_index('code')
- # #计算股息率
- cap['dividend_ratio']=(dividend['bonus_amount_rmb']/10000)/cap['market_cap']
- # #排序并筛选
- cap = cap.sort_values(by=['dividend_ratio'], ascending=sort)
- final_list = list(cap.index)[int(p1*len(cap)):int(p2*len(cap))]
- print("近3年累计分红率排名前{0:.2%}的股有{1}只".format(p2,len(final_list)))
- return final_list
-
- def choice_hl(context, stocks):
- yesterday = context.previous_date
-
- #高股息(全市场最大10%)
- stocks = get_dividend_ratio_filter_list(context, stocks, False, 0, 0.1)
-
- # 1 选取 PEG 在 0.1 到 2之间
- stocks = peg_stock(context,stocks,0.1,2)
-
- #2 业绩筛选 营收增速同比大于1%,净利润增速同比大于1%,季度ROE大于1.5%
- q = query(valuation.code).filter(
- valuation.code.in_(stocks),
- indicator.inc_total_revenue_year_on_year>1,
- indicator.inc_net_profit_year_on_year>1,
- indicator.inc_return >1.5)
-
- df=get_fundamentals(q)
- stocks = list(df['code'])
-
- stock_list = stocks[:g.stock_num] #选前前N只票
-
- return stock_list
-
-
- def peg_stock(context,stock_list,pegmin,pegmax):
-
- q = query(valuation.code).filter(
- valuation.code.in_(stock_list),
- valuation.pe_ratio / indicator.inc_net_profit_year_on_year>pegmin,
- valuation.pe_ratio / indicator.inc_net_profit_year_on_year<pegmax)
- df=get_fundamentals(q)
- stock_list = list(df['code'])
- return stock_list
- # 获取因子值
- def get_jq_factors(stocks, end_date, factor_name):
- factor_values = get_factor_values(stocks, factors=[factor_name], end_date=end_date, count=1)[factor_name].iloc[0].tolist()
- df_factor = pd.DataFrame(columns=['code', factor_name])
- df_factor['code'] = stocks
- df_factor[factor_name] = factor_values
- df_factor = df_factor.dropna()
- return df_factor
- # 1-2 选股模块
- def get_stock_list(context):
- # 指定日期防止未来数据
- yesterday = context.previous_date
- today = context.current_dt
- initial_list = get_all_securities('stock', today).index.tolist()
- # initial_list = get_stock(yesterday)
- stocks = filter_kcbj_stock(initial_list)
- choice = filter_st_stock(stocks)
- choice = filter_paused_stock(choice)
- choice = filter_new_stock(context, choice)
- choice = filter_limitup_stock(context,choice)
- initial_list = filter_limitdown_stock(context,choice)
- stocks = choice_hl(context,initial_list)
-
- return stocks
-
- # 1-3 整体调整持仓
- def monthly_adjustment(context):
-
- target_list = get_stock_list(context)[0 : g.stock_num]
- # 调仓卖出
- for stock in g.hold_list:
- if (stock not in target_list) and (stock not in g.yesterday_HL_list):
- log.info("卖出[%s]" % (stock))
- position = context.portfolio.positions[stock]
- close_position(position)
- else:
- log.info("已持有[%s]" % (stock))
-
- # 调仓买入
- position_count = len(context.portfolio.positions)
- target_num = len(target_list)
- if target_num > position_count:
- print(context.portfolio.cash)
- value = context.portfolio.cash / (target_num - position_count)
- for stock in target_list:
- if context.portfolio.positions[stock].total_amount == 0:
- if open_position(stock, value):
- if len(context.portfolio.positions) == target_num:
- break
-
- # 1-4 调整昨日涨停股票
- def check_limit_up(context):
- now_time = context.current_dt
- if g.yesterday_HL_list != []:
- # 对昨日涨停股票观察到尾盘如不涨停则提前卖出,如果涨停即使不在应买入列表仍暂时持有
- for stock in g.yesterday_HL_list:
- current_data = get_price(stock, end_date=now_time, frequency='1m', fields=['close', 'high_limit'],
- skip_paused=False, fq='pre', count=1, panel=False, fill_paused=True)
- if current_data.iloc[0, 0] < current_data.iloc[0, 1]:
- log.info("[%s]涨停打开,卖出" % (stock))
- position = context.portfolio.positions[stock]
- close_position(position)
- else:
- log.info("[%s]涨停,继续持有" % (stock))
- # 2-1 过滤停牌股票
- def filter_paused_stock(stock_list):
- current_data = get_current_data()
- return [stock for stock in stock_list if not current_data[stock].paused]
- # 2-2 过滤ST及其他具有退市标签的股票
- def filter_st_stock(stock_list):
- current_data = get_current_data()
- return [stock for stock in stock_list
- if not current_data[stock].is_st
- and 'ST' not in current_data[stock].name
- and '*' not in current_data[stock].name
- and '退' not in current_data[stock].name]
- # 2-3 过滤科创北交股票
- def filter_kcbj_stock(stock_list):
- for stock in stock_list[:]:
- if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68' :
- stock_list.remove(stock)
- return stock_list
- # 2-4 过滤涨停的股票
- def filter_limitup_stock(context, stock_list):
- last_prices = history(1, unit='1m', field='close', security_list=stock_list)
- current_data = get_current_data()
- return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
- or last_prices[stock][-1] < current_data[stock].high_limit]
- # 2-5 过滤跌停的股票
- def filter_limitdown_stock(context, stock_list):
- last_prices = history(1, unit='1m', field='close', security_list=stock_list)
- current_data = get_current_data()
- return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
- or last_prices[stock][-1] > current_data[stock].low_limit]
- # 2-6 过滤次新股
- def filter_new_stock(context, stock_list):
- yesterday = context.previous_date
- return [stock for stock in stock_list if
- not yesterday - get_security_info(stock).start_date < datetime.timedelta(days=375)]
- # 3-1 交易模块-自定义下单
- def order_target_value_(security, value):
- if value == 0:
- log.debug("Selling out %s" % (security))
- else:
- log.debug("Order %s to value %f" % (security, value))
- return order_target_value(security, value)
- # 3-2 交易模块-开仓
- def open_position(security, value):
- order = order_target_value_(security, value)
- if order != None and order.filled > 0:
- return True
- return False
- # 3-3 交易模块-平仓
- 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
复制代码
|