# 量化交易策略完整思路（AI竞赛 · 量化PK）

> **版本**：v4（2026-08-24 最新）—— 在 v3 基础上新增**中期趋势确认**（trendMid）修复「均线粘合/超跌反弹反复止损」
> **引擎文件**：`quant.mjs` / `lib/quant.js`（三处副本，md5 已一致 `b68f7689` 转LF后）
> **定位**：可解释的**规则策略**，用于训练里跟真人玩家 PK「AI竞赛 · 量化PK」，图上的量化买卖点 + 实时判断提示 + 结算对比

---

## 一、策略定位

这套量化不是黑箱 AI，而是**纯规则的可解释策略**（MACD/KDJ/均线/单边/连板/M顶），核心目标：

1. **给玩家一个可对标的对手**——玩家训练后看到「你 +X% vs 量化 +Y%」，量化代表**标准化的趋势跟随+风控**基准
2. **可解释**——每个买卖点都带 `reason`（如「MACD金叉且站上MA20，多头趋势开仓」），玩家能看懂量化为什么买卖，作为学习参考
3. **无未来泄露**——所有指标只用「当前根及以前」数据，盲推阶段逐步展开等价实时

**核心哲学**：量化 = **无脑满仓 + 信号强买强卖**（不空仓观望、不择时）的「纪律型选手」，和能自由空仓/择时的真人形成对照，让玩家体会「纪律执行 vs 灵活应变」的差异。

---

## 二、演进史（v1 → v4）

| 版本 | 日期 | 核心改动 | 解决的问题 |
|------|------|---------|-----------|
| v1 | 08-21 | MACD金叉/KDJ低位金叉/上穿MA20买；MACD死叉超买/跌破MA20/浮亏8%止损/浮盈25%止盈 | 基础规则引擎 |
| v2 | 08-22 | 去KDJ超卖抄底（震荡接飞刀）、COOLDOWN冷却、买卖点降噪 | 信号过密、震荡反复买卖 |
| v3 | 08-23 | **止血四步**：COOLDOWN 6→20、breakMA10止损加浮盈≥3%门槛、去KDJ超卖抄底、保留多头门控 | 量化亏麻（牛来-92%、大宵-59%，人类却赚） |
| **v4** | **08-24** | **加 trendMid（MA10>MA20）中期趋势确认** | **均线粘合震荡、超跌反弹被反复止损** |

### v4 的触发背景
用户实测反馈：**瑞芯微 603893（2022-05-17~11-09）**，量化在均线粘合（缠绕）段「站上MA5买入 → 随便一个震荡跌破MA10就止损 → 巨亏」；以及**连续大跌空头排列后沿MA5反弹 → 当单边多头买入 → 立马继续大跌被套**。

---

## 三、完整买卖信号体系

### 核心指标
- **均线**：MA5/MA10/MA20（SMA）
- **MACD**：DIF/DEA（金叉/死叉）
- **KDJ**：K/D/J（超买>80、超卖<20、K上穿D）
- **量能**：MA20量均线（放量≥1.5×、巨量≥2×）
- **趋势判断**：`trendUp`（MA5>MA20）、`trendDown`（MA5<MA20）、`trendMid`（MA10>MA20）、`aligned`（MA5>MA10>MA20 单边多头）

### 买入信号（!pos 时，需 `canBuy=trendUp` 且过 COOLDOWN）
| 优先级 | 信号 | 条件 | 说明 |
|-------|------|------|------|
| 1 | 顺势建仓（推演起点） | warmup时 价上MA20 且 MA5>MA20 且 KDJ<80 | 起点已多头就顺势开，避免单边永远空仓 |
| 2 | MACD金叉 | `golden && aboveMA20` | 金叉且站上MA20 |
| 3 | 上穿MA20 | `ma20CrossUp && trendUp && trendMid` | **[v4新增trendMid]** 收盘上穿MA20且中期也走多 |
| 4 | 回踩MA5 | `pullbackMA5 && trendMid` | **[v4新增trendMid]** 单边多头回踩MA5企稳 |

**v4 核心改动**：信号 3、4 加了 `trendMid`（MA10>MA20）——之前只检查 `trendUp`（MA5>MA20，短期），导致**下跌/震荡反弹里 MA5 刚拐头但 MA10 还在下方**时也买入（假突破），买了又跌破止损 → 反复被收割。要求中期（MA10>MA20）也走多，过滤掉这种假转势。

### 卖出信号（pos 时）
卖出按**持仓所处形态**分多套规则，优先级互斥：

**A. 连板持有模式**（遇涨停激活 `limitHold`）
- 放量绿K → 清仓
- 放巨量炸板 → 减仓
- 浮亏8% → 风险止损；破MA10且浮盈≥3% → 趋势受损止损
- 其余 → 继续持有吃连板

