Skip to content

Latest commit

 

History

History
540 lines (415 loc) · 26 KB

File metadata and controls

540 lines (415 loc) · 26 KB

策略开发指南

写一份策略清单,回测它,验证它,再决定要不要给它钱。

本文覆盖清单的每个字段、表达式文法、成本模型,以及怎么用 maystock-lab 把 「回测很好看」和「未来可能有效」区分开。


0. 三十秒上手

./Scripts/new-strategy.sh "我的ETH趋势" trend ETH-USDT 4H

它会生成 Strategies/我的eth趋势.json,跑一遍回测,再跑一遍走向前验证。 顺序是刻意的:只生成文件会诱使你爱上一个没验证过的净值曲线。

单独用各条命令:

make lab ARGS="list"
make lab ARGS="backtest 01-btc-ema-trend --days 365 --capital 30000"
make lab ARGS="optimize 01-btc-ema-trend --objective calmar --max-dd 10 --min-trades 30"
make lab ARGS="walkforward 01-btc-ema-trend --folds 4"
make lab ARGS="portfolio 01-btc-ema-trend 02-eth-rsi-reversion --weights 0.5,0.5"
make lab ARGS="fees --tier lv1"

回测不需要任何 API Key,只读公开行情。只有 fees --sync 和实盘下单才需要凭证。


1. 清单字段

{
  "schema": 2,                    // 必填;schema 1 仍可读(缺 venue 视为 okx),保存时升为 2
  "id": "my-strategy",            // 省略则从 name 推导
  "name": "我的策略",              // 必填
  "version": "1.0.0",
  "author": "…",
  "notes": "写给三个月后的自己:这套规则为什么成立。",

  "market": {
    "venue": "okx",               // okx | schwab;省略视为 okx
    "instId": "BTC-USDT",         // okx:任意 OKX 标的(OPTION 时是信号所读的标的,见 §1.2);schwab:美股代码,如 SPY
    "instType": "SPOT",           // okx:SPOT | SWAP | OPTION;schwab:STOCK(见 §1.1)
    "bar": "1H"                   // 1m 5m 15m 1H 4H 1D 1W
  },

  "params": [                     // 也接受字典写法,见下
    { "name": "fast", "default": 12, "min": 4, "max": 60, "label": "快线周期" }
  ],

  "signals": {
    "longEntry":  "…",            // 至少要有 longEntry 或 shortEntry
    "longExit":   "…",
    "shortEntry": null,           // 仅允许做空的品种:SWAP、STOCK
    "shortExit":  null
  },

  "sizing": { "mode": "equityPct", "value": 100 },
  "risk":   { "stopLossPct": 4, "cooldownBars": 1 },
  "costs":  { "feeBps": 10, "slippageBps": 5 },  // 或 "fees": [组件…],见 §3;省略则按该交易所的费率表

  "options": { "minDaysToExpiry": 14, "moneynessPct": 0 },  // 仅 OPTION,见 §1.2
  "data":   { "funding": { "source": "fundingRate" } },   // 见 §2.5
  "engine": { "kind": "declarative" }                     // 或 script,见 §2.6
}

1.1 market.venue 与 instType

venue instType 计价币 日历 做空 杠杆
okx SPOT USDT 7×24 否 1
okx SWAP USDT 7×24 是 1~50,逐仓
okx OPTION USDT(权利金以结算币付) 7×24 是(买看跌,见 §1.2) 1,杠杆内生于合约
schwab STOCK USD 纽交所交易日:节假日、提前收盘、夏令时都由内核日历处理 是(保证金账户) 1~2(Reg T)

这张表不是文档说了算:规则在内核 kernel/src/strategy.rs 的 InstrumentPolicy 里, Swift 通过 ms_instrument_policy 读取同一份,清单编译时按它拒绝越界的做空与杠杆。 时间换算(一年多少根、持仓多少根、「一天」从哪里到哪里)同样只有内核的 MarketCalendar 一份,Swift 侧经 KernelCalendar 询问,不自己做 天数 × 86400 / bar 的算术。

1.2 期权:信号读标的,仓位是合约

instType = "OPTION" 时,market.instId 是信号所读的标的(如 BTC-USDT), 仓位则是它的期权合约:做多信号买入看涨,做空信号买入看跌。只买不卖—— 卖出期权亏损无上限且靠交易所保证金约束,引擎不建模,也就不许写。 每笔的最大亏损就是权利金,所以 equityPct 和 riskPerTrade 在这里是一个意思: 拿资金的多少比例去买权利金。

