CORE STRATEGY CODE
策略核心——完整可复刻代码
此页面包含 Markov Fund 的核心策略代码与当前仓位建议。
换电脑或换模型时,访问此页面即可复制代码复刻整套系统。
网址:https://8f449ee5.markovf.pages.dev
·
GitHub:markovfund-site
当前仓位建议(2026-05-15 · bull_low · VIX 15.0)
CAGR +32.0% · Sharpe 1.21 · MaxDD −49.3% | 大类目标:美股 80% / 债券 8% / 黄金 12%
| 代码 | 名称 | 权重 | 大类 |
| GLD | 黄金ETF | 15.2% | 黄金 |
| SNDK | Sandisk | 9.4% | 美股 |
| TIP | TIPS通胀保护债ETF | 9.4% | 债券 |
| LITE | Lumentum | 8.2% | 美股 |
| WDC | Western Digital | 8.1% | 美股 |
| CIEN | Ciena | 8.1% | 美股 |
| IEF | 中期国债ETF | 6.7% | 债券 |
| TER | Teradyne | 6.2% | 美股 |
| QQQ | 纳斯达克100ETF | 5.5% | 美股 |
| GLW | Corning | 5.0% | 美股 |
| SPMO | 标普500动量ETF | 4.9% | 美股 |
| MU | Micron | 3.9% | 美股 |
| COHR | Coherent | 3.8% | 美股 |
| STX | Seagate | 3.2% | 美股 |
| SATS | EchoStar | 2.5% | 美股 |
1. 策略配置常量
4种 Markov 区制下的策略权重矩阵 + 大类资产目标配置。
# ─── 制度权重矩阵(3大核心策略)────────────────────
REGIME_WEIGHTS = {
"bull_low": {"dual_mom":0.55, "resid_mom":0.25, "hrp":0.20},
"bull_high": {"dual_mom":0.25, "resid_mom":0.30, "hrp":0.45},
"bear_low": {"dual_mom":0.05, "resid_mom":0.20, "hrp":0.75},
"bear_high": {"dual_mom":0.00, "resid_mom":0.10, "hrp":0.90},
}
# ─── Top-Down 大类资产目标配置 ──────────────────
REGIME_ASSET_TARGETS = {
"bull_low": {"equity_us":0.80, "bond":0.08, "gold":0.12},
"bull_high": {"equity_us":0.55, "bond":0.25, "gold":0.20},
"bear_low": {"equity_us":0.37, "bond":0.40, "gold":0.23},
"bear_high": {"equity_us":0.20, "bond":0.55, "gold":0.25},
}
# ─── 权益三桶分层分配 ──────────────────────────
EQUITY_BUCKET_SPLIT = {"idx": 0.15, "sector": 0.00, "stock": 0.85}
BTC_MAX_WEIGHT = 0.05 # Markov Plus BTC硬上限
2. Markov 区制识别(核心引擎)
用 SPY 偏离 MA200 + VIX 水平 + MA200 斜率 + VIX 期限结构,经 sigmoid 平滑输出 4 种区制概率。
def _sigmoid(x, k=1.0):
"""sigmoid 平滑函数:将连续值映射到 (0,1) 概率"""
return 1.0 / (1.0 + np.exp(-k * x))
def detect_regime(spy_prices, vix_prices, vix3m_prices=None):
"""返回 bull_prob, high_vol_prob 软概率 + 硬状态标签"""
ma200 = spy_prices.rolling(200).mean()
spy_last = float(spy_prices.iloc[-1])
ma200_last = float(ma200.iloc[-1])
dev = spy_last / ma200_last - 1 # SPY 偏离 MA200
# MA200 斜率(20日变化率)
ma200_slope = (ma200_last / ma200.iloc[-21] - 1) if len(ma200) >= 21 else 0
# VIX 期限结构
vix_last = float(vix_prices.reindex(spy_prices.index).ffill().dropna().iloc[-1])
vix_term = vix_last / vix3m_last if vix3m else 1.0
# 软概率:sigmoid 平滑避免边界跳变
bull_prob = 0.70 * _sigmoid(dev, k=25) + 0.30 * _sigmoid(ma200_slope, k=500)
high_vol_prob = 0.60 * _sigmoid(vix_last - 20, k=0.5) \
+ 0.40 * _sigmoid(vix_term - 1.0, k=10)
bull = bull_prob >= 0.5
high_vol = high_vol_prob >= 0.5
state = ("bull" if bull else "bear") + "_" + ("high" if high_vol else "low")
return {"state": state, "bull_prob": bull_prob, "high_vol_prob": high_vol_prob,
"vix": vix_last, "spy_vs_ma200": round(dev * 100, 2),
"ma200_slope": ma200_slope, "vix_term_ratio": vix_term}
3. 区制自适应策略混合
用软概率对 4 种区制的策略权重做加权插值,实现平滑过渡。
def compute_soft_weights(regime):
"""用 bull_prob × high_vol_prob 对 4 区制权重矩阵做加权插值"""
bp, hp = regime["bull_prob"], regime["high_vol_prob"]
probs = {"bull_low": bp*(1-hp), "bull_high": bp*hp,
"bear_low": (1-bp)*(1-hp), "bear_high": (1-bp)*hp}
blended = {}
for state, prob in probs.items():
for strat, w in REGIME_WEIGHTS[state].items():
blended[strat] = blended.get(strat, 0) + prob * w
return blended
def strategy_regime_integration(ret, regime):
"""按软权重混合 3 大核心策略 → 最终个股权重向量"""
wts = compute_soft_weights(regime)
funcs = {"dual_mom": strategy_dual_momentum,
"resid_mom": strategy_residual_momentum,
"hrp": strategy_hrp}
combined = np.zeros(ret.shape[1])
for name, bw in wts.items():
if bw > 0 and name in funcs:
try: combined += bw * funcs[name](ret)
except: combined += bw / ret.shape[1]
combined = np.maximum(combined, 0)
return combined / combined.sum()
4. 三大核心策略
双重动量(绝对+相对动量过滤)、残差动量(去市场β后的特质动量)、HRP(层次风险平价)。
def strategy_dual_momentum(ret, lookback=252, skip=21):
"""双重动量:绝对动量过滤(>0)+ 相对动量选Top N,等权"""
if len(ret) < lookback + skip: return np.ones(n)/n
abs_mom = (ret.iloc[-skip] / ret.iloc[-lookback] - 1).values # 跳过最近21天
mask = abs_mom > 0
if mask.sum() == 0: return strategy_hrp(ret) # fallback
rel_mom = abs_mom.copy(); rel_mom[~mask] = -np.inf
n_select = max(5, int(mask.sum() * 0.5))
top_idx = np.argsort(rel_mom)[-n_select:]
w = np.zeros(n); w[top_idx] = 1.0 / n_select
return w
def strategy_residual_momentum(ret, lookback=252, skip=21):
"""残差动量:对市场回归取残差 → 残差动量最强的前N只,等权"""
if len(ret) < lookback + skip: return np.ones(n)/n
mkt = ret.mean(axis=1)
resid_mom = np.zeros(n)
for i in range(n):
beta = np.polyfit(mkt.iloc[-lookback:-skip],
ret.iloc[-lookback:-skip, i], 1)[0]
resid = ret.iloc[-lookback:-skip, i] - beta * mkt.iloc[-lookback:-skip]
resid_mom[i] = (1 + resid).prod() - 1
top_n = max(1, int(n * 0.2))
top = np.argsort(resid_mom)[-top_n:]
w = np.zeros(n); w[top] = 1.0 / top_n
return w
def strategy_hrp(ret):
"""层次风险平价:用距离矩阵做层次聚类,自底向上分配等风险贡献权重"""
corr = ret.corr().values
dist = np.sqrt(0.5 * (1 - corr))
Z = linkage(squareform(dist), "ward")
n = len(ret.columns)
w = np.ones(n)
clusters = {i: [i] for i in range(n)}
for row in Z:
a, b = int(row[0]), int(row[1])
merged = clusters[a] + clusters[b]
clusters[len(clusters)] = merged
cov_a = ret.iloc[:, clusters[a]].cov().values.sum()
cov_b = ret.iloc[:, clusters[b]].cov().values.sum()
alpha = cov_b / (cov_a + cov_b) if (cov_a + cov_b) > 0 else 0.5
for idx in clusters[a]: w[idx] *= alpha
for idx in clusters[b]: w[idx] *= (1 - alpha)
return w / w.sum()
5. Top-Down 三桶组合构建
区制 → 大类资产目标 → 权益三桶分层(A:指数15% / B:行业0% / C:个股85%)→ 桶内策略混合。
def build_topdown_portfolio(ret, regime):
"""区制驱动大类配置 + 权益三桶分层 + 桶内策略混合 → 最终权重向量"""
targets = get_topdown_asset_targets(regime) # 软插值大类目标
buckets = classify_tickers(ret.columns)
w = np.zeros(ret.shape[1])
regime_wts = compute_soft_weights(regime)
for bucket, tgt_w in targets.items():
bk_tickers = buckets[bucket]
if not bk_tickers or tgt_w <= 0: continue
if bucket == "equity_us":
# 三桶分层:A=指数(HRP) / B=行业(跳过) / C=个股(制度混合)
idx_tks = [t for t in bk_tickers if t in INDEX_ETF_SET] # QQQ, SPMO
stock_tks = [t for t in bk_tickers
if t not in INDEX_ETF_SET and t not in SECTOR_ETF_SET]
if idx_tks:
w_idx = strategy_hrp(ret[idx_tks])
w[idx_tks] = tgt_w * EQUITY_BUCKET_SPLIT["idx"] * w_idx
if stock_tks:
w_stock = strategy_regime_integration(ret[stock_tks], regime)
w[stock_tks] = tgt_w * EQUITY_BUCKET_SPLIT["stock"] * w_stock
else:
# bond → 6M风险平价 / gold → 等权
w_bk = _intra_bucket_weights(bucket, ret[bk_tickers], regime_wts)
w[bk_tickers] = tgt_w * w_bk
return w / w.sum()
def compute_top20(weights_dict, ticker_names):
"""从权重字典取 Top 20,含大类资产分类"""
ranked = sorted(weights_dict.items(), key=lambda x: x[1], reverse=True)[:20]
result = []
for tk, w in ranked:
ac = "黄金" if tk in GOLD_ETFS else "债券" if tk in BOND_ETFS \
else "比特币" if tk in CRYPTO_SET else "港股" if tk.endswith(".HK") \
else "美股"
result.append({"ticker": tk, "name": ticker_names.get(tk, tk),
"weight": round(w, 4), "asset_class": ac})
return result
6. 入场信号系统(12分制)
SPY 大跌时评估 4 维度胜率。牛市得 0 分正常——市场无回调则无逢低机会。
def compute_entry_signal(spy_prices, vix_last, spy_vs_ma200_pct):
"""4维度评分:VIX恐慌、MA200偏离、短期动量、MA200斜率"""
score = 0
dims = {"dim1": 0, "dim2": 0, "dim3": 0, "dim4": 0}
# 死亡地带拦截:SPY偏离MA200在[-12%, -4%]时强制否决
dead_zone = -12 <= spy_vs_ma200_pct <= -4
if dead_zone: return {"score": 0, "dead_zone": True, "level": "死亡地带"}
# 维度1:VIX恐慌(0-3分)→ VIX越高越恐慌=越接近底部
dims["dim1"] = min(3, max(0, int((vix_last - 15) / 5)))
score += dims["dim1"]
# 维度2:MA200偏离(0-3分)→ 偏离越深越超卖
dims["dim2"] = min(3, max(0, int(abs(spy_vs_ma200_pct) / 5)))
score += dims["dim2"]
# 维度3:短期动量(0-3分)→ 近20日跌幅越大信号越强
mom20 = (spy_prices.iloc[-1] / spy_prices.iloc[-21] - 1)
dims["dim3"] = min(3, max(0, int(abs(mom20) / 0.05)))
score += dims["dim3"]
# 维度4:MA200斜率(0-3分)→ 斜率越低(趋势越弱)分越高
slope = (spy_prices.rolling(200).mean().iloc[-1] /
spy_prices.rolling(200).mean().iloc[-21] - 1)
dims["dim4"] = min(3, max(0, int(max(0, -slope) / 0.002)))
score += dims["dim4"]
stars = "★" * (score // 3) + "☆" * (4 - score // 3)
level = "强烈买入" if score >= 9 else "买入" if score >= 6 \
else "中性" if score >= 3 else "观望"
return {"score": score, "stars": stars, "level": level,
"dead_zone": False, **dims}
7. 运行方式
完整代码位于 fetch_data.py + index.html,每日 05:30 自动运行。
# 安装依赖
pip install yfinance pandas numpy scipy requests
# 手动运行
python3 fetch_data.py
# 本地预览
python3 -m http.server 8080
# 访问 http://localhost:8080
# 部署到 Cloudflare Pages
npx wrangler@3 pages deploy . --project-name=markovf --commit-dirty=true
数据来源:Yahoo Finance (yfinance) · S&P 500 成分股 + 大类资产 ETF
再平衡频率:月中(每月15日附近第一个交易日)
交易成本:10bps 单边,回测已计入
完整源码:https://github.com/你的用户名/markovfund-site