12. 研究工具与蒙特卡洛分析

12.研究工具与蒙特卡洛分析

研究工具是策略开发过程中不可或缺的一环。当图形界面的回测功能无法满足复杂分析需求时,Jesse 提供的研究模块让我们能够在 Python 脚本或 Jupyter Notebook 中调用核心功能,实现批量处理、自定义分析和深度验证。这一章将深入探讨如何配置 Jupyter 环境、使用 Candles API 操作市场数据,以及通过蒙特卡洛分析检验策略的稳健性。

Jupyter 集成配置

Jupyter Notebook 和 JupyterLab 为量化研究提供了交互式编程环境,可以逐行执行代码、实时查看图表,非常适合策略探索和数据分析。Jesse 的研究模块设计初衷就是与这类工具无缝协作。

安装与启动

在 Jesse 项目中使用 Jupyter 前,需要确保安装正确版本。执行以下命令即可完成安装:

pip install jupyterlab

如果采用 Docker 部署方案,需要进入 jesse 容器内部运行该命令。安装完成后,根据偏好选择启动方式:

# 启动经典版 Jupyter Notebook
jupyter notebook

# 启动新版 JupyterLab
jupyter-lab

在服务器或 Docker 环境中运行时,需要额外参数以允许外部访问:

jupyter notebook --ip 0.0.0.0 --no-browser --allow-root

这些参数的作用是将服务绑定到所有网络接口,禁用自动浏览器打开功能,并允许 root 用户运行。启动后,终端会显示访问地址和 token,复制到浏览器即可开始工作。

项目结构要求

Jesse 研究模块依赖项目根目录下的 .env 文件读取数据库配置等关键信息。因此,创建的 Notebook 文件必须保存在 Jesse 项目根目录中,而不是任意位置。这是常见的新手错误,会导致研究模块无法初始化数据库连接。

研究模式 Candles API

研究模块的核心价值在于将图形界面的数据操作功能转化为可编程接口。Candles API 提供了从数据导入、存储到获取的完整链条,让我们能够灵活处理市场数据。

数据导入与存储

import_candles 函数对应图形界面的数据导入功能,适合批量操作场景。例如,需要为多个交易对下载历史数据时,可以编写脚本循环调用:

from jesse import research

# 批量导入 2024 年全年数据
symbols = ['BTC-USDT', 'ETH-USDT', 'SOL-USDT']
for symbol in symbols:
    research.import_candles(
        exchange='Binance Futures',
        symbol=symbol,
        start_date='2024-01-01'
    )

该函数默认显示进度条,在 Jupyter 环境中能直观展示下载进度。如需静默执行,设置 show_progressbar=False 即可。

对于自定义数据源,store_candles 函数允许将 NumPy 数组格式的 K 线数据存入 Jesse 数据库。这在处理 CSV 文件或合成数据时特别有用:

import numpy as np

# 假设已从 CSV 读取数据并转换为 NumPy 数组
custom_candles = np.array([
    [1609459200000, 29000, 29100, 28900, 29050, 1000],  # 时间戳, 开盘, 收盘, 最高, 最低, 成交量
    # ... 更多数据
])

research.store_candles(
    candles=custom_candles,
    exchange='Custom Exchange',
    symbol='BTC-USDT'
)

需要注意的是,Jesse 内部使用 1 分钟 K 线作为基础数据,其他时间周期都是实时生成。因此,传入 store_candles 的数据必须是 1 分钟周期。

数据获取与处理

get_candles 是研究中最常用的函数,它从数据库提取数据并返回 NumPy 数组,格式与策略中的 self.candles 完全一致。函数签名如下:

warmup_candles, trading_candles = research.get_candles(
    exchange='Binance Spot',
    symbol='BTC-USDT',
    timeframe='4h',
    start_date_timestamp=1609459200000,
    finish_date_timestamp=1612137600000,
    warmup_candles_num=210,
    caching=True,
    is_for_jesse=True
)

该函数始终返回两个值:预热数据(warmup_candles)和交易数据(trading_candles)。预热数据用于指标计算,确保策略在正式交易前已有足够历史数据初始化技术指标。如果 warmup_candles_num 设为 0,则第一个返回值为 None。