"market":  { "instId": "BTC-USDT", "instType": "OPTION", "bar": "4H" },
"signals": { "longEntry": "…", "longExit": "…", "shortEntry": "…", "shortExit": "…" },
"sizing":  { "mode": "equityPct", "value": 10 },          // 每次动用 10% 预算买权利金
"risk":    { "stopLossPct": 50, "takeProfitPct": 150, "volLookbackBars": 60 },
"options": {
  "uly": "BTC-USD",              // 结算指数;省略则由标的推导(BTC-USDT → BTC-USD)
  "minDaysToExpiry": 14,         // 买最近一个到期 ≥ 14 天的合约(默认 7)
  "moneynessPct": 0,             // 行权价偏离现价的百分比,正为价外;0 = 平值
  "impliedVolMultiplier": 1.2,   // 回测定价用:隐含波动 = 实现波动 × 1.2(默认)
  "strikeStep": 1000,            // 回测模型的行权价网格;省略 = 现价的 1%
  "contractMultiplier": 0.01,    // 回测模型每张合约的标的数量;实盘读交易所元数据
  "feeCapPctOfPremium": 12.5     // OKX 手续费上限:权利金的 12.5%
}
条款 期权策略里的含义
stopLossPct / takeProfitPct 相对权利金(不是标的价):权利金亏 50% 止损、赚 150% 止盈
atrStop / trailingStopPct / exposure / volatilityTarget / leverage > 1 不支持,导入即拒绝并说明原因
volLookbackBars 回测定价用的实现波动率窗口;同时决定预热长度
cooldownBars / minHoldBars / maxHoldBars / maxDailyLossPct 与其它品种相同
到期 持到期即按内在价值结算;只要信号仍在,下一根重新买入下一个到期

回测是模型定价,不是历史成交价。 OKX 不提供已到期合约的行情,所以回测用 Black–Scholes(无风险利率取 0)按标的收盘价与实现波动率 × impliedVolMultiplier 给每根 K 线定价:入场按下根开盘价定价并加滑点,止损止盈按收盘判定, 到期在含到期时刻的那根开盘按内在价值结算、不收手续费。手续费按 OKX 规则: 名义额 × 费率,封顶权利金的 feeCapPctOfPremium%。它能诚实回答的只有一个问题: 方向信号赚不赚得回权利金;某一天的权利金到底多少,是模型的猜测。 报告和工作台都会标注这一点。

实盘用交易所真实盘口。 运行器读期权链,用与回测同一个筛选函数 (内核 select_contract)挑合约,再读买一/卖一/标记价/指数价, 把权利金预算按 卖一 × 指数 × 每张标的量 换算成整张数,以 IOC 限价单 (卖一上浮 2%)成交;平仓是 reduceOnly 的 IOC 卖单(买一下浮 2%)。 没有卖盘、或卖一高出标记价 25% 以上(空盘口里的占位挂单),不开仓并说明。 权利金和手续费以合约的结算币支付(BTC 期权是 BTC),预算里的 USDT 不会被自动换成 BTC: 账户要持有足够的结算币,或在跨币种 / 组合保证金模式下开启自动借币(交易所计息)。 运行器下单前按 张数 × 每张标的量 × 限价 + 手续费 核对可用余额,不够就不下单, 并在策略状态里写明可用多少、需要多少、缺多少;能不能借由账户配置决定,本程序不替你换币。 报价读下单的那个环境。 模拟盘的期权盘口和标记价是模拟盘自己的,可能与真实市场相差数倍 (同一合约真实卖一 0.021、模拟盘卖一 0.0375);运行器在模拟盘模式下读模拟盘的盘口、标记价、 指数与合约列表来定价、算张数、估值和选合约,实盘则读真实市场。信号用的 K 线始终读真实市场—— 模拟盘跑的必须是将来实盘要跑的那个策略。IOC 单到达时若盘口已不在限价内,交易所会撤单, 策略状态会写明「未成交,已撤单」,不会把发出去的数量当成买到的。 止损止盈按权利金标记价在本程序 tick 上执行——App 关闭期间不设防, 但多头期权的亏损本来就以权利金为限。到期后交易所结算掉仓位, 运行器在对账时按标记价补记为「到期结算」。

params 的三种写法

"params": [ { "name": "fast", "default": 12, "min": 4, "max": 60 } ]   // 保留顺序,推荐
"params": { "fast": { "default": 12, "min": 4, "max": 60 } }           // 按名字排序
"params": { "fast": 12, "slow": 26 }                                   // 只给默认值

三种可以混写:{ "fast": 12, "slow": { "default": 26, "min": 10, "max": 200 } }。

没有 min/max 的参数无法参与寻优。 optimize 只会转动清单里明确 说了可以转多远的旋钮。

连续敞口:趋势跟随必需

除了 longEntry/shortEntry 这套二元信号,还可以写一条 exposure 表达式, 给出 −1..+1 的连续目标敞口。写了它就取代进出场信号。

"signals": {
  // AQR 1/3/12 月时序动量等权合成
  "exposure": "(sign(roc(close, 30)) + sign(roc(close, 90)) + sign(roc(close, 365))) / 3"
},
"sizing": { "mode": "volatilityTarget", "value": 40 },   // 目标年化波动 40%
"risk": { "volLookbackBars": 60, "maxExposure": 1, "rebalanceThreshold": 0.1 }

为什么必须有这个:Moskowitz/Ooi/Pedersen 的趋势跟随一半机制在仓位大小上, 二元仓位根本表达不了。实测 TSMOM 是四轮调研里唯一站得住的东西, 见 RESEARCH-TREND.md。

  • 不允许做空的品种(现货)负敞口会被钳到 0;永续与保证金账户里的股票才能真正持有负敞口
  • 敞口为 NaN(预热期)一律视为 0,绝不猜
  • rebalanceThreshold 是必需的:连续信号不设阈值会每根都换手,手续费吃光一切

sizing.mode

模式 含义
equityPct 占本策略分配资金的百分比(value 为 0~100)
fixedQuote 固定名义额,单位为计价币
riskPerTrade 按止损距离反推头寸,使单笔亏损恒为资金的 value%。必须配置止损,否则清单校验直接拒绝
volatilityTarget 仓位 = 资金 × 目标波动 / 已实现波动,value 为目标年化波动率(%)。需配合 exposure 使用;maxExposure 是安全上限,防止低波动时期把杠杆放飞

risk

字段 说明
stopLossPct / takeProfitPct 相对入场价的百分比
trailingStopPct 移动止损;水位按每根收盘更新,只约束之后的 K 线
atrStop { "period": 14, "mult": 2.5 };与 stopLossPct 并存时取更紧的那个
leverage 上限由品种决定(§1.1):现货 1、永续 50、期权 1(杠杆内生于合约)、美股 2;越界在导入时被拒
cooldownBars 平仓后需等待的根数才允许再入场(反手不受限)
minHoldBars 持仓至少这么多根才响应离场信号;止损不受限
maxDailyLossPct 交易日内亏损达标即平仓并停到次日;「一天」按该市场的日历算(OKX 为 UTC 日,美股为纽约交易日)
volLookbackBars 波动率估计窗口(默认 60 根),仅 volatilityTarget 用
maxExposure 波动率缩放后的敞口上限(默认 1)
rebalanceThreshold 目标敞口与现有敞口差异小于此值就不动手(默认 0.1)

2. 表达式文法

expr        := or
or          := and ( "or" and )*
and         := not ( "and" not )*
not         := "not" not | comparison
comparison  := sum ( ( ">" | ">=" | "<" | "<=" | "==" | "!="
                     | "crosses_above" | "crosses_below" ) sum )?
sum         := product ( ( "+" | "-" ) product )*
product     := unary ( ( "*" | "/" | "%" ) unary )*
unary       := "-" unary | primary
primary     := number | identifier | call | "(" expr ")"

变量:open high low close volume hl2 hlc3 ohlc4 bar_index,以及 params 中每个参数名。

函数

类别 函数
均线 sma(x,n) ema(x,n) wma(x,n) rma(x,n)
摆动 rsi(x,n) roc(x,n)
波动 stdev(x,n) atr(n) natr(n)
极值 highest(x,n) lowest(x,n)
复合 macd(x,f,s) macd_signal(x,f,s,g) macd_hist(x,f,s,g) bb_upper(x,n,k) bb_lower(x,n,k) bb_width(x,n,k)
工具 ref(x,n) abs(x) sign(x) min(a,b) max(a,b) clamp(x,lo,hi) crossover(a,b) crossunder(a,b)

周期参数必须是常量或策略参数(不能随行情变化),且在 1~5000 之间。

2.5 另类数据:把价格之外的东西变成变量

data 块里声明的每个名字都成为表达式里的变量,按 K 线时间轴对齐:

"data": {
  "funding": { "source": "fundingRate" },                    // 自动解析到 BTC-USDT-SWAP
  "lsr":     { "source": "longShortRatio", "ccy": "BTC" },
  "btc":     { "source": "instrumentClose", "instId": "BTC-USDT" }
},
"signals": {
  "longEntry": "funding < -0.0001 and btc > sma(btc, 50)"
}

可用 source(maystock-lab signals 会实测每个源现在能给多少历史):

OKX 交易统计 —— 历史短(1H 只有 30 天、1D 只有 179 天),基本无法验证:

source 含义 键
fundingRate 永续资金费率(小数,0.0001 = 0.01%) instId
openInterestUsd / volumeUsd 持仓量 / 成交额(USD) ccy
longShortRatio 多空账户比,>1 为散户净多 ccy
takerBuyVolume / takerSellVolume / takerImbalance 主动买卖量与失衡度(−1…+1) ccy
marginLoanRatio 杠杆借贷比 ccy
instrumentClose 另一标的收盘价(跨标的过滤) instId

业界公认信号 —— 免费、无需 Key、有多年历史,能真正做验证:

source 含义 来源 历史
fearGreed 恐惧贪婪指数 0–100 alternative.me 8.5 年
activeAddresses 链上活跃地址 blockchain.info 17 年
transactionCount 链上交易笔数 blockchain.info 17 年
hashRate 全网算力 blockchain.info 17 年
minerRevenue 矿工收入 blockchain.info 17 年
onChainVolumeUsd 链上转账额 blockchain.info 16 年
dollarIndex 美元指数(广义贸易加权) FRED 20 年
sp500 标普 500 FRED 10 年
vix VIX 恐慌指数 FRED 36 年
treasury10y 10 年美债收益率 FRED 64 年
coinbasePremium Coinbase 溢价(美国需求代理) Coinbase + OKX 1.2 年

这些是纯公开 GET,不发送任何账户信息。实测结论是它们目前都不显著, 详见 RESEARCH-SIGNALS.md —— 接进来是为了让你能自己验证, 不是为了让你直接用。

水平值要先平稳化。 链上量、美元指数、算力是会连续趋势数年的水平值, 直接用会被慢趋势主导。写成 roc(dxy, 30) 或 (dxy - sma(dxy, 90)) / stdev(dxy, 90) 才是正确设定 —— 实测中矿工收入的裸 t 从 −8.53(水平值)掉到 0.23(变化率)。

对齐规则:第 i 根 K 线只能看到时间戳 ≤ 该根开盘时间的观测,值向前填充, 第一个观测之前为 NaN(未知)。因为信号在收盘时求值,用到的数据必然早已发布 —— 结构上不可能有未来函数。

历史深度是硬约束:统计类接口不分页,1H 只有 30 天、1D 只有 179 天, 资金费率 93 天。覆盖不足时 backtest / walkforward 会明确警告, NaN 区间信号恒为「未知」,策略在那段时间不会开仓。 实测数据与结论见 RESEARCH-SIGNALS.md。

2.6 外部脚本策略

声明式表达式覆盖不了的逻辑,可以交给外部程序:

"engine": {
  "kind": "script",
  "command": "Strategies/scripts/momentum.py",
  "args": [],
  "timeoutSeconds": 30
},
"signals": {}          // 脚本策略不需要入场表达式

协议 —— stdin 进一个 JSON,stdout 出一个 JSON:

in : {"schema":1,"instId":"BTC-USDT","bar":"4H","params":{...},
      "candles":[{"ts":...,"o":..,"h":..,"l":..,"c":..,"v":..}, ...],
      "series":{"funding":[...]}}      // 声明的 data,null 表示未知
out: {"target":["flat","long", ..., null]}   // 每根一个

边界(这是脚本策略能被安全使用的前提):

  • 默认禁用。CLI 需 --allow-scripts,App 需在 账户与连接 → 组合风控 中显式开启。
  • 脚本只决定方向,不决定仓位、不下单。止损、冷却、最短持仓、 资金预算、日内熔断、急停仍由清单与工作台控制。
  • 超时强杀、输出大小上限、长度与取值严格校验。
  • 预热期返回 null(未知)而不是 flat —— 前者让引擎保持空仓, 后者是在断言一个你还没有依据的判断。
maystock-lab backtest 08-script-momentum --days 90 --allow-scripts

示例见 Strategies/scripts/momentum.py。

三值逻辑:NaN 是「未知」,不是 0

指标预热期返回 NaN。这不是实现细节,是安全设计:如果预热期返回 0, close > sma(close, 200) 会在前 200 根全部成立,策略一上来就满仓。

  • 任一操作数为 NaN → 算术与比较结果为 NaN
  • truthy(NaN) = false → 信号永远不会在指标未成型时触发
  • and / or 走 Kleene 逻辑:确定为假 and 未知 = 确定为假 (趋势过滤没通过时,不管另一条腿在不在预热都不开仓)

五个最常见的错误

  1. 通道不加 ref(..., 1) close > highest(high, 20) 里 highest 含当根自己,几乎永远为假。 正确写法:close > ref(highest(high, 20), 1)。
  2. 把「在上方」当成「穿越」 ema(close,12) > ema(close,26) 在整个上升段每根都为真。 要的是 crosses_above。
  3. 没有趋势过滤的均值回归 单边下跌里 RSI 会一路超卖,策略一路接刀。看 maxConsecutiveLosses。
  4. 忽略换手成本 Lv1 现货往返 0.3%。日均来回一次,一个月光成本就 9%。 先跑 maystock-lab fees。
  5. 参数越多越好 每个自由参数需要 30 笔独立交易才有统计意义。4 个参数 = 120 笔门槛, 1H 周期一年通常给不到。参数少反而更可能是真的。

3. 成本模型

回测撮合规则(与实盘运行器共用同一套求值器):

事项 处理
信号时点 第 i 根已确认 K 线收盘后求值
成交时点 第 i+1 根 K 线开盘价 + 滑点
手续费 进出各按名义额收一次
交易日历 由 market.venue 决定:OKX 7×24;美股按纽交所交易日,缺的周末与节假日不算缺口,年化按 252 个交易日
同根触及止损与止盈 按止损先成交(最坏假设)
跳空穿过止损 按开盘价成交,不是止损价
资金费(永续) OKX 真实历史费率,在结算时刻计提
强平(永续) 亏损吃穿 保证金 ×(1−维持保证金率) 即判强平

费率按 market.venue 取该交易所的费率表:OKX 默认普通 Lv1(新账户,最贵的现实情形); 嘉信美股买入零佣金,卖出收 SEC §31(按成交额)与 FINRA TAF(按股数,单笔封顶), 两个费率每年重定,数字带着核对日期放在 feeSchedules.schwab 里,过期改字段不改代码。

maystock-lab fees                 # 两家的费率模型
maystock-lab fees --tier vip1     # 换个 OKX 档位试算
maystock-lab fees --sync          # 从已配置的 okx CLI 拉本账户真实费率(仅 OKX)

清单里写了 costs 就以清单为准;没写就跟随费率表。costs 有两种写法:

  • { "feeBps": 10, "slippageBps": 5 }:双边按名义额收的百分比,交易所手续费的经典形状;
  • { "fees": [ … ], "slippageBps": 2 }:费用组件列表。每项 basis 为 notional(配 bps)/ unit(配 perUnit,按股数)/ order(配 perOrder,按单),side 为 buy / sell / both, 可带 minPerOrder / maxPerOrder 与 label。美股的监管费只在卖出时收且有封顶,只能这样写。

两种写法不能同时出现。费率表里查不到该品种、清单又没写 costs 的策略不会被按零成本回测, 而是直接报错——没有成本模型时把成本当零,是最乐观的读法。


4. 怎么判断一个策略值不值得给钱

回测好看什么都不说明

搜 5000 组参数挑出最好的一组,不是研究,是抽奖后把没中的票扔掉。 optimize 因此总会报告「无边际时这么多次尝试的期望最好夏普」:

运气基准        无边际时 3840 次尝试的期望最好夏普 ≈ 1.86

最优夏普没有明显高过这个数,就是搜出了噪声。

先看天花板在哪

在研究任何策略之前,先跑一次上帝视角:

make lab ARGS="oracle --instId BTC-USDT --bar 1D --days 1400 --swings 5,10,20"

它做两件事:

  1. 完美预知的上限 —— 给它未来数据,完美抓住每个波段能赚多少。 这是全代码库唯一刻意使用未来函数的地方,用来给一切策略划天花板。
  2. 前高前低到底挡不挡得住(无未来函数)—— 价格回到旧水位时, 先反向走还是先突破,附 z 检验与扣除成本后的期望。

实测结论:天花板高得离谱(14 个完美波段 = +14,643%),水位也确实有约 60% 的挡住率, 但边际每次只有 0.1%、机会稀疏、且会随行情衰减。详见 RESEARCH-CEILING.md。

先跑这个,能省下大量在错误方向上的力气。

先问信号有没有信息,再问策略赚不赚钱

回测把几百根 K 线压成几笔交易,统计力低到无法区分真信号和噪声。 先用信息系数判断信号本身:

maystock-lab ic sp500 --ccy BTC --bar 1D --days 2000 \
  --horizons 1,7,30 --transform roc:30 --trials 66

四个必须看懂的数:

指标 含义
修正 t 裸 t ÷ √h。h 根前瞻的相邻观测共享 h−1 根,有效样本约 n/h。只看这个,裸 t 恒被高估
有效样本 n/h。64 个观测得出的结论,不管 t 多好看都是脆的
多重检验门槛 试了 N 组就要用 --trials N,门槛会从 2 抬到更高(66 组 → 3.2)
--transform 非平稳水平值必须先转成 roc:N 或 z:N,否则测的是共同趋势不是预测力

分位收益还要单调:只有首尾两端有差异、中间乱跳,边际多半来自个别极端值。

走向前验证是唯一有意义的检验

maystock-lab walkforward my-strategy --folds 4 --in-sample 0.7

把历史切成 4 折,每折用前 70% 寻优、后 30% 盲测,最后把所有样本外段拼接起来。 拼接曲线是你真正可能拿到的结果。

判据:

指标 门槛
效率比(样本外收益 / 样本内收益) ≥ 0.5,低于即过拟合
盈利折数 ≥ 60%
样本外总收益 > 0,否则不必谈

行业经验值:回测收益到了样本外平均衰减约 26%。衰减是常态,不衰减才可疑。

稳健性徽章

工作台与 backtest 都会给出四档:

徽章 含义
样本不足 交易笔数 < 参数数 × 30
疑似过拟合 样本内夏普 > 3,或样本外效率 < 0.5
可参考 样本量够、无明显过拟合,但跨窗口方向不一致
稳健 样本足、衰减可控、各窗口同向

4.5 横截面因子(多标的)

前面所有内容都是给单个币择时。学术界对加密市场唯一有稳健证据的方向是 横截面:在几十个币之间互相排序,做多一档做空另一档。

make lab ARGS="factors --size 40 --days 365 --rebalance 7 --lookback 28 --skip 7"

三个因子来自 Liu, Tsyvinski & Wu (JF 2022):市场(等权基准)、 规模(−log 市值,小市值得分高)、动量(过去 N 根收益,跳过最近一期避开反转)。

必读的三件事:

  1. 稳定币必须剔除。 收益 ≈0、波动 ≈0 的资产在任何风险调整排序里自动获胜。 工具按名单 + 年化波动 <5% 双重剔除,并列出剔了谁。 实测:不剔除时规模 t = 2.29,剔除后 1.70。
  2. 幸存者偏差是这类研究的头号杀手。 宇宙取自今天仍在上市的币, 退市和归零的从未进入样本。工具每次强制打印这条。
  3. 多空那一列通常拿不到。 现货做空要杠杆账户,且未计融券成本。 「仅做多」才是现货能直接实现的口径。

实测结论是这两个因子在当前条件下都不成立(规模对宇宙大小极度敏感、 动量符号与文献相反),详见 RESEARCH-FACTORS.md。

5. 组合

maystock-lab portfolio 01-btc-ema-trend 02-eth-rsi-reversion \
  --weights 0.5,0.5 --capital 30000 --days 365

输出里最该看的是腿间相关性和分散化两行:

分散化    各腿加权回撤 12.40% → 组合 11.80%(改善 0.60%)
相关性    01-btc-ema-trend ↔ 02-eth-rsi-reversion = 0.83   (过高,几乎没有分散效果)

BTC 与 ETH 的日收益长期高度相关,「BTC + ETH」本身不构成分散。 真正的分散来自低相关的信号逻辑(趋势 + 回归、不同周期),而不是两个同步的标的。


6. 从研究台到实盘

  1. walkforward 通过 → 把 Strategies/<id>.json 拖进 MayStock 策略工作台。
  2. 先在模拟盘分配小仓位跑,对比实盘成交与回测假设的偏差。
  3. 账户与连接页里解锁实盘、切换到实盘(切换前会验证实盘账户),再逐个策略在实盘启动(每个都要确认)。
  4. 每笔订单都会带 clOrdId 策略标签,工作台按标签与 okx spot fills 对账, 对不上的部分显式标为「未归因」。

安全阶梯:默认模拟盘 → 实盘需全局解锁 → 每个策略单独确认 → 随时可急停(停全部并平仓)。