**B. 强单边未走M顶**（`rallyOnce && !mSeenB`）
- 破MA10且浮盈≥3% → 止损；破MA20且空头排列 → 止损；浮亏8% → 止损
- 其余 → 让趋势奔跑，等M顶结构

**C. M顶确认**（`mSeenB && mValley`）
- 跌破颈线 → 清仓（动能背离则标注）
- 浮亏8% → 兜底

**D. 普通模式**（默认）
- 浮盈回吐至保本线（曾≥4%回吐到1%）→ 移动止损
- MACD死叉且KDJ超买 → 顶部止盈
- 破MA20且空头排列 → 趋势走坏止损
- 破MA10且浮盈≥3% → 趋势受损止损
- 乖离MA5>7% → 减仓止盈
- 浮亏8% → 风险止损
- 浮盈≥25%且超买/死叉 → 分批止盈

> **关键：-8% 硬止损保持不动**（实验证实放宽到-10%反而让亏损单拿更久，恶化）。真正的病根在**买入端假突破**，不在止损。

### COOLDOWN 机制（防信号过密）
- 卖出后再买入的最小间隔 = **20根**（止损/止盈不冷却）
- 抑制震荡市频繁反复买卖 → 图上信号点不至于密成一坨

### immuneBreak（防刚买即卖抖动）
- 开仓当根不触发「跌破MA10/乖离」趋势止损，避免刚买就卖的手抖效应

---

## 四、v4 优化验证（12样本真实K线）

用**生涯真实口径**（`genSignals(bb, 0)`，同后端 `run_quant_sessions`）回测 12 只不同形态的标的，**v3 vs v4（方案C）** 对比：

| 标的 | 行情形态 | v3收益 | **v4收益** | v3回撤 | **v4回撤** | 变化 |
|------|---------|--------|-----------|--------|-----------|------|
| 603893 瑞芯微 | 粘合震荡+单边 | +26.6% | +27.3% | 10.7% | 10.1% | ↑ |
| 000001 平安 | 粘合震荡 | +4.6% | +4.6% | 5.1% | 5.1% | = |
| 600519 茅台 | 下行阴跌 | 0% | 0% | 0% | 0% | = |
| 300346 南大 | 单边上涨 | +5.7% | +5.7% | 5.3% | 5.3% | = |
| 002594 比亚迪 | 大起大落 | **-21.9%** | **-16.3%** | 28.1% | 22.9% | **+5.6** |
| 600111 北方稀土 | 周期大起落 | -30.5% | -26.7% | 34.9% | 34.6% | +3.8 |
| 000858 五粮液 | 阴跌 | -2.7% | +19.9% | 23.9% | 16.2% | **+22.6** |
| 601318 平安 | 深跌反弹 | -7.0% | -0.4% | 18.0% | 19.5% | +6.6 |
| 002415 海康 | 震荡下行 | -39.3% | -39.3% | 48.4% | 48.4% | = |
| 300750 宁德 | 单边牛 | +74.4% | +105.7% | 19.6% | 14.9% | **+31.3** |
| 600036 招商 | 横盘阴跌 | -16.7% | -16.7% | 30.6% | 30.6% | = |
| 601899 紫金 | 周期上行 | +13.3% | +8.8% | 22.7% | 22.7% | -4.5 |

**结论**：
- ✅ **10/12 改善或持平**，0 严重恶化
- ✅ 显著提升**趋势捕捉**（宁德 +74%→+105%、五粮液 -2.7%→+19.9%）
- ✅ 显著减少**反复收割损耗**（比亚迪、北方稀土、深跌反弹）
- ⚠️ 唯一小幅变差：601899 紫金矿业 +13.3%→+8.8%（-4.5，单点 trade-off）

---

## 五、关于「超跌反弹」的深入决策（重要）

用户报了一个更刁钻的场景：**连续大跌空头排列后，沿MA5连阳反弹，量化当单边多头买入，立马继续大跌**（如瑞芯微 T176：前空头-43.7%，买入后-5.6%；北方稀土 T284：前空头-50%，买入后-10%）。

我尝试了三种过滤，得到的**反直觉结论**：

| 过滤方法 | 结果 | 判断 |
|---------|------|------|
| MA20 不再下行 | 太严，603893/000001/601899 全部误伤（错过真转势） | ❌ 失败 |
| 加放量/乖离 | 瑞芯微(该买+9%)和北方稀土(该拦-10%)形态相似区分不了 | ❌ 不可靠 |
| 深度空头快速止损 | **超跌反弹类买入平均反而 +2.5%（胜率67%）**，比非超跌类(-1.0%)更好 | ❌ 会反伤 |

**关键发现**：超跌反弹买入整体是**盈利**的（+2.5% 平均），只是**个别笔波动大**（几笔-5%~-10%）。加快速止损会把这些**本可盈利的仓过早割掉**，反而伤整体收益。**本质**是「下跌反弹后的方向选择」00%不可预测，纯规则只能在趋势确认后才进场（这正是 trendMid 的哲学）。

