7. 技术指标精讲

7.技术指标精讲

技术指标是量化交易策略的基石。无论是简单的均线交叉,还是复杂的多因子模型,都离不开这些数学工具对市场数据的提炼和加工。Jesse 框架在这方面下了很大功夫,内置了超过一百种技术指标,而且全部针对性能做了优化。更难得的是,API 设计得极其简洁,几行代码就能完成在其他平台需要几十行才能实现的功能。

导入内置指标

使用 Jesse 的技术指标,第一步是导入模块。框架把所有指标都封装在 jesse.indicators 下面,惯例是给这个模块起个简短的别名 ta(technical analysis 的缩写)。

import jesse.indicators as ta

这行代码之后,就可以调用所有技术指标了。每个指标的函数名都遵循业界通用命名,比如 sma 代表简单移动平均线,rsi 代表相对强弱指标,macd 代表指数平滑异同移动平均线。这种命名方式降低了学习成本,从 TradingView 或者其他平台转过来的交易者能快速上手。

指标函数的第一个参数永远是 candles,类型是 Numpy 数组。在策略开发中,通常只需要当前这根 K 线的指标值,直接传入 self.candles 即可。

# 计算当前 8 周期简单移动平均值
current_sma = ta.sma(self.candles, 8)

这里的 self.candles 是策略类自动维护的 K 线数据,包含了当前交易路由的所有历史 K 线。框架会在每分钟新 K 线生成时自动更新这个数组,所以每次调用指标函数都能得到最新结果。

指标参数类型

Jesse 的指标函数设计了一套灵活的参数系统,理解这些参数的类型和作用,是高效使用指标的关键。

sequential 参数

这是最重要的参数之一,控制返回值的形态。默认值为 False,表示只返回当前 K 线的指标值,类型是单个数值(浮点数)。这在实盘交易和回测中是最常用的模式,因为策略决策通常基于最新数据。

# 只返回当前 RSI 值
current_rsi = ta.rsi(self.candles, period=14)

sequential=True 时,函数会返回一个包含所有历史值的 Numpy 数组。这个模式主要用于研究分析,比如在 Jupyter Notebook 中绘制指标曲线,或者需要访问过去某根 K 线的指标值。

# 返回 RSI 的历史序列
rsi_series = ta.rsi(self.candles, period=14, sequential=True)
# 获取 5 根 K线前的 RSI 值
rsi_5_bars_ago = rsi_series[-5]

需要注意的是,开启 sequential 模式后,框架不会自动对 K 线数据进行切片优化。因为 Jesse 无法预知你需要回溯多少根 K 线,所以会把整个 self.candles 数组传给指标函数。这可能导致不必要的计算开销。对于计算量大的指标,建议手动切片:

# 只计算最近 100 根 K 线的指标序列
recent_candles = self.candles[-100:]
rsi_series = ta.rsi(recent_candles, period=14, sequential=True)

source_type 参数

很多指标允许指定计算的数据源。默认通常是 "close",即收盘价。但有时候可能需要基于最高价、最低价或者其他价格类型计算。

可选的 source_type 包括:

  • "close":收盘价
  • "high":最高价
  • "low":最低价
  • "open":开盘价
  • "volume":成交量
  • "hl2":(最高价 + 最低价)/ 2
  • "hlc3":(最高价 + 最低价 + 收盘价)/ 3
  • "ohlc4":(开盘价 + 最高价 + 最低价 + 收盘价)/ 4
# 基于最高价计算移动平均线
high_sma = ta.sma(self.candles, period=20, source_type="high")

# 基于典型价格计算布林带
typical_price_bb = ta.bollinger_bands(self.candles, period=20, source_type="hlc3")

这种灵活性让策略开发者可以探索不同的价格表征方式,有时候基于典型价格计算的指标比单纯用收盘价更能反映市场真实状况。

matype 参数

