本文是《以AI量化为生》系列的第五篇,我们将深入期货市场的数据管理,介绍如何设计期货数据定时下载机制,涵盖不同品种的当前主力合约、主连合约、加权合约的区别和应用场景。

前言

在前面的文章中,我们已经搭建好了vnpy环境,配置了数据库和数据源。这次我花了一周多的时间,设计一套数据定时下载机制。接下来,让我们开始真正的数据积累了。

期货市场与股票市场有个重要区别:期货合约有到期日。这意味着我们不能简单地下载某个固定代码合约的数据,而需要一个自动化的合约管理机制。

今天我们要解决的问题:

  • 合约类型选择:主力合约、主连合约、加权合约该如何选择?

  • 合约切换管理:如何处理主力合约的切换?

  • 数据连续性:如何确保策略回测的数据连续性?

  • 自动化下载:如何设计一个可靠的定时下载系统?

一、期货合约类型详解

1.1 期货合约的基本概念

期货合约都有固定的到期日,比如rb2601.SHFE表示2026年1月到期的螺纹钢合约。随着时间推移,不同月份的合约会轮流成为市场关注的焦点。

1.2 四种合约类型对比

让我用一个表格来说明不同合约类型的特点:

合约类型

代码示例

特点

适用场景

具体月份合约

rb2510.SHFE, rb2601.SHFE

真实交易合约,有到期日

实盘交易、短期策略

主力合约

rb2601.SHFE

当前成交量最大的合约

实盘交易、跟踪市场热点

主连合约

rb9999.SHFE

主力合约的连续拼接

短期回测、趋势分析

加权合约

rb8888.SHFE

按成交量加权的价格

长期回测、风险评估

1.3 主力合约的切换逻辑

主力合约的切换通常遵循以下规律:

螺纹钢主力合约切换示例

  • 2025年8月:rb2510为主力

  • 2025年9月:rb2601开始活跃

  • 2025年10月:rb2601成为主力

  • 2026年1月:rb2605开始活跃

切换时机

  • 通常在合约到期前1-2个月开始切换

  • 以成交量和持仓量为判断标准

  • 不同品种的切换规律略有差异

1.4 回测中的合约选择

说实话,刚开始做期货量化的时候,我在合约选择上踩了不少坑。让我分享一下实际经验:

主力合约(具体月份合约)

  • ✅ 真实可交易,用于实盘

  • ✅ 价格完全准确

  • ❌ 数据不连续,有到期问题

  • ❌ 需要处理合约切换

主连合约

  • ✅ 所有历史主力合约的价格拼接的连续K线数据

  • ✅ 最贴近真实交易环境,方便长期趋势和技术分析

  • ✅ 短期策略回测(在主力合约生命周期内完成)

  • ❌ 换月跳空是致命伤,由新旧两个合约价差造成

加权(指数)合约

  • ✅ 单个品种下的所有上市合约按持仓量或成交量加权计算的合成价格

  • ✅ 连续、平滑,避免了主力换月时的价格跳空

  • ✅ 反应整个品种市场的整体价格水平和趋势

  • ❌ 适合长期趋势跟踪策略,但存在“虚假”价格

二、期货品种参数配置

在开始下载数据之前,我们需要了解不同期货品种的基本参数。这些参数对于后续的策略开发和风险控制至关重要。

2.1 期货品种参数说明

每个期货品种都有以下关键参数:

  • size:合约乘数,每手交易单位

  • pricetick:最小变动价位

  • deposit_rate:保证金比例(百分比)

  • rate:交易手续费率(万分之*)

  • slippage:固定手续费(元)

2.2 全品种配置汇总

我们的系统支持中国期货市场的全部85个品种,覆盖6大交易所:

