Claude AI生成了对冲基金策略教程:用马尔可夫模型概率模型判断市场方向

文章摘要: 本文将介绍如何使用 Claude AI、Claude Code 和马尔可夫模型分析市场状态,理解牛市、熊市与震荡之间的转换概率,并把这种对冲基金常用的量化思维应用到股票、比特币和其他金融资产中。后续还会介绍 Claude Code 技能安装方法,以及 TradingView 指标的使用步骤。

风险提示:本文仅用于技术研究和教学,不构成任何投资建议。加密货币、股票、期货和外汇交易风险较高,可能造成部分或全部本金损失,请根据自己的风险承受能力谨慎操作。

开始前需要准备的交易平台和分析工具

想跟着本文测试比特币、以太坊等加密货币,可以先准备一个交易所账号:

如果你想进一步观察成交量、订单流和市场流动性,还可以使用:

真正的对冲基金量化交易员是怎么交易的?

 

提示词



你是一名负责安装 `markov-hedge-fund-method` Claude Code 技能的引导代理。你要直接执行操作,而不是只告诉用户应该怎么做。你需要检测操作系统、打开下载页面、运行安装命令并处理错误。用户会在旁边观看整个过程。

你即将安装的技能包含一个 Python 模块,它可以:

通过 yfinance 获取任意股票代码的每日 OHLCV 数据(免费,不需要 API Key)。
根据 20 日滚动收益率,把每天标记为牛市、熊市或横盘。
通过最大似然计数构建转移矩阵。
通过计算矩阵的幂,使用 Chapman-Kolmogorov(查普曼-柯尔莫哥洛夫)方法预测未来 n 步的状态。
求解平稳分布,也就是长期状态占比。
运行滚动前推回测:在每一个时间点,都只使用当天之前已经存在的数据重新估算矩阵,并输出夏普比率和最大回撤。
还可以选择通过 hmmlearn 拟合隐马尔可夫模型(Baum-Welch + Viterbi)。如果在没有 MSVC 编译工具的 Windows 上,hmmlearn 编译失败,就自动跳过 HMM 层,可观测模型仍然可以正常工作。
第一次使用 SPY 的 10 年数据运行时,会在屏幕上显示转移矩阵、平稳分布,以及滚动前推回测的夏普比率和最大回撤。以后,你可以直接让 Claude 对任何股票代码运行这个技能。

这是 Roan(@RohOnChain)的框架。我现在把它安装成一个 Claude Code 技能,这样你今晚就能直接使用。

阶段 0——欢迎与确认横幅
打印下面这条横幅,让用户知道代理正在真正执行,而不是只做说明:

✓ 正在以代理模式运行——开始安装 markov-hedge-fund-method。
然后用通俗的英文告诉用户:“我将把 markov-hedge-fund-method 技能安装到 ~/.claude/skills/markov-hedge-fund-method/。Mac 和 Linux 大约需要 90 秒,Windows 最多需要 2 到 3 分钟。不需要密钥、不需要账号,也不需要管理员密码。准备好了吗?”

等待用户回复“go”“yes”或类似的肯定答复后再继续。如果用户回答“no”或者提出问题,就直接回答,然后再次询问是否准备继续。

阶段 1——环境检查
1.1——检测操作系统
使用 Shell 原生命令检测操作系统,并把结果保存为 OS_KIND,后续继续使用:

uname -s 2>/dev/null || echo “Windows_NT”
输出 Darwin → OS_KIND=mac。打开网址或文件时使用 `open` 命令。
输出 Linux → OS_KIND=linux。打开时使用 `xdg-open` 命令。
输出 Windows_NT,或者当前在 PowerShell 中运行 → OS_KIND=windows。打开时使用 `start` 命令。
打印一行:检测到的操作系统:<mac|linux|windows>。

1.1b——你希望用哪种方式操作?(快速询问,可选)
正式安装前,先问用户一个问题,然后根据 1.1 中检测到的操作系统,使用正确的打开命令执行用户的选择:

说:“安装前快速问一下——你准备打字和我交流,还是更想直接说话?语音输入的速度大约是打字的 3 倍。”
如果用户想使用语音或转录功能,就说:“不错。制作这个工具的 Lewis 使用一款免费的 Mac 应用 Yapper,可以在电脑上的任何地方进行语音输入。需要我帮你打开吗?你可以免费试用 2,000 个单词,不需要绑定银行卡。”如果用户答应,就使用对应操作系统的打开命令——`<open | xdg-open | start> https://getyapper.app`——为用户打开,并在回复中保留链接 https://getyapper.app。给用户一点时间安装,然后继续。
如果用户更愿意打字,或者不想使用 Yapper,也没关系,直接继续。
然后进入 1.2。

1.2——检查是否已经安装(保证重复执行安全)
检查技能文件夹是否已经存在:

ls -la ~/.claude/skills/markov-hedge-fund-method 2>/dev/null
如果文件夹存在:

生成一个带时间戳的后缀:`STAMP=$(date +%Y%m%d-%H%M%S)`。
把现有文件夹移到备份位置,不要删除:
`mv ~/.claude/skills/markov-hedge-fund-method ~/.claude/skills/.markov-hedge-fund-method.bak.$STAMP`
打印:之前的安装已备份到 `~/.claude/skills/.markov-hedge-fund-method.bak.<timestamp>`。现在开始全新安装。
这样即使安装过程中崩溃或被中断,用户也可以安全地重新运行这段提示词。

1.3——检查 uv(Astral 的 Python 工具链)
uv –version
如果命令成功,打印 `✓ uv 已安装`,然后跳到阶段 2。

如果没有安装 uv,就通过 Astral 官方安装程序进行安装。根据 OS_KIND 分支处理:

Mac / Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh
Windows(PowerShell):

powershell -ExecutionPolicy ByPass -c “irm https://astral.sh/uv/install.ps1 | iex”
如果 Linux 上没有 curl,先通过系统包管理器安装(`apt-get install -y curl` / `dnf install -y curl` / `pacman -S –noconfirm curl`)。除非 `id -u` 显示当前用户 UID 不是 0,并且确实没有其他办法,否则不要使用 sudo。需要使用 sudo 时,先把命令显示给用户并等待确认。

安装程序执行后,刷新当前 Shell 的 PATH,让 uv 立刻可以使用:

Mac / Linux:`source $HOME/.local/bin/env 2>/dev/null || export PATH=”$HOME/.local/bin:$PATH”`
Windows:安装程序会把 uv 添加到 PATH;再次执行 `uv –version` 检查。如果当前会话中仍然找不到 uv,就让用户关闭并重新打开终端,然后再次粘贴这段提示词。1.2 的重复执行保护可以保证重新运行是安全的。
再次验证:

uv –version
如果安装后 `uv –version` 仍然失败,就在用户浏览器中打开官方安装文档,让用户选择其他安装方式(Homebrew / winget / Scoop / pipx):

Mac:`open https://docs.astral.sh/uv/getting-started/installation/`
Linux:`xdg-open https://docs.astral.sh/uv/getting-started/installation/`
Windows:`start https://docs.astral.sh/uv/getting-started/installation/`
等待用户确认 `uv –version` 已经可以正常运行后再继续。没有 uv 时不要继续下一步。

阶段 2——配置(创建技能框架并固定 Python 3.12)
2.1——创建技能目录结构
mkdir -p ~/.claude/skills/markov-hedge-fund-method/markov_hedge_fund_method ~/.claude/skills/markov-hedge-fund-method/data
2.2——写入技能文件
写入 `~/.claude/skills/markov-hedge-fund-method/SKILL.md`:


name: markov-hedge-fund-method
description: 适用于任意股票代码的可观测马尔可夫市场状态模型。根据 20 日滚动收益率生成牛市、熊市和横盘状态,构建转移矩阵;通过矩阵幂预测未来 n 步状态;求解平稳分布;并运行滚动前推回测,输出夏普比率和最大回撤。还可以选择通过 hmmlearn 升级为隐马尔可夫模型。

# markov-hedge-fund-method

安装位置:`~/.claude/skills/markov-hedge-fund-method/`。
底层框架作者:Roan(@RohOnChain)。由 Lewis Jackson 安装为 Claude Code 技能。

## 调用方式

使用自然语言调用。用户可以在 Claude Code 中这样说:

– “对 SPY 运行 markov-hedge-fund-method 技能”
– “对 AAPL 运行 markov-hedge-fund-method 技能,使用 60 日回看周期”
– “对 BTC-USD 拟合 HMM”

运行技能时,在技能目录中使用其固定环境执行模块:
cd ~/.claude/skills/markov-hedge-fund-method uv run python -m markov_hedge_fund_method.run –ticker [–years 10] [–window 20] [–no-hmm]

默认股票代码是 `SPY`。默认读取 `10` 年的日线数据。用于市场状态标记的默认滚动窗口是 `20` 个交易日。

## 每次运行都会打印的结果

1. 显示股票代码、日期范围和数据行数的标题信息。
2. 3×3 转移矩阵(牛市 / 熊市 / 横盘),并标注代表状态持续性的对角线。
3. 平稳分布,也就是长期基准状态占比。
4. 每一步都重新估算模型的滚动前推回测结果,包括夏普比率和最大回撤。
5. 如果 `hmmlearn` 可用,还会显示可选的 HMM 状态平均收益率。

## 依赖项

使用 `uv` 管理 `.venv/` 中的 Python 3.12 虚拟环境,并安装:

– `yfinance>=0.2`
– `numpy>=1.26`
– `pandas>=2.0`
– `scikit-learn>=1.4`
– `hmmlearn>=0.3`(可选;即使没有安装,也会自动降级并正常运行)

这个技能不会写入任何凭证,不会读取环境变量。除 `yfinance` 访问 Yahoo Finance 之外,不会进行其他网络请求。
写入 `~/.claude/skills/markov-hedge-fund-method/markov_hedge_fund_method/__init__.py`:

“””马尔可夫对冲基金方法技能——可观测马尔可夫模型,并可选择升级为 HMM。”””
__version__ = “0.1.0”
写入 `~/.claude/skills/markov-hedge-fund-method/markov_hedge_fund_method/regime.py`:

“””可观测马尔可夫市场状态模型。

使用滚动收益率阈值,把每天标记为牛市(1)、熊市(-1)或横盘(0),
然后通过最大似然计数构建 3×3 转移矩阵,求解平稳分布,
并运行滚动前推回测。
“””

from __future__ import annotations

import numpy as np
import pandas as pd

STATES = [“熊市”, “横盘”, “牛市”] # 索引 0、1、2

def label_regimes(close: pd.Series, window: int = 20, threshold: float = 0.02) -> pd.Series:
“””根据滚动收益率,把每天标记为牛市、熊市或横盘。

牛市:滚动收益率 > +threshold
熊市:滚动收益率 < -threshold
横盘:其他情况
“””
rolling_return = close.pct_change(window)
labels = pd.Series(1, index=close.index, dtype=int) # 默认横盘
labels[rolling_return > threshold] = 2 # 牛市
labels[rolling_return < -threshold] = 0 # 熊市
return labels.dropna()

def build_transition_matrix(labels: pd.Series) -> np.ndarray:
“””通过一系列状态标签,使用最大似然估计构建 3×3 转移矩阵。”””
n = 3
counts = np.zeros((n, n), dtype=float)
arr = labels.to_numpy()
for i in range(len(arr) – 1):
counts[arr[i], arr[i + 1]] += 1
row_sums = counts.sum(axis=1, keepdims=True)
row_sums[row_sums == 0] = 1.0 # 避免空行导致除以零
return counts / row_sums

def stationary_distribution(P: np.ndarray) -> np.ndarray:
“””求 P 中特征值为 1 的左特征向量,并归一化,使所有元素之和为 1。”””
eigvals, eigvecs = np.linalg.eig(P.T)
# 找到最接近特征值 1 的特征向量
idx = np.argmin(np.abs(eigvals – 1.0))
vec = np.real(eigvecs[:, idx])
vec = np.abs(vec)
return vec / vec.sum()

def n_step_forecast(P: np.ndarray, n: int) -> np.ndarray:
“””Chapman-Kolmogorov:P 的 n 次方就是 n 步转移矩阵。”””
return np.linalg.matrix_power(P, n)

def signal_from_matrix(P: np.ndarray, current_state: int) -> float:
“””带方向的信号:P(下一状态=牛市|当前状态)- P(下一状态=熊市|当前状态)。

正数 → 做多,负数 → 做空,绝对值大小 → 信号确信程度。
“””
return float(P[current_state, 2] – P[current_state, 0])

def walk_forward_backtest(
close: pd.Series,
labels: pd.Series,
min_train: int = 252,
) -> dict:
“””滚动前推:在每一天 t,只使用 t-1 之前的标签拟合矩阵,
根据当前状态生成信号,持有一天,然后计算表现。

不使用未来数据,也不进行参数调优。
“””
daily_returns = close.pct_change().dropna()
common_index = labels.index.intersection(daily_returns.index)
labels = labels.loc[common_index]
daily_returns = daily_returns.loc[common_index]

if len(labels) < min_train + 30:
return {“sharpe”: float(“nan”), “max_drawdown”: float(“nan”), “n_trades”: 0}

strategy_returns = []
for t in range(min_train, len(labels) – 1):
P_t = build_transition_matrix(labels.iloc[:t])
current_state = int(labels.iloc[t])
signal = signal_from_matrix(P_t, current_state)
position = float(np.sign(signal)) # +1 / 0 / -1——只取信号方向
next_day_return = float(daily_returns.iloc[t + 1])
strategy_returns.append(position * next_day_return)

sr = np.array(strategy_returns, dtype=float)
if sr.std(ddof=1) == 0 or not np.isfinite(sr.std(ddof=1)):
sharpe = float(“nan”)
else:
sharpe = float(sr.mean() / sr.std(ddof=1) * np.sqrt(252))

equity = (1.0 + sr).cumprod()
running_max = np.maximum.accumulate(equity)
drawdown = (equity – running_max) / running_max
max_dd = float(drawdown.min()) if len(drawdown) else float(“nan”)

return {“sharpe”: sharpe, “max_drawdown”: max_dd, “n_trades”: int(len(sr))}
写入 `~/.claude/skills/markov-hedge-fund-method/markov_hedge_fund_method/hmm_extension.py`:

“””可选的隐马尔可夫模型层。延迟导入 hmmlearn,
因此即使 hmmlearn 安装失败,可观测模型仍然可以正常运行。”””

from __future__ import annotations

import numpy as np
import pandas as pd

def fit_hmm(returns: pd.Series, n_components: int = 3, random_state: int = 42):
“””使用每日收益率拟合高斯 HMM。返回(model, hidden_states)。

注意:Baum-Welch 算法可能找到局部最大值。用于实际生产环境时,
应使用多个 random_state 分别拟合,并保留对数似然最高的模型。
“””
try:
from hmmlearn import hmm # 延迟导入
except ImportError:
return None, None

X = returns.dropna().to_numpy().reshape(-1, 1)
model = hmm.GaussianHMM(
n_components=n_components,
covariance_type=”diag”,
n_iter=200,
random_state=random_state,
)
model.fit(X)
hidden_states = model.predict(X)
return model, hidden_states
写入 `~/.claude/skills/markov-hedge-fund-method/markov_hedge_fund_method/run.py`:

“””命令行入口:获取数据 → 标记状态 → 构建矩阵 → 求平稳分布 → 滚动前推回测。

用法:
uv run python -m markov_hedge_fund_method.run –ticker SPY –years 10 –window 20
“””

from __future__ import annotations

import argparse
import os
import sys
import time
from pathlib import Path

import numpy as np
import pandas as pd

from .regime import (
STATES,
label_regimes,
build_transition_matrix,
stationary_distribution,
walk_forward_backtest,
)

HMM_FLAG_FILE = Path(__file__).resolve().parent.parent / “.hmm_available”

def _hmm_available() -> bool:
if HMM_FLAG_FILE.exists():
return HMM_FLAG_FILE.read_text().strip().lower() == “true”
try:
import hmmlearn # noqa: F401
return True
except ImportError:
return False

def _fetch_with_retry(ticker: str, years: int) -> pd.DataFrame:
“””通过 yfinance 获取数据;失败时重试一次。如果两次都为空,就抛出异常。”””
import yfinance as yf

end = pd.Timestamp.utcnow().normalize()
start = end – pd.DateOffset(years=years)