涉及移动平均的指标通常提供 matype 参数,允许选择不同的平均算法。Jesse 支持多达 40 种移动平均类型,这个数字相当惊人。

常用的类型包括:

  • 0:SMA(简单移动平均)
  • 1:EMA(指数移动平均)
  • 2:WMA(加权移动平均)
  • 3:DEMA(双指数移动平均)
  • 4:TEMA(三重指数移动平均)
  • 10:HMA(赫尔移动平均)
  • 11:LinearReg(线性回归)
  • 24:VWMA(成交量加权移动平均)
  • 29:VWAP(成交量加权平均价格)
# 使用赫尔移动平均计算 MACD
hma_macd = ta.macd(self.candles, fast_period=12, slow_period=26, signal_period=9, matype=10)

不同的平均算法对价格变化的响应速度和平滑程度各不相同。EMA 对近期价格更敏感,HMA 在减少滞后方面表现优异,VWMA 则考虑了成交量因素。策略开发者可以根据市场特性选择合适的类型。

devtype 参数

在计算通道类指标时,有时需要指定偏离度的计算方式。devtype 参数提供了三种选择:

  • 0:标准差(standard deviation)
  • 1:平均绝对偏差(mean absolute deviation)
  • 2:中位数绝对偏差(median absolute deviation)
# 使用平均绝对偏差计算布林带
mad_bb = ta.bollinger_bands(self.candles, period=20, devtype=1)

标准差对异常值比较敏感,而平均绝对偏差和中位数绝对偏差更加稳健。在波动剧烈的市场中,后两种计算方式可能提供更可靠的通道边界。

访问指标数值

指标函数的返回值类型取决于指标本身的特性。单值指标(如 RSI、MACD 主线)返回浮点数或数组;多值指标(如布林带、鳄鱼线)返回命名元组(namedtuple)。

单值指标的访问

对于只返回一个数值的指标,使用非常直接:

# 获取当前 ATR 值
current_atr = ta.atr(self.candles, period=14)

# 判断 RSI 是否超买
rsi_value = ta.rsi(self.candles, period=14)
is_overbought = rsi_value > 70

sequential=False 模式下,current_atrrsi_value 都是 Python 的 float 类型,可以直接用于条件判断和数学运算。

多值指标的访问

很多指标会同时返回多个数值,比如布林带返回上轨、中轨、下轨三个值。Jesse 用 namedtuple 来封装这些返回值,既保留了元组的特性,又支持属性访问。

# 计算布林带
bb = ta.bollinger_bands(self.candles, period=20, devstd=2)

# 方法一:作为元组解包
upper_band, middle_band, lower_band = bb

# 方法二:通过索引访问
upper_band = bb[0]
middle_band = bb[1]
lower_band = bb[2]

# 方法三:通过属性访问(推荐)
upper_band = bb.upperband
middle_band = bb.middleband
lower_band = bb.lowerband

属性访问的方式代码可读性最好,bb.upperband 一眼就能看出是布林带上轨。namedtuple 的另一个好处是,如果指标返回值较多,不需要记住索引顺序,直接通过属性名访问即可。

再看一个更复杂的例子,鳄鱼线指标返回三个值:

# 计算鳄鱼线
alligator = ta.alligator(self.candles)

# 访问各个组成部分
jaw = alligator.jaw      # 鳄鱼下颌
teeth = alligator.teeth  # 鳄鱼牙齿
lips = alligator.lips    # 鳄鱼嘴唇

这种设计让代码自解释性很强,几个月后回头看代码,也能立刻理解每个变量的含义。

指标使用示例

理论讲得再多,不如看几个实际例子。下面展示几种典型场景下指标的使用方式。

趋势跟踪策略

趋势跟踪是最经典的交易策略类型。这里用两条指数移动平均线的金叉死叉作为买卖信号。

from jesse.strategies import Strategy
import jesse.indicators as ta