参数 is_for_jesse 是关键配置。当设置为 True 时,返回的数据格式符合 Jesse 回测引擎要求,可直接用于 backtest() 函数。如果仅用于绘图或分析,设为 False 即可。

caching 参数启用后,会将数据缓存到本地磁盘,重复调用相同区间时大幅提升速度。建议在多次回测同一时段时开启。

返回的 NumPy 数组每行代表一根 K 线,列顺序为:[时间戳, 开盘, 收盘, 最高, 最低, 成交量]。这种结构便于使用 NumPy 切片快速访问特定价格序列:

# 获取收盘价序列
close_prices = trading_candles[:, 2]

# 获取最高价序列
high_prices = trading_candles[:, 3]

合成数据生成

研究模块提供了三种合成数据生成函数,用于测试策略逻辑或填补数据空白。

fake_candle 生成单根 K 线,可指定属性或随机生成:

# 随机生成
c1 = research.fake_candle()

# 指定属性
c2 = research.fake_candle({
    'timestamp': 1643104557000,
    'open': 10,
    'close': 11,
    'high': 12,
    'low': 8,
    'volume': 200
})

fake_range_candles 批量生成指定数量的随机 K 线,适合快速构造测试数据集。candles_from_close_prices 则根据收盘价序列生成完整 K 线,其他价格基于简单逻辑推导:

close_prices = [10, 11, 12, 12, 11, 13, 14, 12, 11, 15]
fake_candles = research.candles_from_close_prices(close_prices)

这些工具让我们能够在不依赖真实数据的情况下验证策略的基本逻辑是否正确。

蒙特卡洛蜡烛管道

蒙特卡洛分析是检验策略过拟合的利器。Jesse 实现了两种方法:交易打乱模拟和 K 线变换模拟。其中,K 线变换模拟依赖蜡烛管道(Candle Pipelines)生成替代市场情景。

核心概念

蜡烛管道是一种数据变换器,它在保持市场基本统计特征的前提下,对原始 K 线进行扰动,生成新的价格路径。这种方法测试策略在不同市场条件下的表现,避免策略过度依赖特定历史走势。

Jesse 内置了两种管道实现:

高斯噪声管道

GaussianNoiseCandlesPipeline 为每根 K 线的价格添加高斯噪声,模拟市场微观结构的随机性。使用时需要指定噪声强度:

from jesse.research.monte_carlo.candle_pipelines import GaussianNoiseCandlesPipeline

pipeline_kwargs = {
    "batch_size": 7 * 24 * 60,  # 每次处理一周的 1m 数据
    "close_sigma": 10.0,        # 收盘价噪声标准差
    "high_sigma": 5.0,          # 最高价噪声
    "low_sigma": 5.0            # 最低价噪声
}

close_sigma 是最关键的参数,建议设置为标的资产 1 分钟 K 线的平均绝对价格变动。例如,BTC 在平静期平均 1 分钟波动约 10 美元,则设为 10.0。high_sigma 和 low_sigma 通常设为更小值,保持影线合理性。

batch_size 控制数据分批处理的粒度。较大的批次能保留更多短期趋势结构,较小批次则增加随机性。一般设为一周(7 天 × 24 小时 × 60 分钟)即可。

管道会自动维护 OHLC 的合理性:确保最高价不低于开盘和收盘价的较大值,最低价不高于开盘和收盘价的较小值,且所有价格为正。

移动块自助管道

MovingBlockBootstrapCandlesPipeline 采用移动块自助法,从原始价格变动序列中随机抽取连续块并重新组合。这种方法保留了价格序列的短期自相关性,生成的数据更接近真实市场结构:

from jesse.research.monte_carlo.candle_pipelines import MovingBlockBootstrapCandlesPipeline

pipeline_kwargs = {
    "batch_size": 7 * 24 * 60  # 块大小为一周
}

相比高斯噪声管道,移动块自助法无需手动调整噪声参数,更适合大多数场景。batch_size 在这里决定采样块的长度,影响保留的短期趋势强度。

自定义管道

如果内置管道无法满足需求,可以继承基类实现自定义变换。核心要求是保持 OHLC 关系和价格正性,同时引入足够的随机性以生成多样化的市场情景。

研究回测执行

研究模块的 backtest() 函数让我们脱离图形界面执行回测,返回结果字典包含所有性能指标、交易记录和图表数据。这在批量优化和机器学习场景中不可或缺。