品种分布统计

  • CFFEX(中金所): 8个品种 - IC, IF, IH, IM, T, TF, TL, TS

  • CZCE(郑商所): 27个品种 - AP, CF, CJ, CY, FG, JR, LR, MA, OI, PF, PK, PL, PM, PR, PX, RI, RM, RS, SA, SF, SH, SM, SR, TA, UR, WH, ZC

  • DCE(大商所): 23个品种 - a, b, bb, bz, c, cs, eb, eg, fb, i, j, jd, jm, l, lg, lh, m, p, pg, pp, rr, v, y

  • GFEX(广期所): 3个品种 - lc, ps, si

  • INE(能源中心): 5个品种 - bc, ec, lu, nr, sc

  • SHFE(上期所): 19个品种 - ad, ag, al, ao, au, br, bu, cu, fu, hc, ni, pb, rb, ru, sn, sp, ss, wr, zn

品种类型覆盖

  • 金属类:贵金属(金银)、有色金属(铜铝锌镍锡铅)、黑色金属(螺纹钢热卷铁矿石焦炭焦煤)

  • 能源化工:原油、燃料油、沥青、甲醇、PTA、聚乙烯、PVC、聚丙烯等

  • 农产品:大豆、豆粕、豆油、玉米、棉花、白糖、苹果、红枣等

  • 金融类:股指期货(沪深300、中证500、上证50、中证1000)、国债期货

2.3 主要品种参数配置

让我创建一个期货品种配置文件:

# config/futures_config.py
FUTURES_INFO = {
    # 上期所品种
    "rb": {  # 螺纹钢
        "name": "螺纹钢",
        "exchange": Exchange.SHFE,
        "size": 10,  # 10吨/手
        "pricetick": 1,  # 1元/吨
        "deposit_rate": 0.09,  # 9%保证金
        "rate": 0.00001,  # 万分之一手续费率
        "slippage": 0,  # 0固定手续费
        "active_months": [1, 5, 10],  # 主要交易月份
    },
    "cu": {  # 沪铜
        "name": "沪铜",
        "exchange": Exchange.SHFE,
        "size": 5,  # 5吨/手
        "pricetick": 10,  # 10元/吨
        "deposit_rate": 0.07,  #7%保证金
        "rate": 0,  # 0手续费率
        "slippage": 3,  #3元固定手续费
        "active_months": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
    },
    # ... 更多品种配置
}

这个配置文件包含了期货品种的所有关键参数,为后续的策略开发和风险控制提供基础数据。

三、智能合约管理系统

3.1 设计思路与优化

期货合约管理的核心挑战是:如何为不同品种选择合适数量的活跃合约,既要覆盖主力切换,又要避免下载过多无用数据

我们的系统采用基于active_months的智能合约管理策略,根据每个品种的交易特点进行个性化配置:

当前合约下载策略

  1. 品种分类处理

    • CFFEX品种(股指、国债):每个品种下载2个未来到期合约 + 主连 + 加权,共4个合约

    • 商品期货品种:根据active_months配置,最多6个月份合约 + 主连 + 加权

  2. 具体下载规则

    • 螺纹钢(rb):下载[1,5,10]月份合约,通常为3个月份合约

    • 沪铜(cu):下载全年12个月份,但智能截断为6个连续月份

    • 甲醇(MA):下载[1,5,9]月份合约,共3个月份合约

    • 股指期货(IF/IC/IH/IM):下载接下来2个季月合约([3,6,9,12]中的未来2个)

  3. 统一添加

    • 主连合约:品种代码+9999(如rb9999)

    • 加权合约:品种代码+8888(如rb8888)

因此,我们设计了这套基于品种特性的智能合约管理系统:

3.2 active_months配置策略

我们为每个品种配置了active_months列表,代表该品种的活跃交易月份:

# 不同品种的活跃月份配置(实际代码示例)
FUTURES_INFO = {
    "rb": {  # 螺纹钢:季节性品种
        "active_months": [1, 5, 10],  # 3个主要合约
    },
    "cu": {  # 沪铜:连续交易品种  
        "active_months": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],  # 全年12个月
    },
    "MA": {  # 甲醇:化工品种
        "active_months": [1, 5, 9],  # 3个主要合约
    },
    "IF": {  # 股指期货:季月合约
        "active_months": [3, 6, 9, 12],  # 4个季月
    },
    "i": {   # 铁矿石:黑色系品种
        "active_months": [1, 5, 9],  # 3个主要合约
    },
    "au": {  # 沪金:贵金属
        "active_months": [2, 4, 6, 8, 10, 12],  # 6个双月
    },
}

