🚀 AI 一键生成 ptrade 策略代码

PTrade 教程:如何构建一个市场状态过滤器(牛市/熊市),动态控制策略仓位?

ptrade | 发布时间: 2025/12/16
以下内容由 EasyQuant 生成。

问题描述

请帮我编写一个市场状态过滤器,当市场处于熊市时,策略停止开仓或降低仓位

解决方案

这是一个非常实用的风控需求。在量化交易中,这种机制通常被称为大盘择时过滤器(Market Timing Filter)或红绿灯机制

其核心逻辑是:在执行具体的个股买卖策略之前,先判断大盘指数(如沪深300)的走势。如果大盘处于下跌趋势(熊市),则强制空仓或禁止开新仓;只有当大盘处于上涨趋势(牛市)时,才允许策略正常运行。

下面是一个完整的 PTrade 策略代码示例。

策略逻辑说明

  1. 基准指数:使用 沪深300指数 (000300.SS) 作为市场风向标。
  2. 判断标准:使用 60日均线 (MA60) 作为牛熊分界线。
    • 熊市:当指数收盘价 < 60日均线,判定为熊市,清空所有持仓并停止买入。
    • 牛市:当指数收盘价 > 60日均线,判定为牛市,允许策略正常交易。
  3. 交易标的:为了演示,策略在牛市期间会买入并持有 恒生电子 (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

代码关键点解析

  1. before_trading_start 的作用

    • 我们将大盘状态的判断放在盘前函数中。这样做的好处是每天只计算一次,效率高,且在开盘交易(handle_data)之前就已经确定了当天的风控基调。
    • 使用了 get_history 获取指数(000300.SS)的历史数据。注意 include=False,表示使用昨天收盘的数据来决定今天的操作,避免使用未来数据。
  2. 均线计算

    • 使用了 numpy.mean() 计算过去60天的平均收盘价。
    • 比较 current_index_price(昨日收盘价)与 ma_value(均线价格)。
  3. 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: 不能。均线策略有滞后性。当市场急跌时,价格可能已经跌了很多才会跌破均线触发止损。它的主要作用是避免在长期的单边下跌趋势中持有仓位,从而保护本金,而不是为了躲避每一次短期回调。