1. 从一段“看起来平稳”的收益率序列说起你手上有一份 2000 个交易日的指数日收益率画出来整体均值接近 0波动也大致稳定。但把图放大会发现某些时间段连续出现大阴线、振幅明显放大另一些时间段则窄幅震荡、缓慢上行。如果直接套一个 AR(1) 或 GARCH模型会把这些极端段当成“异常值”压平结果就是波动率被低估尾部风险被掩盖回测时策略在危机段直接失效。这就是马尔可夫区制转移模型Markov regime switching简称 MS 模型要解决的问题。它假设时间序列背后有一个不可直接观测的“状态变量” s_t比如 s_t1 表示低波动/温和上涨区制s_t2 表示高波动/下跌区制。每个区制有自己的均值 μ_s 和波动率 σ_s而区制之间按一个转移矩阵 P 演化。你看到的收益率 x_t是“先由马尔可夫链决定当前处于哪个区制再由该区制的分布生成观测值”这样一个两层结构。它本质上是隐马尔可夫模型HMM的一个特例区别在于 MS 模型更强调“区制”的经济含义和参数的可解释性。它适合谁做宏观利率、汇率、股指、商品期限结构、信用利差这些经济金融时间序列的人需要识别“市场状态切换”做择时或风险预算的人以及想从 R 语言角度把 HMM/MS 模型从公式落到可运行脚本的人。本文交付的是一条完整链路模拟数据 → 手写似然函数 → 约束优化估计 → 用MSwitching包复核 → 平滑概率图 → 残差诊断。全程用 R 脚本配置和参数都给你照着敲就能复现。在开始之前先说清楚工具链。R 本身是本地环境但模型调试阶段经常需要反复问“这段似然函数为什么报错”“这个转移矩阵初值怎么设”我会把这类问答和代码补全放到 TaoToken 的统一 Key 上跑省得在多个平台之间来回切。下面第二节先把这个前置说清楚第三节开始才是真正的建模步骤。2. TaoToken 统一 Key 在 R 建模工作流里的前置配置做 MS 模型时最耗时间的往往不是写似然函数而是排错。比如optim报“非有限值”、MSwitching报“初始值不在可行域”、平滑概率全落在 0.5 附近不动。这些问题的排查需要你贴出代码片段、报错信息、当前参数向量然后让模型帮你定位。如果每次都要重新登录、重新配环境节奏就断了。TaoToken 在这里的角色是一个统一的模型调用入口一个 Key一套 Base URL就能在 R 脚本、命令行、编辑器插件之间共用同一套模型配置。它不替代 R也不替代你的编辑器只是把“问模型”这件事的接入成本降下来。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 这个不加 UTM。具体到 R 建模场景我建议你先把三件套固定下来Base URL、API Key、Model ID。Base URL 用https://taotoken.net/apiAPI Key 在控制台创建地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Model ID 按你实际要用的模型填比如做代码补全和报错解释时选一个擅长 R 的模型。创建 Key 的页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用的是 Claude Code 这类终端编码工具接入配置可以写成这样注意这是工具侧的 settings不是 R 代码{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 你的_TaoToken_Key, ANTHROPIC_MODEL: 你的_Model_ID } }如果你用的是 Cline 这类带 MCP 的编辑器插件配置里同样要写全三件套Base URL 指向https://taotoken.net/apiKey 填控制台生成的Model ID 填你要用的模型。Codex 的auth.json也是同理把 Base URL、Key、Model ID 三个字段对齐即可。这里的关键是不管你在哪个工具里问模型Base URL 和 Key 是同一套不用每个工具单独申请。为什么要在建模前做这一步因为 MS 模型的调试是高频交互的。你改一次初值、跑一次optim、看一次输出可能就要问一次“这个收敛码 1 是什么意思”“这个 Hessian 为什么不可逆”。统一 Key 之后你可以在 RStudio 里写代码在终端里问问题在编辑器里补全三者共享同一套配置不用反复切换账号。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 需要长期跑编码和 Agent 任务的可以用 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。配置完成后你可以先在 R 里做一个最小验证用httr发一个请求确认 Base URL 和 Key 能通。这一步不涉及建模只是确认链路。验证通过后再进入第三节的模型配置。3. 可复制的 R 脚本配置区制数设定与似然函数这一节是全文的核心给你一份可以直接跑的 R 脚本。我按“模拟数据 → 转移矩阵 → 手写似然 → 约束优化 → 包复核”的顺序组织每一步都有参数说明。先装包。MS 模型在 R 里主要用MSwitching辅助用depmixS4和stats。install.packages(c(MSwitching, depmixS4, ggplot2, kableExtra)) library(MSwitching) library(depmixS4) library(ggplot2)第一步模拟两区制数据。设定区制 1 均值 2、波动 1区制 2 均值 -2、波动 2转移概率 p110.95、p220.85。这意味着区制 1 更持久区制 2 更短暂。set.seed(123) T_end - 500 p11 - 0.95 p22 - 0.85 P - matrix(c(p11, 1 - p22, 1 - p11, p22), 2, 2, byrow TRUE) PP的第一行是“当前在区制 1下一期留在 1 的概率 0.95转到 2 的概率 0.05”第二行是“当前在区制 2转到 1 的概率 0.15留在 2 的概率 0.85”。平稳分布算一下区制 1 约 0.75区制 2 约 0.25。第二步模拟马尔可夫链 s_t。用递归循环从 s_11 开始。s - numeric(T_end) s[1] - 1 for (t in 2:T_end) { if (s[t-1] 1) { s[t] - sample(c(1, 2), 1, prob P[1, ]) } else { s[t] - sample(c(1, 2), 1, prob P[2, ]) } } mean(s 1)跑出来mean(s 1)大约在 0.7 到 0.75 之间和理论平稳分布一致。这一步是验证你的转移矩阵设定是否合理。第三步由 s_t 生成观测 x_t。区制 1 用 N(2, 1)区制 2 用 N(-2, 2)。x - numeric(T_end) x[s 1] - rnorm(sum(s 1), mean 2, sd 1) x[s 2] - rnorm(sum(s 2), mean -2, sd 2) plot(x, type l, col steelblue, main 模拟的区制转移序列) points(which(s 2), x[s 2], col red, pch 20, cex 0.6)红色点是区制 2 的观测。你会看到红色段明显波动更大、均值更负。这就是“事后解释容易事前识别难”的地方。第四步手写似然函数。MS 模型的似然要用 Hamilton 滤波递归计算。给定参数 θ(μ1, μ2, σ1, σ2, p11, p22)滤波概率 ξ_t|t 的递推是hmm_lik - function(theta, x) { mu1 - theta[1]; mu2 - theta[2] sd1 - theta[3]; sd2 - theta[4] p11 - theta[5]; p22 - theta[6] if (sd1 0 || sd2 0 || p11 0 || p11 1 || p22 0 || p22 1) { return(1e10) } T_end - length(x) P - matrix(c(p11, 1 - p22, 1 - p11, p22), 2, 2, byrow TRUE) xi - matrix(0, T_end, 2) xi[1, ] - c(0.5, 0.5) lik - 0 for (t in 1:T_end) { if (t 1) { xi[t, ] - xi[t-1, ] %*% P } dens - c(dnorm(x[t], mu1, sd1), dnorm(x[t], mu2, sd2)) num - xi[t, ] * dens den - sum(num) lik - lik log(den) xi[t, ] - num / den } return(-lik) }注意这里返回的是负对数似然因为optim默认求最小。约束条件sd10、sd20、0p111、0p221。初值设成 θ0(1, -1, 1, 1, 0.9, 0.9)满足约束。第五步用optim做约束优化。这里用L-BFGS-B下界和上界分别设好。theta0 - c(1, -1, 1, 1, 0.9, 0.9) lower - c(-10, -10, 0.01, 0.01, 0.01, 0.01) upper - c(10, 10, 10, 10, 0.99, 0.99) opt - optim(theta0, hmm_lik, x x, method L-BFGS-B, lower lower, upper upper, control list(maxit 1000)) opt$par opt$convergenceopt$convergence为 0 表示收敛。opt$par是估计出的六个参数。拿它和真实值 (2, -2, 1, 2, 0.95, 0.85) 对比均值和波动率应该比较接近转移概率会有一定偏差这是小样本的正常现象。第六步用MSwitching包复核。把数据转成ts对象指定区制数 k2并声明均值和波动率都随区制转移。library(MSwitching) x_ts - ts(x) mod - MSwitching(x_ts, k 2, sw c(TRUE, TRUE), control list(parallel FALSE)) summary(mod)输出里会报告每个区制的均值和波动率、转移概率矩阵、AIC/BIC。如果 AIC 比单区制模型明显低说明两区制设定有道理。区制数怎么定一般从 k2 开始逐步加到 k3、k4比较 BIC。BIC 对参数个数惩罚更重不容易过拟合。经济金融序列通常 k2 或 k3 就够k 太大解释性会下降。到这里配置部分就完整了。下一节做验证平滑概率图和残差诊断。4. 验证请求与成功结果平滑概率图与残差诊断模型跑通不等于结果可信。这一节给你两个验证动作一是看平滑概率是否和真实区制对齐二是看残差是否还有结构。先取平滑概率。MSwitching的summary里通常包含滤波概率和平滑概率也可以直接从模型对象里提取。smooth_prob - modFit$smoothing head(smooth_prob)smooth_prob是一个 T×2 矩阵第 1 列是处于区制 1 的平滑概率第 2 列是区制 2。平滑概率的含义是在看完整个样本后反推第 t 期处于某区制的概率。它比滤波概率更准因为它用了全样本信息。画图对比。上面画 x_t下面画区制 2 的平滑概率红色点标出真实区制 2 的位置。par(mfrow c(2, 1), mar c(3, 4, 2, 1)) plot(x, type l, col steelblue, main 观测序列与真实区制) points(which(s 2), x[s 2], col red, pch 20, cex 0.6) plot(smooth_prob[, 2], type l, col darkred, ylab P(区制2), main 区制2的平滑概率) abline(h 0.5, lty 2) points(which(s 2), smooth_prob[s 2, 2], col red, pch 20, cex 0.6)判断标准真实区制 2 的时间段平滑概率应该明显高于 0.5真实区制 1 的时间段平滑概率应该低于 0.5。如果大量点落在 0.5 附近说明两个区制的区分度不够可能是区制数设少了或者两个区制的均值/波动差异太小。算一下分类准确率pred_regime - ifelse(smooth_prob[, 2] 0.5, 2, 1) mean(pred_regime s)模拟数据下这个准确率通常能到 0.9 以上。如果低于 0.8就要回头检查初值和约束。第二个验证动作是残差诊断。MS 模型的残差定义为 x_t 减去当前区制的条件均值再除以条件波动率。标准化残差应该接近白噪声。resid_std - numeric(length(x)) for (t in 1:length(x)) { if (pred_regime[t] 1) { resid_std[t] - (x[t] - opt$par[1]) / opt$par[3] } else { resid_std[t] - (x[t] - opt$par[2]) / opt$par[4] } } acf(resid_std, main 标准化残差的自相关) Box.test(resid_std, lag 10, type Ljung-Box)Box.test的 p 值大于 0.05说明残差没有明显的自相关模型把序列里的结构提取干净了。如果 p 值很小说明还有自相关没被建模可能需要加 AR 项或者增加区制数。再画一个残差的 QQ 图检查正态性假设qqnorm(resid_std) qqline(resid_std, col red)如果点明显偏离直线说明正态假设不成立可以考虑 t 分布或 GED 分布。MSwitching支持指定分布族具体看文档。到这里验证动作就完成了。平滑概率对齐 残差白噪声两个条件都满足才能说模型可用。下一节讲常见报错。5. 本篇常见错排查从 401 到非有限值MS 模型调试中报错分两类一类是接入层的一类是数值层的。接入层的报错通常出现在你用统一 Key 问模型的时候数值层的报错出现在 R 脚本里。先说接入层。如果你在 R 里用httr调模型接口最常见的报错是 401。原因通常是 Key 没填对或者 Base URL 写成了带路径的形式。正确写法是 Base URL 只到https://taotoken.net/apiKey 放在请求头里。如果你用的是 Claude Code 或 Cline报错可能是local proxy failed这通常是本地代理配置和 Base URL 冲突检查一下环境变量里有没有多余的代理设置。还有一种是reading choices报错说明返回体结构和你解析的字段不匹配先打印原始返回体看看。再说数值层。optim报“非有限值”non-finite value原因通常是似然函数在某个参数点算出了log(0)或dnorm返回了 0。解决办法是在似然函数里加保护当den小于 1e-300 时返回一个很大的惩罚值而不是直接取 log。上面hmm_lik里已经加了 sd 和 p 的边界检查但还可以再加一层if (den 1e-300) return(1e10)MSwitching报“初始值不在可行域”通常是转移矩阵初值不满足行和为 1或者波动率初值为负。检查你的sw向量和初值设定。如果报“Hessian 不可逆”说明参数在边界附近或者两个区制的参数太接近模型识别不出来。这时候可以换一组初值或者减少转移参数。还有一个高频问题平滑概率全在 0.5 附近。这通常意味着两个区制的均值差异太小或者样本量不够。解决办法是先做单变量统计看均值和方差是否真的存在分段差异如果差异不明显MS 模型本身就不适用。最后如果你在 Claude Code 里做 R 代码补全记得把 Base URL、Key、Model ID 三件套写全。缺任何一个都会导致请求失败。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。排障时优先看文档里的错误码说明比盲目改代码快。6. 把模型落到真实数据上的几个实用建议模拟数据跑通只是第一步。真实经济金融序列有几个坑第一区制数不一定等于 2利率和汇率可能有三区制第二转移概率可能随时间变化这时候要用 TVTP 模型第三参数估计对初值敏感建议用多组初值跑optim取似然最大的那组。我自己的习惯是先用depmixS4快速试 k2 到 k4看 BIC 曲线再用MSwitching做精细估计。depmixS4的接口更友好适合探索MSwitching的推断更完整适合出报告。两者结果对不上时以MSwitching为准因为它的滤波实现更标准。如果你要长期跑这类模型建议把 Coding Plan 用起来地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它适合需要反复调试、跑 Agent 任务的场景比单次对话更省事。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 需要快速验证某个模型输出时可以用。最后提醒一句MS 模型的平滑概率是“事后”的实盘决策要用滤波概率因为滤波概率只用到当前和过去的信息。很多人拿平滑概率做回测结果虚高这是最常见的误用。把modFit$filtering取出来用滤波概率重新算一遍策略信号才是可交易的版本。