for attempt in (1, 2):
try:
df = yf.download(
ticker,
start=start.strftime(“%Y-%m-%d”),
end=end.strftime(“%Y-%m-%d”),
progress=False,
auto_adjust=True,
)
except Exception as exc: # noqa: BLE001
print(f” ! yfinance 第 {attempt} 次请求出错:{exc}”)
df = pd.DataFrame()

if not df.empty:
return df

if attempt == 1:
print(” ! yfinance 返回了空数据——30 秒后重试。”)
time.sleep(30)

raise RuntimeError(
f”yfinance 重试后仍然没有返回 {ticker} 的数据。”
“Yahoo 可能正在限流,请过几分钟再试。”
)

def main() -> int:
parser = argparse.ArgumentParser(prog=”markov-hedge-fund-method”)
parser.add_argument(“–ticker”, default=”SPY”)
parser.add_argument(“–years”, type=int, default=10)
parser.add_argument(“–window”, type=int, default=20, help=”滚动收益率窗口,单位为交易日”)
parser.add_argument(“–threshold”, type=float, default=0.02, help=”滚动收益率的市场状态标记阈值”)
parser.add_argument(“–no-hmm”, action=”store_true”, help=”即使 hmmlearn 可用,也跳过 HMM 拟合”)
args = parser.parse_args()

print(f”\nmarkov-hedge-fund-method——股票代码={args.ticker} 年数={args.years} 窗口={args.window}”)
print(f” 正在从 Yahoo Finance 获取 {args.ticker} 的数据……”)
df = _fetch_with_retry(args.ticker, args.years)

# 兼容部分 yfinance 版本返回 MultiIndex 多级列的数据结构。
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
close = df[“Close”].dropna()
print(f” 已获取 {len(close)} 行数据|{close.index.min().date()} -> {close.index.max().date()}”)

labels = label_regimes(close, window=args.window, threshold=args.threshold)
P = build_transition_matrix(labels)
pi = stationary_distribution(P)

print(“\n转移矩阵(行=当前状态,列=下一状态):”)
print(f” {STATES[0]:>9s} {STATES[1]:>9s} {STATES[2]:>9s}”)
for i, from_state in enumerate(STATES):
row = ” “.join(f”{P[i, j]*100:7.2f}%” for j in range(3))
marker = ” <- 状态持续性对角线” if i == i else “” # 占位;真正的对角线数值在下方打印
print(f” {from_state:>9s} {row}”)

print(“\n状态持续性对角线:”)
print(f” {STATES[0]} -> {STATES[0]}: {P[0,0]*100:.2f}%”)
print(f” {STATES[1]} -> {STATES[1]}: {P[1,1]*100:.2f}%”)
print(f” {STATES[2]} -> {STATES[2]}: {P[2,2]*100:.2f}%”)

print(“\n平稳分布(长期市场状态占比):”)
for s, p in zip(STATES, pi):
print(f” {s:>9s}: {p*100:.2f}%”)

print(“\n滚动前推回测(每一步重新估算矩阵,不使用未来数据)……”)
result = walk_forward_backtest(close, labels)
sharpe = result[“sharpe”]
mdd = result[“max_drawdown”]
if np.isfinite(sharpe):
print(f” 夏普比率(年化,滚动前推):{sharpe:.3f}”)
else:
print(” 夏普比率:NaN(数据不足——请尝试更长的历史数据或其他股票代码)”)
if np.isfinite(mdd):
print(f” 最大回撤:{mdd*100:.2f}%”)
else:
print(” 最大回撤:NaN”)
print(f” 已评估交易数:{result[‘n_trades’]}”)

if not args.no_hmm and _hmm_available():
print(“\n正在拟合隐马尔可夫模型(通过 hmmlearn 使用 Baum-Welch + Viterbi)……”)
try:
from .hmm_extension import fit_hmm
returns = close.pct_change().dropna()
model, hidden = fit_hmm(returns, n_components=3)
if model is None:
print(” 已跳过 HMM 扩展(运行时导入 hmmlearn 失败)。”)
else:
means = np.array([model.means_[k][0] for k in range(model.n_components)])
order = np.argsort(means)
labels_for_hmm = [“熊市(平均收益最低)”, “横盘”, “牛市(平均收益最高)”]
print(” HMM 各状态的日均收益率(已排序):”)
for rank, k in enumerate(order):
print(f” {labels_for_hmm[rank]:<30s} 状态 {k}: {means[k]*100:+.3f}% / 天”)
print(” 注意:Baum-Welch 可能找到局部最大值。用于实际生产环境时,请使用多个 random_state 分别拟合。”)
except Exception as exc: # noqa: BLE001
print(f” 运行时已跳过 HMM 扩展:{exc}”)
else:
print(“\n已跳过 HMM 扩展(可选);可观测马尔可夫模型已成功安装。”)

print(“\n—————————————————————-“)
print(” 框架作者:Roan(@RohOnChain)。已由 Lewis Jackson 安装为”)
print(” Claude Code 技能。回测只代表历史表现,不代表未来结果。”)
print(“—————————————————————-\n”)
return 0

if __name__ == “__main__”:
sys.exit(main())
写入 `~/.claude/skills/markov-hedge-fund-method/pyproject.toml`:

[project]
name = “markov-hedge-fund-method”
version = “0.1.0”
description = “带有可选 HMM 层的可观测马尔可夫市场状态模型”
requires-python = “==3.12.*”
dependencies = [
“yfinance>=0.2”,
“numpy>=1.26”,
“pandas>=2.0”,
“scikit-learn>=1.4”,
]

[project.optional-dependencies]
hmm = [“hmmlearn>=0.3”]

[build-system]
requires = [“setuptools>=68”]
build-backend = “setuptools.build_meta”

[tool.setuptools.packages.find]
where = [“.”]
include = [“markov_hedge_fund_method*”]
写入 `~/.claude/skills/markov-hedge-fund-method/.gitignore`:

.venv/
__pycache__/
*.pyc
.hmm_available
2.3——通过 uv 固定使用 Python 3.12
cd ~/.claude/skills/markov-hedge-fund-method
uv python install 3.12
uv venv –python 3.12 .venv
验证:

cd ~/.claude/skills/markov-hedge-fund-method && uv run python –version
预期显示 Python 3.12.x。如果 `uv python install 3.12` 失败(很少发生,通常是 Astral 的 Python 镜像暂时无法访问),等待 60 秒后重试一次。如果第二次仍然失败,显示 uv 返回的完整错误信息,并让用户重新运行这段提示词。

阶段 3——安装
3.1——安装必需依赖项
在技能文件夹内安装核心依赖项。这些依赖是必需的,安装失败就是真正的安装失败:

cd ~/.claude/skills/markov-hedge-fund-method
uv pip install “yfinance>=0.2” “numpy>=1.26” “pandas>=2.0” “scikit-learn>=1.4”
如果这四个依赖中的任何一个安装失败,显示 uv 返回的完整错误信息并停止。最常见的原因是网络不可用——让用户检查网络连接,然后重新运行这段提示词。阶段 1.2 的重复执行保护可以确保重新运行是安全的。

3.2——尝试安装可选的 HMM 扩展
在这一组依赖中,hmmlearn 是唯一一个可能在没有 Microsoft C++ Build Tools 的 Windows 电脑上编译失败的库。必须使用错误处理包住安装过程,绝对不能让它导致整个安装失败。

cd ~/.claude/skills/markov-hedge-fund-method
uv pip install “hmmlearn>=0.3” && echo “true” > .hmm_available || echo “false” > .hmm_available
读取 `.hmm_available`。如果内容是 true,打印:

✓ HMM 扩展安装成功——可观测模型和隐状态模型都可以使用。
如果内容是 false,必须准确打印:

已跳过 HMM 扩展(可选);可观测马尔可夫模型已成功安装。
然后再补充下面这一行,让用户知道主体框架仍然可以正常使用:

转移矩阵、平稳分布和滚动前推回测都可以正常运行。以后需要启用 HMM 层时,请安装 Microsoft Visual C++ Build Tools,然后重新运行这段提示词。
不要因为 HMM 安装失败而停止。继续进入阶段 4。

阶段 4——首次运行
使用 SPY 的 10 年数据运行一次技能,让用户亲眼看到它可以工作。这是整个演示中最关键的一步。

