# 聚宽回测代码 - 八条规律短线策略(放宽版)
# 回测周期:2024年10月1日 - 2026年3月31日
# 初始资金:50,000元
# 初始化函数
def initialize(context):
g.stock_num = 5 # 最多持仓5支
g.buy_price = {} # 记录买入价
g.peak_price = {} # 记录最高点(移动止盈)
# 设置参数(可调整)
g.volume_ratio = 1.5 # 倍量倍数(放宽至1.5倍)
g.liquidity_min = 30000000 # 最低成交额3000万
g.red_green_ratio = 1.3 # 红肥绿瘦:阳线成交量/阴线成交量 ≥1.3
g.red_count_ratio = 0.6 # 阳线数量占比 ≥60%
g.callback_min = 0.05 # 最小回调5%
g.callback_max = 0.10 # 最大回调10%
g.stop_loss = 0.08 # 硬止损-8%
g.trailing_stop = 0.10 # 移动止盈10%
# 设置回测参数
run_daily(trade, time='14:50') # 每天14:50执行交易
run_daily(check_sell, time='14:45') # 14:45检查卖出信号
# 检查卖出信号
def check_sell(context, data):
for stock in list(context.portfolio.positions.keys()):
current_price = data[stock].close
buy_price = g.buy_price.get(stock, current_price)
peak = g.peak_price.get(stock, current_price)
# 更新最高点
if current_price > peak:
g.peak_price[stock] = current_price
peak = current_price
# 计算盈亏
profit_rate = (current_price - buy_price) / buy_price
# === 止损:-8% ===
if profit_rate <= -g.stop_loss:
log.info(f'止损卖出 {stock},亏损{profit_rate:.2%}')
order_target(stock, 0)
continue
# === 移动止盈:从高点回撤10% ===
drawdown = (peak - current_price) / peak
if drawdown >= g.trailing_stop and profit_rate > 0:
log.info(f'移动止盈卖出 {stock},从高点回撤{drawdown:.2%}')
order_target(stock, 0)
continue
# === 规律3:放量滞涨 ===
# 获取近5日成交量
hist = attribute_history(stock, 5, '1d', ['volume'], skip_paused=True)
if len(hist) >= 5:
avg_volume = hist['volume'].mean()
today_volume = data[stock].volume
volume_ratio = today_volume / avg_volume
# 放量滞涨:成交量>均量1倍 + 涨幅<1%
if volume_ratio > 1.0 and current_price / data[stock].open < 1.01:
log.info(f'放量滞涨卖出 {stock}')
order_target(stock, 0)
continue
# === 规律8:空仓信号(个股层面) ===
# 获取5日线
hist_5 = attribute_history(stock, 5, '1d', ['close'], skip_paused=True)
if len(hist_5) >= 5:
ma5 = hist_5['close'].mean()
# 5日线拐头向下 + 收盘价在5日线下方
if len(hist_5) >= 6 and hist_5['close'].iloc[-1] < ma5 and hist_5['close'].iloc[-2] >= hist_5['close'].iloc[-3]:
log.info(f'空仓信号卖出 {stock}')
order_target(stock, 0)
# 主交易函数
def trade(context, data):
# === 大盘风控(规律8补充)===
# 获取上证指数
sh_index = get_index_stocks('000001.XSHG')
if len(sh_index) > 0:
sh_data = attribute_history('000001.XSHG', 20, '1d', ['close'], skip_paused=True)
if len(sh_data) >= 20:
ma20 = sh_data['close'].mean()
# 大盘跌破20日线且缩量 → 空仓
if sh_data['close'].iloc[-1] < ma20:
# 检查成交量是否萎缩
vol_hist = attribute_history('000001.XSHG', 3, '1d', ['volume'], skip_paused=True)
if len(vol_hist) >= 3 and vol_hist['volume'].iloc[-1] < vol_hist['volume'].iloc[-2]:
log.info('大盘破20日线+缩量,空仓')
for stock in context.portfolio.positions.keys():
order_target(stock, 0)
return
# === 卖出已有持仓(规律3/4/8已在check_sell处理)===
# 检查并卖出后,如果持仓已满5支,不再买入
if len(context.portfolio.positions) >= g.stock_num:
return
# === 筛选买入标的 ===
# 获取全市场股票池
stock_list = get_all_securities(['stock']).index.tolist()
# 排除ST、停牌、北交所
stock_list = [s for s in stock_list if not s.startswith('430') and not s.startswith('830') and not s.startswith('831') and not s.startswith('832') and not s.startswith('833') and not s.startswith('834') and not s.startswith('835') and not s.startswith('836') and not s.startswith('837') and not s.startswith('838') and not s.startswith('839')]
candidates = []
for stock in stock_list[:500]: # 限制扫描数量,加快回测速度
# 获取近20日数据
hist = attribute_history(stock, 25, '1d', ['close', 'volume', 'high', 'low', 'money'], skip_paused=True)
if len(hist) < 20:
continue
# 排除ST
if 'ST' in hist.index[-1]:
continue
close = hist['close'].values
volume = hist['volume'].values
money = hist['money'].values
current_price = close[-1]
# === 规律1补充:成交额动态调整 ===
avg_money_20 = money[-20:].mean()
if avg_money_20 < g.liquidity_min:
continue
if money[-1] < avg_money_20 * 0.8:
continue
# === 规律1:20日内有倍量阳线 ===
has_volume_spike = False
for i in range(-20, -1):
avg_5_volume = volume[i-5:i].mean() if i-5 >= -len(volume) else volume[-20:-15].mean()
if volume[i] >= avg_5_volume * g.volume_ratio:
# 检查是否为阳线
if close[i] > hist['open'].values[i]:
has_volume_spike = True
break
if not has_volume_spike:
continue
# === 规律2:红肥绿瘦量化 ===
total_red_volume = 0
total_green_volume = 0
red_count = 0
for i in range(-20, 0):
if close[i] > hist['open'].values[i]:
total_red_volume += volume[i]
red_count += 1
else:
total_green_volume += volume[i]
if total_green_volume == 0:
continue
if total_red_volume / total_green_volume < g.red_green_ratio:
continue
if red_count / 20 < g.red_count_ratio:
continue
# === 规律5:上升趋势量化 ===
# 20日均线向上
ma20 = close[-20:].mean()
if close[-1] < ma20:
continue
# 近5日、10日、20日均线多头排列
ma5 = close[-5:].mean()
ma10 = close[-10:].mean()
if not (ma5 > ma10 > ma20):
continue
# === 规律5:回调幅度 ===
# 找近20日最高点
recent_high = max(close[-20:])
drawdown = (recent_high - current_price) / recent_high
if drawdown < g.callback_min or drawdown > g.callback_max:
continue
# 成交量萎缩:回调期间成交量较峰值萎缩50%以上
peak_volume = max(volume[-20:])
if volume[-1] > peak_volume * 0.5:
continue
# === 规律6:买绿不买红 ===
# 当日收跌,跌幅<5%
if current_price >= hist['open'].values[-1]:
continue
daily_change = (current_price - hist['open'].values[-1]) / hist['open'].values[-1]
if daily_change >= 0 or daily_change < -0.05:
continue
# === 规律7:早盘大跌限定(简化,聚宽无法获取1小时内数据,用昨日跌幅替代)===
# 昨日跌幅>5%但仍在20日线上方
if len(hist) >= 2:
yesterday_change = (close[-2] - hist['open'].values[-2]) / hist['open'].values[-2]
if yesterday_change < -0.05 and close[-2] > ma20:
# 符合早盘大跌条件,加分
pass
# 计算综合得分
score = 0
# 倍量强度加分
if volume[-1] >= avg_5_volume * 2.0:
score += 2
elif volume[-1] >= avg_5_volume * 1.8:
score += 1
# 红肥绿瘦强度加分
if total_red_volume / total_green_volume >= 1.8:
score += 2
elif total_red_volume / total_green_volume >= 1.5:
score += 1
# 回调幅度加分(回调8%-10%最佳)
if 0.08 <= drawdown <= 0.10:
score += 2
elif 0.05 <= drawdown < 0.08:
score += 1
candidates.append((stock, score, current_price))
# 按得分排序,取前5支
candidates.sort(key=lambda x: x[1], reverse=True)
# 计算当前持仓数量
current_positions = len(context.portfolio.positions)
# 买入新股票
for stock, score, price in candidates[:g.stock_num - current_positions]:
# 计算买入金额(每支1万元)
buy_value = 10000
# 计算股数(整手)
shares = int(buy_value / price / 100) * 100
if shares > 0:
order(stock, shares)
g.buy_price[stock] = price
g.peak_price[stock] = price
log.info(f'买入 {stock},价格{price:.2f},得分{score}')
# 收盘后日志(可选)
def after_trading_end(context, data):
total_value = context.portfolio.total_val
2026-04-06