问题描述
解决方案
这是一个非常实用的风控需求。在量化交易中,这种机制通常被称为大盘择时过滤器(Market Timing Filter)或红绿灯机制。
其核心逻辑是:在执行具体的个股买卖策略之前,先判断大盘指数(如沪深300)的走势。如果大盘处于下跌趋势(熊市),则强制空仓或禁止开新仓;只有当大盘处于上涨趋势(牛市)时,才允许策略正常运行。
下面是一个完整的 PTrade 策略代码示例。
策略逻辑说明
- 基准指数:使用 沪深300指数 (000300.SS) 作为市场风向标。
- 判断标准:使用 60日均线 (MA60) 作为牛熊分界线。
- 熊市:当指数收盘价 < 60日均线,判定为熊市,清空所有持仓并停止买入。
- 牛市:当指数收盘价 > 60日均线,判定为牛市,允许策略正常交易。
- 交易标的:为了演示,策略在牛市期间会买入并持有 恒生电子 (600570.SS)。
PTrade 策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置策略参数
"""
# 1. 设置要操作的股票(这里以恒生电子为例)
g.security = '600570.SS'
set_universe(g.security)
# 2. 设置大盘风控参数
g.index_security = '000300.SS' # 风控基准:沪深300指数
g.ma_window = 60 # 均线周期:60日均线
g.market_status = 'BULL' # 初始市场状态:BULL(牛), BEAR(熊)
# 3. 设置回测参数(仅回测有效)
set_commission(commission_ratio=0.0003, min_commission=5.0)
set_slippage(slippage=0.002)
def before_trading_start(context, data):
"""
盘前处理函数:每天开盘前判断市场状态
"""
# 获取指数的历史行情数据
# 多取一些数据以确保均线计算准确,这里取 ma_window + 10 天
count = g.ma_window + 10
# 获取历史收盘价,不包含当天(include=False)
history_data = get_history(count, frequency='1d', field='close',
security_list=g.index_security, include=False)
# 检查数据是否获取成功
if history_data is None or len(history_data) < g.ma_window:
log.warning("历史数据不足,无法计算大盘均线,默认维持原状态")
return
# 提取收盘价序列
# 注意:get_history 返回的是 DataFrame,列名是股票代码或 'close'
# 如果是单只股票,直接取 'close' 列;如果是多只,列名是代码
# 这里我们明确指定了 security_list 为单个字符串,通常返回 DataFrame 的列包含该代码或 'close'
# 为了兼容性,我们使用 values 属性转为 numpy 数组处理
close_prices = history_data['close'].values
# 计算均线 (MA60)
ma_value = np.mean(close_prices[-g.ma_window:])
# 获取前一天的收盘价
current_index_price = close_prices[-1]
# 判断市场状态
if current_index_price < ma_value:
g.market_status = 'BEAR'
log.info("大盘风控:当前指数价格(%.2f) < %d日均线(%.2f),判定为熊市,停止开仓/清仓。" % (current_index_price, g.ma_window, ma_value))
else:
g.market_status = 'BULL'
log.info("大盘风控:当前指数价格(%.2f) > %d日均线(%.2f),判定为牛市,策略正常运行。" % (current_index_price, g.ma_window, ma_value))
def handle_data(context, data):
"""
盘中交易函数
"""
# ---------------------------------------------------
# 1. 熊市风控逻辑
# ---------------------------------------------------
if g.market_status == 'BEAR':
# 如果处于熊市,检查是否有持仓
position = get_position(g.security)
# 如果有持仓,则清仓卖出
if position.amount > 0:
log.info("熊市避险:卖出 %s" % g.security)
order_target(g.security, 0)
# 熊市直接返回,不执行后续的买入逻辑
return
# ---------------------------------------------------
# 2. 牛市正常策略逻辑 (示例:简单的买入持有)
# ---------------------------------------------------
# 下面是您的具体策略逻辑,这里仅作示例:
# 如果是牛市且没有持仓,则买入
position = get_position(g.security)
# 简单的示例逻辑:如果没持仓就买入
if position.amount == 0:
log.info("牛市环境:买入 %s" % g.security)
# 用所有现金买入
order_value(g.security, context.portfolio.cash)
def after_trading_end(context, data):
"""
盘后处理
"""
pass
代码关键点解析
-
before_trading_start的作用:- 我们将大盘状态的判断放在盘前函数中。这样做的好处是每天只计算一次,效率高,且在开盘交易(
handle_data)之前就已经确定了当天的风控基调。 - 使用了
get_history获取指数(000300.SS)的历史数据。注意include=False,表示使用昨天收盘的数据来决定今天的操作,避免使用未来数据。
- 我们将大盘状态的判断放在盘前函数中。这样做的好处是每天只计算一次,效率高,且在开盘交易(
-
均线计算:
- 使用了
numpy.mean()计算过去60天的平均收盘价。 - 比较
current_index_price(昨日收盘价)与ma_value(均线价格)。
- 使用了
-
handle_data中的拦截机制:- 代码最上方加入了一个
if g.market_status == 'BEAR':的判断块。 - 清仓操作:使用
order_target(g.security, 0)将目标股票仓位调整为0。 - 阻断执行:使用
return语句。一旦进入熊市逻辑,代码直接返回,不再执行下方任何买入逻辑。这是实现“停止开仓”的关键。
- 代码最上方加入了一个
如何调整此策略
- 更换基准指数:
如果你交易的是创业板股票,可以将g.index_security改为'399006.SZ'(创业板指)。 - 调整灵敏度:
- 如果你希望反应更灵敏,可以将
g.ma_window改为20(20日均线)。 - 如果你希望过滤掉短期震荡,只在大趋势坏掉时离场,可以保留
60或改为120(半年线)。
- 如果你希望反应更灵敏,可以将
- 降低仓位而非清仓:
如果你不想全卖,只想减半仓位,可以将order_target(g.security, 0)修改为:# 获取当前持仓 current_amount = position.amount # 目标是减半 target_amount = int(current_amount * 0.5 / 100) * 100 order_target(g.security, target_amount)
Q&A
Q1: 为什么要在 before_trading_start 里计算均线,而不是在 handle_data 里?
A1: before_trading_start 每天只运行一次,适合处理全天通用的逻辑(如大盘判断)。handle_data 可能每分钟运行一次(分钟回测时),如果在那里频繁获取历史数据并计算均线,会严重拖慢回测速度,且逻辑上没有必要(因为日线级别的均线一天才变一次)。
Q2: get_history 获取指数数据时,需要考虑复权吗?
A2: 对于指数(如沪深300),通常不需要像个股那样考虑分红配股带来的复权问题,直接使用默认数据即可。如果是个股作为基准,建议加上 fq='pre'(前复权)。
Q3: 这个过滤器能避免所有亏损吗?
A3: 不能。均线策略有滞后性。当市场急跌时,价格可能已经跌了很多才会跌破均线触发止损。它的主要作用是避免在长期的单边下跌趋势中持有仓位,从而保护本金,而不是为了躲避每一次短期回调。