3. 市场数据管理

3.市场数据管理

市场数据是量化交易策略的基石。没有高质量、完整的历史数据,再精妙的策略也只是一纸空谈。Jesse 框架在数据管理方面设计得相当周到,既提供了便捷的图形化操作界面,也为深度研究准备了灵活的编程接口。这一章,我们将深入探讨如何在 Jesse 中高效管理市场数据,从导入历史 K 线到生成合成数据,覆盖回测与研究所需的各个环节。

导入历史 K 线

回测的第一步永远是数据。Jesse 提供了专门的页面来处理历史 K 线的导入工作,整个过程直观且自动化程度很高。

打开 Jesse 的仪表盘,找到 "Import Candles" 页面。这里需要填写三个核心参数:交易所、交易对和起始日期。值得注意的是,系统没有提供结束日期的输入框,因为 Jesse 默认会导入数据直到当前时刻。这种设计很贴心——我们只需定期重复执行相同的导入命令,就能保持数据的持续更新,而无需每次都调整时间范围。

举个例子,如果要导入 Binance 上 BTC-USDT 从 2018 年 6 月 1 日至今的所有 K 线,只需在表单中选择 Binance 作为交易所,输入 BTC-USDT 作为交易对,然后将起始日期设为 2018-06-01。点击执行后,Jesse 会自动处理后续所有工作。

导入过程支持断点续传和增量更新。如果因为网络中断导致导入失败,下次执行相同参数的任务时,Jesse 会自动跳过已下载的数据,从断点继续。更妙的是,当需要更新数据时,重复运行相同的导入命令,系统会智能识别并跳过已存在的记录,只下载新增的数据。这意味着我们完全可以设置一个定时任务,每天自动更新数据,而不用担心重复导入或长时间等待。

在导入大量历史数据时,Jesse 会显示进度条,这对于估算等待时间非常有帮助。特别是在 Jupyter Notebook 环境中执行批量导入任务时,进度条能让我们直观了解当前进展。如果不需要这个视觉反馈,可以通过设置 show_progress_bar=False 来关闭它。

选择数据源

数据质量直接影响回测结果的可信度。Jesse 对数据源的支持分为两个层面:回测数据源和实盘交易数据源。这两者的支持范围有所不同,理解这种差异对策略开发至关重要。

回测支持的数据源

目前 Jesse 支持从以下交易所导入历史 K 线用于回测研究:

  • Binance Spot(币安现货)
  • Binance US Spot(币安美国站现货)
  • Binance Perpetual Futures(币安永续合约)
  • Bitfinex Spot(Bitfinex 现货)
  • Coinbase Spot(Coinbase 现货,现称 Coinbase Advanced)
  • Bybit USDT Perpetual(Bybit USDT 永续合约)
  • Bybit USDC Perpetual(Bybit USDC 永续合约)
  • Bybit Spot(Bybit 现货)
  • Gate.io Perpetual Futures(Gate.io 永续合约)

这里需要特别注意一个关键点:交易所名称中的 "Spot" 或 "Futures" 仅表示数据来源,并不限制回测的类型。也就是说,我们可以使用 Binance Spot 的 K 线数据来运行期货模式的回测,反之亦然。这种灵活性让我们能够充分利用不同市场的数据特征来验证策略的健壮性。

每种数据源都有其特点。Binance 提供了最丰富的历史数据,覆盖时间长且品种齐全,是大多数策略回测的首选。Coinbase 的数据质量很高,特别适合美国市场的策略研究。Bybit 和 Gate.io 则提供了更多永续合约的选择。在选择数据源时,建议优先考虑数据完整性和历史长度,因为这直接决定了回测的时间跨度和统计显著性。

实盘交易支持

实盘交易的支持范围更广,包括 Apex Pro、Apex Omni、Hyperliquid、Bybit 全系列、Binance 全系列、Coinbase Advanced 以及 Gate.io 等。Jesse 团队会根据社区需求持续开发新的交易所驱动。如果目标交易所不在支持列表中,可以联系开发者赞助开发,这通常是获得新交易所支持最快的方式。

存储自定义数据

研究过程中,我们经常需要处理非标准来源的数据。Jesse 的研究模块为此提供了 store_candles 函数,允许将自定义的 K 线数据存入数据库,后续可直接用于回测或分析。

这个功能的典型应用场景是从 CSV 文件导入数据。假设我们有一份从其他渠道获取的分钟级交易数据,可以将其转换为 NumPy 数组格式,然后调用 store_candles 函数存入 Jesse 的数据库。存入后,这些数据就可以像标准导入的数据一样,通过 get_candles 函数调取,或在回测中直接使用。

使用 store_candles 时有几个重要细节需要注意。首先,Jesse 内部使用 1 分钟级别的 K 线作为基准,其他时间周期都是在运行时动态生成的。因此,存入的数据必须是 1 分钟周期的 K 线。其次,数据格式需要符合 Jesse 的规范,即一个包含时间戳、开盘价、收盘价、最高价、最低价和成交量的 NumPy 数组。

