Files
gavindiaz 9b0da0279f Add Telegram watcher, dashboard docs, and deployment config
- scripts/telegram-watcher.js: push ENTER signals from signals.csv to Telegram
- DASHBOARD_GUIDE.md: complete dashboard field-by-field documentation
- package.json: add watch:telegram npm script
- .env.example: deployment env template
- .gitignore: exclude ecosystem.config.cjs/start.sh (server-only with paths)

Watcher reads recommendation column and triggers on:
  - transition NO_TRADE -> ENTER:side
  - flip ENTER:UP <-> ENTER:DOWN
  - 30s cooldown prevents spam
2026-07-21 18:00:21 +08:00

358 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 仪表盘逐项解读手册(基于代码逻辑链)
本文严格按 `src/index.js``src/engines/*.js` 的执行顺序,逐行推演仪表盘上每一行/每一个数字是如何生成的、它代表什么、如何判断信号方向。
---
## 一、整体渲染流程
主循环 `main()``src/index.js:398-731`)每 `CONFIG.pollIntervalMs`(默认 ~1500ms)刷新一屏。每一帧的渲染数据来自六个数据源,按以下顺序组装:
```
Binance WS (实时成交) → spotPrice ─┐
Polymarket Live WS (BTC/USD) → polymarketWsPrice ─┐
Chainlink on-chain WSS/HTTP → chainlinkWsPrice ─┼─→ currentPrice (PRICE TO BEAT 当前价)
fetchChainlinkBtcUsd() (HTTP 回退)
Binance REST klines (1m×240, 5m×200) ──→ 1分钟K线序列,喂给所有指标
Polymarket REST (Gamma + CLOB) ──→ 市场/订单簿/UP-DOWN价
策略引擎 ──→ scoreDirection → applyTimeAwareness → computeEdge → decide
```
随后调用 `renderScreen()``src/index.js:71-79`)用 ANSI 重定位光标 + `clearScreenDown` 把整张表重写到终端,**所以你看到的不是新增日志,而是"整帧覆写"**。
---
## 二、逐字段拆解
仪表盘上从上到下共有五个区段,每个区段的字段解释如下。
### 1. 顶部信息(市场识别)
#### `Bitcoin Up or Down - July 21, 4:30AM-4:45AM ET`
来源:`poly.market?.question``src/index.js:648`)。这是 Polymarket Gamma API 返回的 `question` 字段,等价于"市场标题",标识当前 15 分钟窗口的起止时间(美国东部时间)。
#### `Market: btc-updown-15m-1784622600`
来源:`poly.market?.slug``src/index.js:649`)。是 Polymarket 的市场唯一 slug。其中 `1784622600` 是市场开始时的 Unix 时间戳秒数(用作市场 ID)。市场选取逻辑:
- 如果你设置了 `POLYMARKET_SLUG`,则锁定该市场(`src/index.js:285-287`)。
- 否则 `pickLatestLiveMarket()` 从 Gamma 系列 `seriesId=10192``btc-up-or-down-15m`)的活跃事件中挑"最新正在进行的 15 分钟窗口"(`src/index.js:296-302`,并有 25s 缓存避免重复请求)。
- 每 15 分钟滚动一次新窗口(Polymarket 自身的事件节奏)。
#### `Time left: 04:49`
来源:`timeLeftMin``src/index.js:452, 658`)。计算方法:
```
settlementMs = poly.market.endDate → 转毫秒
timeLeftMin = (settlementMs - Date.now()) / 60_000
```
颜色编码规则(`src/index.js:651-657`):
- 绿色:`[10, 15]` 分钟 — EARLY 阶段
- 黄色:`[5, 10)` 分钟 — MID 阶段
- 红色:`[0, 5)` 分钟 — LATE 阶段(最后冲刺)
⚠️ **关键解读**:剩余时间是策略相位切换的唯一信号。它直接影响 `decide()` 的入场阈值(见下文 Decision Logic)。
---
### 2. 策略预测区
#### `TA Predict: LONG 63% / SHORT 37%`
这是整张表**最关键**的输出,由两条流水线生成:
**第一步 — `scoreDirection()``src/engines/probability.js:3-53`**
从六个原始信号累加 up/down 计数,起始都是 `1`
| 触发条件 | 加成 |
|---|---|
| `price > vwap` | up += 2 |
| `price < vwap` | down += 2 |
| `vwapSlope > 0` | up += 2 |
| `vwapSlope < 0` | down += 2 |
| `rsi > 55 && rsiSlope > 0` | up += 2 |
| `rsi < 45 && rsiSlope < 0` | down += 2 |
| MACD histogram 扩张且为正 | up += 2 |
| MACD histogram 扩张且为负 | down += 2 |
| MACD 主线 > 0 | up += 1 |
| MACD 主线 < 0 | down += 1 |
| Heiken Ashi 连续 ≥2 根同色 | 该方向 += 1 |
| 跌破 VWAP 后未收回(failed reclaim | down += 3 ⚠️ |
最终 `rawUp = up / (up + down)``src/engines/probability.js:51`)。
**第二步 — `applyTimeAwareness()``src/engines/probability.js:55-59`**
随着剩余时间减少,模型预测会被"拉回"到 50/50
```
timeDecay = remainingMinutes / 15 (clamp 0..1)
adjustedUp = 0.5 + (rawUp - 0.5) * timeDecay (clamp 0..1)
adjustedDown= 1 - adjustedUp
```
**这意味着**:越临近到期,TA 的偏向越被"折扣",因为只剩下几分钟,已发生的价格走势对最终结果的代表性越弱。极端情形:到 `timeLeftMin=0``adjustedUp = 0.5` 完全无偏向。
仪表盘上 `Predict` 就是这个 `adjustedUp/adjustedDown``src/index.js:537-543`)。
#### `Heiken Ashi: green x8`
来源:`computeHeikenAshi()` + `countConsecutive()``src/indicators/heikenAshi.js`)。
最后 N 根 Heiken Ashi 同色(绿=收>开)的连击数。`x8` 表示当前已经连续 8 根绿色 K 线 → 短期明显多头。
⚠️ **注意**:在 TA 评分里只触发 +1`heikenCount >= 2` 时单边 +1,计数本身没有单调加权,见 `src/engines/probability.js:44-47`)。**它是确认信号,不是主导信号。**
#### `RSI: 52.6 ↑`
来源:`computeRsi(closes, period=14)` + `slopeLast(rsiSeries, 3)``src/index.js:465-473`)。
`52.6` 是当前 14 周期 RSI`↑` 表示最近 3 根的斜率 > 0。
⚠️ **关键解读 — RSI 在本系统里被硬阈值化**`src/engines/probability.js:29-32`):
- 只有 `rsi > 55 且 rsiSlope > 0` 才触发 up += 2
- 只有 `rsi < 45 且 rsiSlope < 0` 才触发 down += 2
- **52.6(中性区)→ 零贡献**
所以"RSI 52.6 ↑"看起来像多头,但 TA 计分里完全没贡献。它只是显示给你看,不影响模型。
#### `MACD: bullish (expanding)`
来源:`computeMacd(closes, 12, 26, 9)``src/index.js:475`)。
`bullish (expanding)` 表示 `hist > 0``histDelta > 0``src/index.js:519-523`)。
`expanding` 触发 `up += 2``src/engines/probability.js:34-38`)。所以这个字段是 TA 模型里**真实的加分项**。
#### `Delta 1/3Min: -$4.40, -0.01% | -$1.03, -0.00%`
来源:`src/index.js:525-530`
- `delta1m = lastClose - klines1m[-2].close`
- `delta3m = lastClose - klines1m[-4].close`
⚠️ **它只是展示,不参与 TA 计分**。在 `scoreDirection()` 的输入里没有 delta1/delta3。所以这个字段是"信息参考",**不是模型输入**。
#### `VWAP: 65,923 (0.63%) | slope: UP`
来源:`computeSessionVwap()` + `computeVwapSeries()` + `slopeLast()``src/index.js:457-462`)。
- `65,923` = 当日开盘至今的成交量加权均价
- `0.63%` = 当前价相对 VWAP 的偏离(price 在 VWAP 之上,正数)
- `slope: UP` = 最近 `vwapSlopeLookbackMinutes` 分钟的 VWAP 单调变化方向为正
这两个字段**在 TA 计分里贡献最大**(`src/engines/probability.js:19-27`):
- `price > vwap` → up += 2
- `vwapSlope > 0` → up += 2
合计 +4,是单项最大的多头信号源。
---
### 3. Polymarket 实时市场区
#### `POLYMARKET: ↑ UP 0.94¢ | ↓ DOWN 0.05¢`
来源:`fetchPolymarketSnapshot()``src/index.js:305-396`)。
**价格优先级**`src/index.js:388-389`):
```
upPrice = CLOB buy 价 ?? Gamma outcomePrices[upIndex]
downPrice = CLOB buy 价 ?? Gamma outcomePrices[downIndex]
```
先取订单簿实时买价(=`bestAsk`,即你能立即以多大概率买到的成本),失败时回落到 Gamma 的 midpoint 价格。
**如何解读**
- `0.94¢` 意味着押 UP 每份成本 $0.94,赢了收到 $1.00 → **潜在收益 ≈ 6.4%**
- `0.05¢` 意味着押 DOWN 每份成本 $0.05,赢了收到 $1.00 → **潜在收益 ≈ 20 倍**
⚠️ **赔率反映 Polymarket 集体押注的预期概率**(隐含 UP ≈ 94%DOWN ≈ 5%)。但这个"市场概率"和你的 TA 模型概率可以不一致 → 这就是 **edge**(见下文)。
#### `Liquidity: 6,027`
来源:`poly.market.liquidityNum ?? poly.market.liquidity``src/index.js:576-578`)。
是 Polymarket 该市场的报价方总资金(USDC)。**只显示,不参与任何计算**。
> 解读建议:低于 ~$1000 的市场深度很浅,挂单价差大(spread 大),下单时滑点可能吃掉所有 edge。
#### `Time left: 04:49`(第二个)
来源与顶部 `Time left` **不同**:这里是 `settlementLeftMin`(基于 `poly.market.endDate`),而顶部是基于 `timing.remainingMinutes`(基于 15 分钟 K 线窗口)。多数情况下两者数值接近但不完全相等。
颜色用同样的 EARLY/MID/LATE 编码(`src/index.js:660-668`)。
#### `PRICE TO BEAT: $66,274`
**这是整个仪表盘决定胜负的关键数字。**
来源链路(`src/index.js:585-597`):
1. 进入新市场(slug 变化)时清空 `priceToBeatState`
2.`marketStartMs <= Date.now()` 且当前 `currentPrice` 可用时,**锁存**当前 Chainlink 价格作为本市场的 PRICE TO BEAT。
3. 之后 PRICE TO BEAT 固定不变(直到切到下一个市场)。
锁定值来源优先级(`src/index.js:435-439`):
```
polymarket_ws (Polymarket Live WS BTC/USD) > chainlink_ws (Chainlink 链上 WSS) > fetchChainlinkBtcUsd() (HTTP)
```
⚠️ 也就是说:**PRICE TO BEAT 取的是 Polymarket UI 同一个 Chainlink 喂价源**,不是 Binance 现货!这能避免"Binance 与 Polymarket 数据源不同步"造成的胜负误判。
**到期判定规则**Polymarket 官方):到期时刻 BTC 价格(同样取 Chainlink> PRICE TO BEAT → UP 赢;否则 DOWN 赢。
#### `CURRENT PRICE: $66,291.57 ↓ (+$17.27)`
来源:
- 价格:`currentPrice = chainlink.price``src/index.js:581`),来源与 PRICE TO BEAT 相同
- 颜色箭头:与上一帧比较(`colorPriceLine``src/index.js:109-131`
- `↑` 绿色:上涨
- `↓` 红色:下跌
- `+$17.27`:当前价 PRICE TO BEAT`ptbDelta``src/index.js:606-618`
- 正数绿色:在 BEAT 之上 → 倾向 UP 赢
- 负数红色:在 BEAT 之下 → 倾向 DOWN 赢
**解读**:当前比 BEAT 高 $17.27。要反转需要 BTC 在剩余时间内回撤至少 $17.27。
---
### 4. Binance 现货参考区
#### `BTC (Binance): $66,338 (+$46.02, +0.07%)`
来源:`binanceStream.getLast()?.price ?? fetchLastPrice()``src/index.js:580`)。
- `+46.02`:相对上一帧 WS tick 的变动
- 括号外的 `(diff)`Binance 现货价 当前 Chainlink 价(`src/index.js:636-644`
⚠️ **只是参考,不参与 TA 计分**。但可以用来判断:
- 两个数据源是否脱钩(diff 持续 >$50 → 数据源不一致风险)
- 现货是否比 Chainlink 先行(>0 → 现货领先,可能预示上行)
---
### 5. 时间与会话区
#### `ET | Session: 04:40:10 | Europe`
来源:`fmtEtTime(new Date())` + `getBtcSession(new Date())``src/index.js:173-199`)。
`Session` 是基于 UTC 小时划分的**全球流动性时段**(仅显示,不参与计算):
- Asia: 07 UTC
- Europe: 715 UTC
- US: 1321 UTC
- 重叠时段会有 "Europe/US overlap" / "Asia/Europe overlap"
**实战含义**:欧美重叠时段(13–16 UTC)波动率最大,亚盘(0–7 UTC)波动最小。短窗口(15 分钟)市场对流动性敏感,低流动性时段 edge 模型容易失效。
---
## 三、决策逻辑(用户最关心的"该怎么判断")
虽然仪表盘**不直接显示**入场信号,但主循环每帧都在调用:
```
rec = decide({ remainingMinutes, edgeUp, edgeDown, modelUp, modelDown })
```
代码在 `src/engines/edge.js:23-48`
### Step 1 — 计算市场隐含概率
```
marketUp = marketYes / (marketYes + marketNo)
marketDown = 1 - marketUp
```
⚠️ 因为订单簿买价 `marketYes + marketNo ≠ 1`(spread),所以要先归一化。本例:UP 0.94 + DOWN 0.05 = 0.99 → marketUp ≈ 94.9%。
### Step 2 — 计算 edge
```
edgeUp = modelUp - marketUp // TA 对 UP 的看法 − 市场对 UP 的隐含概率
edgeDown = modelDown - marketDown
```
**edge 才是交易价值所在**:模型和市场分歧越大,edge 越高,理论上"无风险套利空间"越大。
### Step 3 — 三阶段阈值(剩余时间分桶)
| Phase | 剩余时间 | edge 阈值 | modelProb 阈值 |
|---|---|---|---|
| EARLY | > 10 min | 5% | 55% |
| MID | 510 min | 10% | 60% |
| LATE | ≤ 5 min | 20% | 65% |
### Step 4 — 入场判定
只有当:
1. `bestEdge ≥ 阶段阈值`
2. `bestModelProb ≥ 阶段概率下限`
才返回 `action: ENTER`,否则 `NO_TRADE`
---
## 四、回到你的示例数据
```
TA Predict: LONG 63% / SHORT 37% ← timeAware 模型概率
Heiken Ashi: green x8 ← 确认(+1 已包含)
RSI: 52.6 ↑ ← 中性区,零贡献
MACD: bullish (expanding) ← +2(关键多头信号)
Delta 1/3: -$4.40, -0.01% ← 展示用,不入模型
VWAP: 65,923 (0.63%) | UP ← price>vwap(+2) + slope>0(+2) = +4
POLYMARKET: ↑ UP 0.94¢ | ↓ DOWN 0.05¢
PRICE TO BEAT: $66,274
CURRENT PRICE: $66,291.57 ↓ (+$17.27) ← 当前比 BEAT 高 $17.27
```
**逐项核对模型**(你看到的 LONG 63% 是怎么来的):
`timeLeftMin ≈ 4.8`4:49)→ `timeDecay = 4.8/15 = 0.32`
假设其他指标命中情况(基于字段):
- VWAP +4price>vwap 且 slope up
- MACD +2expanding bullish
- Heiken Ashi +1green ×8count ≥ 2
- RSI 52.6:中性,无加成
- failedVwapReclaim:未触发
→ upScore ≈ 1+4+2+1 = 8
→ downScore ≈ 1
→ rawUp = 8/9 ≈ 0.889
→ adjustedUp = 0.5 + (0.889 - 0.5) × 0.32 = 0.624 ≈ **62%**
(与面板的 63% 吻合,差距来自小数四舍五入或个别次要信号 0/1 边界)
**市场隐含**marketUp ≈ 0.94/0.99 ≈ 0.949
**edgeUp** = 0.62 0.949 = **0.33**(负 edge
LATE 阶段(剩 <5 分钟)要求 edge ≥ 0.20 且 modelProb ≥ 0.65
- edge 0.33 < 0.20 → ❌
- modelProb 0.62 < 0.65 → ❌
- **结论:`NO_TRADE`**
也就是说:**这个时刻模型和市场完全同向(都看 UP),没有可赚的 edge。**
---
## 五、这套仪表盘的设计哲学
1. **数据源优先级**Polymarket Live WS(直接复用 Polymarket UI 的 BTC/USD Chainlink 喂价)→ 链上 Chainlink WSS → HTTP 回退。所有"涨跌判定"逻辑都用 Chainlink 价,避免与 Polymarket 的结算价脱钩。
2. **Binance 现货只作参考**,不进入 TA 计分。
3. **TA 模型信号是堆叠式评分**(不是加权平均),单指标上限 +4(VWAP),MACD 上限 +2Heiken 上限 +1,RSI 硬阈值化(中位区无效)。
4. **时间感知是衰减机制**:剩余越少,模型预测越往 50/50 拉回。LATE 阶段 edge 必须 ≥20%,几乎只在模型和市场严重分歧时才入场。
5. **入场动作隐藏在 CSV log 中**`./logs/signals.csv`),仪表盘只显示中间过程。命令行不会有"BUY UP"提示,所有决策要在 `signals.csv` 里看 `recommendation` 字段:`ENTER:UP:LATE:STRONG` 之类的格式。
---
## 六、常见误读警示
| 误读 | 正确认知 |
|---|---|
| "RSI 52.6 ↑ = 多头信号" | 中性区,**零贡献** |
| "Delta 1/3 显示跌 → 模型看空" | Delta 只是展示,**不入模型** |
| "Predict 63% = 应该买 UP" | Predict 是 modelProb,需要和 marketUp 比较才算 edge |
| "TIME LEFT 04:49 还有时间" | LATE 阶段,**入场门槛最严**edge ≥20%、prob ≥65% |
| "UP 0.94¢ 稳赢要下" | 押 UP 收益仅 ~6%DOWN 0.05¢ 才是 20 倍冷门 |
| "VWAP 0.63% 很弱" | 0.63% 在 15 分钟窗口是**显著偏离**,是核心多头证据 |
| "BTC Binance 和 Chainlink 价差 = 信号" | 价差是数据源同步度问题,**非交易信号** |
| "Heiken Ashi x8 决定方向" | 仅触发 +1,影响远小于 VWAP/MACD |
---
## 七、文件落点速查
| 你想了解的内容 | 文件:行 |
|---|---|
| 仪表盘渲染主循环 | `src/index.js:398-731` |
| PRICE TO BEAT 锁存逻辑 | `src/index.js:585-597` |
| Polymarket 价抓取(含回退) | `src/index.js:305-396` |
| TA 信号评分细节 | `src/engines/probability.js:3-53` |
| 时间感知衰减 | `src/engines/probability.js:55-59` |
| 入场决策(edge/threshold | `src/engines/edge.js:23-48` |
| 市场状态分类(regime | `src/engines/regime.js` |
| 时间颜色与剩余阶段编码 | `src/index.js:651-668` |
| CSV 决策日志格式 | `src/index.js:407-419, 709-722` |