问题描述
解决方案
这是一个基于 OBV (On-Balance Volume,能量潮) 指标的量化交易策略。
策略原理
OBV 指标通过统计成交量的变动趋势来推测股价趋势。
- 计算 OBV:
- 如果今日收盘价 > 昨日收盘价,则今日 OBV = 昨日 OBV + 今日成交量。
- 如果今日收盘价 < 昨日收盘价,则今日 OBV = 昨日 OBV - 今日成交量。
- 如果今日收盘价 = 昨日收盘价,则今日 OBV = 昨日 OBV。
- 交易逻辑 (OBV 均线交叉策略):
- 为了平滑信号,我们计算 OBV 的移动平均线 (MA_OBV)。
- 买入信号:当 OBV 线上穿 MA_OBV 线(金叉),视为资金流入,看涨。
- 卖出信号:当 OBV 线跌破 MA_OBV 线(死叉),视为资金流出,看跌。
策略代码
import talib
import numpy as np
def initialize(context):
"""
初始化函数,设置股票池、参数和手续费
"""
# 设置要操作的股票,这里以恒生电子为例
g.security = '600570.SS'
# 设置股票池
set_universe(g.security)
# 策略参数
# OBV均线周期,通常取30天,可根据回测调整
g.obv_ma_window = 30
# 设置回测时的手续费,股票买入万三,卖出万三加千一印花税
set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')
# 设置滑点
set_slippage(slippage=0.002)
def handle_data(context, data):
"""
盘中运行函数,每个单位时间(如每天)运行一次
"""
security = g.security
# 1. 获取历史数据
# 我们需要足够的数据来计算OBV和它的均线
# 获取数量 = 均线周期 + 缓冲期(用于指标初始化)
count = g.obv_ma_window + 100
# 获取收盘价和成交量,使用前复权(pre)以保证价格连续性
# get_history 返回的是 DataFrame
hist = get_history(count, frequency='1d', field=['close', 'volume'], security_list=security, fq='pre')
# 如果数据不足,直接返回
if len(hist['close']) < count:
return
# 提取 close 和 volume 为 numpy 数组,talib 需要 numpy 数组作为输入
close_prices = hist['close'].values
volumes = hist['volume'].values
# 2. 计算 OBV 指标
# 使用 talib 库计算 OBV
# 注意:talib.OBV 返回的是一个数组
obv = talib.OBV(close_prices, volumes)
# 3. 计算 OBV 的移动平均线 (MA)
# 这里使用简单移动平均 SMA
obv_ma = talib.SMA(obv, timeperiod=g.obv_ma_window)
# 4. 获取当前和前一时刻的指标值用于判断交叉
# 当前周期的 OBV 和 MA
current_obv = obv[-1]
current_obv_ma = obv_ma[-1]
# 前一周期的 OBV 和 MA
prev_obv = obv[-2]
prev_obv_ma = obv_ma[-2]
# 检查计算结果是否包含 NaN (数据不足时 talib 会返回 NaN)
if np.isnan(current_obv_ma) or np.isnan(prev_obv_ma):
return
# 5. 获取当前持仓和资金情况
position = get_position(security)
current_cash = context.portfolio.cash
# 6. 生成交易信号
# 金叉:上穿均线 -> 买入
# 条件:前一时刻 OBV <= 前一时刻 MA,且 当前 OBV > 当前 MA
if prev_obv <= prev_obv_ma and current_obv > current_obv_ma:
# 如果有可用资金,全仓买入
if current_cash > 0:
log.info("OBV金叉,买入股票: %s" % security)
order_value(security, current_cash)
# 死叉:下穿均线 -> 卖出
# 条件:前一时刻 OBV >= 前一时刻 MA,且 当前 OBV < 当前 MA
elif prev_obv >= prev_obv_ma and current_obv < current_obv_ma:
# 如果有持仓,清仓卖出
if position.amount > 0:
log.info("OBV死叉,卖出股票: %s" % security)
order_target(security, 0)
代码解析
-
数据获取 (
get_history):- 我们获取了
close(收盘价) 和volume(成交量)。 count设置得比较大 (g.obv_ma_window + 100),这是因为 OBV 是累积指标,且计算移动平均线 (MA) 需要前面的数据作为铺垫,否则初期计算结果会是NaN。
- 我们获取了
-
指标计算 (
talib):- PTrade 支持
talib库,这是计算技术指标最高效的方法。 talib.OBV(close, volume)直接计算出 OBV 序列。talib.SMA(obv, timeperiod)对 OBV 序列再做一次平滑处理,得到均线。
- PTrade 支持
-
信号判断:
- 策略通过比较
current(当前) 和prev(前一期) 的数值关系来判断“交叉”。 - 金叉:
prev_obv <= prev_ma且curr_obv > curr_ma。 - 死叉:
prev_obv >= prev_ma且curr_obv < curr_ma。
- 策略通过比较
-
交易执行:
order_value(security, cash):用当前所有现金买入。order_target(security, 0):将持仓调整为 0,即清仓。
注意事项
- Python 版本:代码兼容 Python 3.5+,未使用 f-string 等新特性。
- 复权:
get_history中使用了fq='pre'(前复权)。对于技术指标策略,使用前复权非常重要,否则分红派息导致的价格缺口会扭曲指标计算。 - 有效性:OBV 策略在趋势明显的市场中效果较好,但在震荡市中可能会频繁发出假信号,建议结合其他指标(如 MACD 或 RSI)进行过滤。
Q&A
Q: 为什么 OBV 指标需要配合均线使用?
A: 原始的 OBV 曲线波动较大且没有固定的取值范围(不像 RSI 在 0-100 之间)。单独看 OBV 的数值没有意义,重要的是它的趋势。通过加上一条移动平均线(MA),可以构建出相对的参照系,从而通过金叉和死叉来捕捉趋势的转折点。
Q: 代码中的 talib 是什么?
A: talib (Technical Analysis Library) 是一个广泛使用的金融技术分析库,PTrade 平台原生支持该库。它计算指标的速度比自己用 Python 写循环要快得多,且计算逻辑标准。
Q: 如何修改策略操作多只股票?
A: 目前代码针对单只股票 (g.security 为字符串)。若要操作多只股票,需将 g.security 设为列表,并在 handle_data 中使用 for security in g.security: 循环遍历每只股票,分别获取数据、计算指标和下单。