''' 设定的市值17—26亿 可以根据指数的变化来更改 比如3300点集中在15—25亿市值最小的四十只 3400点 集中在17—27亿 对应指数乘以一个系数 对应市值选出40只 ''' from jqdata import * from datetime import datetime, timedelta ## 初始化函数,设定要操作的股票、基准等等 def initialize(context): set_benchmark('000852.XSHG') #对标中证1000 # True为开启动态复权模式,使用真实价格交易 set_option('use_real_price', True) # 设定成交量比例 set_option('order_volume_ratio', 1) # 股票类交易手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱 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') # 交易日计时器 g.days = 0 # 分仓常量参数,无须人为修改 g.TR = 1; # 区间最高价 g.summit = {} g.muster = [] # 运行状态 1/运行; 0/停运 g.OpenYN = 1 # 开始范围 #g.mystart = 13 # 截至范围 #g.myend = 23 # 调仓频率 g.refresh_rate = 15 # 运行函数1 run_daily(trade, time='10:18') # 运行函数2 run_daily(CPtrade, time='10:17') # 持仓数量 g.stocknum = 40 # 上证指数对应系数 g.XS = 4/500 #止盈比率 g.CloseRate = 1.07 #止损比率 g.LossRate = 0.93 # 均线上涨比率 g.RSIRate = 1.0001 # 保证金仓位比率: 1/2为半仓 g.TradeRate = 0.5 # 调试日志展示数量 g.debug_log_limit = 60 g.debug_boundary_window = 5 g.market_cap_map = {} def _fmt_num(value, digits=2): try: return ('%.' + str(digits) + 'f') % float(value) except Exception: return str(value) def _safe_bar_values(bar_data, field): try: values = bar_data[field] except Exception: values = getattr(bar_data, field) try: return [float(x) for x in list(values)] except Exception: try: return list(values) except Exception: return [] def _safe_bar_last(bar_data, field): values = _safe_bar_values(bar_data, field) if values: return values[-1] return None def _market_cap_text(stock): cap = getattr(g, 'market_cap_map', {}).get(stock) if cap is None: return 'None' return _fmt_num(cap, 2) + '亿' def _describe_stock(stock, extras=None): parts = [stock, '市值=' + _market_cap_text(stock)] if extras: parts.extend(extras) return ','.join(parts) def _log_detail_items(label, details, limit=None, chunk=10): total = len(details) if total == 0: log.info(label + ': 0') return if limit is None: limit = total shown = details[:limit] log.info('%s: 总数=%d, 展示=%d' % (label, total, len(shown))) for idx in range(0, len(shown), chunk): part = shown[idx:idx + chunk] log.info('%s[%d-%d]: %s' % ( label, idx + 1, idx + len(part), ' | '.join(part) )) if total > limit: log.info('%s: 其余%d项省略' % (label, total - limit)) def _log_market_cap_snapshot(df, caller): if df is None or len(df) == 0: log.info('市值筛选快照[%s]: 无数据' % caller) return top_n = min(len(df), g.debug_log_limit) top_details = [] for i in range(top_n): row = df.iloc[i] top_details.append('%d.%s:%s亿' % ( i + 1, row['code'], _fmt_num(row['market_cap'], 2) )) _log_detail_items('市值排序前段[%s]' % caller, top_details, limit=top_n, chunk=8) start_idx = max(0, g.stocknum - g.debug_boundary_window - 1) end_idx = min(len(df), g.stocknum + g.debug_boundary_window) boundary_details = [] for i in range(start_idx, end_idx): row = df.iloc[i] boundary_details.append('%d.%s:%s亿' % ( i + 1, row['code'], _fmt_num(row['market_cap'], 2) )) _log_detail_items('市值排序边界[%s]' % caller, boundary_details, limit=len(boundary_details), chunk=5) ## 选出小市值股票 def check_stocks(context, caller='unknown'): g.today = context.current_dt validate_date() #不停运参数 g.OpenYN = 1 log.info('选股开始[%s]: 日期=%s, g.days=%d, refresh_rate=%d, 持仓数=%d' % ( caller, context.current_dt.strftime('%Y-%m-%d %H:%M:%S'), g.days, g.refresh_rate, len(context.portfolio.positions) )) # 检查日期是否在范围内 if g.OpenYN == 0: log.warn("该时段属于停运==================范围") return [] else: # g.OpenYN = 1 g.security = '000001.XSHG' #000852.XSHG close_data = get_bars(g.security, count=1, unit='1d', fields=['close']) # 取得过去五天的平均价格 MA5 = close_data['close'].mean() ################################################### close5_data = get_bars(g.security, count=5, unit='1d', fields=['close']) # 获取股票的收盘价 close10_data = get_bars(g.security, count=10, unit='1d', fields=['close']) # 取得过去五天的平均价格 MA5 = close5_data['close'].mean() # 取得过去十天的平均价格 MA10 = close10_data['close'].mean() #5日线下穿,则半仓交易 if MA5 < MA10*g.RSIRate: g.TR = g.TradeRate elif MA5 >= MA10*g.RSIRate: g.TR = 1 ################################################### close5_list = _safe_bar_values(close5_data, 'close') log.info('指数均线调试[%s][%s] close[-5:]=%s, ma5=%s, ma10=%s, ma5 0: #Y = (current_price - 3000) *g.XS + 14 Y = (current_price - 2000) *g.XS + 7 g.mystart = Y g.myend = Y + 10 # mystart = g.start # myend = g.end mystart = round(g.mystart) myend = round(g.myend) log.info('价格区间为:'+ str(mystart) + '~'+str(myend)) # 设定查询条件 q = query( valuation.code, valuation.market_cap ).filter( valuation.market_cap.between(mystart,myend) ).order_by( valuation.market_cap.asc() ) # 选出低市值的股票,构成buylist df = get_fundamentals(q) g.market_cap_map = {} if df is not None and len(df) > 0: for _, row in df.iterrows(): try: g.market_cap_map[row['code']] = float(row['market_cap']) except Exception: g.market_cap_map[row['code']] = None log.info('市值筛选结果[%s]: %d只股票' % (caller, 0 if df is None else len(df))) _log_market_cap_snapshot(df, caller) buylist =list(df['code']) # 过滤停牌,ST,科创,新股,1元股 buylist = filter_paused_stock(buylist, caller=caller) final_list = buylist[:g.stocknum] g.muster = final_list _log_detail_items( '最终选股[%s]' % caller, [_describe_stock(stock) for stock in final_list], limit=len(final_list), chunk=8 ) boundary_slice = buylist[max(0, g.stocknum - g.debug_boundary_window): min(len(buylist), g.stocknum + g.debug_boundary_window)] _log_detail_items( '最终选股边界[%s]' % caller, [_describe_stock(stock) for stock in boundary_slice], limit=len(boundary_slice), chunk=5 ) return final_list def before_trading_start(context): # 取得当前日期 g.todayDT = context.current_dt g.today = context.current_dt.strftime('%Y-%m-%d') g.start = context.current_dt + timedelta(-2) g.market_cap_map = {} ## 交易函数 def CPtrade(context): ## 选股 stock_list = check_stocks(context, caller='CPtrade') if g.OpenYN == 0: #log.warn("日期属于范围") ## 获取持仓列表 sell_list = list(context.portfolio.positions.keys()) # 如果有持仓,则卖出 if len(sell_list) > 0 : for stock in sell_list: order_target_value(stock, 0) return ## 交易函数 def trade(context): ## 选股 stock_list = check_stocks(context, caller='trade') if g.OpenYN == 0: #log.warn("日期属于范围") ## 获取持仓列表 sell_list = list(context.portfolio.positions.keys()) # 如果有持仓,则卖出 if len(sell_list) > 0 : for stock in sell_list: order_target_value(stock, 0) return g.changeYN = 0 curr_data = get_current_data() log.info('交易调试[trade]: 日期=%s, g.days=%d, refresh_hit=%s, TR=%s, cash=%s, total_value=%s, 持仓数=%d, 目标池数=%d' % ( context.current_dt.strftime('%Y-%m-%d %H:%M:%S'), g.days, str(g.days % g.refresh_rate == 0), _fmt_num(g.TR, 4), _fmt_num(context.portfolio.cash, 2), _fmt_num(context.portfolio.total_value, 2), len(context.portfolio.positions), len(stock_list) )) _log_detail_items( '交易目标池[trade]', [_describe_stock(stock) for stock in stock_list], limit=len(stock_list), chunk=8 ) # -------------------------------------------------------------------------- for stockPos in context.portfolio.positions: # SS、记录股票峰值信息 # if g.summit.get(stock, 0) g.CloseRate) if (avg_cost and grid_high_limit is not None) else False log.info('止盈止损评估[%s] %s: qty=%s, avg_cost=%s, current=%s, return=%s%%, stop_threshold=%s, profit_threshold=%s, high_limit=%s, bar.close=%s, paused=%s, in_target=%s, stop_hit=%s, profit_hit=%s' % ( context.current_dt.strftime('%Y-%m-%d'), stockPos, str(getattr(hold, 'total_amount', getattr(hold, 'amount', 'None'))), _fmt_num(avg_cost, 4), _fmt_num(current_price, 4), _fmt_num(return_ratio * 100, 2), _fmt_num(g.LossRate, 4), _fmt_num(g.CloseRate, 4), _fmt_num(grid_high_limit, 4), _fmt_num(grid_close, 4), str(grid_paused), str(stockPos in stock_list), str(stop_hit), str(profit_hit) )) if stop_hit: order_target_value(stockPos, 0) g.changeYN = 1 log.error('止损清仓:%s,当前价=%.2f,成本价=%.2f,收益率=%.2f%%,high_limit=%s,bar.close=%s' % ( stockPos, current_price, hold.avg_cost, return_ratio * 100, _fmt_num(grid_high_limit, 4), _fmt_num(grid_close, 4) )) # S3、今日高开、今日未涨停则清仓 elif profit_hit: order_target_value(stockPos, 0) g.changeYN = 1 log.warn('止盈清仓:%s,当前价=%s,成本价=%s,收益率=%s%%,high_limit=%s,bar.close=%s' % ( stockPos, _fmt_num(current_price, 4), _fmt_num(hold.avg_cost, 4), _fmt_num(return_ratio * 100, 2), _fmt_num(grid_high_limit, 4), _fmt_num(grid_close, 4) )) else: g.changeYN = 0 if g.changeYN == 1: sell_list = list(context.portfolio.positions.keys()) replacement_candidates = [stock for stock in stock_list if stockPos != stock and stock not in sell_list] _log_detail_items( '补仓候选[%s][%s]' % (context.current_dt.strftime('%Y-%m-%d'), stockPos), [_describe_stock(stock) for stock in replacement_candidates], limit=min(len(replacement_candidates), g.debug_log_limit), chunk=6 ) # log.warn('portfoliocash:' + str(context.portfolio.cash)) for stock in stock_list: if len(context.portfolio.positions.keys()) < g.stocknum: ## 获取持仓列表 sell_list = list(context.portfolio.positions.keys()) if stockPos != stock and stock not in sell_list : ## 分配资金 if len(context.portfolio.positions) < g.stocknum : Num = g.stocknum - len(context.portfolio.positions) Cash = context.portfolio.cash * g.TR / Num #gridbuy = get_price(stock, start_date=g.start, end_date=g.today, fields=['open', 'high', 'low_limit', 'close', 'high_limit']) #if gridbuy.open[-1] > gridbuy.low_limit[-1]: log.info('补仓买入:卖出=%s,买入=%s,Cash=%s,Num=%d,TR=%s,持仓数=%d' % ( stockPos, stock, _fmt_num(Cash, 2), Num, _fmt_num(g.TR, 4), len(context.portfolio.positions) )) order_value(stock, Cash) #else : # log.warn("忽略跌停股票:" + stock) break else: continue if g.days%g.refresh_rate == 0: log.info('定期调仓触发:日期=%s,g.days=%d,refresh_rate=%d,TR=%s' % ( context.current_dt.strftime('%Y-%m-%d'), g.days, g.refresh_rate, _fmt_num(g.TR, 4) )) ## 获取持仓列表 sell_list = list(context.portfolio.positions.keys()) rebalance_sell_list = [stock for stock in sell_list if stock not in stock_list] _log_detail_items( '定期调仓卖出名单', [_describe_stock(stock) for stock in rebalance_sell_list], limit=len(rebalance_sell_list), chunk=8 ) # 如果有持仓,则卖出 if len(sell_list) > 0 : for stock in sell_list: if stock not in stock_list: log.info('定期调仓卖出:%s' % stock) order_target_value(stock, 0) ## 分配资金 if len(context.portfolio.positions) < g.stocknum : Num = g.stocknum - len(context.portfolio.positions) # Cash = context.portfolio.cash/Num * g.TR Cash = context.portfolio.cash * g.TR / g.stocknum else: Cash = 0 log.info('定期调仓资金分配:Cash=%s,Num=%s,cash=%s,total_value=%s' % ( _fmt_num(Cash, 2), str(Num if len(context.portfolio.positions) < g.stocknum else 0), _fmt_num(context.portfolio.cash, 2), _fmt_num(context.portfolio.total_value, 2) )) ## 买入股票 for stock in stock_list: if len(context.portfolio.positions.keys()) < g.stocknum: if stock not in sell_list: log.info('定期调仓买入:%s,Cash=%s' % (stock, _fmt_num(Cash, 2))) order_value(stock, Cash) # 天计数加一 g.days = 1 else: g.days += 1 # 过滤日期 # 一月十号到三十一号 # 四月十号到四月二十九日 期间 # 八月十日到八月三十一日 # 十月二十日 到十月三十日 def validate_date(): # date = g.todayDT.strftime('%Y-%m-%d') # date = datetime.strptime(g.todayDT, "%Y-%m-%d") date = g.today # 检查是否是4月10日到4月29日之间 if date.month == 1 and 15 <= date.day <= 30: g.OpenYN = 0 elif date.month == 4 and 15 <= date.day <= 29: g.OpenYN = 0 elif date.month == 8 and 15 <= date.day <= 31: g.OpenYN = 0 elif date.month == 10 and 20 <= date.day <= 30: g.OpenYN = 0 elif date.month == 12 and 20 <= date.day <= 30: g.OpenYN = 0 else : g.OpenYN = 1 # 过滤停牌股票 def filter_paused_stock(stock_list, caller='unknown'): curr_data = get_current_data() raw_count = len(stock_list) risk_filtered = [] risk_removed = [] for stock in stock_list: reasons = [] if curr_data[stock].day_open == curr_data[stock].high_limit: reasons.append('涨停开盘') if curr_data[stock].day_open == curr_data[stock].low_limit: reasons.append('跌停开盘') if curr_data[stock].last_price == curr_data[stock].high_limit: reasons.append('当前涨停') if curr_data[stock].last_price == curr_data[stock].low_limit: reasons.append('当前跌停') if curr_data[stock].paused: reasons.append('停牌') if curr_data[stock].is_st: reasons.append('ST') if 'ST' in curr_data[stock].name: reasons.append('名称含ST') if '*' in curr_data[stock].name: reasons.append('名称含*') if '退' in curr_data[stock].name: reasons.append('名称含退') if stock.startswith('688'): reasons.append('科创板') if reasons: risk_removed.append(_describe_stock(stock, [ '原因=' + '/'.join(reasons), 'open=' + _fmt_num(curr_data[stock].day_open, 2), 'last=' + _fmt_num(curr_data[stock].last_price, 2), 'high_limit=' + _fmt_num(curr_data[stock].high_limit, 2), 'low_limit=' + _fmt_num(curr_data[stock].low_limit, 2), 'name=' + str(curr_data[stock].name) ])) else: risk_filtered.append(stock) log.info('风险过滤[%s]: %d -> %d, 移除=%d' % ( caller, raw_count, len(risk_filtered), len(risk_removed) )) _log_detail_items( '风险过滤移除[%s]' % caller, risk_removed, limit=min(len(risk_removed), g.debug_log_limit), chunk=4 ) one_yuan_filtered = [] one_yuan_removed = [] for stock in risk_filtered: if curr_data[stock].day_open > 1: one_yuan_filtered.append(stock) else: one_yuan_removed.append(_describe_stock(stock, [ '原因=1元股过滤', 'open=' + _fmt_num(curr_data[stock].day_open, 2), 'last=' + _fmt_num(curr_data[stock].last_price, 2) ])) log.info('1元股过滤[%s]: %d -> %d, 移除=%d' % ( caller, len(risk_filtered), len(one_yuan_filtered), len(one_yuan_removed) )) _log_detail_items( '1元股过滤移除[%s]' % caller, one_yuan_removed, limit=min(len(one_yuan_removed), g.debug_log_limit), chunk=4 ) new_list = [] ma_pass_details = {} ma_removed_details = [] for stock in one_yuan_filtered: # 获取股票的收盘价 close5_data = get_bars(stock, count=5, unit='1d', fields=['close']) # 获取股票的收盘价 close10_data = get_bars(stock, count=10, unit='1d', fields=['close']) # 获取股票的收盘价 close20_data = get_bars(stock, count=20, unit='1d', fields=['close']) # 取得过去五天的平均价格 MA5 = close5_data['close'].mean() # 取得过去十天的平均价格 MA10 = close10_data['close'].mean() # 取得过去二十天的平均价格 MA20 = close20_data['close'].mean() # 取得上一时间点价格 # current_price = close_data['close'][-1] close5_list = [round(float(v), 2) for v in _safe_bar_values(close5_data, 'close')] ma_condition_1 = MA5 > MA10 * g.RSIRate ma_condition_2 = MA10 > MA20 detail = _describe_stock(stock, [ 'close[-5:]=' + str(close5_list), 'ma5=' + _fmt_num(MA5, 4), 'ma10=' + _fmt_num(MA10, 4), 'ma20=' + _fmt_num(MA20, 4), 'ma5>ma10*RSIRate=' + str(ma_condition_1), 'ma10>ma20=' + str(ma_condition_2) ]) if MA5 > MA10*g.RSIRate> MA20*g.RSIRate: new_list.append(stock) ma_pass_details[stock] = detail else: ma_removed_details.append(detail) log.info('均线过滤[%s]: %d -> %d, 移除=%d' % ( caller, len(one_yuan_filtered), len(new_list), len(ma_removed_details) )) _log_detail_items( '均线过滤移除[%s]' % caller, ma_removed_details, limit=min(len(ma_removed_details), g.debug_log_limit), chunk=3 ) ma_boundary_details = [ma_pass_details[stock] for stock in new_list[:min(len(new_list), g.stocknum + g.debug_boundary_window)]] _log_detail_items( '均线通过前段[%s]' % caller, ma_boundary_details, limit=len(ma_boundary_details), chunk=3 ) return new_list