下面是一个完整的示例,展示如何将一组收盘价转换为标准 K 线并存储:

from jesse import research
import numpy as np

# 假设我们有一组收盘价数据
close_prices = [10, 11, 12, 12, 11, 13, 14, 12, 11, 15]

# 使用 Jesse 提供的函数将收盘价转换为完整的 K 线数据
# 这个函数会自动生成合理的时间戳和 OHLCV 值
np_candles = research.candles_from_close_prices(close_prices)

# 将生成的 K 线存入数据库,标记为来自"Test Exchange"的 BTC-USDT 数据
research.store_candles(np_candles, 'Test Exchange', 'BTC-USDT')

这段代码首先定义了一组简单的收盘价序列,然后调用 candles_from_close_prices 函数将其转换为完整的 K 线数组。这个转换函数非常智能,它会为第一个 K 线设置标准起始时间戳(2021 年 1 月 1 日),之后每个 K 线的时间戳依次递增一分钟。开盘价、最高价、最低价会根据收盘价自动计算,生成合理的波动范围。最后,我们调用 store_candles 将这些数据存入数据库,指定交易所名称为 'Test Exchange',交易对为 'BTC-USDT'。

存入后,这些数据就可以通过 get_candles 函数调取使用。get_candles 是 Jesse 研究模块中最重要的数据获取函数之一,它不仅能读取数据库中的 K 线,还能自动处理预热数据(warm-up candles)的获取。

import jesse.helpers as jh

# 获取 Binance Spot 上 ETH-USDT 的 4 小时 K 线
# 时间范围从 2019-07-28 到 2019-09-28
warmup_candles, trading_candles = research.get_candles(
    'Binance Spot', 
    'ETH-USDT', 
    '4h', 
    jh.date_to_timestamp('2019-07-28'), 
    jh.date_to_timestamp('2019-09-28')
)

# 如果没有设置预热数据,第一个返回值会是 None
print(warmup_candles)
# 输出: None

# 查看第一条 K 线的数据结构
print(trading_candles[0])
# 输出: array([
#     1.56427200e+12,  # 时间戳(毫秒)
#     2.07300000e+02,  # 开盘价
#     2.07750000e+02,  # 收盘价
#     2.08230000e+02,  # 最高价
#     2.06170000e+02,  # 最低价
#     2.15143531e+04  # 成交量
# ])

get_candles 函数返回两个值:预热数据和交易数据。预热数据用于指标计算时的历史数据需求,比如计算 200 日均线就需要至少 200 条之前的 K 线。如果 warmup_candles_num 参数设置为 0,函数会返回 None 作为第一个值。第二个返回值是实际用于回测或分析的交易数据。

函数参数中,caching 选项非常实用。当设置为 True 时,Jesse 会将获取的数据缓存到本地,下次请求相同数据时可以直接读取缓存,大幅提升速度。is_for_jesse 参数则控制返回数据的格式,如果数据将用于 Jesse 的回测函数,需要设置为 True 以确保格式兼容。

生成合成 K 线

在策略研究的早期阶段,使用真实市场数据可能并不是最佳选择。真实数据噪音大、模式复杂,不利于验证策略的核心逻辑是否有效。Jesse 的研究模块为此提供了一套完整的合成 K 线生成工具,让我们能够快速创建各种市场情景,专注于策略逻辑的测试。

基础单根 K 线生成

fake_candle 函数可以生成单根模拟 K 线。如果不指定任何参数,它会生成一根包含随机但合理价格数据的 K 线,时间戳也会自动设置。这在需要快速创建测试数据时非常方便。

from jesse import research

# 生成一根随机的模拟 K 线
c1 = research.fake_candle()
print(c1)
# 输出: [1.60945986e+12 9.70000000e+01 1.02000000e+02 1.02000000e+02
#  9.60000000e+01 2.20000000e+01]

输出是一个 NumPy 数组,包含六个元素:时间戳、开盘价、收盘价、最高价、最低价和成交量。所有数值都是基于合理的市场波动随机生成的。

我们也可以精确控制 K 线的每个属性:

# 生成指定属性的 K 线
c2 = research.fake_candle({
    'timestamp': 1643104557000,
    'open': 10,
    'close': 11,
    'high': 12,
    'low': 8,
    'volume': 200,
})
print(c2)
# 输出: [1.64310456e+12 1.00000000e+01 1.10000000e+01 1.20000000e+01
#  8.00000000e+00 2.00000000e+02]

这种精确控制的能力在单元测试中特别有用。我们可以创建具有特定特征的 K 线,验证策略在特定市场条件下的行为是否符合预期。

批量 K 线序列生成

单根 K 线虽然灵活,但测试策略通常需要连续的价格序列。fake_range_candles 函数可以一次性生成指定数量的 K 线序列。