基础用法

backtest() 是纯函数,所有输入通过参数传递,不依赖全局状态,因此支持多进程并行。函数签名如下:

result = backtest(
    config=config,
    routes=routes,
    extra_routes=extra_routes,
    candles=candles,
    warmup_candles=None,
    generate_charts=False,
    generate_tradingview=False,
    generate_hyperparameters=False,
    generate_equity_curve=False,
    generate_csv=False,
    generate_json=False,
    generate_logs=False,
    hyperparameters=None,
    fast_mode=False
)

参数分为三类:策略配置、数据输入和输出控制。config 字典定义起始资金、手续费、杠杆等交易参数;routes 列表指定交易路线;candles 和 warmup_candles 提供市场数据。

输出控制参数均为布尔值,决定是否生成对应格式的结果。在批量测试中,关闭不必要的输出能显著提速。

简单示例

以下示例展示如何在单文件中完成策略定义、数据生成和回测执行:

import jesse.helpers as jh
from jesse.strategies import Strategy
from jesse import utils
from jesse.research import backtest, candles_from_close_prices

# 生成合成数据
prices = [10, 11, 12, 12, 11, 13, 14, 12, 11, 15]
fake_candles = candles_from_close_prices(prices)

# 定义策略
class TestStrategy(Strategy):
    def should_long(self):
        return self.price > self.candles[-2][2]  # 当前价高于前一根收盘

    def go_long(self):
        qty = utils.size_to_qty(self.balance * 0.5, self.price)
        self.buy = qty, self.price

# 准备配置
config = {
    'starting_balance': 10_000,
    'fee': 0,
    'type': 'futures',
    'futures_leverage': 2,
    'futures_leverage_mode': 'cross',
    'exchange': 'Fake Exchange',
    'warm_up_candles': 0
}

routes = [
    {'exchange': 'Fake Exchange', 'strategy': TestStrategy, 
     'symbol': 'BTC-USDT', 'timeframe': '4h'}
]

candles = {
    jh.key('Fake Exchange', 'BTC-USDT'): {
        'exchange': 'Fake Exchange',
        'symbol': 'BTC-USDT',
        'candles': fake_candles,
    }
}

# 执行回测
result = backtest(config, routes, [], candles, generate_charts=True)

# 访问结果
print(result['metrics']['total_return'])
print(result['charts'])  # 图表文件路径

这个例子中,策略逻辑非常简单:只要当前价格高于前一根 K 线收盘价就做多。通过合成数据,我们可以快速验证策略框架是否正确。

真实数据回测

实际研究中,我们通常使用历史数据。以下函数展示了自动化回测的完整流程:

def execute_strategy(strategy_name, exchange, symbol, timeframe, 
                     config, start_date, finish_date):
    # 获取数据和预热期数据
    warmup_candles, trading_candles = research.get_candles(
        exchange, symbol, timeframe,
        jh.date_to_timestamp(start_date),
        jh.date_to_timestamp(finish_date),
        config['warm_up_candles'],
        caching=True,
        is_for_jesse=True
    )

    routes = [
        {'exchange': exchange, 'strategy': strategy_name, 
         'symbol': symbol, 'timeframe': timeframe}
    ]

    # 转换数据格式
    trading_candles_dict = {
        jh.key(exchange, symbol): {
            'exchange': exchange,
            'symbol': symbol,
            'candles': trading_candles,
        }
    }
    warmup_candles_dict = {
        jh.key(exchange, symbol): {
            'exchange': exchange,
            'symbol': symbol,
            'candles': warmup_candles,
        }
    }

    # 执行回测
    result = backtest(
        config,
        routes,
        [],
        candles=trading_candles_dict,
        warmup_candles=warmup_candles_dict,
        generate_charts=True,
        generate_equity_curve=True,
        generate_csv=True,
        generate_json=True,
        generate_logs=True,
        fast_mode=True
    )

    return result

关键点在于数据格式的转换。get_candles 返回的是 NumPy 数组,而 backtest 要求字典格式,键为 jh.key(exchange, symbol) 生成的唯一标识。

fast_mode 参数能大幅提升回测速度,但当前版本不支持多路线交易。对于单路线策略,建议始终开启。

蒙特卡洛回测整合