配置原则

  1. 智能截断:连续交易品种(如沪铜全年12个月)会被系统自动截断为6个连续月份

  2. CFFEX特殊处理:股指和国债期货无论配置多少月份,都只下载2个未来合约

  3. 覆盖主力切换:确保包含当前主力和未来1-2个主力合约

  4. 品种特色:根据每个品种的实际交易特点配置活跃月份

3.3 智能合约生成算法

基于active_months配置,系统会自动生成合约列表,核心函数get_priority_contracts()

def get_priority_contracts(symbol: str, base_date: datetime = None) -> List[str]:
    """获取优先下载的合约列表(智能截断active_months)"""
    if base_date isNone:
        base_date = datetime.now()
    
    info = get_futures_info(symbol)
    active_months = info.get("active_months", [1, 3, 5, 7, 9, 11])
    exchange_suffix = info["exchange"].value
    current_month = base_date.month
    current_year = base_date.year
    
    # CFFEX特殊处理:每次最多只下载2个未来合约
    if exchange_suffix == "CFFEX":
        contracts = []
        found_months = []
        
        # 找到接下来的2个活跃月份
        for i in range(24):  # 查找24个月内的合约
            check_month = (current_month + i) % 12
            if check_month == 0:
                check_month = 12
            check_year = current_year + (current_month + i - 1) // 12
            
            if check_month in active_months and len(found_months) < 2:
                # 跳过当前月份,只要未来的合约
                if check_year > current_year or check_month > current_month:
                    found_months.append((check_year, check_month))
        
        # 生成合约代码
        for year, month in found_months:
            year_suffix = str(year)[-2:]
            month_str = f"{month:02d}"
            future_contract = f"{symbol}{year_suffix}{month_str}.{exchange_suffix}"
            contracts.append(future_contract)
    
    else:
        # 其他交易所:智能截断逻辑,最多6个月
        if len(active_months) > 6:
            # 从下个月开始,选择连续的6个月
            selected_months = []
            start_month = current_month + 1
            for i in range(6):
                target_month = start_month + i
                if target_month > 12:
                    target_month = target_month - 12
                
                if target_month in active_months:
                    selected_months.append(target_month)
            
            active_months = selected_months
        
        # 生成合约代码
        contracts = []
        for month in active_months:
            # 确定年份:如果月份小于等于当前月份,则使用下一年
            if month <= current_month:
                year = current_year + 1
            else:
                year = current_year
            
            year_suffix = str(year)[-2:]
            month_str = f"{month:02d}"
            
            if exchange_suffix == "CZCE":
                # 郑商所格式:MA509 (5表示2025年,09表示9月)
                czce_year = str(year)[-1:]  # 取年份最后一位
                contract = f"{symbol}{czce_year}{month_str}.{exchange_suffix}"
            else:
                contract = f"{symbol}{year_suffix}{month_str}.{exchange_suffix}"
            
            contracts.append(contract)
    
    # 添加主连和加权合约
    continuous_contract = f"{symbol}9999.{exchange_suffix}"
    weighted_contract = f"{symbol}8888.{exchange_suffix}"
    contracts.extend([continuous_contract, weighted_contract])
    
    return contracts

算法特点

  1. CFFEX特殊处理:股指和国债期货只下载2个未来到期合约

  2. 智能截断:商品期货如果活跃月份超过6个,自动截断为6个连续月份

  3. 年份处理:自动判断合约年份,过期月份使用下一年

  4. 交易所格式:郑商所使用特殊的年份格式(1位数字)

3.4 实际下载效果