# 生成包含 3 根 K 线的序列
range_candles = research.fake_range_candles(3)
print(range_candles)
# 输出: [[1.60945920e+12 1.06000000e+02 1.09000000e+02 1.09000000e+02
#   1.05000000e+02 9.70000000e+01]
#  [1.60945926e+12 1.09000000e+02 1.10000000e+02 1.10000000e+02
#   1.08000000e+02 1.50000000e+01]
#  [1.60945932e+12 1.10000000e+02 1.17000000e+02 1.17000000e+02
#   1.09000000e+02 2.00000000e+00]]

生成的序列具有连贯性,每根 K 线的时间戳依次递增,价格也在前一根的基础上合理波动,模拟了真实市场的连续性。这种批量生成的方式在需要快速创建测试数据集时效率很高。

从收盘价序列生成 K 线

最常见的合成数据需求是将一组已知的收盘价转换为完整的 K 线数据。candles_from_close_prices 函数专门处理这种场景。它接收一个收盘价列表,自动生成包含时间戳和合理 OHLCV 值的完整 K 线数组。

# 定义一组收盘价序列
close_prices = [10, 11, 12, 12, 11, 13, 14, 12, 11, 15]

# 转换为完整 K 线数据
np_candles = research.candles_from_close_prices(close_prices)

# 现在可以直接用于回测
result = backtest(
    config,
    routes,
    extra_routes,
    {jh.key(exchange_name, symbol): {
        'exchange': exchange_name,
        'symbol': symbol,
        'candles': np_candles,
    }},
)

这个函数在策略开发的早期验证阶段特别有价值。我们可以设计一些理想化的价格走势,比如清晰的上升趋势、下降趋势或震荡区间,然后观察策略是否能正确识别这些模式并做出相应反应。通过控制输入的收盘价序列,我们可以完全掌控测试场景,排除真实市场中不可预测的噪音干扰。

合成数据在研究中的应用

合成 K 线不仅用于简单的逻辑验证,更是高级研究工具的基础。Jesse 的蒙特卡洛分析模块就大量依赖这些合成数据生成函数。

蒙特卡洛分析通过创建大量略有不同的市场情景,测试策略的稳健性。其中,基于交易的蒙特卡洛会打乱交易顺序,而基于 K 线的蒙特卡洛则会使用蜡烛管道(candle pipelines)来修改原始市场数据。

from jesse.research.monte_carlo.candle_pipelines import GaussianNoiseCandlesPipeline

# 运行基于 K 线的蒙特卡洛分析
results = monte_carlo_candles(
    config=config,
    routes=routes,
    data_routes=data_routes,
    candles=candles,
    warmup_candles=warmup_candles,
    num_scenarios=100,
    candles_pipeline_class=GaussianNoiseCandlesPipeline,
    candles_pipeline_kwargs={"batch_size": 7 * 24 * 60},  # 一周的数据作为一个批次
)

在这个例子中,GaussianNoiseCandlesPipeline 会为原始 K 线添加高斯噪声,创建 100 个略有不同的市场情景。通过观察策略在这些情景下的表现差异,可以评估策略对市场噪音的敏感程度。如果策略在大部分情景中表现稳定,说明其逻辑健壮;如果表现波动巨大,则可能需要重新审视策略的核心假设。

另一个强大的管道是 MovingBlockBootstrapCandlesPipeline,它通过移动块自助法重新采样价格变动,在保持短期模式的同时创造新的市场路径。这种方法比简单添加噪声更贴近真实市场的随机特性。

合成数据生成函数为策略研究提供了极大的灵活性。无论是快速验证一个想法,还是进行复杂的稳健性测试,这些工具都能帮助我们更高效地迭代和优化策略。

数据管理的最佳实践

在实际项目中,良好的数据管理习惯能节省大量时间。首先,建立规范的数据更新流程。对于主力策略使用的交易对,建议设置每日自动更新任务,确保数据始终处于最新状态。Jesse 的增量更新机制让这个过程非常轻量,通常只需几分钟就能完成。

其次,合理组织自定义数据。当从多个来源获取数据时,建议使用清晰的命名规范,比如在交易所名称中包含数据来源信息(如 'CSV_Import_Binance'),这样在后续分析中能快速识别数据出处。

最后,充分利用缓存机制。在频繁获取相同数据的研究场景中,开启 caching=True 能显著提升工作效率。缓存数据存储在本地,下次请求时直接读取,避免了重复的数据库查询和网络传输。

数据是量化交易的燃料。Jesse 提供的市场数据管理工具,从简单的图形化导入到复杂的合成数据生成,形成了一个完整的工作流。掌握这些工具,意味着我们能更专注于策略逻辑本身,而不是被数据问题所困扰。

下一章,我们将进入策略开发的核心环节——定义交易进出规则。有了高质量的数据作为基础,接下来就要看如何让策略在市场中识别机会并执行交易了。