期货 / 期货:配置、合约与数据

期货

期货:配置、合约与数据

期货回测策略入门:运行配置与期货账户、真实合约代码与交易所后缀、最小可运行策略,回调函数与期货成交回报,交易日与夜盘,当前行情字段,以及用 panda_data 查询期货历史行情、合约资料与主力映射。

期货策略与股票用同一套回测引擎,但账户、合约、开平仓和换月规则都不一样。本页讲账户、合约与数据;下单见开平仓与订单,换月见主力换月与排查。日线和分钟回测写法相同。

一、运行配置与账户

运行前选择期货账户、日期区间、日线或分钟频率,并配置资金、手续费、保证金和滑点。策略通过 context.run_info 读取这些配置,不在代码里覆盖。

属性 类型、单位 用途
future_account 字符串 本次期货账户;默认回测账号为 '5588'
start_date / end_date YYYYMMDD 字符串 回测起止日期
frequency 字符串 '1d' 日线,'1M' 分钟
future_starting_cash 数值,元 期货初始资金
future_slippage 数值,最小变动价位的个数 回测撮合滑点,设为非负整数
commission_multiplier 数值,倍 手续费倍率
margin_multiplier 数值,倍 保证金倍率
matching_type 整数 撮合参考价,见市价、限价与撮合价格

context.account = context.run_info.future_account 保存账户,再用 context.future_account_dict[context.account] 读取账户对象。初始化阶段设置参数、准备数据;从交易回调开始才读取资金和持仓。

策略参数只用数字、布尔值、字符串或字符串列表,例如合约列表、目标手数和窗口长度。行情缓存、订单登记表等运行状态在 initialize 中自己建立。

二、合约代码

下单使用真实交割合约,例如 A2501.DCE。示例中的合约是历史回测用的代码,运行区间必须落在它有数据、可交易的期间;换到其他期间时选对应的真实合约。

后缀 交易所
.SHF 上海期货交易所
.DCE 大连商品交易所
.CZC 郑州商品交易所
.CFE 中国金融期货交易所
.INE 上海国际能源交易中心
.GFE 广州期货交易所

品种代码(如 A)用于查主力;A_DOMINANT.DCE 这类连续代码用于研究行情。下单前要从主力映射取得对应的真实 symbol,不要把连续代码或交易所原始代码直接当成交割合约。

三、最小策略

下面这份完整策略在取得有效行情、确认没有在途订单且没有持仓时,开 1 手多仓。它只用来理解流程,不含平仓信号。

python
from panda_backtest.api.api import buy_open, get_open_future_orders


def initialize(context):
    context.account = context.run_info.future_account
    context.symbol = "A2501.DCE"
    context.hands = 1
    context.sub_future_symbol([context.symbol])


def handle_data(context, data):
    bar = data[context.symbol]
    if bar is None or not (0 < bar.close < float("inf")):
        return
    account = context.future_account_dict.get(context.account)
    if account is None:
        raise ValueError("期货账户不存在")
    if get_open_future_orders(context.account, context.symbol):
        return
    position = account.positions[context.symbol]
    if position.buy_quantity == 0 and position.sell_quantity == 0:
        orders = buy_open(context.account, context.symbol, int(context.hands))
        for order in orders or []:
            print(f"订单={order.order_id} 状态={order.status} 信息={order.message}")


def on_future_trade_rtn(context, order):
    print(f"成交回报:{order.order_book_id} 累计={order.filled_quantity}手")


def future_order_cancel(context, order):
    print(f"撤单或拒单:{order.order_id} {order.message}")

四、回调函数

函数 是否必需 适合处理的事情
initialize(context) 参数、缓存、订阅合约、历史数据预加载
before_trading(context) 每个交易日前准备数据、更新当日目标合约
handle_data(context, data) 每个日线或分钟 Bar 执行信号判断和交易
on_future_trade_rtn(context, order) 期货成交后的订单回报,记录成交与未完成数量
future_order_cancel(context, order) 撤单或拒单回报,释放订单登记状态
after_trading(context) 每个交易日结束后汇总权益和持仓
注意

期货成交回调的第二个参数是订单对象(Order),不是股票那样的成交明细对象:本次成交手数读 cur_filled_quantity,累计成交手数读 filled_quantity

回测可能在下单调用内部就完成撮合并触发回调,不要假定「下单函数返回以后才发生回报」。先建立缓存再下单;返回后只登记仍处于未完成状态的订单。

五、交易日与自然日

属性或方法 类型、返回 说明
context.now YYYYMMDD 字符串 当前自然日
context.trade_date YYYYMMDD 字符串 当前交易日;夜盘时可能与自然日不同
context.trade_time datetime 当前回测时点
context.hms HHMMSS 字符串 当前时刻
context.trade_date_list 字符串列表 本次回测加载的交易日
context.is_last_trade_date() 布尔值 是否为本次回测最后一个交易日
context.get_next_count_date(date, count) 日期字符串 在已加载交易日内偏移,正数向后、负数向前;越界返回 '99990101'
context.sub_future_symbol(symbol_list) 无返回值 登记需要的期货行情合约
context.un_sub_future_symbol(symbol_list=None) 无返回值 取消指定订阅;省略列表时清空该类订阅