cd ~/.claude/skills/markov-hedge-fund-method
uv run python -m markov_hedge_fund_method.run –ticker SPY –years 10
预期输出如下(具体数值会变化,但结构固定):

markov-hedge-fund-method——股票代码=SPY 年数=10 窗口=20
正在从 Yahoo Finance 获取 SPY 的数据……
已获取约 2500 行数据|<start_date> -> <end_date>

转移矩阵(行=当前状态,列=下一状态):
熊市 横盘 牛市
熊市 XX.XX% XX.XX% XX.XX%
横盘 XX.XX% XX.XX% XX.XX%
牛市 XX.XX% XX.XX% XX.XX%

状态持续性对角线:
熊市 -> 熊市:XX.XX%
横盘 -> 横盘:XX.XX%
牛市 -> 牛市:XX.XX%

平稳分布(长期市场状态占比):
熊市:XX.XX%
横盘:XX.XX%
牛市:XX.XX%

滚动前推回测(每一步重新估算矩阵,不使用未来数据)……
夏普比率(年化,滚动前推):X.XXX
最大回撤:-XX.XX%
已评估交易数:约 2000
如果内部重试后,yfinance 仍然因为网络或限流错误而失败,就显示完整错误信息,并告诉用户:

“现在无法连接 Yahoo。技能本身已经完整安装成功——等几分钟 Yahoo 恢复后,再运行 `uv run python -m markov_hedge_fund_method.run`。其他部分都已经正常完成。”

不要把这种情况当成安装失败。技能已经安装成功,只是首次演示的数据获取失败。

阶段 5——确认
打印最终总结。格式和措辞要与下面一致,因为这是脚本向用户承诺会看到的内容:

================================================================
✓ markov-hedge-fund-method 技能已安装到 ~/.claude/skills/markov-hedge-fund-method/

已安装:
• 可观测马尔可夫模型(转移矩阵、n 步预测、
平稳分布、滚动前推回测)
• HMM 扩展:<已安装 | 已跳过(可选)>

使用 SPY 10 年数据的首次运行已完成。转移矩阵、平稳分布、
滚动前推夏普比率和最大回撤已经显示在上方。

现在,你可以在任何 Claude Code 会话中这样对 Claude 说:
• 对 AAPL 运行 markov-hedge-fund-method 技能
• 对 BTC-USD 运行 markov-hedge-fund-method 技能,使用 60 日回看周期
• 对 QQQ 拟合 HMM

回测只代表历史表现,不代表未来结果。矩阵展示的是客观测量结果——
你可以把它应用到任何自己交易的标的上。
================================================================
把 `<已安装 | 已跳过(可选)>` 替换成 `.hmm_available` 中记录的实际状态。

 

 

下面是用于 TradingView 的 Pine Script

//@version=5
// =============================================================================
// 马尔可夫市场状态——牛市 / 熊市 / 横盘
// =============================================================================
// 在图表上实时显示马尔可夫市场状态框架。
// 使用滚动对数收益率规则,把每根 K 线标记为牛市、熊市或横盘,
// 然后根据图表当前可见的历史数据构建 3×3 转移矩阵和平稳分布,
// 并把两者显示在图表角落的表格中。
// 使用方法——加载到 BTCUSDT 日线图。阈值可以根据需要调整。默认回看周期为 20 根 K 线,
// 上下阈值为 +/-5%。转移矩阵的对角线代表状态持续性,是最关键的观察结果——
// 对角线单元格使用更明亮的底色和白色文字突出显示;非对角线文字使用约 70% 的白色透明度。
//
// 测试用例(手动测试——需要在 TradingView 中验证):
// – BTCUSDT 日线、完整历史:状态色带应在明显的市场状态转折处切换牛市、熊市和横盘,
// 例如 2017 年第四季度的抛物线上涨、2018 年熊市、2020 年 3 月疫情暴跌和 2024 年之后的牛市。
// – 转移矩阵每一行的总和应约等于 100%(行归一化中的浮点数舍入允许有 +/-0.5% 误差)。
// – 使用真实 BTC 数据时,平稳分布应在 50 次迭代后收敛,
// 总和应为 1.0(允许 +/-0.001 误差)。如果性能变慢,可以降低 stationary_power。
//
// 配色(与 Roan 的分镜设计一致——hsl(150,38%,64%) / hsl(354,48%,64%) /
// hsl(220,14%,68%)——低饱和、柔和的色调,不使用霓虹色):
// 牛市:rgb(132,187,161)——柔和绿色
// 熊市:rgb(197,127,134)——低饱和玫瑰红
// 横盘:rgb(164,171,183)——冷灰色
// =============================================================================

indicator(“马尔可夫市场状态 – 牛市 / 熊市 / 横盘”, overlay = true, max_labels_count = 500)

// ────────────────────────────────────────────────────────────────────────────
// 输入参数
// ────────────────────────────────────────────────────────────────────────────
grp_logic = “市场状态逻辑”
grp_display = “显示设置”
grp_position = “表格位置”

lookback_window = input.int(20, title = “回看周期(K 线根数)”, minval = 5, maxval = 250, group = grp_logic, tooltip = “用于计算滚动对数收益率的 K 线数量。”)
bull_threshold_pct = input.float(5.0, title = “牛市阈值(%)”, minval = 0.1, maxval = 50.0, step = 0.1, group = grp_logic, tooltip = “当滚动对数收益率高于这个百分比时,把当前 K 线标记为牛市。”)
bear_threshold_pct = input.float(5.0, title = “熊市阈值(%)”, minval = 0.1, maxval = 50.0, step = 0.1, group = grp_logic, tooltip = “当滚动对数收益率低于这个数值的负值时,把当前 K 线标记为熊市。这个阈值可以与牛市阈值分开设置,以实现不对称阈值。”)
stationary_power = input.int(50, title = “平稳分布幂次(迭代次数)”, minval = 10, maxval = 200, group = grp_logic, tooltip = “计算平稳分布时,矩阵连续相乘的次数。对于正常的 3×3 随机矩阵,迭代 50 次基本已经收敛。”)

show_regime_ribbon = input.bool(true, title = “显示市场状态色带”, group = grp_display)
show_regime_banner = input.bool(true, title = “显示当前市场状态横幅”, group = grp_display)
show_matrix_table = input.bool(true, title = “显示转移矩阵”, group = grp_display)
show_stationary_table = input.bool(true, title = “显示平稳分布”, group = grp_display)
show_transition_labels = input.bool(true, title = “在图表上标记状态切换”, group = grp_display, tooltip = “每当市场状态发生变化时放置一个标签,例如:牛市 → 熊市。”)
table_text_size = input.string(“huge”, title = “表格文字大小”, options = [“small”,”normal”,”large”,”huge”], group = grp_display, tooltip = “转移矩阵和长期状态占比表格的文字大小。huge 大约是旧版默认大小的 3 倍,适合录屏或截图。”)
min_regime_hold = input.int(4, title = “状态至少持续多少根 K 线后才显示标签”, minval = 1, maxval = 50, group = grp_display, tooltip = “一个新状态必须连续保持这么多根已确认 K 线,才会在图表上显示切换标签。这样可以减少震荡区间里的标签刷屏;数值越高,标签越少、越干净。”)

banner_position_input = input.string(“top_left”, title = “横幅位置”, options = [“top_left”,”top_center”,”top_right”,”middle_left”,”middle_center”,”middle_right”,”bottom_left”,”bottom_center”,”bottom_right”], group = grp_position)
matrix_position_input = input.string(“top_right”, title = “矩阵位置”, options = [“top_left”,”top_center”,”top_right”,”middle_left”,”middle_center”,”middle_right”,”bottom_left”,”bottom_center”,”bottom_right”], group = grp_position)
stationary_position_input = input.string(“bottom_right”, title = “平稳分布位置”, options = [“top_left”,”top_center”,”top_right”,”middle_left”,”middle_center”,”middle_right”,”bottom_left”,”bottom_center”,”bottom_right”], group = grp_position)