当前系统的合约下载情况(以2026年1月为例):

  • 螺纹钢(rb):5个合约

    • 月份合约:rb2601, rb2605, rb2610(基于[1,5,10]配置)

    • 连续合约:rb9999, rb8888

  • 沪铜(cu):8个合约

    • 月份合约:cu2601, cu2602, cu2603, cu2604, cu2605, cu2606(智能截断为6个连续月)

    • 连续合约:cu9999, cu8888

  • 甲醇(MA):5个合约

    • 月份合约: MA601,MA605, MA609(基于[1,5,9]配置,郑商所年份格式)

    • 连续合约:MA9999, MA8888

  • 股指期货(IF):4个合约

    • 月份合约:IF2603, IF2606(接下来2个季月)

    • 连续合约:IF9999, IF8888

优化效果:相比下载所有月份合约,数据量减少60-80%,同时确保覆盖所有活跃交易的重要合约。

四、数据下载核心实现

4.1 下载器设计

我设计了一个功能完整的期货数据下载器,支持:

  • 多数据源:天勤、米筐等

  • 批量下载:支持多品种并行下载

  • 增量更新:只下载新增数据

  • 错误重试:网络异常自动重试

  • 数据验证:确保数据质量

核心下载逻辑:

class FuturesDataDownloader:
    """期货数据下载器"""
    
    def download_contract_data(self, vt_symbol: str, start_date: datetime = None, end_date: datetime = None) -> bool:
        """下载单个合约的数据"""
        # 转换为天勤格式
        tq_symbol = self.convert_to_tq_symbol(vt_symbol)
        
        # 获取数据
        if start_date and end_date:
            klines = self.tq_api.get_kline_data_series(tq_symbol, 60, start_dt=start_date, end_dt=end_date)
        else:
            klines = self.tq_api.get_kline_serial(tq_symbol, 60, data_length=10000)
        
        # 转换为vnpy格式并保存
        bars = self.convert_to_vnpy_bars(klines, vt_symbol)
        self.save_bars_batch(bars)
        
        returnTrue

4.2 天勤数据源适配

天勤数据的合约代码格式与vnpy不同,需要做转换:

vnpy格式

天勤格式

说明

rb2510.SHFE

SHFE.rb2510

普通合约

rb9999.SHFE

KQ.m@SHFE.rb

主连合约

rb8888.SHFE

KQ.i@SHFE.rb

加权合约

def convert_to_tq_symbol(self, vt_symbol: str) -> str:
    """将vnpy格式转换为天勤格式"""
    symbol_part, exchange_part = vt_symbol.split('.')
    
    if symbol_part.endswith('9999'):
        # 主连合约
        base_symbol = symbol_part[:-4]
        returnf"KQ.m@{exchange_part}.{base_symbol}"
    elif symbol_part.endswith('8888'):
        # 加权合约
        base_symbol = symbol_part[:-4]
        returnf"KQ.i@{exchange_part}.{base_symbol}"
    else:
        # 普通合约
        returnf"{exchange_part}.{symbol_part}"

五、定时下载系统

5.1 配置文件设计

为了方便管理,我设计了一个JSON格式的配置文件:

{
  "enabled_symbols": [
    "AP", "CF", "CJ", "CY", "FG", "IC", "IF", "IH", "IM", "JR",
    "LR", "MA", "OI", "PF", "PK", "PL", "PM", "PR", "PX", "RI",
    "RM", "RS", "SA", "SF", "SH", "SM", "SR", "T", "TA", "TF",
    "TL", "TS", "UR", "WH", "ZC", "a", "ad", "ag", "al", "ao",
    "au", "b", "bb", "bc", "br", "bu", "bz", "c", "cs", "cu",
    "eb", "ec", "eg", "fb", "fu", "hc", "i", "j", "jd", "jm",
    "l", "lc", "lg", "lh", "lu", "m", "ni", "nr", "p", "pb",
    "pg", "pp", "ps", "rb", "rr", "ru", "sc", "si", "sn", "sp",
    "ss", "v", "wr", "y", "zn"
  ],
"update_mode": "incremental",
"batch_size": 5,
"retry_times": 3,
"retry_delay": 60,
"log_level": "INFO",
"notification": {
    "enabled": false,
    "email": true,
    "feishu": false
  }
}