class TrendFollowing(Strategy):
    @property
    def fast_ema(self):
        # 快速 EMA,周期 12
        return ta.ema(self.candles, period=12)
    
    @property
    def slow_ema(self):
        # 慢速 EMA,周期 26
        return ta.ema(self.candles, period=26)
    
    def should_long(self) -> bool:
        # 当快速 EMA 上穿慢速 EMA 时做多
        return self.fast_ema > self.slow_ema
    
    def should_short(self) -> bool:
        # 当快速 EMA 下穿慢速 EMA 时做空
        return self.fast_ema < self.slow_ema

这个例子中,我们把指标计算放在 @property 装饰的方法里。这样做的好处是,指标值会被缓存,多次调用不会重复计算。Jesse 的策略引擎会在每根 K 线开始时自动清空缓存,确保数据最新。

均值回归策略

均值回归策略假设价格会围绕某个中心值波动。布林带是这类策略的常用工具。

class MeanReversion(Strategy):
    @property
    def bollinger(self):
        # 计算布林带,周期 20,标准差倍数 2
        return ta.bollinger_bands(self.candles, period=20, devstd=2)
    
    @property
    def rsi(self):
        # 计算 RSI,周期 14
        return ta.rsi(self.candles, period=14)
    
    def should_long(self) -> bool:
        # 价格触及下轨且 RSI 超卖,做多
        price = self.candles[-1][2]  # 当前收盘价
        return price <= self.bollinger.lowerband and self.rsi < 30
    
    def should_short(self) -> bool:
        # 价格触及上轨且 RSI 超买,做空
        price = self.candles[-1][2]
        return price >= self.bollinger.upperband and self.rsi > 70

这个策略结合了布林带和 RSI 两个指标,要求价格触及通道边界的同时,动量指标也发出超买超卖信号。多指标共振可以提高信号的可靠性。

多时间框架分析

Jesse 支持在同一个策略中访问不同时间框架的数据。这在实际交易中非常有用,比如用日线判断大趋势,用小时线找入场点。

class MultiTimeframe(Strategy):
    @property
    def daily_trend(self):
        # 获取日线 K 线数据
        daily_candles = self.get_candles('Binance', 'BTC-USDT', '1d')
        # 计算日线 20 周期 SMA
        return ta.sma(daily_candles, period=20)
    
    @property
    def hourly_rsi(self):
        # 获取小时线 K 线数据
        hourly_candles = self.get_candles('Binance', 'BTC-USDT', '1h')
        # 计算小时线 RSI
        return ta.rsi(hourly_candles, period=14)
    
    def should_long(self) -> bool:
        # 日线趋势向上,且小时线 RSI 超卖,做多
        current_price = self.candles[-1][2]
        return current_price > self.daily_trend and self.hourly_rsi < 30

self.get_candles() 方法可以获取任意交易对、任意时间框架的 K 线数据。框架会自动处理数据对齐问题,确保不会出现未来数据泄露(look-ahead bias)。

动态参数调整

有时候需要根据市场状态动态调整指标参数。比如波动率大的市场用更长的周期来平滑噪音。

class AdaptiveStrategy(Strategy):
    @property
    def volatility(self):
        # 用 ATR 衡量波动率
        return ta.atr(self.candles, period=14)
    
    @property
    def adaptive_period(self):
        # 波动率越大,周期越长
        base_period = 20
        volatility_factor = self.volatility / self.candles[-1][2]  # ATR / 价格
        return int(base_period * (1 + volatility_factor * 5))
    
    @property
    def adaptive_sma(self):
        # 使用动态周期计算 SMA
        return ta.sma(self.candles, period=self.adaptive_period)

这种自适应方法让策略更具灵活性,但也要注意避免过度优化。参数变化过于频繁可能导致策略不稳定。

完整指标参考

Jesse 内置的指标库非常全面,涵盖了趋势、动量、波动率、成交量等各个类别。下面分类介绍一些常用指标,完整的列表可以参考官方文档。

趋势指标