主力映射、每日信号和日线数据都用 context.trade_date 对齐。get_next_count_date 的起始日期必须在已加载列表中,不能用它查询回测区间之前的预热日;区间外的前一交易日在初始化时用 panda_data.get_prev_trade_date 查询。

六、读取当前行情

bar = data[symbol] 读取当前 Bar。data 不是普通字典,不要用 symbol in data 判断行情是否存在;拿到对象后检查价格:

python
def read_valid_close(data, symbol):
    bar = data[symbol]
    if bar is None or not (0 < bar.close < float("inf")):
        return None
    return float(bar.close)

日线、分钟都可以用以下字段。价格单位跟随品种报价单位(元/吨、元/克、指数点等);价格 × 合约乘数 × 手数才是名义金额。

字段 类型、单位 含义
symbol 字符串 合约代码
open / high / low / close 数值,报价单位 当前 Bar 开高低收
volume 数值,手 当前 Bar 成交量
amount 数值,元 成交额
open_interest 数值,手 持仓量
settlement 数值,报价单位 结算价,使用前检查是否有效
last 数值,报价单位 行情对象中的最新价字段
  • 日期:统一用 context.trade_date。分钟行情的 trade_dateYYYYMMDD 字符串,date 是自然日整数,timeHHMMSS 整数;日线的 bar.trade_date 不保证总有有效值。比较日期前先统一成 YYYYMMDD 字符串。
  • 扩展字段:成交额、持仓量以实际行情列为准,当前期货数据用 amountopen_interest,不要把 turnoveroi 的默认值当成有效数据。分钟行情缺结算价时可能以收盘价填充,不能据此还原实际结算。
  • 盘口:分钟对象里有 bidprice1askprice1 字段,但有字段不代表数据源提供了有效盘口;值为 0 或缺失时不能用来定价。

七、查询历史行情和合约资料

import panda_data,平台提供数据访问环境,策略不自行登录或配置密钥。历史查询放在 initialize 或每日一次的 before_trading 中,把结果缓存给 handle_data 用。

方法 主要参数 返回
get_future_daily symbolstart_dateend_datefields 日线 DataFrame
get_future_min 同上,加 frequency 分钟 DataFrame;频率为 '1m''5m''15m''60m'
get_future_detail symbol=Nonefields=Noneexchange=Nonedate=None 指定日期可交易合约的资料;不传日期查最新
get_future_detail_all 同上,加 is_trading=None 可包含已到期或不可交易合约的资料
get_future_dominant underlying_symbolstart_dateend_date 每个品种、每个交易日对应的主力合约
get_prev_trade_date dateexchange='SH'n=1 前第 n 个交易日的日期字符串
get_trade_cal start_dateend_dateexchange='SH'is_trading_day=1fields=None 交易日历 DataFrame
  • 行情和主力查询的起止日期用 YYYYMMDD,必须填写;期货日线单次区间不超过 5 年。symbolunderlying_symbolfields 可以是单个字符串或列表。
  • get_future_min 的可选参数 time_zone 是日内时间段过滤(如 ('10:00', '11:00')),不是时区。数据查询的 '1m' 与回测配置的 '1M' 大小写不同。
  • 交易日历的 exchange 支持 'SH''HK''US',不能把 .SHF 这类合约后缀传给它;中国市场用默认 'SH',具体品种的夜盘和休市还要结合交易时点和有效行情判断。

下面的函数在初始化时预加载一个合约的日线和合约乘数:

python
import panda_data


def load_history_and_contract(context, symbol):
    start = context.run_info.start_date
    end = context.run_info.end_date
    daily = panda_data.get_future_daily(
        symbol=[symbol], start_date=start, end_date=end,
        fields=["symbol", "date", "close"],
    )
    detail = panda_data.get_future_detail_all(
        symbol=[symbol], date=start,
        fields=["symbol", "contract_multiplier"],
    )
    if daily is None or daily.empty:
        raise ValueError("所选合约在回测区间内没有日线数据")
    if detail is None or detail.empty:
        raise ValueError("缺少合约资料")
    if "contract_multiplier" not in detail.columns:
        raise ValueError("缺少合约乘数字段")
    multiplier = float(detail.iloc[0]["contract_multiplier"])
    if not (0 < multiplier < float("inf")):
        raise ValueError("合约乘数无效")
    return daily, multiplier

使用返回的表时注意:

  • 历史表常用 symboldateopenhighlowclosevolume 等列,它是 DataFrame,不能照搬当前 Bar 的全部字段名。amount 是成交额、open_interest 是持仓量;分钟表的 trading_date 是交易日期,minutedatetime 是时点。分钟表的 turnover 是换手率,不是成交额。
  • 指定 fields 后,读取前检查必要列、空值和无穷值。全区间预加载只为减少查询,某一天的信号仍只能用那个时点已知的数据。
  • 合约资料里 contract_multiplier 是合约乘数,margin_rate 是最低保证金率(小数),is_trading 表示查询日期是否可交易。资料中的保证金率不等于本次回测或真实账户最终使用的保证金率。
  • 工作流若传入了因子数据,可以通过 context.df_factor 读取 DataFrame。它的列名、时间范围和频率由上游因子节点决定,在初始化时校验所需列,并按交易日和真实合约对齐。