核心配置项说明

update_mode(更新模式): 该字段决定每次下载的数据量:

  • incremental:增量更新模式

    • 下载2000条最新数据

    • 有夜盘品种(如铜、螺纹钢):约4天数据(21小时/天)

    • 日盘品种(如尿素、苹果):约8天数据(4小时/天)

    • 适合日常维护,速度快,流量消耗小

  • full:全量更新模式

    • 下载10000条数据(天勤免费版最大限制)

    • 约1-2个月的历史数据

    • 适合初次下载某个品种或数据修复

    • 数据量大,下载时间较长

batch_size(批处理大小)

  • 每批处理的品种数量,采用顺序处理而非并发

  • 默认值5,表示每次处理5个品种后稍作停顿

  • 主要用于控制下载节奏,避免对天勤服务器造成过大压力

  • 不是并发数量,而是分批处理的批次大小

合约选择逻辑: 系统会根据每个品种在futures_config.py中的active_months配置自动选择合约:

# 示例:不同品种的合约选择结果
"rb": 5个合约 -> rb2510, rb2601, rb2605, rb9999, rb8888
"cu": 8个合约 -> cu2509, cu2510, cu2511, cu2512, cu2601, cu2602, cu9999, cu8888  
"MA": 5个合约 -> MA509, MA601, MA605, MA9999, MA8888
"IF": 6个合约 -> IF2509, IF2512, IF2603, IF2606, IF9999, IF8888

其他配置项

  • enabled_symbols:启用下载的品种列表,当前配置包含全部85个期货品种,覆盖6大交易所

  • retry_times:网络异常时的重试次数,默认3次

  • retry_delay:重试间隔时间(秒),默认60秒

  • log_level:日志级别,INFO显示关键信息,DEBUG显示详细信息

  • notification:通知配置,支持邮件和飞书通知(需要额外配置)

5.3 命令行工具

为了方便使用,我创建了一个命令行工具,让我们看看每个命令的具体效果:

1. 下载所有配置的品种

python scripts/download_futures_data.py

效果:下载config/download_config.json中enabled_symbols列表的所有品种的多个合约,包括多个未来主力合约、主连合约和指数合约。

2. 下载指定品种

python scripts/download_futures_data.py --symbols rb hc cu

效果:只下载螺纹钢、热轧卷板、沪铜三个品种,每个品种下载其优先合约(rb会下载rb2510、rb2601、rb2605、rb9999、rb8888等5个合约)

3. 设置下载模式和天数

python scripts/download_futures_data.py --mode priority --days 7

效果:使用优先合约模式,只下载最近7天的数据,适合日常增量更新

python scripts/download_futures_data.py --mode all --days 30

效果:下载所有相关合约,获取最近30天的数据,数据量会比priority模式大3-4倍

4. 查看系统信息

# 显示当前配置
python scripts/download_futures_data.py --show-config
# 输出:当前启用的品种列表、下载模式、数据范围等配置信息

# 列出支持的品种
python scripts/download_futures_data.py --list-symbols
# 输出:系统支持的85个期货品种列表,包括品种代码、中文名称、交易所

六、实际操作演示

让我们来实际测试一下这个下载系统:

6.1 环境准备

首先确保环境配置正确:

# 安装天勤数据源插件
pip install vnpy_tqsdk

注:数据库驱动在上一篇文章中已经介绍过安装方法,请根据你选择的数据库类型安装对应驱动。

6.2 配置数据源

.env文件中配置天勤账户:

# 天勤数据源配置
DATAFEED_NAME=tqsdk
DATAFEED_USERNAME=your_phone_number
DATAFEED_PASSWORD=your_password

6.3 批量下载测试

