''' 三重滤网交易系统 V2.0 by Alexander Elder (优化版) 核心改进: 1. 第一重滤网:月线MACD金叉/多头(更稳定的趋势判断) 2. 第二重滤网:日线RSI/KDJ超卖回调(放宽条件) 3. 第三重滤网:价格企稳或突破(更灵活的入场) ''' import jqdata import numpy as np ## 初始化函数 def initialize(context): set_benchmark('000300.XSHG') set_option('use_real_price', True) set_option('order_volume_ratio', 1) 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.stocknum = 10 # 增加持仓数 g.max_position_ratio = 0.98 # 第一重滤网参数(月线MACD) g.macd_fast = 12 g.macd_slow = 26 g.macd_signal = 9 # 第二重滤网参数(日线震荡 - 放宽) g.rsi_period = 14 g.rsi_oversold = 50 # 放宽到50 g.rsi_low = 35 # 极度超卖 g.kdj_oversold = 40 # 放宽到40 # 第三重滤网参数(入场确认 - 放宽) g.ma_short = 5 # 5日均线 g.ma_mid = 20 # 20日均线 # 止盈止损参数 g.stop_loss = 0.06 g.take_profit = 0.20 g.trailing_stop = 0.08 # 市值筛选(亿) g.min_market_cap = 20 g.max_market_cap = 1500 # 记录 g.highest_profit = {} g.hold_days = {} # 定时任务 run_daily(morning_screen, '09:35') run_daily(check_positions, '14:00') run_daily(afternoon_trade, '14:50') ## 早盘筛选 def morning_screen(context): g.buy_list = triple_screen_filter(context) log.info("【三重滤网】筛选出 %d 只股票" % len(g.buy_list)) ## 三重滤网筛选 def triple_screen_filter(context): # 基础选股 q = query( valuation.code, valuation.market_cap ).filter( valuation.market_cap.between(g.min_market_cap, g.max_market_cap) ).order_by( valuation.market_cap.asc() ).limit(500) df = get_fundamentals(q) if df.empty: return [] stock_list = list(df['code']) stock_list = filter_basic(stock_list) # 三重滤网筛选 candidates = [] for stock in stock_list[:150]: result = check_triple_screen(stock) if result['pass']: candidates.append((stock, result['score'])) # 按评分排序 candidates.sort(key=lambda x: x[1], reverse=True) return [c[0] for c in candidates[:g.stocknum * 3]] ## 检查三重滤网信号 def check_triple_screen(stock): """ 三重滤网检查 V2.0: 1. 第一重:月线MACD金叉/多头 2. 第二重:日线RSI/KDJ回调 3. 第三重:价格企稳或突破 """ result = {'pass': False, 'score': 0} try: # 获取更多日线数据(计算月线MACD需要约130天) df = attribute_history(stock, 150, '1d', ['close', 'high', 'low'], skip_paused=True) if len(df) < 130: return result close = df['close'].values high = df['high'].values low = df['low'].values # ========== 第一重滤网:月线MACD ========== # 模拟月线:每20天取收盘价(或用月末价格) monthly_close = [] for i in range(19, len(close), 20): monthly_close.append(close[i]) if len(monthly_close) < 6: # 数据不足,改用日线MACD判断大趋势 dif, dea, macd = calculate_macd(close) if dif[-1] <= dea[-1]: # MACD死叉 return result if dif[-1] <= 0: # DIF在零轴下方 return result else: # 计算月线MACD m_dif, m_dea, m_macd = calculate_macd(np.array(monthly_close)) # 月线MACD条件(放宽): # 1. MACD金叉(DIF上穿DEA)或 # 2. MACD柱由绿变红 或 # 3. DIF > 0 且 DIF > DEA(多头) macd_golden = m_dif[-1] > m_dea[-1] and m_dif[-2] <= m_dea[-2] macd_turn_red = m_macd[-1] > 0 and m_macd[-2] <= 0 macd_bullish = m_dif[-1] > 0 and m_dif[-1] > m_dea[-1] if not (macd_golden or macd_turn_red or macd_bullish): return result result['score'] += 30 # 趋势分 # ========== 第二重滤网:日线震荡指标 ========== rsi = calculate_rsi(close, g.rsi_period) k, d, j = calculate_kdj(high, low, close) # RSI条件(放宽): # 1. RSI < 50(相对低位)或 # 2. RSI从低位回升 rsi_current = rsi[-1] if len(rsi) > 0 else 50 rsi_prev = rsi[-2] if len(rsi) > 1 else 50 rsi_signal = (rsi_current < g.rsi_oversold) or \ (rsi_prev < g.rsi_low and rsi_current > rsi_prev) or \ (rsi_current < 60 and rsi_current > rsi_prev) # KDJ条件(放宽): # 1. K < D 后金叉 或 # 2. J < 40 或 # 3. K/D都在低位回升 kdj_golden = k[-1] > d[-1] and k[-2] <= d[-2] kdj_oversold = j[-1] < g.kdj_oversold or k[-1] < g.kdj_oversold kdj_rising = k[-1] > k[-2] and d[-1] > d[-2] and k[-1] < 60 kdj_signal = kdj_golden or kdj_oversold or kdj_rising if not (rsi_signal or kdj_signal): return result result['score'] += 30 # ========== 第三重滤网:入场确认(放宽)========== current_price = close[-1] ma5 = np.mean(close[-g.ma_short:]) ma20 = np.mean(close[-g.ma_mid:]) # 入场条件(满足任一): # 1. 价格站上5日均线 # 2. 价格接近20日均线(±3%) # 3. 价格突破近5日高点 recent_high = np.max(high[-6:-1]) price_above_ma5 = current_price > ma5 price_near_ma20 = abs(current_price - ma20) / ma20 < 0.03 price_breakout = current_price > recent_high * 0.99 if not (price_above_ma5 or price_near_ma20 or price_breakout): return result result['score'] += 40 # 额外加分 if rsi_current < 35: result['score'] += 15 if kdj_golden: result['score'] += 10 if current_price > ma5 > ma20: result['score'] += 10 result['pass'] = True return result except: return result ## 计算MACD def calculate_macd(close, fast=12, slow=26, signal=9): ema_fast = calculate_ema(close, fast) ema_slow = calculate_ema(close, slow) dif = ema_fast - ema_slow dea = calculate_ema(dif, signal) macd = (dif - dea) * 2 return dif, dea, macd ## 计算EMA def calculate_ema(data, period): ema = np.zeros(len(data)) ema[0] = data[0] multiplier = 2 / (period + 1) for i in range(1, len(data)): ema[i] = (data[i] - ema[i-1]) * multiplier + ema[i-1] return ema ## 计算RSI def calculate_rsi(close, period=14): delta = np.diff(close) gain = np.where(delta > 0, delta, 0) loss = np.where(delta < 0, -delta, 0) avg_gain = np.zeros(len(delta)) avg_loss = np.zeros(len(delta)) avg_gain[period-1] = np.mean(gain[:period]) avg_loss[period-1] = np.mean(loss[:period]) for i in range(period, len(delta)): avg_gain[i] = (avg_gain[i-1] * (period-1) + gain[i]) / period avg_loss[i] = (avg_loss[i-1] * (period-1) + loss[i]) / period rs = avg_gain / (avg_loss + 1e-10) rsi = 100 - 100 / (1 + rs) return rsi ## 计算KDJ def calculate_kdj(high, low, close, n=9, m1=3, m2=3): length = len(close) rsv = np.zeros(length) k = np.zeros(length) d = np.zeros(length) j = np.zeros(length) for i in range(n-1, length): hn = np.max(high[i-n+1:i+1]) ln = np.min(low[i-n+1:i+1]) rsv[i] = (close[i] - ln) / (hn - ln + 1e-10) * 100 k[n-1] = 50 d[n-1] = 50 for i in range(n, length): k[i] = (m1-1)/m1 * k[i-1] + 1/m1 * rsv[i] d[i] = (m2-1)/m2 * d[i-1] + 1/m2 * k[i] j[i] = 3 * k[i] - 2 * d[i] return k, d, j ## 检查持仓 def check_positions(context): if len(context.portfolio.positions) == 0: return for stock in list(context.portfolio.positions.keys()): position = context.portfolio.positions[stock] if position.closeable_amount <= 0: continue cost = position.avg_cost current_price = position.price if cost <= 0: continue profit_ratio = (current_price - cost) / cost g.hold_days[stock] = g.hold_days.get(stock, 0) + 1 if stock not in g.highest_profit: g.highest_profit[stock] = profit_ratio else: g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio) highest = g.highest_profit[stock] # === 止损 === if profit_ratio < -g.stop_loss: log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100)) order_target(stock, 0) clean_stock_data(stock) continue # === 趋势反转卖出 === if check_trend_reversal(stock): log.info("【趋势反转】%s 周线趋势转弱" % stock) order_target(stock, 0) clean_stock_data(stock) continue # === 移动止盈 === if highest >= g.take_profit: drawdown = highest - profit_ratio allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18) if drawdown >= allowed_drawdown: log.info("【移动止盈】%s 最高%.1f%% 回撤%.1f%%" % (stock, highest*100, drawdown*100)) order_target(stock, 0) clean_stock_data(stock) continue # === 分批止盈 === if profit_ratio >= g.take_profit * 2: sell_amount = int(position.closeable_amount * 0.3 / 100) * 100 if sell_amount >= 100: log.info("【止盈】%s +%.1f%%" % (stock, profit_ratio*100)) order(stock, -sell_amount) ## 检查趋势反转(使用MACD) def check_trend_reversal(stock): try: df = attribute_history(stock, 60, '1d', ['close'], skip_paused=True) if len(df) < 50: return False close = df['close'].values dif, dea, macd = calculate_macd(close) # MACD死叉且DIF < 0 if dif[-1] < dea[-1] and dif[-1] < 0: return True # MACD连续3天下降且为负 if macd[-1] < 0 and macd[-2] < 0 and macd[-3] < 0: if macd[-1] < macd[-2] < macd[-3]: return True return False except: return False ## 清理数据 def clean_stock_data(stock): for d in [g.highest_profit, g.hold_days]: if stock in d: del d[stock] ## 尾盘交易 def afternoon_trade(context): buy_stocks(context) ## 买入函数 def buy_stocks(context): if not hasattr(g, 'buy_list') or not g.buy_list: return position_count = len(context.portfolio.positions) if position_count >= g.stocknum: return available_cash = context.portfolio.available_cash * g.max_position_ratio buy_count = min(g.stocknum - position_count, len(g.buy_list)) if buy_count <= 0 or available_cash < 10000: return cash_per_stock = available_cash / buy_count bought = 0 for stock in g.buy_list: if bought >= buy_count: break if stock in context.portfolio.positions: continue # 再次确认三重滤网信号 result = check_triple_screen(stock) if result['pass']: order_value(stock, cash_per_stock) log.info("【买入】%s 评分:%.0f" % (stock, result['score'])) g.highest_profit[stock] = 0 g.hold_days[stock] = 0 bought += 1 ## 基础过滤 def filter_basic(stock_list): if not stock_list: return [] current_data = get_current_data() filtered = [] for stock in stock_list: if current_data[stock].paused: continue if current_data[stock].is_st: continue if 'ST' in current_data[stock].name or '*' in current_data[stock].name: continue if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'): continue if current_data[stock].last_price >= current_data[stock].high_limit: continue if current_data[stock].last_price <= current_data[stock].low_limit: continue filtered.append(stock) return filtered