**最终决定**：**不加超跌快速止损**。因为数据表明这类买入整体净盈利，它的高波动是趋势跟随的固有成本，无法用规则根治。用户看到的「买入后立即深套」是其中失败的几笔，但策略整体在此类上是赚钱的。

---

## 六、代码地图

### 三份引擎副本（已统一 md5 = `b68f7689`，转LF后）
| 文件 | 位置 | 用途 |
|------|------|------|
| `quant.mjs` | `kline-assistant/backend/` | **生涯后端 PK 权威**（被 quant_run.mjs 调用） |
| `lib/quant.js` | `kline-trainer/`（双盲） | 双盲训练实时提示 + 单股分析页 |
| `lib/quant.js` | `kline-assistant/frontend/` | 生涯 TrainingView 实时提示 |

> ⚠️ **必须同步**：三份引擎逻辑必须一致，否则后端PK结果与前端实时提示脱节。

### 核心函数（export）
| 函数 | 作用 |
|------|------|
| `genSignals(bars, warmup)` | 生成买卖信号（核心，状态机） |
| `quantifyAtBar(signals, bar)` | 某根K线量化动作+理由（实时提示） |
| `backtest(signals, bars, warmup, cash)` | 全仓回测收益（PK 用） |
| `describeTrend(bars, warmup)` | 当前趋势/形态文本描述（GLM复盘附件） |

### 调用链
```
生涯 PK：
  career.py /api/career/history
    └─ run_quant_sessions(sessions)      # 每局独立回测
        └─ node quant_run.mjs            # stdin收 {sessions} → stdout收益
            └─ quant.mjs genSignals+backtest  # warmup=0

双盲/生涯前端实时提示：
  TrainingView.jsx / train/page.jsx
    └─ import lib/quant → genSignals + quantifyAtBar
```

---

## 七、部署记录（2026-08-24）

**SSH 已恢复**（`xiaobang666_tencent` 密钥，非 skey；此前被 fail2ban ban）。

### 服务器部署清单
| 文件 | 操作 | 生效方式 |
|------|------|---------|
| `/opt/kline-assistant/backend/quant.mjs` | scp + `tr -d '\r'` | **无需重启**（quant_run.mjs 每次独立 subprocess） |
| `/opt/kline-trainer/lib/quant.js` | scp + `tr -d '\r'` | `npm run build` + restart kline-trainer |
| `/opt/kline-assistant/frontend/lib/quant.js` | scp + `tr -d '\r'` | `npm run build` + restart career-frontend |

### 部署验证（全部通过）
- ✅ 三份引擎 md5 完全一致（`b68f7689`）
- ✅ 6 服务全 active：career-backend / career-frontend / career-proxy / kline-trainer / kline-backend / fansite
- ✅ 关键端口归 systemd MainPID（3101/3800 无游离 next-server）
- ✅ node 语法检查通过（三份）
- ✅ `quant_run.mjs` 端到端调用成功

### 备份
- 服务器：`quant.mjs.bak-v3-20260824` ×3（生涯后端/双盲/生涯前端）
- 本地 v3 参考副本：`quant_v3_reference.mjs`（md5 `4c350684`，转LF后=服务器原`4404d28f`）

### ⚠️ 部署铁律
1. **scp 后必须 `tr -d '\r'`** 转 LF（本地 CRLF vs 服务器 LF，否则 node 异常/md5 不一致）
2. **改 kline-trainer/生涯前端 JS 必须 `npm run build` + restart**（.next 编译缓存旧代码）
3. **build 前先杀游离 next-server**（记忆铁律：游离进程占 3800/3101 会 EADDRINUSE）
4. **服务归属核对**：`ss -ltnp` 确认端口 PID == systemd MainPID（非游离进程）
5. **服务器两套重复 unit**（`career-*` vs `kline-career-*`）：实际运行的是 `career-*`（`kline-career-*` 已 disabled legacy）

---

## 八、经验教训（复盘）

1. **止损端不是病根**：v3 的 `-8%` 硬止损和 `breakMA10` 其实合理，实验证实放宽止损反而恶化。真正病根是**买入端假突破**（震荡/反弹里 MA5>MA20 但 MA10<MA20 就买）。
2. **顺势信号要加中期确认**：`trendUp`（MA5>MA20）只反映短期，需叠加 `trendMid`（MA10>MA20）确认中期，穿越反弹假突破。
3. **超跌反弹的取舍**：方向 00% 不可预测，规则无法提前判定；数据表明其整体净盈利，不能因个别深套而过度防守 → 用「趋势确认」而非「快速止损」。
4. **单一改动可解释**：v4 只改了 5 行（加 trendMid + 两个买入条件），diff 干净、可回滚、可解释——比大改引擎更稳。
