一、一条画了三年的线,突然不响了
我有一个跑了很久的商品期货策略,做的是短纤(PF)对 PTA(TA)和乙二醇(EG)的三腿组合短纤三方套利实盘。逻辑简单到可以写在餐巾纸上:
短纤是聚酯,由 PTA 和乙二醇聚合而来。物耗配方是每吨短纤消耗 0.855 吨 PTA 加 0.335 吨乙二醇。那么:
加工利润 = PF价格 - (0.855 × PTA价格 + 0.335 × EG价格)
加工利润不可能无限扩张,也不可能长期为负——利润太高会刺激开工率上升压回来,利润太低装置就减产。所以它应该在一个区间里摆动。
代码写出来十几行:
javascript
var costPF = (0.855 * ptaMA[ptaR.length - 1] + 0.335 * egMA[egR.length - 1]);
var diffMA = pfMA[pfR.length - 1] - costPF;
if (posInfo.length === 0 && diffMA > 1030) { // 利润过高,做空利润
p.OpenLong(ptaId, 1 * Amount);
p.OpenLong(egId, 1 * Amount);
p.OpenShort(pfId, 2 * Amount);
}
if (posInfo.length === 0 && diffMA < 930) { // 利润过低,做多利润
p.OpenShort(ptaId, 1 * Amount);
p.OpenShort(egId, 1 * Amount);
p.OpenLong(pfId, 2 * Amount);
}
问题出在某一段时间里,这个策略不再开仓了。
不是亏钱。是资金曲线变成一条水平线,日志里干干净净什么都没有。我一开始以为是行情太平淡,翻了半天才反应过来:能源价格中枢整体上移之后,加工费的绝对水平被抬高了,930 这条线再也碰不到。
这比亏钱更麻烦。亏钱你会立刻去查,而一条平的曲线会让你以为策略只是在耐心等待。
二、为什么不继续调参数
第一反应当然是把 930/1030 往上挪。但挪多少?挪完之后下次能源再变怎么办?
再往下想,会发现要修的不止是阈值:
0.855 和 0.335 是化学,不是金融。 它们描述的是物质守恒——生产一吨短纤,物理上必须消耗这么多原料。但价格上的联动强度是另一回事,它取决于成本传导效率、装置开工率、进口替代、下游议价能力。这些每年都在变,而配方写死在代码里,变了它也不知道,只会安静地产生越来越差的信号。
绝对价差假设三条腿同尺度线性联动。 EG 价格在 4500 附近,PF 在 7300 附近。EG 涨 10% 是 450 块,PF 涨 10% 是 730 块。当某条腿因为地缘或者装置事故单边暴涨时,绝对价差会被它拽着走,可三者之间的相对估值关系其实没变。策略会把一个纯粹的尺度效应误读成机会。
所以这不是调参数能解决的问题,是度量方式本身选错了。
我需要的是一把会自己校准的尺子。
三、标的:为什么是这三个
先把关系说清楚,这是所有套利的前提。
| 品种 | 交易所 | 合约乘数 | 参考价 | 每手名义 | 在链条中的位置 |
|---|---|---|---|---|---|
| PF 短纤 | 郑商所 | 5 吨/手 | ~7300 | 36,500 元 | 产成品 |
| TA PTA | 郑商所 | 5 吨/手 | ~5800 | 29,000 元 | 主要原料 |
| eg 乙二醇 | 大商所 | 10 吨/手 | ~4500 | 45,000 元 | 次要原料 |
这三者的关系不是统计出来的,是工艺决定的:不消耗 PTA 和乙二醇就造不出短纤,这是硬约束。这一点非常重要——
纯靠历史数据挖出来的高相关是最危险的东西。你不知道它为什么存在,也就不知道它什么时候会断,断了之后更不知道该扛还是该跑。
先能用一句话说清"它们为什么应该有关系",再去跑回归。顺序反了,做出来的就是数据挖掘。
注意上表里那个加粗的数字:乙二醇是 10 吨/手,另外两个是 5 吨/手。这个细节后面会要命。
四、模型:把系数交给市场去量
改造的核心只有一句话——不再假设系数,而是从市场上量出来。
用对数价格做协整回归:
ln(PF) = c + β1 · ln(TA) + β2 · ln(EG) + ε
滚动窗口,每根 K 线重新解一次。
为什么必须做在对数上? 因为对数回归的系数是弹性:β₁ = 0.68 的意思是"PTA 涨 1%,PF 涨 0.68%"。弹性是无量纲的,价格中枢从 5000 涨到 8000,弹性可以纹丝不动。第一节那个"中枢上移导致阈值失效"的问题,从根上就没了。
残差 ε 的经济含义是"PTA 和乙二醇解释不了的那部分短纤价格"——真实加工利润,加上短纤自身的供需错配。这正是我们想交易的东西。
三腿最小二乘的实现(高斯消元解正规方程,不依赖任何矩阵库):
javascript
// ln(target) = c + β1·ln(ref1) + β2·ln(ref2) + ε
function regressSpread(lnTarget, lnRefs) {
var k = lnRefs.length, n = lnTarget.length, dim = k + 1;
var XtX = [], Xty = [];
for (var i = 0; i < dim; i++) {
XtX.push([]); Xty.push(0);
for (var j = 0; j < dim; j++) XtX[i].push(0);
}
function col(j, t) { return j === 0 ? 1 : lnRefs[j - 1][t]; } // j=0 是截距列
for (var t = 0; t < n; t++) {
for (var a = 0; a < dim; a++) {
Xty[a] += col(a, t) * lnTarget[t];
for (var b = 0; b < dim; b++) XtX[a][b] += col(a, t) * col(b, t);
}
}
var beta = solveLinearSystem(XtX, Xty);
if (!beta) return null; // 奇异矩阵:两条对冲腿可能高度共线
var resid = [];
for (var s = 0; s < n; s++) {
var pred = beta[0];
for (var m = 0; m < k; m++) pred += beta[m + 1] * lnRefs[m][s];
resid.push(lnTarget[s] - pred);
}
return { c: beta[0], betas: beta.slice(1), resid: resid };
}
然后对残差做 z-score:
z = (ε - mean) / sd
阈值从此不再是元/吨,而是标准差倍数。 市场波动放大时 sd 跟着放大,同样的 ±1.5 对应的绝对价差自动变宽。不需要人工重标定——这就是那把会自己校准的尺子。
五、信号:三态状态机
不搞复杂的分档,就三个状态:
| 状态 | 触发条件 | 含义 | 三腿方向 |
|---|---|---|---|
FLAT | 初始 / |z| < 0.5 | 无观点 | 空仓 |
SHORT_SPREAD | z > +1.5 | 短纤相对原料太贵,加工利润过高 | 空 PF / 多 TA、EG |
LONG_SPREAD | z < -1.5 | 短纤相对原料太便宜,加工利润被压缩 | 多 PF / 空 TA、EG |
离场只有两个出口:z 回到 ±0.5 以内,或者持仓超过最大时长。
javascript
var newState = state;
if (state === 'FLAT') {
// z高 = 短纤相对原料太贵 = 加工利润过高 → 空短纤/多原料,等利润回落
if (zLatest > ENTRY_Z) newState = 'SHORT_SPREAD';
// z低 = 短纤相对原料太便宜 = 加工利润被压缩 → 多短纤/空原料,等利润修复
else if (zLatest < -ENTRY_Z) newState = 'LONG_SPREAD';
} else {
if (forceExit) { newState = 'FLAT'; Log('⏰ 超过最大持仓时长,强制离场'); }
else if (Math.abs(zLatest) < EXIT_Z) { newState = 'FLAT'; Log('✅ z回归至离场带内,平仓'); }
}
进场和离场之间留了 1.5 到 0.5 这段缓冲,是为了避免在阈值附近来回抖动反复开平。
值得一提的是,方向的经济含义和老策略完全一致——z 高对应老策略的"价差过大做空短纤"。变的不是交易什么,是怎么判断"过大"。
六、仓位:β 配的是钱,不是手数
这是我在改造过程中花时间最多的地方,也是老策略错得最系统的地方。
老策略开仓写的是 PTA 1手 : EG 1手 : PF 2手。这个比例大概来自"配方里 PTA 是 EG 的两倍多"这种直觉。
它是错的。
从对数回归两边微分,得到的是收益率之间的关系(r 表示收益率):
r_PF = β1 · r_TA + β2 · r_EG + dε
一笔名义金额为 N 的仓位,盈亏是 N × r。把三条腿加起来:
PnL = N_PF · r_PF + N_TA · r_TA + N_EG · r_EG
= N_PF · (β1·r_TA + β2·r_EG + dε) + N_TA · r_TA + N_EG · r_EG
= (N_PF·β1 + N_TA) · r_TA + (N_PF·β2 + N_EG) · r_EG + N_PF · dε
└────── 要归零 ──────┘ └────── 要归零 ──────┘ └─ 想留下的 ─┘
要把原料端的系统性方向风险对冲掉,就得让前两项系数为零:
N_TA = -β1 × N_PF
N_EG = -β2 × N_PF
这是名义金额的关系,不是手数的关系。
javascript
// 对数回归两边微分得 r_PF = β1·r_TA + β2·r_EG + dε,
// 要消掉原料端的方向性风险就得让 N_TA = -β1·N_PF、N_EG = -β2·N_PF。
// 注意这是【名义金额】关系——换算成手数还要除以 价格×合约乘数。
var dir = state === 'SHORT_SPREAD' ? -1 : 1;
var wTarget = dir * 1.0;
var wRefs = [], gross = Math.abs(wTarget);
for (var j = 0; j < reg.betas.length; j++) {
var wr = -reg.betas[j] * wTarget;
wRefs.push(wr); gross += Math.abs(wr);
}
var scale = gross > 0 ? (GROSS_EXPOSURE / gross) : 0;
weights[0] = wTarget * scale;
for (var q = 0; q < wRefs.length; q++) weights[q + 1] = wRefs[q] * scale;
商品期货的两道额外关卡
第一道:合约乘数。
数字货币里 数量 × 价格 = 名义,直接得很。商品期货中间隔着乘数:
名义金额 = 价格 × 合约乘数 × 手数
回到第三节那张表——乙二醇一手的名义金额(45,000)比 PTA(29,000)高出 55%。按手数配比,EG 的实际敞口会严重超配,对冲不但没做干净,还引入了新的方向性风险。
第二道:整数手。
假设 β₁ = 0.68、β₂ = 0.21,PF 开 4 手:
PF 名义 = 4 × 7300 × 5 = 146,000 元
TA 理论手数 = 0.68 × 146000 / 29000 = 3.42 手 → 只能下 3 或 4 手
EG 理论手数 = 0.21 × 146000 / 45000 = 0.68 手 → 只能下 1 手(误差 47%)
我的处理方式是:取整,算误差,误差太大就放弃这次机会。
javascript
function amountsToLots(targetAmounts, prices) {
var lots = [], sides = [], exact = [], errs = [];
for (var i = 0; i < CODES.length; i++) {
var perLot = prices[i] * MULTS[i]; // 每手名义
var e = Math.abs(targetAmounts[i]) / perLot;
var l = Math.round(e);
exact.push(e); lots.push(l);
sides.push(targetAmounts[i] >= 0 ? 'long' : 'short');
errs.push(e > 1e-9 ? Math.abs(l - e) / e : 0);
}
return { lots: lots, sides: sides, exact: exact, errs: errs };
}
然后在下单前逐腿检查,任一腿不合格就整组放弃:
javascript
if (plan.lots[i] < 1) {
Log('⚠️', LABELS[i], '目标手数不足1手,资金规模撑不起这个对冲比例,放弃本次开仓');
abort = true;
} else if (plan.errs[i] > MAX_HEDGE_ERR) {
Log('⚠️', LABELS[i], '整数手取整误差', _N(plan.errs[i]*100, 1) + '% 超过容忍上限,放弃本次开仓');
abort = true;
}
为什么宁可不做? 因为带着歪对冲进场,等于在残差里混进了没对冲干净的单边方向性风险。这时候 z 收敛了你照样可能亏钱——而且你会归因错,以为是策略逻辑有问题。
一笔归因不清的亏损,比错过一次机会贵得多。
策略还会把实际成交后的"有效 β"反推出来打在面板上,随时能看到理论和现实差多少:
javascript
function effectiveBetas(lots, prices) {
var nT = lots[0] * prices[0] * MULTS[0];
if (!(nT > 0)) return [0, 0];
return [lots[1] * prices[1] * MULTS[1] / nT,
lots[2] * prices[2] * MULTS[2] / nT];
}
七、物耗配方没被删掉,它被降级了
回到 0.855 和 0.335。改造之后它们不参与任何下单计算,但我没有删掉——换了个用法。
把吨耗折算成弹性:某原料在对数回归里的系数,理论上等于它在产成品价值中的成本占比。
β1_理论 = 0.855 × PTA价格 / PF价格
β2_理论 = 0.335 × EG价格 / PF价格
代入参考价:
β1_理论 = 0.855 × 5800 / 7300 = 0.679
β2_理论 = 0.335 × 4500 / 7300 = 0.207
合计 = 0.886
剩下的 1 - 0.886 = 0.114,就是加工环节自己贡献的部分——能耗、人工、折旧和利润。这个数字本身就有解读价值。
javascript
var theoryB1 = THEORY_C1 * prices[1] / prices[0];
var theoryB2 = THEORY_C2 * prices[2] / prices[0];
var gap1 = Math.abs(reg.betas[0] - theoryB1) / theoryB1;
var gap2 = Math.abs(reg.betas[1] - theoryB2) / theoryB2;
if (gap1 > THEORY_GAP_WARN || gap2 > THEORY_GAP_WARN) {
Log('⚠️ 实测β偏离理论物耗较多 | 实测', _N(reg.betas[0], 3), 'vs 理论', _N(theoryB1, 3),
'| 产业链结构可能已变化,建议人工复核');
}
判读方式:
- 实测 β 长期贴着理论 β → 化工逻辑还在起作用,这个协整关系有基本面支撑
- 实测 β 持续大幅偏离理论 β → 产业链结构可能真的变了。新增产能投放、替代工艺、进出口政策调整、装置长期检修,都会造成这种偏离
老方法把配方当真理,错了不知道。新方法把配方当参照物,错了会报警。 这是本质区别。
八、数据卫生:四道拒绝阈值
回归是个很容易被脏数据毒害的东西。残差标准差被压到接近 0,z 就会被除出天文数字,然后策略在一个完全虚假的信号上满仓进场。
所以下单之前设了四道闸:
第一道,价格冻结检测。 休市或者停板期间,对冲腿的标记价格可能一动不动,这会让回归系数变得极不稳定。
javascript
for (var j = 0; j < lnRefs.length; j++) {
var refMax = Math.max.apply(null, lnRefs[j]);
var refMin = Math.min.apply(null, lnRefs[j]);
if (refMax - refMin < MIN_REF_LOGRANGE) {
Log('⚠️ ' + LABELS[j+1] + ' 在回归窗口内价格几乎无变化(休市/停板),本轮跳过');
return null;
}
}
第二道,换月跳空检测。 888 主力连续合约在换月点有人为跳变,会污染整个窗口的回归。
javascript
if (Math.abs(series[s][b] - series[s][b-1]) > MAX_BAR_JUMP) {
Log('⚠️', LABELS[s], '回归窗口内有异常跳变(疑似换月跳空),本轮跳过');
return null;
}
第三道,残差标准差下限。
javascript
if (!(sd > MIN_RESID_STD)) {
Log('⚠️ 残差标准差异常偏小(' + sd.toFixed(8) + '),回归可能不可靠,本轮跳过');
return null;
}
第四道,z 值合理性上限。
javascript
if (!isFinite(zLatest) || Math.abs(zLatest) > Z_SANITY_CAP) {
Log('⚠️ z值异常(' + zLatest + '),超过合理范围,视为计算故障,本轮忽略');
return null;
}
四道闸有一个共同的设计原则:跳过本轮,但不动已有持仓。 数据有问题不代表持仓有问题,在信息不可靠的时候乱动仓位比按兵不动更危险。
另外整个 z 计算链路都带 [DEBUG] 日志——收盘价、对数价格 range、回归系数、残差统计、原始 z 值。出问题的时候直接看日志就能定位到是哪一步歪了,不用猜。
九、风险边界
紧急风控。 单腿出现超过 4% 的不利变动时,砍掉一半仓位:
javascript
var adverse = side === 'long' ? (last - cur) / last : (cur - last) / last;
if (adverse >= EMERGENCY_DROP) {
var cut = Math.max(1, Math.floor(lots * EMERGENCY_REDUCE));
// ...减仓
}
这里必须说清楚一个明知的缺陷:单腿减仓会破坏对冲结构。 砍掉一半 PF 而 TA、EG 不动,剩下的组合就不是 beta 中性的了。我接受这个取舍——极端行情下先把暴露降下来,结构的事等下一轮信号重建。如果你不接受,把 EMERGENCY_DROP 设成一个很大的数就等于关掉了这个功能。
最大持仓时长。 默认 10 天。协整关系可能长期不回归,死等的成本是保证金占用和心态。
换月不移仓。 持仓期间主力换月,价格序列会跳空,继续持有等于拿旧关系判断新价格:
javascript
if (changed && (_G('bh_state') || 'FLAT') !== 'FLAT') {
Log('🔄 主力合约换月,先平仓再重新判断');
closeAllByMode();
_G('bh_state', 'FLAT');
}
牺牲一次持仓的连续性,换来判断依据的干净。
主力合约逐腿独立查询。 老策略用 'PF' + ptaId.slice(-3) 这种拼法,拿 PTA 主力的月份去拼另外两个品种的代码。三个品种主力月份不同步的时候,会拼出流动性极差甚至根本不存在的合约。现在改成各查各的。
十、完整参数表
| 参数 | 默认值 | 含义 |
|---|---|---|
CODES | PF,TA,eg | 三腿品种代码,第 1 个是目标腿(产成品) |
MULTS_STR | 5,5,10 | 合约乘数(吨/手),换品种必须同步改 |
TRADE_MODE | paper | paper 模拟盘 / live 实盘 |
INIT_CAPITAL | 100000 | 模拟盘初始资金(元) |
LOOKBACK | 240 | 回归与 z-score 窗口(根 K 线) |
ENTRY_Z | 1.5 | 进场 z 阈值 |
EXIT_Z | 0.5 | 离场 z 阈值 |
MAX_HOLD_HOURS | 240 | 最大持仓小时数,0 = 不限 |
DRIFT_THRESHOLD | 0.20 | 持仓期 β 漂移超过此比例触发重新对冲 |
LEVERAGE | 2 | 三腿合计名义 / 权益 |
GROSS_EXPOSURE | 0.7 | 敞口占用上限 |
MARGIN_RATE | 0.12 | 保证金率估算(含期货公司加收) |
MAX_HEDGE_ERR | 0.25 | 整数手取整后允许的最大对冲误差 |
THEORY_C1 / C2 | 0.855 / 0.335 | 理论物耗,仅用于告警对照 |
EMERGENCY_DROP | 0.04 | 单腿不利变动触发紧急减仓的阈值 |
Z_SANITY_CAP | 15 | |z| 超过此值视为计算故障 |
MIN_RESID_STD | 0.0005 | 残差标准差下限 |
MAX_BAR_JUMP | 0.15 | 单根 K 线对数跳变上限(识别换月跳空) |
LOOKBACK 是最需要按周期调的参数。日线建议 60~120,小时线建议 240~480。太短 β 抖动剧烈,信号全是噪音;太长跟不上产业变化。
十一、怎么测
这里我不贴收益曲线,原因很简单:一条漂亮的回测曲线对这类策略几乎没有信息量,而我不想用它来替代真正该看的东西。
真正该看的是这几项:
开仓次数。 半年只开两三次,那么无论收益如何,样本量都不足以说明任何问题。反过来一天开好几次,多半是 LOOKBACK 太短导致 β 抖动。
对冲误差日志。 每次入场都会打印理论手数、实际手数、取整误差和有效 β。误差长期贴着容忍上限,说明资金规模和这三个品种的名义金额比例不匹配——要么加资金,要么换组合。
被拒绝的次数和原因。 第八节那四道闸各触发了多少次?如果"残差标准差过小"频繁触发,说明窗口选得有问题。
分段看 β 的稳定性。 把回测期切成若干段分别回归,看 β₁ 和 β₂ 的漂移幅度。β 稳定是这类策略能长期跑的前提;β 一直在大幅摆动,说明关系本身就不牢靠,后面的一切都是沙上建塔。
理论 β 与实测 β 的偏离曲线。 这条线如果系统性走阔,说明产业结构在变化期,此时的历史统计规律未必适用于未来。
最后必须提醒:回测里的滑点假设对三腿策略极不友好。三条腿的滑点是叠加的,而回测很难还原真实盘口深度。这也是我把 TRADE_MODE 默认设成 paper 的原因——先用模拟盘跑一段,观察信号质量,再谈实盘。
十二、这一版还差什么
这一节可能比前面所有内容加起来都重要。这是一份研究原型,不是一个可以直接托管资金的成品。
统计层面的欠账:
-
没做协整检验。 我只跑了 OLS,没有做 ADF 单位根检验或 Johansen 协整检验。这意味着我并没有从统计上证明这三者存在协整关系,只是假设它存在然后去用。残差可能根本就不平稳。
-
z 值不能当标准正态读。 因为回归是在样本内做的,残差的分布不是标准正态,
z = 2绝不等于"只有 2.5% 的概率"。它只是一个相对度量——今天比过去这段时间的平均水平偏离了多少个身位,仅此而已。 -
没有半衰期估计。 我不知道这个残差平均需要多久才能回归一半。
MAX_HOLD_HOURS = 240这个数字是拍的,不是算的。合理的做法是估计 OU 过程的半衰期再反推持仓上限。 -
OLS 有样本内偏差。 滚动窗口缓解了一部分,但每次回归都用到了当期数据,严格来说存在轻微的前视成分。
执行层面的欠账:
-
没有 z 止损。 如果 z 从 1.5 一路走到 5 都不回头,策略会一直扛到最大持仓时长。协整关系真的破裂时,这个设计会让亏损扩大到远超预期。
-
实盘调仓是单次尝试。
liveRebalance发完单就结束了,没有"确认成交 → 补齐差额"的循环。部分成交会导致腿断——一条腿成交另两条没成交,组合瞬间变成裸单边。这是当前版本最需要优先补的洞。 -
紧急减仓破坏对冲结构。 第九节已经说过,这是明知的取舍。
-
没有对手续费、滑点、跨月价差单独建模。 三条腿的手续费和滑点是叠加的,而回测里这部分往往被低估。跨月成分被回归当作关系的一部分吸收了,但没有被单独剥离出来观察。
如果要往实盘推进,我的优先级会是:先补 6(成交确认循环),再补 5(z 止损),然后是 3(半衰期)。 1 和 2 属于研究严谨性问题,不补也能跑,但你得知道自己在用一个未经证明的假设。
十三、这套框架还能用在哪
值得强调的是,这套框架和短纤-PTA-乙二醇没有任何绑定关系。改三个品种代码、三个合约乘数、两个物耗系数,就能跑别的组合:
javascript
var CODES = "PF,TA,eg"; // 换成你要做的三个品种
var MULTS_STR = "5,5,10"; // 对应的合约乘数,必须同步改
按上手难度从易到难:
跨月套利。 同品种不同月份。只有两条腿,β 天然接近 1,关系是所有组合里最硬的——标的物完全相同,差的只是时间。适合用来验证框架本身有没有 bug。注意临近交割月流动性会迅速衰减,且有逼仓风险。
同产业链上下游(本文这一类,交易的是加工利润):
| 组合 | 交易的是 |
|---|---|
| 螺纹钢 / 铁矿石 / 焦炭 | 钢厂利润 |
| 大豆 / 豆油 / 豆粕 | 压榨利润(经典中的经典) |
| 甲醇 / PP | MTO 装置利润 |
| 焦煤 / 焦炭 | 焦化利润 |
这一类的优势是有物耗配方可以做理论 β 参照,能用第七节的方法监控关系健康度。
同类替代品(交易的是替代关系):豆油/棕榈油/菜油、螺纹/热卷、玉米/淀粉。没有配方可参照,但替代逻辑同样扎实。
挑选标的的四条原则
- 必须有产业逻辑支撑。 先能用一句话说清"它们为什么应该有关系",再去跑回归。
- 检查流动性木桶。 三条腿里最差的那条决定你的容量上限。
- 注意保证金。 跨品种套利一般享受不到组合保证金优惠,实际占用比想象的高。用满仓去估算资金需求。
- 腿数不要超过三条。 每多一条腿,整数手误差、滑点、腿断概率都在放大,收益却不一定线性增加。
结语
回头看,这次改造真正改掉的不是几个参数,而是一个思维习惯:不要把自己相信的东西写死进代码里。
0.855 和 0.335 是对的——在化学的意义上永远是对的。错的是我曾经默认它们在价格的意义上也永远成立。现在它们还在代码里,但位置从"下单依据"挪到了"告警指标":模型不再假装自己永远正确,而是在自己开始不正确的时候会喊一声。
公式负责描述偏离,工具负责执行,风控负责让你活到下一次机会。这三件事里,最后一件最容易被忽略,也最不能省。
策略源码:短纤(PF) - PTA(TA) - 乙二醇(EG) 三腿配对统计套利策略
风险提示
本文所述为相对价值统计套利,不是无风险套利。协整关系可能阶段性甚至永久失效,常见触发因素包括:装置长期检修或新产能集中投放、出口退税与反倾销等政策变动、交易所调整交割标准或保证金规则、单边逼仓行情、以及极端行情下三条腿相关性同时趋近于 1。
文中所有参数均为示例值,代码为研究原型(见第十二节的欠账清单),请勿直接用于实盘。策略默认运行在模拟盘模式,切换实盘前请务必完成第十二节列出的关键补齐项,并以极小资金验证实际成交质量。
期货交易有杠杆,亏损可能超过本金。
- 1





