量化学习平台
文章
市场宽度
背离图
登录
注册
大市值价投高股息爱国者策略(爱我中华)
策略
作者: 水滴
```python # 风险及免责提示:该策略由聚宽用户在聚宽社区分享,仅供学习交流使用。 # 原文一般包含策略说明,如有疑问请到原文和作者交流讨论。 # 原文网址:https://www.joinquant.com/post/46122 # 标题:大市值价投高股息爱国者策略(爱我中华) # 作者:MarioC # 原回测条件:2022-01-01 到 2024-01-16, ¥1000000, 每天 from jqdata import * from jqfactor import * import numpy as np import pandas as pd import pickle # 初始化函数 def initialize(context): # 设定基准 set_benchmark('000985.XSHG') # 用真实价格交易 set_option('use_real_price', True) # 打开防未来函数 set_option("avoid_future_data", True) # 将滑点设置为0 set_slippage(FixedSlippage(0)) # 设置交易成本万分之三,不同滑点影响可在归因分析中查看 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') # 初始化全局变量 g.no_trading_today_signal = False g.hold_list = [] # 当前持仓的全部股票 g.yesterday_HL_list = [] # 记录持仓中昨日涨停的股票 # 设置交易运行时间 run_daily(prepare_stock_list, '9:05') run_monthly(weekly_adjustment, 1, '9:30') # run_weekly(weekly_adjustment, 1, '9:30') run_daily(check_limit_up, '14:00') # 检查持仓中的涨停股是否需要卖出 run_daily(close_account, '14:30') # 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 = [] # 判断今天是否为账户资金再平衡的日期 # g.no_trading_today_signal = today_is_between(context, '04-05', '04-30') # 1-2 选股模块 def get_stock_list(context): # 指定日期防止未来数据 yesterday = context.previous_date today = context.current_dt # 获取初始列表 initial_list = get_index_stocks('000985.XSHG', today) # initial_list = get_index_stocks('399101.XSHE', today) # initial_list = get_index_stocks('000852.XSHG', today) initial_list = filter_all_stock2(context, initial_list) final_list = [] q = query( valuation.code, valuation.market_cap, valuation.pe_ratio, income.total_operating_revenue ).filter( valuation.pb_ratio < 1, cash_flow.subtotal_operate_cash_inflow > 1e5, indicator.adjusted_profit > 1e5, # indicator.roa > 0.1, indicator.inc_net_profit_year_on_year > 0, valuation.code.in_(initial_list) ) check_out_lists = list(get_fundamentals(q).code) lst = GGX_stock(context,check_out_lists) #高股息外挂 lst = filter_aiguo_stock(context,lst) for stock in lst: if stock not in final_list: final_list.append(stock) return final_list def filter_aiguo_stock(context, stock_list): current_data = get_current_data() keywords = ['爱','我','中','华'] LIST = [] for stock in stock_list: for KEY in current_data[stock].name: if str(KEY) in keywords: LIST.append(stock) return LIST def weekly_adjustment(context): if g.no_trading_today_signal == False: # 获取应买入列表 target_list = get_stock_list(context) # 调仓卖出 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 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 GGX_stock(context, stock_list): current_data = get_current_data() LIST = get_dividend_ratio_filter_list(context, stock_list, False, 0, 0.5) return LIST 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)) def filter_all_stock2(context, stock_list): # 过滤次新股(新股、老股的分界日期,两种指定方法) # 新老股的分界日期, 自然日180天 # by_date = context.previous_date - datetime.timedelta(days=180) # 新老股的分界日期,120个交易日 by_date = get_trade_days(end_date=context.previous_date, count=375)[0] all_stocks = get_all_securities(date=by_date).index.tolist() stock_list = list(set(stock_list).intersection(set(all_stocks))) curr_data = get_current_data() return [stock for stock in stock_list if not ( stock.startswith(( '3','68', '4', '8')) or # 创业,科创,北交所 curr_data[stock].paused or curr_data[stock].is_st or # ST ('ST' in curr_data[stock].name) or ('*' in curr_data[stock].name) or ('退' in curr_data[stock].name) or (curr_data[stock].day_open == curr_data[stock].high_limit) or # 涨停开盘, 其它时间用last_price (curr_data[stock].day_open == curr_data[stock].low_limit) # 跌停开盘, 其它时间用last_price )] 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) def open_position(security, value): order = order_target_value_(security, value) if order != None and order.filled > 0: return True return False 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 # 4-2 清仓后次日资金可转 def close_account(context): if g.no_trading_today_signal == True: if len(g.hold_list) != 0: for stock in g.hold_list: position = context.portfolio.positions[stock] close_position(position) 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' or stock[0] == '3': 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)] ```
文章分类
关于作者
水滴
注册时间: