期货
期货:配置、合约与数据
期货回测策略入门:运行配置与期货账户、真实合约代码与交易所后缀、最小可运行策略,回调函数与期货成交回报,交易日与夜盘,当前行情字段,以及用 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 手多仓。它只用来理解流程,不含平仓信号。
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 判断行情是否存在;拿到对象后检查价格:
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_date是YYYYMMDD字符串,date是自然日整数,time是HHMMSS整数;日线的bar.trade_date不保证总有有效值。比较日期前先统一成YYYYMMDD字符串。 - 扩展字段:成交额、持仓量以实际行情列为准,当前期货数据用
amount、open_interest,不要把turnover、oi的默认值当成有效数据。分钟行情缺结算价时可能以收盘价填充,不能据此还原实际结算。 - 盘口:分钟对象里有
bidprice1、askprice1字段,但有字段不代表数据源提供了有效盘口;值为 0 或缺失时不能用来定价。
七、查询历史行情和合约资料
先 import panda_data,平台提供数据访问环境,策略不自行登录或配置密钥。历史查询放在 initialize 或每日一次的 before_trading 中,把结果缓存给 handle_data 用。
| 方法 | 主要参数 | 返回 |
|---|---|---|
get_future_daily |
symbol、start_date、end_date、fields |
日线 DataFrame |
get_future_min |
同上,加 frequency |
分钟 DataFrame;频率为 '1m'、'5m'、'15m'、'60m' |
get_future_detail |
symbol=None、fields=None、exchange=None、date=None |
指定日期可交易合约的资料;不传日期查最新 |
get_future_detail_all |
同上,加 is_trading=None |
可包含已到期或不可交易合约的资料 |
get_future_dominant |
underlying_symbol、start_date、end_date |
每个品种、每个交易日对应的主力合约 |
get_prev_trade_date |
date、exchange='SH'、n=1 |
前第 n 个交易日的日期字符串 |
get_trade_cal |
start_date、end_date、exchange='SH'、is_trading_day=1、fields=None |
交易日历 DataFrame |
- 行情和主力查询的起止日期用
YYYYMMDD,必须填写;期货日线单次区间不超过 5 年。symbol、underlying_symbol、fields可以是单个字符串或列表。 get_future_min的可选参数time_zone是日内时间段过滤(如('10:00', '11:00')),不是时区。数据查询的'1m'与回测配置的'1M'大小写不同。- 交易日历的
exchange支持'SH'、'HK'、'US',不能把.SHF这类合约后缀传给它;中国市场用默认'SH',具体品种的夜盘和休市还要结合交易时点和有效行情判断。
下面的函数在初始化时预加载一个合约的日线和合约乘数:
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
使用返回的表时注意:
- 历史表常用
symbol、date、open、high、low、close、volume等列,它是 DataFrame,不能照搬当前 Bar 的全部字段名。amount是成交额、open_interest是持仓量;分钟表的trading_date是交易日期,minute、datetime是时点。分钟表的turnover是换手率,不是成交额。 - 指定
fields后,读取前检查必要列、空值和无穷值。全区间预加载只为减少查询,某一天的信号仍只能用那个时点已知的数据。 - 合约资料里
contract_multiplier是合约乘数,margin_rate是最低保证金率(小数),is_trading表示查询日期是否可交易。资料中的保证金率不等于本次回测或真实账户最终使用的保证金率。 - 工作流若传入了因子数据,可以通过
context.df_factor读取 DataFrame。它的列名、时间范围和频率由上游因子节点决定,在初始化时校验所需列,并按交易日和真实合约对齐。
