🚀 AI 一键生成 joinquant 策略代码

JoinQuant 平台如何实现能量潮(OBV)指标交易策略?

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

问题描述

请编写一个基于 OBV 指标的交易策略

解决方案

这是一个基于 OBV (On-Balance Volume, 能量潮) 指标的量化交易策略。

策略原理

OBV 指标通过统计成交量的变动趋势来推测股价趋势。

  1. OBV计算
    • 如果今日收盘价 > 昨日收盘价,则今日成交量为正,累加到 OBV。
    • 如果今日收盘价 < 昨日收盘价,则今日成交量为负,从 OBV 中减去。
    • 如果今日收盘价 = 昨日收盘价,OBV 保持不变。
  2. 交易信号
    • 买入信号:当 OBV 指标向上突破 OBV 的均线(MA_OBV)时,视为资金流入,看涨。
    • 卖出信号:当 OBV 指标向下向跌破 OBV 的均线(MA_OBV)时,视为资金流出,看跌。

策略代码

# -*- 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')
    
    # 设定股票类每笔交易时的手续费
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 设定要操作的股票(这里以平安银行为例)
    g.security = '000001.XSHE'
    
    # 设定OBV均线的时间窗口
    g.obv_ma_window = 20
    
    # 每天开盘时运行
    run_daily(market_open, time='every_bar')

def market_open(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 1. 获取历史数据
    # 我们需要计算 OBV 的均线,假设窗口为 N,我们需要获取 N+1 天的数据来计算 N 个价格变动
    # 为了计算 OBV 序列的均值,我们需要足够长的历史数据来构建 OBV 序列
    # 这里获取 2 * g.obv_ma_window 天的数据以确保数据的稳定性
    fetch_len = 2 * g.obv_ma_window
    
    # 获取收盘价和成交量
    df = attribute_history(security, fetch_len, '1d', ['close', 'volume'], df=True)
    
    # 如果数据不足,直接返回
    if len(df) < fetch_len:
        return

    # 2. 计算 OBV 指标
    # 计算每日收盘价的变化:diff = 今日收盘 - 昨日收盘
    # diff > 0, 取 volume; diff < 0, 取 -volume; diff = 0, 取 0
    
    # 计算价格变化
    df['price_change'] = df['close'].diff()
    
    # 根据价格变化确定成交量的符号
    # np.sign: >0返回1, <0返回-1, =0返回0
    df['signed_volume'] = np.sign(df['price_change']) * df['volume']
    
    # 填充第一天的 NaN (diff产生的) 为 0
    df['signed_volume'] = df['signed_volume'].fillna(0)
    
    # 计算累积 OBV
    df['OBV'] = df['signed_volume'].cumsum()
    
    # 3. 计算 OBV 的均线 (MA_OBV)
    # 计算过去 g.obv_ma_window 天的 OBV 均值
    obv_ma = df['OBV'].rolling(window=g.obv_ma_window).mean().iloc[-1]
    
    # 获取当前的 OBV 值
    current_obv = df['OBV'].iloc[-1]
    # 获取上一期的 OBV 值和均值(用于判断交叉)
    prev_obv = df['OBV'].iloc[-2]
    prev_obv_ma = df['OBV'].rolling(window=g.obv_ma_window).mean().iloc[-2]
    
    # 4. 获取账户资金和持仓情况
    cash = context.portfolio.available_cash
    position = context.portfolio.positions[security].closeable_amount
    
    # 5. 生成交易信号并下单
    
    # 金叉:OBV 上穿 OBV均线 (当前OBV > 均线 且 上一期OBV < 上一期均线)
    # 或者简化逻辑:当前 OBV > 均线 且 持仓为0
    if current_obv > obv_ma and prev_obv <= prev_obv_ma:
        if cash > 0:
            # 全仓买入
            order_value(security, cash)
            log.info(f"买入信号触发: OBV({current_obv:.2f}) 上穿 MA({obv_ma:.2f})")
            
    # 死叉:OBV 下穿 OBV均线 (当前OBV < 均线 且 上一期OBV > 上一期均线)
    elif current_obv < obv_ma and prev_obv >= prev_obv_ma:
        if position > 0:
            # 清仓卖出
            order_target(security, 0)
            log.info(f"卖出信号触发: OBV({current_obv:.2f}) 下穿 MA({obv_ma:.2f})")

    # 记录数据以便在回测图中查看
    record(OBV=current_obv, MA_OBV=obv_ma)

代码解析

  1. 初始化 (initialize):

    • 设置了基准(沪深300)、真实价格模式和手续费。
    • 定义了全局变量 g.security (操作标的) 和 g.obv_ma_window (OBV均线周期,设为20天)。
  2. 数据获取与计算 (market_open):

    • 使用 attribute_history 获取历史收盘价和成交量。
    • OBV核心逻辑
      • 使用 diff() 计算价格涨跌。
      • 使用 np.sign() 判断成交量的正负方向。
      • 使用 cumsum() 计算累积的 OBV 值。
    • 使用 rolling().mean() 计算 OBV 的移动平均线。
  3. 交易逻辑:

    • 买入:当 OBV 线从下方向上穿越 OBV 均线(金叉)时,全仓买入。
    • 卖出:当 OBV 线从上方向下穿越 OBV 均线(死叉)时,清仓卖出。
  4. 绘图 (record):

    • 调用 record 函数将 OBV 值和均线值绘制在回测结果的图表中,方便可视化分析策略表现。

注意事项

  • OBV的相对性:OBV 的绝对数值取决于计算的起始点,绝对值本身没有意义,有意义的是其走势与均线的相对位置
  • 适用场景:OBV 策略通常在趋势明显的市场中表现较好,在震荡市中可能会频繁触发假信号,建议结合其他指标(如 MACD 或 KDJ)进行过滤。

Q&A

Q: 如何修改策略以操作多只股票?
A: 可以将 g.security 修改为一个列表(例如 g.stocks = ['000001.XSHE', '600519.XSHG']),然后在 market_open 函数中使用 for security in g.stocks: 循环,对每一只股票分别执行获取数据、计算指标和下单的逻辑。

Q: 为什么计算 OBV 时要获取比均线周期更长的数据?
A: OBV 是一个累积指标。虽然我们只需要最近 20 天的均值,但为了让 OBV 的曲线形态稳定,通常需要更长的数据窗口来计算累积值。此外,rolling(window=20) 计算均值时,前 19 个数据会产生 NaN,因此必须获取超过 20 天的数据才能得到有效的均值。

Q: 如何调整策略的灵敏度?
A: 修改 g.obv_ma_window 的值。减小该值(如改为 10)会使策略更灵敏,交易更频繁,但可能误触假信号;增大该值(如改为 60)会使策略更平稳,但可能滞后于市场变化。