// ────────────────────────────────────────────────────────────────────────────
// 把位置字符串映射为 position 常量
//
// Pine 的 `position.*` 常量属于 `simple` 类型,也就是脚本加载时确定,
// 所以这个映射只在编译时运行一次,不会每根 K 线都运行。
// 把 series 字符串传给 simple position,是可配置表格在 v5 中最常见的坑之一。
// ────────────────────────────────────────────────────────────────────────────
position_from_string(s) =>
switch s
“top_left” => position.top_left
“top_center” => position.top_center
“top_right” => position.top_right
“middle_left” => position.middle_left
“middle_center” => position.middle_center
“middle_right” => position.middle_right
“bottom_left” => position.bottom_left
“bottom_center” => position.bottom_center
“bottom_right” => position.bottom_right
=> position.top_right

banner_pos = position_from_string(banner_position_input)
matrix_pos = position_from_string(matrix_position_input)
stationary_pos = position_from_string(stationary_position_input)

// 表格文字大小——数值单元格使用用户选择的大小,表头和标签小一档,
// 这样无论使用哪种尺寸,都能保留清晰的视觉层级。
size_from_string(s) =>
switch s
“small” => size.small
“normal” => size.normal
“large” => size.large
“huge” => size.huge
=> size.large
notch_down(s) =>
switch s
“huge” => “large”
“large” => “normal”
“normal” => “small”
=> “small”
val_size = size_from_string(table_text_size)
hdr_size = size_from_string(notch_down(table_text_size))

// ────────────────────────────────────────────────────────────────────────────
// 配色
//
// Pine 的 `color.rgb(r, g, b, transp)` 使用 0–100 的透明度,其中 100 表示完全透明,
// 与 CSS 的 alpha 方向正好相反。这里特意说明,避免以后修改时反复弄混。
// 分镜设计中的十六进制颜色:
// 牛市 #84BBA1(hsl 150,38,64)· 熊市 #C57F86(hsl 354,48,64)· 横盘 #A4ABB7(hsl 220,14,68)
// ────────────────────────────────────────────────────────────────────────────
// 色带颜色——高透明度(alpha 约 30%,transp=70),让价格走势仍然是画面重点
c_bull_ribbon = color.rgb(132, 187, 161, 70)
c_bear_ribbon = color.rgb(197, 127, 134, 70)
c_side_ribbon = color.rgb(164, 171, 183, 70)

// 实色块——透明度更低(alpha 约 70%,transp=30),用于对角线单元格和横幅
c_bull_solid = color.rgb(132, 187, 161, 30)
c_bear_solid = color.rgb(197, 127, 134, 30)
c_side_solid = color.rgb(164, 171, 183, 30)

// 非对角线单元格颜色——更加淡化(alpha 约 15%,transp=85)
c_bull_dim = color.rgb(132, 187, 161, 85)
c_bear_dim = color.rgb(197, 127, 134, 85)
c_side_dim = color.rgb(164, 171, 183, 85)

// 通用界面颜色
c_bg = color.new(color.black, 30)
c_text_active = color.white
c_text_dim = color.new(color.white, 30)
c_border = color.new(color.white, 60)

