- 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
16 KiB
仪表盘逐项解读手册(基于代码逻辑链)
本文严格按 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].closedelta3m = 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 += 2vwapSlope > 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):
- 进入新市场(slug 变化)时清空
priceToBeatState。 - 当
marketStartMs <= Date.now()且当前currentPrice可用时,锁存当前 Chainlink 价格作为本市场的 PRICE TO BEAT。 - 之后 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: 0–7 UTC
- Europe: 7–15 UTC
- US: 13–21 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 | 5–10 min | 10% | 60% |
| LATE | ≤ 5 min | 20% | 65% |
Step 4 — 入场判定
只有当:
bestEdge ≥ 阶段阈值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 +4(price>vwap 且 slope up)
- MACD +2(expanding bullish)
- Heiken Ashi +1(green ×8,count ≥ 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。
五、这套仪表盘的设计哲学
- 数据源优先级:Polymarket Live WS(直接复用 Polymarket UI 的 BTC/USD Chainlink 喂价)→ 链上 Chainlink WSS → HTTP 回退。所有"涨跌判定"逻辑都用 Chainlink 价,避免与 Polymarket 的结算价脱钩。
- Binance 现货只作参考,不进入 TA 计分。
- TA 模型信号是堆叠式评分(不是加权平均),单指标上限 +4(VWAP),MACD 上限 +2,Heiken 上限 +1,RSI 硬阈值化(中位区无效)。
- 时间感知是衰减机制:剩余越少,模型预测越往 50/50 拉回。LATE 阶段 edge 必须 ≥20%,几乎只在模型和市场严重分歧时才入场。
- 入场动作隐藏在 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 |