| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- # 克隆自聚宽文章:https://www.joinquant.com/post/50551
- # 标题:大小外择时小市值2.0
- # 作者:DanD
- from jqdata import *
- from jqfactor import *
- import numpy as np
- import pandas as pd
- import pickle
- import talib
- import warnings
- from jqlib.technical_analysis import *
- warnings.filterwarnings("ignore")
- # 初始化函数
- def initialize(context):
- # 设定基准
- set_benchmark('000300.XSHG')
- # 用真实价格交易
- set_option('use_real_price', True)
- # 打开防未来函数
- set_option("avoid_future_data", True)
- # 将滑点设置为0.246%
- set_slippage(PriceRelatedSlippage(0.00246),type='stock')
- # 设置交易成本万分之三,不同滑点影响可在归因分析中查看
- 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.market_temperature = "warm"
- g.stock_num = 3
- g.highest = 50
- g.buy_stock_count = 5
- g.hold_list = [] # 当前持仓的全部股票
- g.yesterday_HL_list = [] # 记录持仓中昨日涨停的股票
- g.bought_stocks = {} #记录补跌的股票和金额
- g.foreign_ETF = [
- '518880.XSHG', # 黄金ETF
- '513030.XSHG', # 德国ETF
- '513100.XSHG', # 纳指ETF
- '164824.XSHE', # 印度LOF
- '159866.XSHE', # 日经ETF
- ]
- # 设置交易运行时间
- run_daily(prepare_stock_list, '9:05')
- run_monthly(singal, 1, '9:00')
- run_weekly(clear, 1, '9:30')
- run_weekly(monthly_adjustment, 1, '9:30')
- run_daily(stop_loss, '14:00')
- def clear(context):#卖出补跌的仓位
- log.info(f"g.bought_stocks: {g.bought_stocks}")
- if g.bought_stocks!={}:
- for stock, amount in g.bought_stocks.items():
- if stock in context.portfolio.positions:
- order_value(stock, -amount) # 卖出股票至目标价值为0
- log.info("Sell cover stocks: %s, value: %s" % (stock, amount))
- # 清空记录
- g.bought_stocks.clear()
- def prepare_stock_list(context):
- """
- 准备每日股票列表,包括持仓股票列表和昨日涨停股票列表。
- 1. 遍历当前持仓中的所有股票,将其加入到 g.hold_list 中。
- 2. 如果持仓列表不为空:
- - 获取持仓股票在前一个交易日的收盘价和涨停价。
- - 过滤出收盘价等于涨停价的股票,存入昨日涨停列表 g.yesterday_HL_list。
- 3. 如果持仓列表为空,昨日涨停列表 g.yesterday_HL_list 也置为空。
-
- 返回:
- - g.hold_list: 持仓股票清单
- - g.yesterday_HL_list: 持仓的昨日涨停清单
- """
- log.info('Daily run is enabled')
- # 获取已持有列表
- g.hold_list = []
- for position in list(context.portfolio.positions.values()):
- stock = position.security
- g.hold_list.append(stock)
- log.info(f"Day position {len(g.hold_list)}:{g.hold_list}")
- # 获取昨日涨停列表
- 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 stop_loss(context):
- """
- 执行止损策略,根据条件卖出股票或进行补仓。
- 1. 初始化计数器 num 用于记录卖出股票的数量和获取当前时间 now_time。
- 2. 如果昨日涨停列表 g.yesterday_HL_list 不为空:
- - 对于每只昨日涨停的股票,获取当前价格和涨停价。
- - 如果当前价格低于涨停价,打印日志信息并卖出该股票,计数器 num 加1。
- - 如果当前价格等于涨停价,打印日志信息并继续持有该股票。
- 3. 初始化两个列表 SS 和 S 用于记录持仓股票的跌幅。
- 4. 遍历持仓列表 g.hold_list:
- - 如果股票在当前持仓中,并且当前价格低于平均成本价的92%,执行止损卖出操作,计数器 num 加1。
- - 否则,将股票加入列表 S,并计算当前价格相对于平均成本价的涨跌幅,存入 SS。
- 5. 如果有股票被止损卖出(num >= 1),且 SS 列表不为空:
- - 重置 num 为3。
- - 找出 SS 中跌幅最大的 num 支股票。
- - 计算每只股票的补仓金额为当前现金除以 num。
- - 对于跌幅最大的股票,执行补仓操作,并记录日志信息。如果股票不在已买入列表 g.bought_stocks 中,更新其买入金额。
- """
- num = 0
- 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]Limit ends, sell" % (stock))
- position = context.portfolio.positions[stock]
- close_position(position)
- num = num+1
- else:
- log.info("[%s]Limit continues, continue to hold" % (stock))
- SS=[]
- S=[]
- for stock in g.hold_list:
- if stock in list(context.portfolio.positions.keys()):
- if context.portfolio.positions[stock].price < context.portfolio.positions[stock].avg_cost * 0.92:
- order_target_value(stock, 0)
- log.info("Loss control: Selling out %s" % (stock))
- num = num+1
- else:
- S.append(stock)
- NOW = (context.portfolio.positions[stock].price - context.portfolio.positions[stock].avg_cost)/context.portfolio.positions[stock].avg_cost
- SS.append(np.array(NOW))
- else:
- log.info(f"stop_loss else after for loop, num: {num}, SS: {len(SS)}")
- if num >=1:
- if len(SS) > 0:
- # 清空记录
- num=3
- min_values = sorted(SS)[:num]
- min_indices = [SS.index(value) for value in min_values]
- min_strings = [S[index] for index in min_indices]
- log.info(f"Need to cover positions are min_strings: {min_strings}, min_indices: {min_indices}")
- cash = context.portfolio.cash/num
- for ss in min_strings:
- cover_order = order_value(ss, cash)
- if cover_order:
- log.info("Cover the n that fell the most with Order %s and %.2f" % (ss, cash))
- log.debug(f"cover_order: {cover_order}")
- amount = cover_order.amount
- log.debug(f"amount: {type(amount)}, {amount}")
- if ss not in g.bought_stocks:
- g.bought_stocks[ss] = amount
- else:
- g.bought_stocks[ss] = amount + g.bought_stocks[ss]
- log.info(f"g.bought_stocks: {g.bought_stocks}")
- def filter_roic(context,stock_list):
- """
- 根据最近一期的ROIC(投入资本回报率)过滤股票列表。
- 1. 获取前一个交易日的日期。
- 2. 遍历输入的股票列表 stock_list,对于每只股票:
- - 获取该股票最近一期的 ROIC 值。
- - 如果 ROIC 大于 0.08,将该股票添加到 list 中。
- """
- yesterday = context.previous_date
- list=[]
- for stock in stock_list:
- roic=get_factor_values(stock, 'roic_ttm', end_date=yesterday,count=1)['roic_ttm'].iloc[0,0]
- if roic>0.08:
- list.append(stock)
- return list
-
- def filter_highprice_stock(context,stock_list):
- # 过滤出最近一分钟的收盘价低于10元的股票,或当前已持有的股票。
- last_prices = history(1, unit='1m', field='close', security_list=stock_list)
- return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
- or last_prices[stock][-1] < 10]
-
- def filter_highprice_stock2(context,stock_list):
- # 过滤出最近一分钟的收盘价低于300元的股票,或当前已持有的股票。
- last_prices = history(1, unit='1m', field='close', security_list=stock_list)
- return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
- or last_prices[stock][-1] < 300]
-
- def get_recent_limit_up_stock(context, stock_list, recent_days):
- # 获取最近指定天数内出现过涨停的股票。
- stat_date = context.previous_date
- new_list = []
- for stock in stock_list:
- df = get_price(stock, end_date=stat_date, frequency='daily', fields=['close','high_limit'], count=recent_days, panel=False, fill_paused=False)
- df = df[df['close'] == df['high_limit']]
- if len(df) > 0:
- new_list.append(stock)
- return new_list
-
- def get_recent_down_up_stock(context, stock_list, recent_days):
- """获取最近指定天数内出现过跌停的股票。"""
- stat_date = context.previous_date
- new_list = []
- for stock in stock_list:
- df = get_price(stock, end_date=stat_date, frequency='daily', fields=['close','low_limit'], count=recent_days, panel=False, fill_paused=False)
- df = df[df['close'] == df['low_limit']]
- if len(df) > 0:
- new_list.append(stock)
- return new_list
- #1-2 选股模块
- def get_stock_list(context):
- """
- 小市值选股逻辑:
- 针对399101里的股票,过滤次新、科创北交、st,先获取实质5-30的最小市值100支股票
- 再过滤掉停牌、当前涨停和跌停的股票
- 最后获得市值最小的50支股票
- """
- final_list = []
- MKT_index = '399101.XSHE'
- initial_list = get_index_stocks(MKT_index)
- initial_list = filter_new_stock(context, initial_list)
- initial_list = filter_kcbj_stock(initial_list)
- initial_list = filter_st_stock(initial_list)
-
- q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(initial_list),valuation.market_cap.between(5,30)).order_by(valuation.market_cap.asc())
- df_fun = get_fundamentals(q)
- df_fun = df_fun[:100]
-
- initial_list = list(df_fun.code)
- initial_list = filter_paused_stock(initial_list)
- initial_list = filter_limitup_stock(context, initial_list)
- initial_list = filter_limitdown_stock(context, initial_list)
- #log.info('initial_list has {} stocks'.format(len(initial_list)))
- q = query(valuation.code,valuation.market_cap).filter(valuation.code.in_(initial_list)).order_by(valuation.market_cap.asc())
- df_fun = get_fundamentals(q)
- df_fun = df_fun[:50]
- final_list = list(df_fun.code)
- return final_list
- #1-2 选股模块
- def get_stock_list_2(context):
- """
- 1. 获取综小中指的成分股 initial_list。
- 2. 对 initial_list 进行一系列过滤操作:
- - 过滤掉新股。
- - 过滤掉科创北交股票。
- - 过滤掉ST股票。
- 5. 构建查询条件 q,筛选出符合以下条件的股票:
- - 股票代码在 initial_list 中。
- - 总市值在5亿到30亿之间。
- - 归属于母公司所有者的净利润为正。
- - 净利润为正。
- - 营业收入大于1亿。
- 6. 按总市值升序排序,限制返回最多50只股票。
- 7. 获取基本面数据 df,并从中提取股票代码列表 final_list。
- 8. 获取 final_list 中股票最近一天的收盘价。
- 9. 返回符合以下条件的股票列表:
- - 当前持有的股票。
- - 最近一天收盘价不高于全局变量 g.highest 的股票。
- """
- final_list = []
- MKT_index = '399101.XSHE'
- initial_list = get_index_stocks(MKT_index)
- initial_list = filter_new_stock(context, initial_list)
- initial_list = filter_kcbj_stock(initial_list)
- initial_list = filter_st_stock(initial_list)
- # 国九更新:过滤近一年净利润为负且营业收入小于1亿的
- # 国九更新:过滤近一年期末净资产为负的 (经查询没有为负数的,所以直接pass这条)
- # 国九更新:过滤近一年审计建议无法出具或者为负面建议的 (经过净利润等筛选,审计意见几乎不会存在异常)
- q = query(
- valuation.code,
- valuation.market_cap, # 总市值 circulating_market_cap/market_cap
- income.np_parent_company_owners, # 归属于母公司所有者的净利润
- income.net_profit, # 净利润
- income.operating_revenue # 营业收入
- #security_indicator.net_assets
- ).filter(
- valuation.code.in_(initial_list),
- valuation.market_cap.between(5,30),
- income.np_parent_company_owners > 0,
- income.net_profit > 0,
- income.operating_revenue > 1e8
- ).order_by(valuation.market_cap.asc()).limit(50)
-
- df = get_fundamentals(q)
-
- final_list = list(df.code)
- last_prices = history(1, unit='1d', field='close', security_list=final_list)
-
- return [stock for stock in final_list if stock in g.hold_list or last_prices[stock][-1] <= g.highest]
-
- def SMALL(context, choice):
- """
- 获取经过多重筛选后的股票列表,并根据财务指标进行进一步筛选。
- 1. 获取两组初步筛选的股票列表 target_list_1 和 target_list_2。
- 2. 合并去重这两组股票列表,得到 target_list。
- 3. 限制 target_list 的长度为全局变量 g.stock_num 的三倍。
- 4. 构建查询条件,筛选出符合以下条件的股票:
- - 股票代码在 target_list 中。
- - 按总市值升序排序。
- 5. 获取基本面数据,并提取股票代码列表 final_list。
- 6. 返回最终筛选的股票列表 final_list。
- """
- target_list_1 = get_stock_list(context)
- target_list_2 = get_stock_list_2(context)
- target_list= list(dict.fromkeys(target_list_1 + target_list_2))
- target_list=target_list[:g.stock_num*3]
- #target_list = get_stock_list_2(context)[:g.stock_num*3]
- final_list = get_fundamentals(query(
- valuation.code,
- indicator.roe,
- indicator.roa,
- ).filter(
- valuation.code.in_(target_list),
- #valuation.pb_ratio<1
- ).order_by(
- valuation.market_cap.asc()
- )).set_index('code').index.tolist()
- return final_list
-
- def singal(context):
- """
- 1. 首先获得B_stocks,沪深300指数的成分股和S_stocks,中小综指的成分股,去掉科创北上、st、次新股
- 2. 从B_stocks当中获得市值最大的20支股,从S_stocks当中获得市值最小的20支股
- 3. 针对B_stocks和S_stocks,使用最新的价格和过去的价格比值*100,各自获得一个平均值B_mean,S_mean
- 4. g.signal的4种情况
- - B_mean>S_mean且B_mean>5:small
- - B_mean>S_mean且5>=B_mean>0:big
- - B_mean<S_mean且S_mean>0:small
- - 所有其他:etf (黄金或外盘)
-
- 返回:
- - g.singal: small, big, etf三种投资标的的选择
- """
- today = context.current_dt
- dt_last = context.previous_date
- N=10
- B_stocks = get_index_stocks('000300.XSHG', dt_last)
- B_stocks = filter_kcbj_stock(B_stocks)
- B_stocks = filter_st_stock(B_stocks)
- B_stocks = filter_new_stock(context, B_stocks)
-
- S_stocks = get_index_stocks('399101.XSHE', dt_last)
- S_stocks = filter_kcbj_stock(S_stocks)
- S_stocks = filter_st_stock(S_stocks)
- S_stocks = filter_new_stock(context, S_stocks)
-
- q = query(
- valuation.code, valuation.circulating_market_cap
- ).filter(
- valuation.code.in_(B_stocks)
- ).order_by(
- valuation.circulating_market_cap.desc()
- )
- df = get_fundamentals(q, date=dt_last)
- Blst = list(df.code)[:20]
-
- q = query(
- valuation.code, valuation.circulating_market_cap
- ).filter(
- valuation.code.in_(S_stocks)
- ).order_by(
- valuation.circulating_market_cap.asc()
- )
- df = get_fundamentals(q, date=dt_last)
- Slst = list(df.code)[:20]
- #
- B_ratio = get_price(Blst, end_date=dt_last, frequency='1d', fields=['close'], count=N, panel=False
- ).pivot(index='time', columns='code', values='close')
- change_BIG = (B_ratio.iloc[-1] / B_ratio.iloc[0] - 1) * 100
- A1 = np.array(change_BIG)
- A1 = np.nan_to_num(A1)
- B_mean = np.mean(A1)
- # log.info(f"B_ratio: {B_ratio}")
- # log.info(f"change_BIG: {change_BIG}")
- # log.info(f"A1: {A1}")
- # log.info(f"B_mean: {B_mean}")
-
- S_ratio = get_price(Slst, end_date=dt_last, frequency='1d', fields=['close'], count=N, panel=False
- ).pivot(index='time', columns='code', values='close')
- change_SMALL = (S_ratio.iloc[-1] / S_ratio.iloc[0] - 1) * 100
- A1 = np.array(change_SMALL)
- A1 = np.nan_to_num(A1)
- S_mean = np.mean(A1)
- if B_mean>S_mean and B_mean>0:
- if B_mean>5:
- g.signal='small'
- log.info('Big has ended, change to Small')
- else:
- g.signal='big'
- log.info('Big')
- elif B_mean < S_mean and S_mean > 0:
- g.signal='small'
- log.info('Small')
- else:
- log.info('Foreign')
- g.signal='etf'
-
- # 1-3 整体调整持仓
- def monthly_adjustment(context):
- """
- 每月(目前是每周)进行整体持仓调整,根据市场信号调整股票组合,进行买入和卖出操作。
- 1. 根据全局信号 g.signal 确定调整策略:
- - 如果信号为 'big',调用 White_Horse 方法获取大市值股票的目标列表。
- - 如果信号为 'small',从中小综指获取成分股,过滤掉科创板、ST和次新股后,调用 SMALL 方法获取小市值股票的目标列表。
- - 如果信号为 'etf',使用全局变量 g.foreign_ETF 作为目标列表。
- - 如果信号不在预期范围内,打印提示信息。
- 5. 打印目标股票列表 target_list。
- 6. 对目标列表进行进一步的过滤:
- - 使用 filter_limitup_stock 过滤掉涨停的股票。
- - 使用 filter_limitdown_stock 过滤掉跌停的股票。
- - 使用 filter_paused_stock 过滤掉停牌的股票。
- 7. 遍历当前持仓的股票 g.hold_list:
- - 对于不在目标列表和昨日持仓列表 g.yesterday_HL_list 中的股票,平掉其仓位。
- 8. 获取当前持仓数量 position_count 和目标股票数量 target_num。
- 9. 如果目标股票数量大于当前持仓数量:
- - 计算每个新股票的投资金额 value 为当前现金除以新增目标股票数量。
- - 遍历目标股票列表 target_list,对于未持有的股票,尝试开仓。
- - 如果开仓成功且持仓数量达到目标数量,停止操作。
- """
- log.info("Make weekly overall position adjustments")
- today = context.current_dt
- dt_last = context.previous_date
- target_list=[]
- log.info(f"g.signal: {g.signal}")
- if g.signal=='big':
- target_list = White_Horse(context)
-
- elif g.signal=='small':
- S_stocks = get_index_stocks('399101.XSHE', dt_last)
- S_stocks = filter_kcbj_stock(S_stocks)
- S_stocks = filter_st_stock(S_stocks)
- S_stocks = filter_new_stock(context, S_stocks)
- choice = S_stocks
- target_list = SMALL(context,choice)
-
- elif g.signal=='etf':
- target_list = g.foreign_ETF
- else:
- log.info("g.signal is not the one expected")
-
- log.info(f"target_list before filter: {target_list}")
- target_list = filter_limitup_stock(context,target_list)
- target_list = filter_limitdown_stock(context,target_list)
- target_list = filter_paused_stock(target_list)
- log.info(f"target_list after filter: {target_list}")
- # target_list = target_list[:5] # 可以控制持股数量到5
- for stock in g.hold_list:
- if (stock not in target_list) and (stock not in g.yesterday_HL_list):
- position = context.portfolio.positions[stock]
- close_position(position)
- position_count = len(context.portfolio.positions)
- target_num = len(target_list)
- if target_num > position_count:
- value = context.portfolio.cash / (target_num - position_count)
- for stock in target_list:
- if stock not in list(context.portfolio.positions.keys()):
- if open_position(stock, value):
- if len(context.portfolio.positions) == target_num:
- break
-
- def boll_filter(stocks,date):
- """
- 使用布林带策略过滤股票列表,返回符合条件的股票。
- 1. 获取指定日期 date 的股票数据,包括高价、高价和收盘价。
- - 使用 get_bars 函数获取 stocks 列表中每只股票的最近一天数据。
- - 将数据的索引设置为股票代码。
- 2. 计算布林带指标:
- - 使用 Bollinger_Bands 函数计算上轨(upperband)、中轨(middleband)和下轨(lowerband)。
- - 参数设置为:时间周期为20,标准差倍数为2,单位为每日。
- 3. 将布林带指标结果添加到数据框 x 中:
- - 将 upperband、middleband 和 lowerband 的结果分别存储到 x 数据框中的 'up'、'mid' 和 'lowe' 列。
- 4. 过滤符合条件的股票:
- - 筛选出收盘价低于上轨且最低价高于下轨的股票。
- - 这些股票符合布林带策略的买入信号。
- 5. 返回符合条件的股票代码列表。
- 参数:
- - stocks: 股票代码列表。
- - date: 筛选的日期。
- 返回:
- - 符合布林带策略条件的股票代码列表。
- """
- x=get_bars(stocks, 1, unit='1d', fields=['high','low','close'],end_dt=date,df=True)
- x.index=stocks
- upperband, middleband, lowerband=Bollinger_Bands(stocks, date, timeperiod=20,
- nbdevup=2, nbdevdn=2, unit = '1d', include_now = True, fq_ref_date = None)
- log.info(f"result for bollinger_bands, upperband: {type(upperband)}, {upperband}; middleband: {type(middleband)}, {middleband}; lowerband: {type(lowerband)}, {lowerband}")
- x['up']= pd.DataFrame(upperband, index=[0]).T.values
- x['mid']=pd.DataFrame(middleband, index=[0]).T.values
- x['lowe']=pd.DataFrame(lowerband, index=[0]).T.values
- x=x[(x['close']<x['up'])&(x['lowe']<x['low'])]
- return(list(x.index))
- # 3-1 交易模块-自定义下单
- def order_target_value_(security, value):
- if value == 0:
- log.info("Selling out %s" % (security))
- else:
- log.info("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
- 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)]
- ## 开盘前运行函数
- def White_Horse(context):
- """
- 该方法根据市场温度筛选出符合特定财务指标的股票列表。
- 1. 首先调用 Market_temperature 函数获取市场温度。
- 2. 初始化一个空列表 check_out_lists 用于存储筛选出的股票。
- 3. 获取当前的股票数据 current_data,以及200天前的所有股票列表 all_stocks。
- 4. 将 all_stocks 更新为沪深300指数的成分股。
- 5. 根据以下条件过滤股票:
- - 排除涨停或跌停开盘的股票。
- - 排除停牌的股票。
- - 排除 ST、*ST、退市风险股。
- - 排除创业板、科创板、北交所的股票。
- 6. 根据市场温度(冷、暖、热)设置不同的筛选条件和排序规则:
- - 冷:筛选市净率(PB)在0到1之间,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于2.0,收益增长率大于1.5,净利润同比增长率大于-15的股票,并按 ROA/PB 降序排列。
- - 暖:筛选市净率(PB)在0到1之间,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于1.0,收益增长率大于2.0,净利润同比增长率大于0的股票,并按 ROA/PB 降序排列。
- - 热:筛选市净率(PB)大于3,经营活动现金流入大于0,调整后利润大于0,现金流入与调整后利润比大于0.5,收益增长率大于3.0,净利润同比增长率大于20的股票,并按 ROA 降序排列。
- 7. 将符合条件的股票代码存入 check_out_lists。
- 8. 返回筛选出的股票列表 check_out_lists,并记录日志信息。
- """
- Market_temperature(context)
- log.info(f"This month's temperature is:{g.market_temperature}")
- check_out_lists = []
- current_data = get_current_data()
- check_date = context.previous_date - datetime.timedelta(days=200)
- # all_stocks = list(get_all_securities(date=check_date).index)
- # log.info(f"first all_stocks: {len(all_stocks)}, {all_stocks}")
- all_stocks = get_index_stocks("000300.XSHG")
- # log.info(f"second all_stocks: {len(all_stocks)}, {all_stocks}")
- # 过滤创业板、ST、停牌、当日涨停
- all_stocks = [stock for stock in all_stocks if not (
- (current_data[stock].day_open == current_data[stock].high_limit) or # 涨停开盘
- (current_data[stock].day_open == current_data[stock].low_limit) or # 跌停开盘
- current_data[stock].paused or # 停牌
- current_data[stock].is_st or # ST
- ('ST' in current_data[stock].name) or
- ('*' in current_data[stock].name) or
- ('退' in current_data[stock].name) or
- (stock.startswith('30')) or # 创业
- (stock.startswith('68')) or # 科创
- (stock.startswith('8')) or # 北交
- (stock.startswith('4')) # 北交
- )]
- if g.market_temperature == "cold":
- q = query(
- valuation.code,
- ).filter(
- valuation.pb_ratio > 0,
- valuation.pb_ratio < 1,
- cash_flow.subtotal_operate_cash_inflow > 0,
- indicator.adjusted_profit > 0,
- cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>2.0,
- indicator.inc_return > 1.5,
- indicator.inc_net_profit_year_on_year > -15,
- valuation.code.in_(all_stocks)
- ).order_by(
- (indicator.roa/valuation.pb_ratio).desc()
- ).limit(
- g.buy_stock_count + 1
- )
- elif g.market_temperature == "warm":
- q = query(
- valuation.code,
- ).filter(
- valuation.pb_ratio > 0,
- valuation.pb_ratio < 1,
- cash_flow.subtotal_operate_cash_inflow > 0,
- indicator.adjusted_profit > 0,
- cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>1.0,
- indicator.inc_return > 2.0,
- indicator.inc_net_profit_year_on_year > 0,
- valuation.code.in_(all_stocks)
- ).order_by(
- (indicator.roa/valuation.pb_ratio).desc()
- ).limit(
- g.buy_stock_count + 1
- )
- elif g.market_temperature == "hot":
- q = query(
- valuation.code,
- ).filter(
-
- valuation.pb_ratio > 3,
- cash_flow.subtotal_operate_cash_inflow > 0,
- indicator.adjusted_profit > 0,
- cash_flow.subtotal_operate_cash_inflow/indicator.adjusted_profit>0.5,
- indicator.inc_return > 3.0,
- indicator.inc_net_profit_year_on_year > 20,
- valuation.code.in_(all_stocks)
- ).order_by(
- indicator.roa.desc()
- ).limit(
- g.buy_stock_count + 1
- )
-
- check_out_lists = list(get_fundamentals(q).code)
- # 取需要的只数
- #check_out_lists = check_out_lists[:g.buy_stock_count]
- log.info("Today's stock pool:%s" % check_out_lists)
- return check_out_lists
- # tttttttttttt
-
- def Market_temperature(context):
- """
- 根据沪深300过去220天的收盘价,计算一个权重market_height:
- (最新5个的平均值-最小值)/(最大值-最小值)
- - 冷:market_height小于0.2,temp为200
- - 热:market_height大于0.9,temp为400
- - 温:最新60个的平均值/最小值>1.2,temp为300
- """
-
- index300 = attribute_history('000300.XSHG', 220, '1d', ('close'), df=False)['close']
- market_height = (mean(index300[-5:]) - min(index300)) / (max(index300) - min(index300))
- if market_height < 0.20:
- g.market_temperature = "cold"
- elif market_height > 0.90:
- g.market_temperature = "hot"
- elif max(index300[-60:]) / min(index300) > 1.20:
- g.market_temperature = "warm"
-
- if g.market_temperature == "cold":
- temp = 200
- elif g.market_temperature == "warm":
- temp = 300
- else:
- temp = 400
-
- if context.run_params.type != 'sim_trade': # 不是模拟交易
- record(temp=temp)
|