测试下载多个品种:

# 下载指定品种
python scripts/download_futures_data.py --symbols rb hc cu

# 输出示例
批量下载开始,共 3 个品种
============================================================

开始下载 rb 数据...
==================================================
优先下载合约: 5 个

下载: rb2510.SHFE
下载合约: rb2510.SHFE -> SHFE.rb2510
✓ rb2510.SHFE 下载完成,共 2016 条数据

下载: rb2601.SHFE
下载合约: rb2601.SHFE -> SHFE.rb2601
✓ rb2601.SHFE 下载完成,共 1890 条数据

rb 下载完成: 5/5 成功

开始下载 hc 数据...
==================================================
hc 下载完成: 5/5 成功

开始下载 cu 数据...
==================================================
cu 下载完成: 8/8 成功

总计: 18/18 合约下载成功

七、定时任务配置

7.1 Linux Cron配置

在Linux系统上,我们可以使用cron来设置定时任务:

# 编辑crontab
crontab -e

# 添加定时任务
# 每天早上9点下载数据
0 9 * * * cd /path/to/atmquant && python scripts/download_futures_data.py

# 每小时增量更新
0 * * * * cd /path/to/atmquant && python scripts/download_futures_data.py --update-mode incremental --days 1

7.2 Windows任务计划

在Windows上可以使用任务计划程序:

# 创建批处理文件 download_data.bat
@echo off
cd /d "C:\path\to\atmquant"
python scripts/download_futures_data.py
pause

然后在任务计划程序中设置定时运行这个批处理文件。

7.3 Python定时任务

也可以使用Python的schedule库:

import schedule
import time
from scripts.download_futures_data import FuturesDataScheduler

def run_download():
    """执行下载任务"""
    scheduler = FuturesDataScheduler()
    scheduler.run_download()

# 每天9点执行
schedule.every().day.at("09:00").do(run_download)

# 每小时执行增量更新
schedule.every().hour.do(lambda: run_download_incremental())

whileTrue:
    schedule.run_pending()
    time.sleep(60)

八、常见问题解决

8.1 天勤SDK相关问题

问题1:登录失败

TqSdk登录失败: 用户名或密码错误

解决方案

  • 检查用户名是否为手机号

  • 确认密码正确

  • 检查账户是否被冻结

问题2:数据获取失败

获取数据失败: 合约不存在

解决方案

  • 检查合约代码格式是否正确

  • 确认合约是否已上市

  • 验证交易所代码是否正确

8.2 数据库相关问题

问题1:数据库连接失败

数据库连接失败: Access denied

解决方案

  • 检查数据库服务是否启动

  • 验证用户名密码

  • 确认数据库权限设置

问题2:数据保存失败

数据保存失败: Duplicate entry

解决方案

  • 检查是否有重复数据

  • 使用INSERT IGNORE或ON DUPLICATE KEY UPDATE

  • 清理异常数据

8.3 网络相关问题

问题1:网络超时

网络请求超时

解决方案

  • 增加超时时间设置

  • 使用重试机制

  • 检查网络连接稳定性

问题2:频率限制

API调用频率超限

解决方案

  • 降低请求频率

  • 使用批量请求接口

  • 升级账户等级

这个期货数据下载系统为我们后续的策略开发奠定了坚实的数据基础。

下一步计划

有了稳定的数据下载系统,接下来最重要的就是日志记录和监控告警了。毕竟系统跑起来后,我们得知道它运行得怎么样。

下一篇我会分享如何搭建日志管理和监控告警系统:

  • 怎么进行交易系统运行的日志管理

  • 如何设置告警通知(邮件、飞书、钉钉等)

  • 简单的系统运行监控和自动重启机制

这样即使人不在电脑前,也能及时知道系统的运行状态。


本文是《以AI量化为生》系列文章的第五篇,完整代码已开源至GitHub:https://github.com/seasonstar/atmquant

关注公众号"堂主的ATMQuant",获取更多量化交易干货!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