🚀 AI 一键生成 joinquant 策略代码

如何构建一个基于价格或指标偏离的均值回归交易策略?

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

问题描述

请为我编写一个均值回归策略

解决方案

这是一个基于 布林带(Bollinger Bands) 的均值回归策略。

策略原理

均值回归的核心思想是价格终将回归其价值中枢。在本策略中:

  1. 中轨:使用过去 N 天的收盘价移动平均线作为价值中枢。
  2. 上轨/下轨:在中轨基础上加减 K 倍的标准差。
  3. 买入信号:当股价跌破下轨时,认为股价被低估(超卖),预期将回归均值,执行买入。
  4. 卖出信号:当股价突破上轨时,认为股价被高估(超买),预期将回调,执行卖出。

策略代码

# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd

def initialize(context):
    """
    初始化函数,设定基准、手续费、滑点及策略参数
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定股票交易手续费:买入万三,卖出万三加千一印花税,最低5元
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 设定全局参数
    g.n = 20          # 布林带计算周期(均线周期)
    g.k = 2           # 布林带宽度(标准差倍数)
    g.stock_num = 10  # 最大持仓股票数量
    
    # 设定股票池:这里选取沪深300成分股
    g.security_list = get_index_stocks('000300.XSHG')
    
    # 每天开盘时运行
    run_daily(market_open, time='09:30')

def market_open(context):
    """
    每日交易逻辑
    """
    # 获取当前持仓
    current_positions = context.portfolio.positions
    # 获取当前可用资金
    cash = context.portfolio.available_cash
    
    # 获取待操作的股票列表(沪深300)
    security_list = g.security_list
    
    # 获取当前时刻的标的状态数据(用于判断停牌)
    current_data = get_current_data()
    
    # 1. 卖出逻辑:遍历持仓,如果价格突破上轨,则卖出
    for security in list(current_positions.keys()):
        # 获取过去 N 天的收盘价数据
        # 注意:这里取 g.n + 1 天,是为了计算截止到昨天的指标,避免未来函数
        hist_data = attribute_history(security, g.n, '1d', ['close'])
        
        if len(hist_data) < g.n:
            continue
            
        # 计算布林带
        close_prices = hist_data['close']
        mid_line = close_prices.mean()           # 中轨:均线
        std = close_prices.std()                 # 标准差
        upper_band = mid_line + g.k * std        # 上轨
        
        # 获取昨日收盘价(作为信号判断依据)
        current_price = close_prices[-1]
        
        # 如果昨日收盘价高于上轨,且股票未停牌,则止盈卖出
        if current_price > upper_band and not current_data[security].paused:
            order_target_value(security, 0)
            log.info("股票 %s 突破上轨,触发卖出信号" % security)

    # 2. 买入逻辑:遍历股票池,如果价格跌破下轨,则尝试买入
    
    # 统计当前持仓数量
    current_hold_count = len(context.portfolio.positions)
    # 如果持仓已满,则不再买入
    if current_hold_count >= g.stock_num:
        return

    # 计算每只股票的目标买入资金(简单的等权分配)
    # 注意:这里用总资产来计算,以维持仓位平衡
    target_value = context.portfolio.total_value / g.stock_num
    
    for security in security_list:
        # 如果已经持仓或停牌,跳过
        if security in current_positions or current_data[security].paused:
            continue
        
        # 如果持仓数量已达上限,停止买入循环
        if len(context.portfolio.positions) >= g.stock_num:
            break
            
        # 获取历史数据
        hist_data = attribute_history(security, g.n, '1d', ['close'])
        if len(hist_data) < g.n:
            continue
            
        # 计算布林带
        close_prices = hist_data['close']
        mid_line = close_prices.mean()           # 中轨
        std = close_prices.std()                 # 标准差
        lower_band = mid_line - g.k * std        # 下轨
        
        # 获取昨日收盘价
        current_price = close_prices[-1]
        
        # 如果昨日收盘价低于下轨,触发买入信号
        if current_price < lower_band:
            # 使用可用资金买入
            if context.portfolio.available_cash >= target_value:
                order_target_value(security, target_value)
                log.info("股票 %s 跌破下轨,触发买入信号" % security)

def after_trading_end(context):
    """
    盘后处理
    """
    pass

常见问题 (Q&A)

Q: 为什么使用 attribute_history 获取数据?
A: attribute_history 是聚宽获取历史行情数据的核心函数。在日频回测中,我们在开盘时(09:30)调用它获取过去 N 天的数据(通常截止到昨天收盘),以此计算技术指标。这避免了使用当天的未来数据(未来函数)。

Q: 策略中的 g.ng.k 是什么意思?
A: 这是布林带的两个核心参数。g.n = 20 表示使用过去20天的移动平均线作为中轨;g.k = 2 表示上下轨距离中轨的宽度是2倍标准差。这是经典的布林带参数设置,您可以根据需要调整,例如改为 g.n=10 使策略更灵敏。

Q: 为什么买入时要判断 current_data[security].paused
A: 在实盘或回测中,股票可能会停牌。对停牌的股票下单是无效的,且可能导致程序报错或逻辑混乱。使用 get_current_data() 获取标的状态并检查 paused 属性是编写健壮策略的必要步骤。

Q: 如何控制风险?
A: 代码中设置了 g.stock_num = 10,即最多持有10只股票。资金分配采用简单的等权模型(总资产/10)。这是一种基本的分散投资风控手段,防止单只股票波动对账户造成过大影响。