将回测与蒙特卡洛分析结合,可以全面评估策略质量。以下完整示例展示了从数据准备到结果可视化的完整流程:

from jesse.research import get_candles, monte_carlo_candles, monte_carlo_trades
from jesse.research.monte_carlo import (
    print_monte_carlo_candles_summary,
    plot_monte_carlo_candles_chart,
    print_monte_carlo_trades_summary,
    plot_monte_carlo_trades_chart
)

# 配置参数
config = {
    "starting_balance": 10_000,
    "fee": 0.05 / 100,
    "type": "futures",
    "futures_leverage": 10,
    "futures_leverage_mode": "cross",
    "warm_up_candles": 210,
}

routes = [
    {"exchange": "Binance Perpetual Futures", "symbol": "BTC-USDT", 
     "timeframe": "5m", "strategy": "MyStrategy"},
]

# 准备数据
warmup_candles, trading_candles = get_candles(
    'Binance Perpetual Futures', 'BTC-USDT', '5m',
    jh.date_to_timestamp('2024-01-01'),
    jh.date_to_timestamp('2024-06-01'),
    warmup_candles_num=210,
    caching=True,
    is_for_jesse=True
)

candles_dict = {
    jh.key('Binance Perpetual Futures', 'BTC-USDT'): {
        'exchange': 'Binance Perpetual Futures',
        'symbol': 'BTC-USDT',
        'candles': trading_candles,
    }
}

warmup_dict = {
    jh.key('Binance Perpetual Futures', 'BTC-USDT'): {
        'exchange': 'Binance Perpetual Futures',
        'symbol': 'BTC-USDT',
        'candles': warmup_candles,
    }
}

# 执行蒙特卡洛分析
mc_results = monte_carlo_candles(
    config=config,
    routes=routes,
    data_routes=[],
    candles=candles_dict,
    warmup_candles=warmup_dict,
    num_scenarios=100,
    progress_bar=True,
    fast_mode=True,
    candles_pipeline_class=GaussianNoiseCandlesPipeline,
    candles_pipeline_kwargs={
        "batch_size": 7 * 24 * 60,
        "close_sigma": 10.0,
        "high_sigma": 5.0,
        "low_sigma": 5.0
    }
)

# 打印摘要和图表
print_monte_carlo_candles_summary(mc_results)
plot_monte_carlo_candles_chart(mc_results)

这段代码首先获取 2024 年上半年的 5 分钟 K 线,然后运行 100 次蜡烛变换模拟。print_monte_carlo_candles_summary 会输出关键指标的统计摘要,plot_monte_carlo_candles_chart 生成结果分布图。

同样的流程适用于交易打乱模拟:

trades_results = monte_carlo_trades(
    config=config,
    routes=routes,
    data_routes=[],
    candles=candles_dict,
    warmup_candles=warmup_dict,
    num_scenarios=1000,
    progress_bar=True,
    benchmark=True,
    fast_mode=True
)

print_monte_carlo_trades_summary(trades_results)
plot_monte_carlo_trades_chart(trades_results)

交易打乱模拟通常需要更多场景(1000 次以上)才能获得统计显著性,因为它测试的是交易时序的影响,需要更大样本量。

结果解读

蒙特卡洛分析的核心输出是四列对比表:Original(原始结果)、Worst 5%(最差 5% 情景)、Median(中位数)、Best 5%(最佳 5% 情景)。

理想情况下,原始结果应接近中位数。如果原始结果远优于最佳 5% 情景,说明策略可能过度拟合特定历史路径,实盘表现很可能回落。相反,若原始结果接近中位数,表明策略在不同市场条件下表现稳健,即使遇到不利情况也能获得可接受的结果,实盘中有望取得更好表现。

对于交易打乱模拟,关注原始结果在分布中的位置。若处于最佳 5% 区间,说明策略收益严重依赖交易顺序的运气成分;若接近中位数,则证明交易选择本身具有优势。

通过研究工具与蒙特卡洛分析的结合,我们能够在策略上线前进行充分的压力测试,识别过拟合风险,只将真正稳健的策略投入实盘。这种基于统计验证的方法,是专业量化交易与业余尝试的根本区别之一。

下一章将探讨如何将经过验证的策略部署到实盘交易环境,配置交易所 API,并在纸交易模式中做最终测试。