趋势指标用于判断市场方向,是趋势跟踪策略的核心。

移动平均线类

  • sma:简单移动平均
  • ema:指数移动平均
  • hma:赫尔移动平均,滞后更小
  • vwma:成交量加权移动平均
  • alma:Arnaud Legoux 移动平均,平滑度高
# 赫尔移动平均,对价格变化响应更快
hma_line = ta.hma(self.candles, period=20)

# 成交量加权移动平均,考虑成交量因素
vwma_line = ta.vwma(self.candles, period=20)

MACD 类

  • macd:经典 MACD
  • apo:绝对价格振荡器
  • ppo:百分比价格振荡器
# MACD 返回 namedtuple,包含 macd, signal, hist 三个值
macd_result = ta.macd(self.candles, fast_period=12, slow_period=26, signal_period=9)
macd_line = macd_result.macd
signal_line = macd_result.signal
histogram = macd_result.hist

其他趋势指标

  • adx:平均趋向指数,衡量趋势强度
  • ichimoku_cloud:一目均衡表,综合性趋势工具
  • supertrend:超级趋势,动态支撑阻力
# ADX 值大于 25 通常认为趋势较强
adx_value = ta.adx(self.candles, period=14)

# 一目均衡表返回多个值
ichimoku = ta.ichimoku_cloud(self.candles)
conversion_line = ichimoku.conversion_line
base_line = ichimoku.base_line
span_a = ichimoku.span_a
span_b = ichimoku.span_b

动量指标

动量指标衡量价格变化速度,用于捕捉超买超卖和背离信号。

RSI 类

  • rsi:相对强弱指标
  • stochrsi:随机 RSI
  • ift_rsi:逆 Fisher 变换 RSI
# 经典 RSI,超过 70 超买,低于 30 超卖
rsi_value = ta.rsi(self.candles, period=14)

# 随机 RSI 更敏感
stoch_rsi = ta.stochrsi(self.candles, period=14)

随机指标类

  • stoch:随机振荡器
  • kdj:KDJ 指标,随机指标的变种
# KDJ 返回 k, d, j 三个值
kdj_result = ta.kdj(self.candles)
k_line = kdj_result.k
d_line = kdj_result.d
j_line = kdj_result.j

其他动量指标

  • cci:商品通道指数
  • momentum:动量指标
  • tsi:真实强度指数
# CCI 超过 +100 或低于 -100 表示超买超卖
cci_value = ta.cci(self.candles, period=20)

波动率指标

波动率指标反映市场波动幅度,对仓位管理和止损设置很重要。

  • atr:平均真实波幅
  • bollinger_bands:布林带
  • keltner:肯特纳通道
  • donchian_channels:唐奇安通道
# ATR 常用于动态止损
atr_value = ta.atr(self.candles, period=14)
stop_loss = self.position.entry_price - atr_value * 2

# 布林带返回三个值
bb = ta.bollinger_bands(self.candles, period=20, devstd=2)

成交量指标

成交量指标结合价格和成交量,验证价格走势的可靠性。

  • ad:累积/派发线
  • adosc:累积/派发振荡器
  • obv:能量潮
  • mfi:资金流量指标
# 资金流量指标,类似 RSI 但考虑成交量
mfi_value = ta.mfi(self.candles, period=14)

复合指标

Jesse 还实现了一些流行的复合指标,这些指标通常结合多个基础指标。

  • alligator:鳄鱼线
  • gator:鳄鱼振荡器
  • awesome_oscillator:动量震荡指标
  • accelerator_oscillator:加速震荡指标
# 鳄鱼线由三条移动平均线组成
alligator = ta.alligator(self.candles)
jaw = alligator.jaw    # 13 周期 SMA,未来 8 周期
teeth = alligator.teeth # 8 周期 SMA,未来 5 周期
lips = alligator.lips   # 5 周期 SMA,未来 3 周期

性能优化技巧

