AI助手–小白自然语言生成工作流入门期货回测工作流
1.1 代码框代码助手
- 专业的量化助手,合格的大语言模型。
- 能够帮助小白在实践中快速学习专业领域知识。
- 下面是我让他帮我讲解的这个策略核心逻辑和核心概念,不错不错!
一、这个策略整体在干什么?
一句话概括:
用“短期均线”和“长期均线”的金叉/死叉信号,在白银期货主力合约上自动做多、做空,并按照一套风险规则控制每次开的手数。
交易标的:
AG_DOMINANT.SHF:白银期货的“主力连续合约”(后面详细解释)。
实际真正下单是当天的主力合约,比如 AG2506.SHF 这样的具体月份合约。
核心逻辑:
每天收盘后,拿到白银主力合约当天收盘价,把它记到历史价格里。
用最近 10 天价格算“短期均线”,用最近 30 天价格算“长期均线”。
看短期均线和长期均线的关系:
短期均线从下往上,穿过长期均线 = 金叉 → 看多
短期均线从上往下,穿过长期均线 = 死叉 → 看空
有了“看多/看空”信号,就根据当前账户总资产、保证金率、最大杠杆等条件计算出本次应该开多少手。
如果是金叉:
先把现有空单全部平掉
再按计算好的手数开多(买入开仓)
如果是死叉:
先把现有多单全部平掉
再按计算好的手数开空(卖出开仓)
同时,这个策略还会在:
开盘前(before_trading) 打印账户资金情况
每次成交(on_future_trade_rtn) 打印订单成交信息
收盘后(after_trading) 打印当天的收益情况
二、代码结构对应的策略流程
- initialize(context) – 初始化
你可以理解为:
回测/实盘一开始,只执行一次的“策略设置”阶段。
里面主要做了几件事:
指定账户
context.account = ‘5588’
在回测框架里,5588 是期货账户的编号,相当于“用哪个期货账号来交易”。
指定交易标的
context.underlying_symbol = ‘AG_DOMINANT.SHF’
这是“白银主力连续合约”的代码。
它不是一个真正可以直接下单的合约,而是一个“主力合约的别名”。
每天需要再查一次当日真正的主力月份合约,例如 AG2506.SHF。
设置均线参数
context.short_window = 10 # 短期均线天数
context.long_window = 30 # 长期均线天数
短期均线:最近 10 天收盘价的平均值。
长期均线:最近 30 天收盘价的平均值。
用短 vs 长 的交叉来产生买卖信号。
准备保存历史价格与信号
context.close_history = [] # 保存真实合约的历史收盘价
context.prev_signal = 0 # 上一日信号:1多,-1空,0无
风险与仓位管理参数
context.risk_fraction = 0.2 # 每次最多用 20% 总权益做保证金
context.max_leverage = 5.0 # 总敞口不超过 5 倍杠杆
context.min_hands = 1 # 最少交易 1 手
context.commission_rate = 0.00005 # 假设万五手续费(单边)
context.margin_rate = 0.12 # 假设保证金率 12%
这些参数都是用来限制“本次最多开多少手”,防止满仓梭哈。
订单列表用于记录
context.order_ids = []
保存下单返回的订单 ID,方便在成交回报里核对和调试。
- _get_main_contract_symbol() – 找主力月合约
_get_main_contract_symbol(trade_date, underlying_symbol)
作用:
给它一个日期 trade_date 和主力连续代码 AG_DOMINANT.SHF,它去 panda_data 查询这一天到底哪一个具体合约是主力,比如 AG2506.SHF,然后返回这个真正可以交易的代码。
使用的接口是:panda_data.get_market_data(…, type=“future”)
返回的表里有两列:
dominant_id:主力月的短代码,比如 AG2506
exchange:交易所,比如 SHF
最后拼成 AG2506.SHF 这样的标准代码。
- _calc_position_size() – 计算应该开多少手
函数签名:
_calc_position_size(total_value, price, contract_multiplier,
risk_fraction, max_leverage,
margin_rate, min_hands)
它要解决一个问题:
在我现在有 total_value 这么多总权益,合约价格是 price,合约乘数是 contract_multiplier,并且我设定了“风险资金最大用多少”“总杠杆最大多少”这几条规则的情况下,本次最多可以开多少手?
关键约束:
风险资金上限:
可用保证金上限 = total_value * risk_fraction
杠杆限制:
总名义敞口 <= total_value * max_leverage
保证金 = 名义敞口 × 保证金率:
名义敞口:合约价格 × 合约乘数 × 手数
手数必须是整数,且 >= 最低手数 min_hands。
步骤:
根据风险资金算一个 “允许的最大名义敞口”:
notional_by_margin = (total_value * risk_fraction) / margin_rate
根据杠杆限制算另一个最大名义敞口:
notional_by_leverage = total_value * max_leverage
取两者中的较小者:
max_notional = min(notional_by_margin, notional_by_leverage)
算单手名义价值:
per_hand_notional = price * contract_multiplier
用 max_notional 除以每手价值:
hands = int(max_notional // per_hand_notional)
这里用了整除 //,自动向下取整。
如果算出来小于最小手数 min_hands,就不开仓(返回 0)。
- handle_data(context, data) – 每天的交易逻辑
这个是策略最核心的部分,每个交易日执行一次。
(1) 拿到当日主力月合约代码
real_symbol = _get_main_contract_symbol(trade_date, context.underlying_symbol)
比如得到 AG2506.SHF。
(2) 从 data 中取出它的行情 bar
bar = data[real_symbol]
close_price = bar.close
这里用的是 日线 bar:
open/high/low/close/volume 等信息
策略只用收盘价 close。
(3) 维护价格历史
context.close_history.append(close_price)
if len(context.close_history) > context.long_window:
context.close_history.pop(0)
只保存最近 long_window(30)天的数据。
如果不足 30 天,就先不做交易:
if len(context.close_history) < context.long_window:
print(“历史数据不足…等待累积”)
return
(4) 计算短期/长期均线
short_ma = np.mean(context.close_history[-context.short_window:])
long_ma = np.mean(context.close_history)
短期均线 = 最近 10 天收盘价的平均值
长期均线 = 最近 30 天收盘价的平均值
(5) 判定当前信号方向
if short_ma > long_ma:
current_signal = 1 # 看多
elif short_ma < long_ma:
current_signal = -1 # 看空
else:
current_signal = 0 # 无方向
(6) 读取当前持仓
futures_account = context.future_account_dict.get(context.account)
positions = futures_account.positions
position = positions.get(real_symbol) if positions else None
long_qty = position.buy_quantity if position else 0
short_qty = position.sell_quantity if position else 0
closable_long = position.closable_buy_quantity if position else 0
closable_short = position.closable_sell_quantity if position else 0
buy_quantity = 当前多头持仓手数
sell_quantity = 当前空头持仓手数
closable_* = 今日可以平掉的部分
(7) 只在“信号发生变化”时交易
prev_signal = context.prev_signal
context.prev_signal = current_signal
if current_signal == prev_signal:
return # 信号没变,不调仓
避免每天都调仓,只在“从多变空/从空变多/从无变成有信号”等这些交叉点操作。
(8) 获取合约乘数并计算本次目标手数
先从 panda_data.get_future_list 拿到合约乘数:
contract_df = panda_data.get_future_list(
symbol=[real_symbol],
fields=[“symbol”, “contract_multiplier”],
is_trading=None
)
contract_multiplier = float(contract_df.iloc[0][“contract_multiplier”])
再用刚才讲的 _calc_position_size() 计算:
target_hands = _calc_position_size(
total_value=futures_account.total_value,
price=close_price,
contract_multiplier=contract_multiplier,
risk_fraction=context.risk_fraction,
max_leverage=context.max_leverage,
margin_rate=context.margin_rate,
min_hands=context.min_hands,
)
total_value = 总资产(含持仓市值和现金)
若算出来 target_hands == 0,直接放弃这次信号,不开仓。
(9) 根据信号执行交易
1)金叉:从空/空仓 → 看多
if current_signal == 1 and prev_signal <= 0:
# 1) 平掉所有空头
if closable_short > 0:
buy_close(context.account, real_symbol, closable_short)
# 2) 开多至目标手数
if long_qty < target_hands:
open_hands = target_hands - long_qty
buy_open(context.account, real_symbol, open_hands)
2)死叉:从多/空仓 → 看空
elif current_signal == -1 and prev_signal >= 0:
# 1) 平掉所有多头
if closable_long > 0:
sell_close(context.account, real_symbol, closable_long)
# 2) 开空至目标手数
if short_qty < target_hands:
open_hands = target_hands - short_qty
sell_open(context.account, real_symbol, open_hands)
这里用到的交易函数:
buy_open :买入开仓(建立多头)
sell_open :卖出开仓(建立空头)
buy_close :买入平仓(平掉空头)
sell_close :卖出平仓(平掉多头)
MarketOrderStyle:市价单,即按当前市场价成交。
- 报单回报 & 开盘前/收盘后
on_future_trade_rtn(context, order)
每当有期货订单成交,就会触发这里:
打印:订单ID、合约代码、买卖方向、开仓/平仓、价格、数量、状态等
如果订单已成交,且在 context.order_ids 里,就移除(表示这笔订单已经结束)。
before_trading(context)
开盘前(每天一次):
打印当前日期、账户总权益、可用资金、保证金占用。
after_trading(context)
收盘后(每天一次):
打印总权益、持仓浮动盈亏、已实现平仓盈亏。
三、策略中涉及的关键专有名词解释
下面集中把刚才已提到的名词,再用更系统的方式解释一下。
- 主力连续合约(AG_DOMINANT.SHF)
主力合约:某个期货品种里,当前成交最活跃、持仓量最大的那个月份合约。
连续合约:平台帮你自动“拼接”主力合约的历史:
2024年主力是 2406 → 就用 2406
过一段时间主力换到 2412 → 就自动切到 2412
主力连续合约代码:AG_DOMINANT.SHF 就是“白银主力”的一个虚拟代码。
真实下单时还是要用具体合约,比如 AG2506.SHF。
在你的代码里:
context.underlying_symbol = ‘AG_DOMINANT.SHF’
real_symbol = _get_main_contract_symbol(…)
就是先用连续代码找出当日真正的主力月。
- 合约乘数(contract_multiplier)
每一手期货代表的“标的数量”,比如:
假设白银期货是 “1 手 = 15kg 白银”(具体数值以交易所真实规则为准)
在代码里:
每手名义价值 = 价格 × 合约乘数
用于计算名义敞口、保证金等。
- 名义敞口 / 杠杆
名义敞口(notional exposure):
持仓规模折算成“标的价值”的金额
= 合约价格 × 合约乘数 × 手数
杠杆倍数(leverage):
= 名义敞口 ÷ 账户总权益
如果总权益 100万,名义敞口 500万,杠杆 = 5 倍
你的代码里:
max_leverage = 5.0 → 名义敞口最多控制在 5 倍以内。
- 保证金 / 保证金率(margin_rate)
期货不是一次性付全款,而是只需要先缴纳一部分作为保证金。
保证金率:
比如保证金率 12%,意味着:
做 100 万的名义敞口,只需要交 12 万保证金。
在代码里:
margin_rate = 0.12
notional_by_margin = (total_value * risk_fraction) / margin_rate
意思是:
先规定:我愿意拿 20% 账户总资产做保证金(risk_fraction)
再根据保证金率反推回,最多可以承受多少名义敞口。
- 手续费率(commission_rate)
这里设的是 0.00005,就是万分之 0.5(万五)
实际回测中的真实费用由回测引擎决定,这个参数更多是用来预估与风险控制。
- 均线、金叉、死叉
移动平均线(Moving Average, MA):
N 日均线就是:最近 N 天的收盘价平均值。
短期均线:这里是 10 日均线
长期均线:这里是 30 日均线
金叉(Golden Cross):
短期均线从下向上穿过长期均线。
一般被解读为“趋势由空转多”,买入信号。
死叉(Dead Cross):
短期均线从上向下穿过长期均线。
一般被解读为“趋势由多转空”,卖出或做空信号。
代码中用 prev_signal 和 current_signal 来识别“交叉”:
current_signal == 1 且 prev_signal <= 0 → 金叉
current_signal == -1 且 prev_signal >= 0 → 死叉
- 多头 / 空头 / 开仓 / 平仓
多头(long):
你买入一手白银期货,赌的是“价格会上涨”。
空头(short):
你先卖出一手期货(不需要事先持有),赌的是“价格会下跌”。
开仓:建立新的仓位。
多头开仓:buy_open
空头开仓:sell_open
平仓:了结现有仓位。
多头平仓:sell_close
空头平仓:buy_close
在这个策略:
金叉 → 平空 + 开多
死叉 → 平多 + 开空
- 市价单(MarketOrderStyle)
市价单:以当前市场能成交的价格尽快成交,不事先指定具体价位。
优点:基本能成交
缺点:成交价格可能有一定滑点(与当前看到的价格略有差距)。
- 持仓量字段
在期货持仓对象里:
buy_quantity:当前多头手数
sell_quantity:当前空头手数
closable_buy_quantity:今日可平的多头手数
closable_sell_quantity:今日可平的空头手数
策略里的用法:
closable_* 用于平仓;
*_quantity 用于判断已经有多少仓位,避免重复加仓到超过目标值。
四、如果你完全是小白,可以先这样理解
你只需要看:
白银期货价格趋势 → 用两条均线去判断。
只在“趋势发生拐点”的时刻进场:
短期线从下穿上 → 市场可能转多 → 做多白银。
短期线从上穿下 → 市场可能转空 → 做空白银。
每次下多大仓位,不是凭感觉,而是根据:
当前总资产有多少
你愿意冒多大风险(最多用资产的 20% 做保证金)
不让杠杆超过 5 倍
程序会自动:
先平掉当前反方向的仓位
再建立新的方向仓位到目标规模
如果你希望,我可以在下一步帮你:
画一张“时间线 + 价格 + 均线 + 交易点”的示意图来解释一次完整的交易;
或者,把参数改成更保守 / 更激进的版本,告诉你会有什么风险变化。
1.2 ai工作流助手
- 自然语言交互直接生成完整工作流
- 完整的回测模块;