// “矩阵卡片”配色——在 Pine 的限制范围内,尽量接近
// references/05-list-cards/matrix-card-target.png(Pine 不支持圆角和发光效果)。
c_card_bg = color.new(#0B0F0D, 8) // 近黑色面板
c_card_frame = color.new(#3FDE7E, 78) // 淡绿色边框
c_accent = #3FDE7E // 明亮的信号绿色
c_diag_bg = color.new(#3FDE7E, 80) // 绿色调的对角线单元格
c_diag_txt = #6BF0A6 // 明亮绿色数字
c_off_txt = color.new(color.white, 55) // 变暗的非对角线数字
c_hdr_txt = color.new(color.white, 35) // 柔和的列标题和行标题
c_foot_txt = color.new(color.white, 60) // 柔和的页脚文字

regime_solid(r) => r == 1 ? c_bull_solid : r == 2 ? c_bear_solid : c_side_solid
regime_ribbon(r) => r == 1 ? c_bull_ribbon : r == 2 ? c_bear_ribbon : c_side_ribbon
regime_dim(r) => r == 1 ? c_bull_dim : r == 2 ? c_bear_dim : c_side_dim
regime_name(r) => r == 1 ? “牛市” : r == 2 ? “熊市” : “横盘”
regime_abbr(r) => r == 1 ? “牛市” : r == 2 ? “熊市” : “横盘”

// ────────────────────────────────────────────────────────────────────────────
// 每根 K 线的市场状态标签
//
// log_ret = log(close / close[lookback])——整个回看窗口内的总对数收益率。
// regime:0 = 横盘,1 = 牛市,2 = 熊市(整数编码与规格说明第 3 节一致)。
// ────────────────────────────────────────────────────────────────────────────
log_ret = math.log(close / close[lookback_window])
regime = na(log_ret) ? int(na) : log_ret > bull_threshold_pct / 100.0 ? 1 : log_ret < -bear_threshold_pct / 100.0 ? 2 : 0

// ────────────────────────────────────────────────────────────────────────────
// 市场状态色带——为每根 K 线添加轻微的背景色
//
// 默认使用透明度约 90% 的 `bgcolor()`(alpha 约为 10%),让价格走势仍然是重点。
// 这会给整根 K 线所在的背景区域着色。Pine 的 `bgcolor` 无法在叠加指标中只限制在底部带状区域,
// 否则会破坏图表的自动缩放。因此,这里使用非常高的透明度进行补偿:transp=90,
// 在 Pine 的 0–100 透明度刻度中,100 表示完全透明。
//
// 如果确实想要固定在底部的色带,可以改用一个非叠加指标,
// 或者在固定的底部 Y 坐标上逐根 K 线使用 `plotshape`,
// 但这样会与图表的价格坐标轴产生冲突。对于 Pine v5 来说,高透明度的 bgcolor 是更实用的写法。
//
// 当前默认采用 90% 透明度,也就是 Pine 反向刻度中的 transp=90。
// 色带调色板 c_*_ribbon 本身使用 transp=70,下面再通过 color.new() 把透明度提高到 90。
// ────────────────────────────────────────────────────────────────────────────
ribbon_color_for_bar = regime == 1 ? color.rgb(132, 187, 161, 90) : regime == 2 ? color.rgb(197, 127, 134, 90) : color.rgb(164, 171, 183, 90)
bgcolor(show_regime_ribbon ? ribbon_color_for_bar : na, title = “市场状态色带”)

// ────────────────────────────────────────────────────────────────────────────
// 转移次数统计——通过 `var` 在不同 K 线之间保存状态
//
// 使用 `var` 声明的数组只会创建一次,并在不同 K 线之间持续保留。
// 如果没有 `var`,数组会在每根 K 线上重置,最终看到的全部都是零。
// 3×3 矩阵按行优先方式压平成一维数组:
// idx = prev_regime * 3 + curr_regime。
// 只在 K 线已经确认时计数,避免实时 K 线被重复统计。
// ────────────────────────────────────────────────────────────────────────────
var counts = array.new_int(9, 0)

prev_regime = regime[1]
if barstate.isconfirmed and not na(prev_regime) and not na(regime)
idx = prev_regime * 3 + regime
array.set(counts, idx, array.get(counts, idx) + 1)

// ────────────────────────────────────────────────────────────────────────────
// 状态切换标签——加入防抖,只给持续有效的状态变化添加标记
//
// 在震荡区间里,原始状态会频繁来回切换,如果每根 K 线都放一个标签,图表会变得完全无法阅读。
// 因此,新状态必须连续保持 `min_regime_hold` 根已确认 K 线,才会绘制一个标签,
// 并把标签放回真正发生切换的那根 K 线上,例如“牛市 -> 熊市”。
// `last_lbl_regime` 用于去重,保证每次持续有效的状态变化只标记一次。
// 上方的转移次数统计和矩阵仍然使用原始状态;这个过滤器只影响图表标签。
// ────────────────────────────────────────────────────────────────────────────
var int last_lbl_regime = na

held = not na(regime)
for k = 0 to min_regime_hold – 1
held := held and not na(regime[k]) and regime[k] == regime

if barstate.isconfirmed and not na(regime) and held and regime != last_lbl_regime
if show_transition_labels and not na(last_lbl_regime)
flip_off = min_regime_hold – 1
label.new(bar_index – flip_off, high[flip_off], regime_abbr(last_lbl_regime) + ” -> ” + regime_abbr(regime), yloc = yloc.abovebar, style = label.style_label_down, color = regime_solid(regime), textcolor = color.white, size = size.normal)
last_lbl_regime := regime

// ────────────────────────────────────────────────────────────────────────────
// 表格对象——通过 `var` 只创建一次,而不是每根 K 线都重新创建
//
// 每根 K 线都重新创建表格,是 v5 表格最常见的坑之一,
// 会造成画面闪烁并浪费 CPU。`var` 可以保证 `table.new` 只运行一次,
// 之后只在最后一根 K 线上通过 `table.cell` 更新内容。
// ────────────────────────────────────────────────────────────────────────────
var table tbl_banner = table.new(banner_pos, 1, 1, bgcolor = c_bg, border_width = 0)
var table tbl_matrix = table.new(matrix_pos, 4, 6, bgcolor = c_card_bg, border_width = 2, border_color = c_card_bg, frame_color = c_card_frame, frame_width = 1)
var table tbl_stationary = table.new(stationary_pos, 3, 4, bgcolor = c_card_bg, border_width = 2, border_color = c_card_bg, frame_color = c_card_frame, frame_width = 1)

// ────────────────────────────────────────────────────────────────────────────
// 3×3 矩阵乘法
//
// Pine v5 没有一个可以在所有版本中稳定使用的通用矩阵乘法函数。
// 较新的版本中存在 `matrix.*` 命名空间,但这里仍然把展开计算作为主要方案,原因是:
// (a)它可以在所有 v5 环境中运行;
// (b)矩阵固定为 3×3,展开计算的成本很低;
// (c)它只会在每次图表渲染时运行一次,因为外层使用 barstate.islast 限制。
//
// 现代版 matrix.* 替代方案——如果你的 TradingView 版本支持,并且你更喜欢这种写法,
// 可以取消下面代码的注释。代码长度大约可以减少一半,计算结果完全相同:
//
// matrix<float> Pm = matrix.new<float>(3, 3, 0.0)
// for r = 0 to 2
// for c = 0 to 2
// matrix.set(Pm, r, c, array.get(P_flat, r*3 + c))
// matrix<float> Mm = matrix.copy(Pm)
// for _ = 1 to stationary_power – 1
// Mm := matrix.mult(Mm, Pm)
//
// 下面是可以在所有环境中运行的展开计算备用方案。
// ────────────────────────────────────────────────────────────────────────────
matmul_3x3(A, B) =>
a00 = array.get(A, 0)
a01 = array.get(A, 1)
a02 = array.get(A, 2)
a10 = array.get(A, 3)
a11 = array.get(A, 4)
a12 = array.get(A, 5)
a20 = array.get(A, 6)
a21 = array.get(A, 7)
a22 = array.get(A, 8)

b00 = array.get(B, 0)
b01 = array.get(B, 1)
b02 = array.get(B, 2)
b10 = array.get(B, 3)
b11 = array.get(B, 4)
b12 = array.get(B, 5)
b20 = array.get(B, 6)
b21 = array.get(B, 7)
b22 = array.get(B, 8)

C = array.new_float(9, 0.0)
array.set(C, 0, a00 * b00 + a01 * b10 + a02 * b20)
array.set(C, 1, a00 * b01 + a01 * b11 + a02 * b21)
array.set(C, 2, a00 * b02 + a01 * b12 + a02 * b22)
array.set(C, 3, a10 * b00 + a11 * b10 + a12 * b20)
array.set(C, 4, a10 * b01 + a11 * b11 + a12 * b21)
array.set(C, 5, a10 * b02 + a11 * b12 + a12 * b22)
array.set(C, 6, a20 * b00 + a21 * b10 + a22 * b20)
array.set(C, 7, a20 * b01 + a21 * b11 + a22 * b21)
array.set(C, 8, a20 * b02 + a21 * b12 + a22 * b22)
C

// 辅助函数——把 0 到 1 的概率格式化为整数百分比,例如 0.823 → “82%”
fmt_pct(p) => str.tostring(math.round(p * 100)) + “%”

// ────────────────────────────────────────────────────────────────────────────
// 最后一根 K 线:根据计数构建 P,迭代求平稳分布,并填充表格
//
// 所有计算量较大的操作都通过 barstate.islast 限制,
// 因此每次图表渲染只运行一次。
// ────────────────────────────────────────────────────────────────────────────
if barstate.islast

// ── 构建 P:按行对计数进行归一化 ──────────────────────────────────────
// P 是一个 3×3 随机矩阵,每一行的总和等于 1.0。
// 如果某个市场状态从未出现在可见历史中,也就是 row_sum == 0,
// 就使用均匀分布 1/3 作为备用值,从而保证矩阵乘法在数值上安全,避免 NaN 传播。
P = array.new_float(9, 0.0)
for r = 0 to 2
row_sum = array.get(counts, r * 3) + array.get(counts, r * 3 + 1) + array.get(counts, r * 3 + 2)
for c = 0 to 2
cell = row_sum > 0 ? array.get(counts, r * 3 + c) / row_sum : 1.0 / 3.0
array.set(P, r * 3 + c, cell)

// ── 平稳分布:执行 M := M * P,共迭代(stationary_power – 1)次 ──────
// 对于正常的 3×3 随机矩阵,迭代 50 次后基本都会收敛,
// 此时 M 的每一行都会变成同一个平稳分布三维向量。
M = array.copy(P)
for _i = 1 to stationary_power – 1
M := matmul_3x3(M, P)

// 收敛后的 M 中,任意一行都是平稳分布;这里取第 0 行。
stat_bull = array.get(M, 0)
stat_bear = array.get(M, 1)
stat_side = array.get(M, 2)

// ────────────────────────────────────────────────────────────────────────
// 市场状态横幅——左上角,单个单元格
// ────────────────────────────────────────────────────────────────────────
if show_regime_banner
table.cell(tbl_banner, 0, 0, “当前状态:” + regime_name(regime), text_color = c_text_active, bgcolor = regime_solid(regime), text_size = size.large, text_halign = text.align_center, text_valign = text.align_center)

// ────────────────────────────────────────────────────────────────────────
// 转移矩阵表格——4 列 × 6 行
//
// [横跨表头的标题] 第 0 行(概念上横跨整行;Pine 不支持 rowspan/colspan,所以使用一个标题单元格加空白单元格)
// [空白] [牛市] [熊市] [横盘] 第 1 行(列标题——“明天”的状态)
// [牛市] [82%] [ 6%] [12%] 第 2 行
// [熊市] [ 8%] [74%] [18%] 第 3 行
// [横盘] [21%] [17%] [62%] 第 4 行
// [页脚] 第 5 行
//
// 对角线单元格使用更明亮的状态底色和白色文字;
// 非对角线单元格使用更淡的状态底色和约 70% 透明度的白色文字。
// Pine 没有粗体选项,因此通过更大的字号和更亮的颜色建立视觉层级。
// 对角线是整个结果中最关键的观察重点。
// ────────────────────────────────────────────────────────────────────────
if show_matrix_table
// 标题行在视觉上横跨整行。Pine 不支持 colspan,
// 所以把标题放在单元格(0,0),并让(1..3,0)保持相同背景的空白。
// 第 0 行——卡片标题:“马尔可夫状态”位于左侧并使用绿色,“3×3”位于右侧并弱化显示。
table.cell(tbl_matrix, 0, 0, “马尔可夫状态”, text_color = c_accent, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_left)
table.cell(tbl_matrix, 1, 0, “”, bgcolor = c_card_bg)
table.cell(tbl_matrix, 2, 0, “”, bgcolor = c_card_bg)
table.cell(tbl_matrix, 3, 0, “3×3”, text_color = c_foot_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_right)

// 第 1 行——代表明天状态的列标题,弱化显示
table.cell(tbl_matrix, 0, 1, “”, bgcolor = c_card_bg)
table.cell(tbl_matrix, 1, 1, “牛市”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)
table.cell(tbl_matrix, 2, 1, “熊市”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)
table.cell(tbl_matrix, 3, 1, “横盘”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)

// 第 2–4 行——分别代表一种“今天”的市场状态
for r = 0 to 2
row_name = r == 0 ? “牛市” : r == 1 ? “熊市” : “横盘”
table.cell(tbl_matrix, 0, r + 2, row_name, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)

for c = 0 to 2
p = array.get(P, r * 3 + c)
is_diag = (r == c)
// 对角线:绿色调卡片单元格、明亮绿色数字、大字号;
// 非对角线:普通卡片背景、变暗数字、小字号。
cell_bg = is_diag ? c_diag_bg : c_card_bg
cell_txt = is_diag ? c_diag_txt : c_off_txt
cell_sz = is_diag ? val_size : hdr_size
table.cell(tbl_matrix, c + 1, r + 2, fmt_pct(p), text_color = cell_txt, bgcolor = cell_bg, text_size = cell_sz, text_halign = text.align_center, text_valign = text.align_center)

// 第 5 行——页脚:左侧显示“下一状态概率 P”(弱化),右侧显示“实时”(绿色)
table.cell(tbl_matrix, 0, 5, “下一状态概率 P”, text_color = c_foot_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_left)
table.cell(tbl_matrix, 1, 5, “”, bgcolor = c_card_bg)
table.cell(tbl_matrix, 2, 5, “”, bgcolor = c_card_bg)
table.cell(tbl_matrix, 3, 5, “实时”, text_color = c_accent, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_right)

// ────────────────────────────────────────────────────────────────────────
// 平稳分布表格——3 列 × 4 行
//
// [横跨表格的标题] 第 0 行
// [牛市] [熊市] [横盘] 第 1 行
// [41%] [28%] [31%] 第 2 行
// [页脚] 第 3 行
// ────────────────────────────────────────────────────────────────────────
if show_stationary_table
// 标题——“长期状态占比”(绿色),风格与矩阵卡片一致
table.cell(tbl_stationary, 0, 0, “长期状态占比”, text_color = c_accent, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_left)
table.cell(tbl_stationary, 1, 0, “”, bgcolor = c_card_bg)
table.cell(tbl_stationary, 2, 0, “”, bgcolor = c_card_bg)

table.cell(tbl_stationary, 0, 1, “牛市”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)
table.cell(tbl_stationary, 1, 1, “熊市”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)
table.cell(tbl_stationary, 2, 1, “横盘”, text_color = c_hdr_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_center)

table.cell(tbl_stationary, 0, 2, fmt_pct(stat_bull), text_color = c_diag_txt, bgcolor = c_diag_bg, text_size = val_size, text_halign = text.align_center, text_valign = text.align_center)
table.cell(tbl_stationary, 1, 2, fmt_pct(stat_bear), text_color = c_diag_txt, bgcolor = c_diag_bg, text_size = val_size, text_halign = text.align_center, text_valign = text.align_center)
table.cell(tbl_stationary, 2, 2, fmt_pct(stat_side), text_color = c_diag_txt, bgcolor = c_diag_bg, text_size = val_size, text_halign = text.align_center, text_valign = text.align_center)

table.cell(tbl_stationary, 0, 3, “稳态”, text_color = c_foot_txt, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_left)
table.cell(tbl_stationary, 1, 3, “”, bgcolor = c_card_bg)
table.cell(tbl_stationary, 2, 3, “实时”, text_color = c_accent, bgcolor = c_card_bg, text_size = hdr_size, text_halign = text.align_right)

// ────────────────────────────────────────────────────────────────────────────
// 文件结束
// ────────────────────────────────────────────────────────────────────────────

 

真正的对冲基金量化交易员是怎么交易的?

普通交易者分析行情时,通常会看K线、画趋势线,再搭配均线、MACD、RSI等技术指标寻找买卖点。

专业量化团队的思路却不太一样。

他们不会只问:“这张图看起来会不会上涨?”而是会问:

在当前这种市场状态下,历史上第二天继续上涨、转为下跌或者进入震荡的概率分别是多少?

这种方法的核心不是依赖感觉,而是把市场划分成不同状态,再通过历史数据统计状态之间的转换概率。

本文要介绍的核心方法叫作 Markov Hedge Fund Method,中文可以理解为“马尔可夫对冲基金方法”。它可以配合 Claude AI 和 Claude Code 使用,帮助我们分析、验证和优化交易策略。

如果你暂时没有 Claude 会员,也可以通过银河录像局了解 ChatGPT、Claude 等AI会员服务。
链接:https://nf.video/C1dxn

什么是马尔可夫模型?

马尔可夫模型的核心逻辑可以用一句话概括:

市场下一步怎么走,重点取决于当前处于什么状态,再利用历史数据计算下一状态出现的概率。

在最基础的模型中,可以把市场划分为三种状态。

1. 牛市状态(Bull State)

价格整体偏强,上涨动能较明显,接下来继续维持强势的概率相对较高。

2. 熊市状态(Bear State)

价格整体偏弱,下跌压力较明显,接下来继续走弱的概率相对较高。

3. 震荡状态(Sideways State)

价格缺少明确方向,主要在一定区间内来回波动。

划分市场状态以后,模型会统计这些状态在历史上如何互相转换,例如:

  • 今天是牛市,明天继续牛市的概率是多少?
  • 今天是牛市,明天转为熊市的概率是多少?
  • 今天是熊市,明天继续熊市的概率是多少?
  • 今天是震荡,明天转为牛市或熊市的概率是多少?

3×3马尔可夫转移概率矩阵

三种市场状态之间的转换结果,可以放进一个 3×3转移概率矩阵(Transition Probability Matrix) 中。

矩阵的“行”代表今天的市场状态,“列”代表明天可能进入的市场状态。每一行的概率加起来必须等于100%,因为下一个交易日一定会落入牛市、熊市或震荡中的一种。

举个简单的例子:

当前状态 明天牛市 明天熊市 明天震荡
牛市 70% 10% 20%
熊市 15% 65% 20%
震荡 25% 25% 50%

如果今天处于牛市状态,矩阵显示明天继续牛市的概率为70%,转为熊市的概率为10%,进入震荡的概率为20%。

这样一来,“市场可能继续上涨”就不再只是主观判断,而是变成一个可以计算和验证的统计结果。

市场状态为什么具有“粘性”?

马尔可夫模型中有一个重要概念叫 Stickiness,中文通常译为“粘性”或“状态持续性”。

它表示市场进入某一种状态以后,短期内继续保持该状态的概率往往更高。例如:

  • 今天处于牛市,明天仍可能保持牛市;
  • 今天处于熊市,明天仍可能保持熊市;
  • 今天处于震荡,明天也可能继续震荡。

这和交易中常说的“趋势是你的朋友”很相似。

区别在于,传统技术分析可能依靠图形和经验判断趋势,而马尔可夫模型会利用历史数据,计算某种状态持续或转换的具体概率。

如何预测未来两天、三天甚至更久?

如果只预测下一个交易日,可以直接使用一次转移概率。

如果想预测未来两天、三天或者更久,可以使用矩阵乘方。例如,今天处于牛市,明天继续牛市的概率是80%。为了方便理解,可以粗略想象成:

80% × 80% = 64%

也就是说,在极度简化的假设下,连续两天保持牛市状态的概率约为64%。

不过,真正计算多期状态概率时,需要使用完整的转移矩阵进行矩阵乘法,而不是只把单个概率连续相乘。

预测周期越长,结果通常越趋于分散。到了较长时间以后,牛市、熊市和震荡的概率可能逐渐接近平稳分布,短期交易信号也会越来越弱。因此,这类模型通常更适合判断短期或中短期的状态变化,而不是预测很久以后的具体涨跌。

如何把概率转换成交易信号?

一种简单的处理方法是:

交易方向强度 = 明天牛市概率 − 明天熊市概率

假设模型给出的结果为:

  • 明天牛市概率:65%
  • 明天熊市概率:20%
  • 明天震荡概率:15%

那么交易方向强度就是:

65% − 20% = +45%

结果为正数,代表市场状态偏多;结果为负数,代表市场状态偏空。

例如,牛市概率为20%,熊市概率为60%,那么:

20% − 60% = −40%

这表示模型整体偏空。

需要注意的是,概率信号只能作为策略中的一个组成部分,不能单独决定仓位。实际交易还要结合止损、最大回撤、波动率、交易成本和风险预算。信号越强,不代表一定会盈利,更不能简单理解为应该使用更高杠杆。

对于期货交易者,可以使用NinjaTrader专业期货交易工具进行图表分析、策略测试和交易管理。
链接:https://ninjatraderdomesticvendor.sjv.io/DyDM3q

从马尔可夫模型升级到隐藏马尔可夫模型

基础方法通常需要人为设定规则,例如涨幅超过某个标准定义为牛市,跌幅低于某个标准定义为熊市,中间区域定义为震荡。

这种划分方式比较直观,但也存在主观性。

更高级的方法叫作 Hidden Markov Model,隐藏马尔可夫模型,简称HMM。它不要求我们提前给每段行情贴上标签,而是让模型根据收益率、波动率、趋势持续性等历史特征,自动识别潜在的市场状态。

隐藏马尔可夫模型有什么优势?

隐藏马尔可夫模型不会直接把“涨了多少”简单等同于牛市,也不会只根据某一天的涨跌判断市场方向。它会综合观察多个数据特征,例如:

  • 价格收益率;
  • 市场波动率;
  • 上涨或下跌的持续时间;
  • 成交量变化;
  • 不同状态之间的切换规律。

然后,模型会自动判断某一段行情更接近牛市、熊市还是震荡。

这种方法的优势,是减少人为设定阈值带来的主观偏差。对于波动较大的比特币、以太坊和科技股来说,同样的5%涨跌幅,代表的市场意义可能完全不同。HMM可以根据不同资产的历史特征,识别更适合该资产的市场状态。

不过,隐藏马尔可夫模型也不是“预测神器”。它识别的是历史数据中可能存在的状态结构,并不能保证未来市场一定按照相同规律运行。因此,在实际交易中仍然需要进行样本外测试、滚动回测和风险控制。

如何使用Claude Code安装量化分析技能?

接下来,我们可以把前面介绍的马尔可夫市场状态分析方法,制作成一个能够在 Claude Code 中重复调用的AI技能。

Claude Code是Claude提供的AI编程工具,可以读取本地文件、运行代码、检查程序错误,并根据用户的要求创建完整的分析项目。

安装完成后,你可以直接向Claude提出类似的要求:

  • 对比特币运行马尔可夫市场状态分析;
  • 分析苹果股票当前更接近牛市、熊市还是震荡;
  • 计算某只股票的状态转移矩阵;
  • 对已有的交易策略进行滚动前推测试;
  • 检查策略最大回撤和样本外表现;
  • 使用HMM识别市场状态。

如果你暂时没有Claude会员,也可以通过银河录像局了解Claude、ChatGPT等AI会员服务

链接:https://nf.video/C1dxn

第一步:安装Claude并打开Code功能

先在电脑上安装Claude桌面版,然后登录自己的Claude账号。

打开Claude以后,在顶部功能区找到 Code。这个功能主要用于编程、文件处理和本地项目开发。

不同版本的Claude界面可能略有变化,具体以你当前看到的页面为准。

第二步:新建项目文件夹

在电脑中新建一个文件夹,用于保存量化分析技能和运行过程中生成的数据。

文件夹名称可以自己设置,例如:

AI量化策略

然后在Claude Code中选择“打开文件夹”,找到刚才创建的文件夹,并允许Claude访问这个工作区。

建议为每个项目单独建立文件夹,避免代码、数据文件和回测结果混在一起。

第三步:粘贴安装提示词

把准备好的安装提示词复制到Claude Code中。

一个完整的提示词应该要求Claude完成以下工作:

  1. 检查电脑的操作系统和Python环境;
  2. 创建项目所需的文件和目录;
  3. 安装必要的Python依赖;
  4. 获取并清洗历史行情数据;
  5. 建立马尔可夫状态模型;
  6. 计算3×3状态转移矩阵;
  7. 计算平稳分布和状态持续性;
  8. 进行滚动前推测试;
  9. 输出最大回撤和策略表现;
  10. 创建可以重复调用的Claude Code技能。

发送提示词后,Claude可能会请求读取文件、执行命令或安装依赖。确认操作内容没有问题后,再允许它继续执行。

不要随意运行来源不明的代码,也不要在提示词或代码中填写交易所密码、API私钥、银行卡信息等敏感数据。

如何调用已经安装好的技能?

技能安装完成以后,可以关闭Claude,然后重新打开一个新的Claude Code会话。

在输入框中输入斜杠命令,搜索刚才创建的技能。不同技能的调用名称可能不同,具体以安装时生成的名称为准。

调用技能后,可以输入:

请对BTC-USD运行马尔可夫市场状态分析,并输出当前状态、转移概率矩阵、平稳分布、最大回撤和滚动前推测试结果。

也可以分析股票:

请对AAPL运行马尔可夫市场状态分析,并比较当前牛市、熊市和震荡状态的概率。

还可以对多个资产进行横向比较:

请比较BTC-USD、ETH-USD、AAPL和SPY的市场状态持续性、转移概率和历史最大回撤。

Claude会根据项目中已有的程序,调用历史行情数据并输出分析报告。

如何正确解读Claude生成的结果?

假设Claude分析某只股票后给出以下结果:

  • 当前牛市概率:70%;
  • 当前熊市概率:10%;
  • 当前震荡概率:20%;
  • 牛市状态持续概率:80%;
  • 历史最大回撤:35%。

这并不代表该股票明天一定会上涨。

它只能说明:根据模型使用的历史数据和状态定义,当前行情更接近模型中的牛市状态,而且过去进入这种状态以后,继续维持牛市的概率相对较高。

同时,35%的历史最大回撤也说明,这个资产或策略曾经出现过比较大的亏损阶段。

因此,不能只关注“牛市概率”,还要同时观察:

  • 模型使用了多长时间的数据;
  • 当前结果是否通过样本外测试;
  • 不同参数下结果是否稳定;
  • 状态划分是否合理;
  • 是否包含手续费和滑点;
  • 最大回撤是否能够承受;
  • 不同市场阶段表现是否一致。

为什么一定要做滚动前推测试?

很多交易策略在历史回测中表现很好,但投入真实市场以后就失效,常见原因是过度拟合。

滚动前推测试(Walk-Forward Testing)的基本方法是:

先使用一段历史数据训练模型,再用后面一段从未参与训练的数据进行测试。然后不断向前移动时间窗口,重复训练和测试过程。

例如:

  • 使用2020年至2022年的数据训练;
  • 使用2023年的数据测试;
  • 再使用2021年至2023年的数据训练;
  • 使用2024年的数据测试。

这样可以更接近真实交易环境,因为模型每次面对的测试数据,都是训练阶段没有见过的数据。

如果一个策略只在训练数据中盈利,但在多个样本外阶段持续亏损,通常说明它可能存在过度拟合,不适合直接用于真实交易。

把马尔可夫模型应用到比特币交易

比特币具有波动率高、趋势持续性明显、牛熊转换速度快等特点,因此非常适合用来研究市场状态模型。

准备测试比特币策略,可以使用以下交易平台:

Binance币安

注册Binance币安,使用邀请码减免20%交易手续费

注册链接:https://bit.ly/3PF8egr
邀请码:BFCYEW71

OKX欧易

注册OKX欧易,使用邀请码减免20%交易手续费

注册链接:https://www.lywebuuz.com/join/88576911
邀请码:88576911

Bybit

注册Bybit合约交易平台

注册链接:https://bit.ly/3EnpvbB
邀请码:95292

需要再次强调:模型给出的市场状态概率不等于确定性预测。即使牛市概率最高,市场仍然可能下跌。进行加密货币交易时,应合理控制仓位,设置止损,避免使用无法承受的高杠杆。

为什么还要结合订单流和成交量分析?

马尔可夫模型主要利用历史价格和统计规律判断市场状态,但价格本身并不能完整反映当前买卖双方的力量。

订单流工具可以帮助交易者观察:

  • 主动买入和主动卖出的成交量;
  • 关键价位的成交密集区;
  • 买卖盘失衡;
  • 大额挂单和流动性变化;
  • 价格上涨或下跌时是否获得成交量支持。

想进一步分析订单流,可以使用ATAS专业订单流与成交量分析软件

链接:https://bit.ly/3PnQhWU

也可以使用Bookmap查看主力挂单、二级行情和流动性热力图

链接:https://bookmap.com/members/aff/go/JDG1

马尔可夫模型可以帮助判断市场更可能处于什么状态,订单流工具则可以帮助观察当前买卖力量是否支持这个判断。两者结合使用,比单独依赖某一个指标更加全面。

© 版权声明

相关文章