在策略开发中,性能优化不容忽视。特别是当策略包含多个指标、多个交易对时,计算效率直接影响回测速度和实盘表现。

使用 @cached 装饰器

如果某个指标在策略中被多次调用,可以用 @cached 装饰器缓存结果。框架会在每根 K 线开始时自动清理缓存,确保数据新鲜。

from jesse.strategies import Strategy
from jesse.services.cache import cached
import jesse.indicators as ta

class CachedStrategy(Strategy):
    @cached
    @property
    def my_indicator(self):
        # 这个计算结果会被缓存
        return ta.complex_indicator(self.candles, param1=10, param2=20)
    
    def should_long(self) -> bool:
        # 第一次调用会计算,后续调用直接返回缓存值
        val1 = self.my_indicator
        val2 = self.my_indicator  # 从缓存读取
        return val1 > val2

对于计算复杂的自定义指标,@cached 能显著提升性能。但要注意,只有无参数或参数固定的属性才能缓存。

合理设置 warmup_candles_num

Jesse 的策略类有一个 warmup_candles_num 属性,指定策略需要多少根 K 线来初始化指标。设置得过小,指标计算不准确;设置得过大,浪费计算资源。

class MyStrategy(Strategy):
    def __init__(self):
        super().__init__()
        # 策略需要至少 200 根 K 线才能正常工作
        self.warmup_candles_num = 200

对于周期较长的指标,比如 200 周期 SMA,需要至少 200 根 K 线才能得到正确值。如果使用了多个指标,取它们的最大周期作为 warmup_candles_num

避免重复计算

在策略中,同一个指标可能在多个地方用到。最佳实践是将其定义为属性,而不是在每个方法中重复计算。

# 不推荐:重复计算
def should_long(self):
    return ta.rsi(self.candles, 14) > 30 and ta.rsi(self.candles, 14) < 50

def should_short(self):
    return ta.rsi(self.candles, 14) > 70

# 推荐:定义属性
@property
def rsi(self):
    return ta.rsi(self.candles, 14)

def should_long(self):
    return self.rsi > 30 and self.rsi < 50

def should_short(self):
    return self.rsi > 70

这种方式不仅性能更好,代码也更容易维护。如果需要修改 RSI 周期,只需改动一处。

在研究模式中使用 sequential=True

在 Jupyter Notebook 中做研究时,总是设置 sequential=True 来获取完整序列。这样可以绘制指标曲线,观察整体形态。

from jesse import research
import jesse.indicators as ta
import jesse.helpers as jh
import matplotlib.pyplot as plt

# 获取历史数据
_, candles = research.get_candles(
    'Binance', 'BTC-USDT', '1h',
    jh.date_to_timestamp('2024-01-01'),
    jh.date_to_timestamp('2024-02-01')
)

# 计算指标序列
sma_50 = ta.sma(candles, period=50, sequential=True)
sma_200 = ta.sma(candles, period=200, sequential=True)

# 绘制图表
plt.figure(figsize=(15, 8))
plt.plot(candles[:, 2], label='Close')
plt.plot(sma_50, label='SMA 50')
plt.plot(sma_200, label='SMA 200')
plt.legend()
plt.show()

研究模式下的可视化能帮助我们直观理解指标行为,验证策略逻辑是否合理。

小结

Jesse 的技术指标系统兼顾了简洁性和灵活性。统一的 API 设计让开发者能快速上手,丰富的参数选项又满足了高级需求。无论是简单的均线策略,还是复杂的多因子模型,都能找到合适的工具。

性能优化方面,框架提供了缓存机制和自动切片功能,但开发者也需要注意避免重复计算,合理设置 warmup 周期。在研究模式下,利用 sequential=True 可以深入分析指标特性。

下一章将探讨如何开发自定义指标。当内置指标无法满足需求时,我们可以用 Numpy 和 Numba 创建高性能的自定义指标,进一步扩展 Jesse 的能力边界。