anthonydavalos/betsniper
GitHub: anthonydavalos/betsniper
基于逆向工程 API 与 WebSocket 流量分析的量化体育套利引擎,集成凯利公式风险管理与多策略实时扫描。
Stars: 1 | Forks: 0
# 🎯 BetSniper V3 - 量化体育套利引擎
`。
- 使用额外的别名扩展了 `src/utils/dynamicAliases.json` 以改善匹配 (拼写变体、小联盟和 U21)。
- 完整详情请见 [CHANGELOG.md](CHANGELOG.md),条目 `v3.4.19`。
### 2026-03-22 更新 (v3.4.17)
- 真实已结束现在支持完整历史记录,不受短窗口截断限制 (`historyLimit=0`),并在 FINISHED 选项卡中进行强制初始数据填充。
- 修复了前端轮询中的陈旧关闭问题,以便间隔在决定历史记录限制时尊重当前的选项卡/模式。
- 强化了远程同步,具有部分缓存检测 (`limitBound`) 和在需要 `fetchAll` 时的自动旁路。
- Auto-snipe 添加了明确的 SIM 确认和在重新报价时的单次重试。
- Booky 确认中的赔率漂移现在可通过环境配置 (`BOOKY_LIVE_MAX_ODD_DRIFT`,`BOOKY_PREMATCH_MAX_ODD_DRIFT`)。
- 完整详情请见 [CHANGELOG.md](CHANGELOG.md),条目 `v3.4.17`。
### 2026-03-24 更新 (v3.4.18)
- 新的实时诊断端点:`GET /api/opportunities/live/diagnostics`,包含管道 (`raw/dedup/stable/final`)、聚合原因和最近事件。
- LIVE_SNIPE 现在暴露了机会前原因 (例如 `ev_non_positive`、`stake_below_1`、`real_prob_invalid`),用于审计信号为何未进入的真实原因。
- Provider requote (`providerCode=4`) 端到端已修复:
- 后端保留 `BOOKY_PLACEWIDGET_REQUOTE_REQUIRED`。
- 前端显示特定的 re-quote 消息并允许引导的即时重试。
- 监视器中的记分牌完整性得到加强:
- 消除了虚假的 `0-0`,
- 分数标准化,
- `DESYNC` 标签,
- 在短暂的数据断线期间回退到 `STALE`。
- 完整详情请见 [CHANGELOG.md](CHANGELOG.md),条目 `v3.4.18`。
### 1) Arcadia Gateway:稳定的自动刷新且无循环
- `server.js` 现在可以自动启动 `services/pinnacleGateway.js` (`PINNACLE_GATEWAY_AUTOSTART=true`)。
- 添加了重新启动的冷却时间 (`PINNACLE_GATEWAY_AUTOSTART_MIN_INTERVAL_MS`),以避免重复打开 Puppeteer/login。
- 具有可配置频率的陈旧触发器看门狗 (`PINNACLE_GATEWAY_TRIGGER_CHECK_INTERVAL_MS`)。
- 新的操作脚本:`npm run pinnacle:gateway`。
### 2) LIVE 中精确的陈旧检测
- `src/services/liveValueScanner.js` 结合了 Pinnacle 与 Altenar 的时钟去同步保护,具有可配置的阈值 (`PINNACLE_STALE_TIME_DIFF_MINUTES`)。
- 陈旧触发器仅在具有持久性 (每个事件 >= 2 次命中) 时发出,并遵循冷却时间 (`PINNACLE_STALE_TRIGGER_MIN_INTERVAL_MS`)。
- 视觉时钟对齐调整仅针对较小的漂移 (<= 1 分钟),避免掩盖冻结的 sockets。
### 3) 带有可选自动登录的 Pinnacle Gateway
- `services/pinnacleGateway.js` 添加了尽力而为的自动登录 (框架 + 常见选择器),由以下参数控制:
- `PINNACLE_AUTO_LOGIN_ENABLED`
- `PINNACLE_LOGIN_USERNAME`
- `PINNACLE_LOGIN_PASSWORD`
- 新的陈旧检查/宽限期控制:
- `PINNACLE_STALE_CHECK_INTERVAL_MS`
- `PINNACLE_STALE_RELOAD_ALLOW_DURING_GRACE`
- Arcadia 的最小 sockets 数量可配置,从 1 开始运行 (`PINNACLE_ARCADIA_MIN_SOCKETS=1`)。
### 4) 更具可观察性和稳定性的 LIVE 管道
- `src/services/scannerService.js` 现在记录 `raw/dedup/stable/final` 管道,以解释为什么内部检测可能未到达端点的最终 payload。
- 修复了当 `QUOTE_STABILITY_MIN_HITS <= 1` 时的稳定性过滤器,不再要求不必要的额外确认。
### 5) 更具弹性的 Booky token UX
- `client/src/App.jsx` 改进了 Booky token 的静默自动续订:
- 如果 `/api/booky/token/renew` 失败或未启动,则缩短下次重试的时间 (无需等待整个长冷却期)。
- 如果后端响应 `busy`,则遵循正常的冷却时间,以免向续订请求发送垃圾信息。
### 6) 配置和匹配
- `.env.example` 已使用所有新的 Arcadia/Gateway/autologin 旋钮进行了更新。
- `src/utils/dynamicAliases.json` 添加了新别名,以加强具有变体名称的联赛中的匹配。
### 7) 具有最终结果和可审计原因的 AUTO_SNIPE
- `src/services/scannerService.js` 现在按尝试保留最终结果,状态如下:
- `CONFIRMED`
- `REJECTED`
- `UNCERTAIN`
- 如果 `LIVE_SNIPE` 机会保持手动,则记录 `reason=...` 以避免静默丢弃。
- auto-snipe 引擎的启动记录了有效参数。
### 8) LIVE_SNIPE 的安全重入策略
- 结合重入保护,以避免在没有价格改善的情况下几乎相同的条目:
- `AUTO_SNIPE_REENTRY_MIN_ODD_IMPROVEMENT_PCT`
- `AUTO_SNIPE_REENTRY_MIN_ODD_POINTS`
- `AUTO_SNIPE_MAX_ENTRIES_PER_PICK`
- 如果没有实质性改善或超过了每次选择的限制,流程会标记明确的原因 (`reentry-no-improvement`,`reentry-cap`)。
### 9) 高置信度匹配器 (手动辅助)
- `client/src/components/ManualMatcher.jsx` 结合了带有综合分数的建议引擎。
- 用于受控批次的新操作:
- `SUGERIR`
- `APLICAR`
- `APLICAR TOP 20`
- 分数结合了主/客队相似度、交换风险、时间窗口和联赛/国家背景。
### 10) 在 Booky 拒绝中保留的 provider 证据
- `src/services/bookySemiAutoService.js` 在包装 `placeWidget` 错误时保留 `providerStatus/providerBody/requestId`。
- 这改善了对明确拒绝 (`BOOKY_PLACEWIDGET_REJECTED`) 的审计和真实投注的事后分析。
### 11) Chrome 配置文件中 Arcadia 与 Booky 的分离
- `services/pinnacleGateway.js` 现在默认使用 Pinnacle 的专用配置文件 (`data/pinnacle/chrome-profile`)。
- 移除了与 Arcadia 中 `BOOK_PROFILE` 的默认耦合。
- 保留通过环境变量 `PINNACLE_CHROME_PROFILE_DIR` 的覆盖。
### 12) 具有会话状态的更精确的 Arcadia 登录
- `services/pinnacleGateway.js` 明确检测 Pinnacle 中何时已有活动会话 (`Account-Menu`,bankroll/存款)。
- 仅当检测到真实的登录块 (`header-login-loginButton`,`Forms-Element-username/password`) 时才尝试自动登录。
- 加强了与 header 选择器的兼容性 (`input#username`,`input#password`,在 `header-login-loginButton` 中提交)。
### 13) ACity 自动登录移至正确位置 (Booky 脚本)
- `scripts/extract-booky-auth-token.js` 和 `scripts/capture-altenar-betslip.js` 实现了 ACity 流程:
- 触发 `button#login` / `#login`,
- 检测 `input[name="user"]`,
- 健壮的提交 (`INICIAR SESION` / `INGRESAR` + 回退)。
- 如果已登录 (header 中包含 `MIS APUESTAS` + `DEPOSITAR`),则跳过自动登录。
### 14) Pinnacle 的专用配置文件配置
- `.env.example` 记录了:
- `PINNACLE_CHROME_PROFILE_DIR=data/pinnacle/chrome-profile`
- 操作建议:将 Arcadia 和 Booky 保留在单独的配置文件中,以避免会话污染。
### 15) Altenar token 自动同步到 Google Sheets
- `scripts/extract-booky-auth-token.js` 捕获并持久化:
- `ALTENAR_BOOKY_AUTH_TOKEN` (用于真实 `placeWidget` 流程的 JWT),以及
- `ALTENAR_WIDGET_AUTH_TOKEN` (用于 scanner/live/prematch 的 `api/widget` 原始 token)。
- 到 Google Sheets 的同步仅为 `ALTENAR_BOOKY_AUTH_TOKEN` 执行。
- 用于 webhook 的环境变量:
- `GSHEETS_TOKEN_WEBHOOK_URL`
- 安全政策:
- `.env.example` 仅包含该变量的示例/注释。
- `.env` (本地,未版本化) 必须包含 Apps Script 的真实 URL。
- 操作行为:
- 如果未定义 webhook,token 捕获将继续而不会失败。
- 如果 webhook 失败,会记录 warning/error 但不会阻止 token 续订。
确切的更新顺序:
1. Puppeteer 检测到带有有效 `Authorization` 的 Altenar 请求。
2. 脚本更新 `.env` (`ALTENAR_BOOKY_AUTH_TOKEN=Bearer ...` 和/或 `ALTENAR_WIDGET_AUTH_TOKEN=`)。
3. 在同一次执行的同一时刻,立即向 webhook 发送带有 `{ token: "Bearer ..." }` 的 `POST` 请求。
4. Apps Script (`doPost`) 接收 payload 并更新 `TOKEN!A1`。
5. 提取过程以成功结束。
注意:在 `.env` 中保存之前不会执行到 Sheets 的同步;它总是在 token 本地更新插入之后发生。
发送到 webhook 的预期 payload 示例:
```
{
"token": "Bearer "
}
```
### 16) 带有防循环的 widget token 自动续订 (401/403)
- 后端不按固定间隔续订;仅当 Altenar 在扫描器的关键端点 (`GetLivenow`、`GetEventDetails`、`GetUpcoming`) 中响应认证错误 (`401` 或 `403`) 时才被动续订。
- 检测到 `401/403` 时,会在后台触发使用 Puppeteer 的 token 捕获脚本以刷新 `ALTENAR_WIDGET_AUTH_TOKEN`。
- 为避免循环 (多个扫描器同时失败),续订遵循每个进程的全局冷却时间:
- `ALTENAR_WIDGET_TOKEN_RENEW_COOLDOWN_MS` (建议默认值:`120000`)。
- 自动捕获的最长时间由以下参数控制:
- `ALTENAR_WIDGET_TOKEN_RENEW_TIMEOUT_MS` (建议默认值:`90000`)。
操作行为:
1. 在扫描器的 Altenar 调用中出现 `401/403`。
2. 尝试启动自动续订 (如果未处于冷却期)。
3. 如果最近已有尝试,则抑制新尝试并记录剩余秒数的警告。
4. 如果捕获在 `.env` 中获取了新 token,建议重启后端以干净地重新加载环境。
调优指南:
- 正常操作:`ALTENAR_WIDGET_TOKEN_RENEW_COOLDOWN_MS=60000` 到 `120000`。
- 高负载/数据流噪音:保持 `120000` 或提高到 `180000`。
- 特定调试:暂时设置为 `30000`。
- 避免过低的值 (`<15000`) 以免批量打开 Puppeteer 流程。
### 17) 针对 401/403 和集成不匹配更具鲁棒性的真实投放置
- `src/services/bookySemiAutoService.js` 现在明确区分 auth failures (`401/403`) 和市场拒绝。
- 如果 `placeWidget` 返回 `401/403`,后端会响应 `BOOKY_TOKEN_RENEWAL_REQUIRED` (428) 并附带诊断和辅助续订触发。
- 验证 JWT (`payload.Integration`) 是否与 `ALTENAR_INTEGRATION` 匹配;如果不匹配,则阻止确认并给出明确的原因。
- `GET /api/booky/token-health` 暴露了额外的信号:
- `tokenIntegration`
- `tokenUserName`
- `integrationMismatch`
### 18) 与 widget auth 对齐的真实投注准备
- 在真实投放置的准备中,`GetEventDetails` 使用统一的公共配置 (`getAltenarPublicRequestConfig`) 以避免 headers/auth 的不对齐。
- 如果 `GetEventDetails` 响应 `401/403`,则返回 `BOOKY_WIDGET_TOKEN_RENEWAL_REQUIRED` 以及 `eventId` 和自动续订状态。
### 19) UI 不同步时的自动票据恢复
- `client/src/App.jsx` 添加了在确认时出现 `ticket no encontrado` 时的受控恢复:
- 搜索同一机会 (`eventId + selection + market`) 的有效 `DRAFT`。
- 使用恢复的票据仅重试一次 `confirm`。
- 如果确认,则避免假阴性并热刷新状态。
### 20) Arcadia Live:当 WS 正常时的 HTTP 节流
- `services/pinnacleLight.js` 在以下情况减少冗余的 HTTP live 轮询:
- websocket 已打开,
- 有最近的帧,
- 最后一个 HTTP 快照仍然新鲜。
- 保持定期的 HTTP 刷新以保持一致性并清理孤立的市场。
- 新的可选旋钮:
- `PINNACLE_LIVE_HTTP_MAX_STALE_MS` (内部默认值 `20000`)
- `PINNACLE_LIVE_WS_FRESH_WINDOW_MS` (内部默认值 `8000`)
## 🚀 核心功能
### 🧠 量化核心引擎
**带有对数阻尼的同步凯利公式**
- 凯利公式的进阶实现,避免了“任意截断”。
- 使用指数饱和函数:`Stake = Cap × (1 - e^(-Kelly/Cap))`。
- 允许具有巨大优势的下注获得更多资金,而不会危及资金池安全。
- **破产风险 (ROR):** < 0.5%,通过渐近控制实现。
**动态风险配置**
系统根据每种策略固有的波动性自动调整下注的激进程度:
| 策略 | 凯利分数 | 波动性 | 用例 |
|------------|----------------|-------------|-------------|
| `PREMATCH_VALUE` | 0.25 (1/4) | 低 | 拥有可靠历史数据的赛前赔率 |
| `LIVE_VALUE` | 0.125 (1/8) | 中 | 带有市场噪音的实时套利 |
| `LIVE_SNIPE` | 0.10 (1/10) | 高 | "La Volteada" - 高度不确定性事件 |
**基于净资产价值的注额计算**
- 基于 `可用余额 + 活跃风险` 计算仓位规模。
- 避免在拥有多笔同时进行的下注时出现系统性的“投资不足”。
- 示例:如果余额为 $1000 并且有 $200 的活跃下注,系统基于 NAV = $1200 进行计算。
### 🕵️ 专业扫描器
**1. Arcadia Gateway (真实来源)**
- 与 Pinnacle API 的低延迟 WebSocket 连接。
- 通过消除保证金提取“公平”赔率。
- 当 token 过期时通过 Puppeteer 自动续订会话。
- 冻结数据 检测和自动重启。
**2. 赛前扫描器**
- 每日扫描即将举行的事件 (48小时窗口)。
- 交叉比对 Pinnacle 与 Altenar 的赔率,识别赛前机会。
- 结合模糊逻辑 + Levenshtein Distance 的智能匹配器,用于名称标准化。
**3. 实时扫描器**
- 具有**自适应轮询** (~2 秒到 ~7 秒,取决于活动/错误) 的高频扫描。
- 实时检测两种类型的机会:
- **Value Bets Live:** 进行中事件的赔率差异。
- **"La Volteada":** 专策略 (见策略部分)。
**4. 监视仪表板**
- Pinnacle 与 Altenar 赔率的实时比较视图。
- 趋势视觉指示器 (上/下箭头) 和更新脉冲。
- “未链接”事件检测 (无 Pinnacle 匹配)。
### 🛡️ 安全系统
**僵尸协议 (自动恢复)**
- 检测从实时数据流中消失的事件 (暂停、提前结束)。
- 自动查询结果 API (`GetEventResults`) 以进行精确清算。
- 防止投注无限期地卡在 PENDING 状态。
**防止重复下注**
- 内存中的锁机制 (`processingBets Set`) 以防止重复下注。
- 针对手动丢弃事件的持久黑名单过滤器。
- 在注册机会之前进行最低注额 (S/1.00) 验证。
**陈旧数据检测**
- 比较 Pinnacle 和 Altenar 之间的比赛时间。
- 如果差异超过 3 分钟,触发 WebSocket 的自动重启。
- 触发文件 (`pinnacle_stale.trigger`) 用于进程间通信。
### 🎨 用户界面 (React + TailwindCSS)
**多选项卡仪表板**
1. **赛前:** 具有计算出的 EV 和 Kelly 的未来机会列表。
2. **实时:** 实时检测到的机会 ("La Volteada" + Value Bets)。
3. **活跃:** 正在进行的下注,带有实时比分和时间追踪。
4. **历史:** 具有损益和统计数据的已清算投注完整记录。
5. **监视器:** Pinnacle vs Altenar 赔率视觉比较器 (专业模式)。
6. **匹配器:** 用于手动链接系统未能自动关联的事件的工具。
## 📊 交易策略
### 1. 赛前价值下注
**描述:** 检测赛前赔率之间的差异。
**流程:**
1. 每日从 Pinnacle 摄入即将举行的事件 (“公平”赔率)。
2. 标准化球队和联赛名称 (模糊匹配)。
3. 与同一事件中 Altenar 的赔率进行比较。
4. 当以下条件成立时识别价值:`真实概率 × Altenar赔率 > 1`。
**优势:**
- 数据稳定 (无波动)。
- 有更多时间进行手动分析。
- 突然变化的风险较低。
**风险:** 低 (0.25 Kelly)。
### 2. 实时价值下注
**描述:** 正在进行的事件中的算法套利。
**流程:**
1. 使用**自适应轮询** (~2 秒到 ~7 秒,取决于活动和错误) 持续扫描实时比赛。
2. 实时比较更新的赔率。
3. 通过 Pinnacle Live 公平赔率检测正价值。
4. 如果 Kelly 建议 stake ≥ S/1.00 则执行。
**优势:**
- 频繁的机会。
- 赔率波动更大 = 利润更高。
**风险:** 中 (0.125 Kelly)。
### 3. "La Volteada" (实时狙击策略)
**描述:** 检测潜在逆转的专有策略。
**入场条件:**
1. **事件特征:** 赛前热门 (真实概率 > 55%)。
2. **比赛状态:** 热门球队**恰好落后 1 球**。
3. **时间窗口:** 比赛第 15 - 80 分钟。
4. **统治力验证:**
- 无红牌 (红牌 = 0)。
- 统计数据 (控球率,射门) 有利于热门球队 (可选)。
**数学逻辑:**
- 使用 Pinnacle Live 赔率重新计算逆转概率。
- 由于高波动性,应用超保守的 Kelly (0.10)。
- 寻找 Altenar 虚高的赔率 (通常对于热门球队 > 2.5 倍)。
**真实示例:**
```
Tigres UANL (Favorito Pre-Match: ~70%) vs Pumas
Score Actual: 0-1 (Tigres perdiendo) - Minuto 35'
Cuota Pinnacle Live (Tigres): 1.50 → Prob Real: ~60%
Cuota Altenar (Tigres): 2.20 → EV = 32%
Kelly (0.10): Stake sugerido = $8 (NAV = $1200)
```
**优势:**
- 利用市场恐慌 (Altenar 高估了弱队)。
- 在波动性联赛中出现频率高。
**风险:** 高 (0.10 Kelly)。需要快速清算。
### 4. 下一个进球价值 (大小)
**描述:** 检测大小盘市场的进攻压力。
**条件:**
1. 控球率 > 60% 的统治球队。
2. 射门差距 > 3。
3. 分钟 > 60'。
**目标:** 当比赛“激烈”时投注 "Over 2.5" 或 "Over 3.5"。
**状态:** 实验性 (需要校准)。
## 💰 财务管理 (投资组合理论)
### 凯利公式:背后的数学原理
**凯利公式** 根据统计优势确定承担风险的资金的最优分数:
$$f^* = \frac{bp - q}{b}$$
其中:
- `p` = 获胜的真实概率 (Pinnacle 公平赔率)
- `q` = 失败的概率 (1 - p)
- `b` = 每单位下注的净利润 (赔率 - 1)
**纯凯利公式的问题:** 在其原始形式中,凯利公式在高优势情况下可能建议非常大的下注 (资金的 10-20%),使交易者暴露于高波动性中。
### 已实施的改进
**1. 分数凯利**
- 对纯凯利的保守乘数。
- 以较低的增长率换取较低的波动性。
- BetSniper 根据市场波动性使用自适应分数。
**2. 对数阻尼**
我们不是任意削减大额下注,而是应用:
$$Stake_{Real} = Cap \times (1 - e^{-\frac{Stake_{Kelly}}{Cap}})$$
**效果:**
- 小额下注 (< 2%):几乎呈线性增长 (不受惩罚)。
- 大额下注 (> 5%):向 Cap (3.5%) 渐近增长。
- **结果:** 利用巨大优势而不会冒破产风险。
**概念图:**
```
Stake Real (%)
│
3.5%├─────────────────────── (Asíntota)
│ ╱─
│ ╱─
2.0%│ ╱─
│ ╱─
1.0%│ ╱─
│╱──────────────────────→ Kelly Crudo (%)
0 2 4 6 8 10
```
### NAV (净资产价值)
**定义:** 总资产 = 可用余额 + 活跃下注的注额。
**为什么要使用它?**
- 场景:你有 $1000 的余额和 5 笔各 $50 的活跃下注 ($250 在游戏中)。
- **常见错误:** 基于 $1000 计算 Kelly → 在新机会上投资不足。
- **NAV 解决方案:** 基于 $1250 (NAV) 计算 Kelly → 按比例分配真实资产的下注。
**实现:**
```
const currentNAV = portfolio.balance +
portfolio.activeBets.reduce((sum, b) => sum + b.stake, 0);
const kellyStake = calculateKellyStake(realProb, odd, currentNAV, strategy);
```
### 风险控制
**执行前验证:**
1. **最低注额:** S/1.00 (避免不切实际的微型下注)。
2. **流动性:** 下注不得超过可用余额 (即使 NAV 建议)。
3. **重复检查:** 验证同一事件中不存在活跃下注。
4. **黑名单:** 过滤手动丢弃的事件。
**自动清算:**
- **赛前:** 开始后 2.2 小时的缓冲区,然后验证结果。
- **实时:** 如果 `时间 >= 90'` 或事件从数据流中消失,立即清算。
- **僵尸投注:** 如果 `GetEventDetails` 失败,则查询结果 API。
## 🛠️ 系统架构
### 组件图
```
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React) │
│ ┌──────────┬───────────┬──────────┬──────────┬──────────┐ │
│ │ Pre-Match│ En Vivo │ Activas │Historial │ Monitor │ │
│ └────┬─────┴─────┬─────┴─────┬────┴────┬─────┴────┬─────┘ │
│ │ │ │ │ │ │
└───────┼───────────┼───────────┼─────────┼──────────┼───────┘
│ │ │ │ │
└───────────┴───────────┴─────────┴──────────┘
▼
┌───────────────────────────────────────────────────┐
│ EXPRESS API (server.js) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Background Scanner (Bucle Infinito) │ │
│ │ - Pre-Match Scan (cada 2 min) │ │
│ │ - Live Scan (polling adaptativo) │ │
│ │ - Active Bets Monitoring │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ API Routes │ │
│ │ /api/opportunities │ │
│ │ /api/portfolio │ │
│ │ /api/monitor │ │
│ │ /api/matcher │ │
│ │ /api/booky │ │
│ └─────────────────────────────────────────────┘ │
└──────────────┬──────────────────┬────────────────┘
│ │
┌──────────────▼─────┐ ┌────────▼──────────────┐
│ LowDB (db.json) │ │ Axios Clients │
│ - Matches │ │ - Altenar API │
│ - Portfolio │ │ - Pinnacle (REST) │
│ - Blacklist │ └───────────────────────┘
└────────────────────┘
┌───────────────────────┐
│ services/ │
│ pinnacleLight.js │
│ (Proceso Separado) │
│ │
│ ┌──────────────────┐ │
│ │ Puppeteer │ │
│ │ (Chrome Headless)│ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ WebSocket Client │
│ (wss://arcadia) │
│ │ │
│ ▼ │
│ pinnacle_live.json │
└──────────────────────┘
```
### 数据流 (实时交易)
1. **摄入 (终端 2 - `pinnacleLight.js`):**
- Puppeteer 导航到 Pinnacle 并提取认证 headers。
- 在 `wss://api.arcadia.pinnacle.com/ws` 打开 WebSocket。
- 大约每 500ms 接收一次帧,进行解析并写入 `data/pinnacle_live.json`。
2. **处理 (终端 1 - `server.js`)**
- 后台扫描器以自适应轮询 (~2秒到 ~7秒) 读取 `pinnacle_live.json`。
- 查询 Altenar 中的实时事件 (`GetLivenow`)。
- 模糊匹配器链接 Pinnacle ↔ Altenar 事件。
- 评估策略条件 (价值、Volteada、下一个进球)。
- 计算 Kelly 并在缓存中注册机会。
3. **展示 (终端 3 - `client/`):**
- 前端每 5 秒查询一次 `/api/opportunities`。
- 在“实时”选项卡中渲染机会。
- 用户可以执行手动下注 ("APOSTAR" 按钮)。
4. **执行 (模拟交易):**
- `placeAutoBet()` 在 `db.json` 中注册下注。
- 从余额中扣除 stake。
- 添加到 `activeBets` 列表。
5. **监控:**
- 在扫描器的每个周期,`updateActiveBetsWithLiveData()` 验证:
- 事件是否仍在进行中 (更新比分/时间)。
- 是否已结束 (查询 `GetEventDetails` 或 `GetEventResults`)。
- 如果有官方结果则清算下注。
## 🔧 预操作配置指南
在安装系统之前,您需要在 BetSniper 使用的外部服务中拥有活跃的账户。本节详细说明了**要开立哪些账户**、**从每个账户中提取哪些数据**以及**如何将它们放入 `.env`**。
### 步骤 1:您需要订阅的服务
#### 1A. Pinnacle (必需 — 真实概率来源)
| 字段 | 详情 |
|---|---|
| **Web** | [pinnacle.com](https://www.pinnacle.com) |
| **类型** | Sharp Bookie — 极低利润率,接受赢家 |
| **你需要什么** | 拥有对 "Sports" 部分 (可见的 Live Soccer) 访问权限的活动账户 |
| **限制** | 并非在所有国家都可用。如有必要,请使用 VPN (推荐:NL 或 MT 服务器)。 |
| **在 BetSniper 中的用途** | 仅作为赔率来源。**不在 Pinnacle 上下注。** |
| **成本** | 免费 (您只需要账户即可访问实时赔率 API) |
#### 1B. DoradoBet (live 模式必需 — 主要目标 bookie)
| 字段 | 详情 |
|---|---|
| **Web** | [doradobet.com](https://doradobet.com) |
| **平台** | Altenar (与 ACity 相同的后端) |
| **你需要什么** | 具有真实余额的注册账户 |
| **`.env` 中的配置文件** | `BOOK_PROFILE=doradobet` |
| **在 BetSniper 中的用途** | Value bets 检测 + 真实下注 (如果您启用 `BOOKY_REAL_PLACEMENT_ENABLED`) |
| **限制** | 主要在秘鲁可用。如果您从其他国家/地区操作,请验证可用性。 |
#### 1C. Casino Atlantic City — ACity (备选 — 相同的 Altenar 引擎)
| 字段 | 详情 |
|---|---|
| **Web** | [casinoatlanticcity.com/apuestas-deportivas](https://www.casinoatlanticcity.com/apuestas-deportivas) |
| **平台** | Altenar (相同的 API,不同的 `integration` 和 `origin`) |
| **你需要什么** | 具有真实余额的注册账户 |
| **`.env` 中的配置文件** | `BOOK_PROFILE=acity` |
| **在 BetSniper 中的用途** | DoradoBet 的替代品。您可以在单独的会话中同时操作两者。 |
### 步骤 2:`.env` 的详细配置
复制模板文件:
```
cp .env.example .env
```
然后按照本指南逐个变量地编辑 `.env`:
#### 🔧 系统变量
```
NODE_ENV=development
```
```
PORT=3000
```
```
TZ=America/Lima
```
```
DISABLE_BACKGROUND_WORKERS=false
DISABLE_LIVE_SCANNER=false
DISABLE_PREMATCH_SCHEDULER=false
DISABLE_PINNACLE_INGEST_CRON=false
DISABLE_MONITOR_DASHBOARD=false
# 实时调优
LIVE_VALUE_MIN_EV=0.02
LIVE_VALUE_MIN_DISPLAY_STAKE=0.10
LIVE_VALUE_NON_1X2_STAKE_FACTOR=1
LIVE_SNIPE_REQUIRE_PINNACLE_LIVE=true
LIVE_VALUE_REQUIRE_SCORE_SYNC=true
LIVE_VALUE_SCORE_SYNC_MAX_GOAL_DIFF=0
LIVE_VALUE_ENABLE_STABILITY_FILTER=true
LIVE_VALUE_STABILITY_MIN_HITS=2
LIVE_VALUE_STABILITY_MIN_AGE_MS=4000
LIVE_GLOBAL_STABILITY_ENABLED=true
LIVE_GLOBAL_STABILITY_MIN_HITS=2
# 投注时 prematch 热重算
PREMATCH_REFRESH_RECALCULATE_PINNACLE=true
PREMATCH_PINNACLE_CACHE_TTL_MS=15000
# prematch 混合下载窗口(滑动 + 预加载)
PREMATCH_WINDOW_PRIMARY_HOURS=6
PREMATCH_WINDOW_PREFETCH_HOURS=6
PREMATCH_WINDOW_OVERLAP_MINUTES=30
```
#### 🎯 Altenar 配置文件变量 (目标 Bookie)
这些变量将通过 `npm run book:dorado` 或 `npm run book:acity` 自动写入。但是如果您更希望手动编辑它们:
**对于 DoradoBet:**
```
BOOK_PROFILE=doradobet
ALTENAR_INTEGRATION=doradobet
ALTENAR_ORIGIN=https://doradobet.com
ALTENAR_REFERER=https://doradobet.com/deportes-en-vivo
```
**对于 ACity:**
```
BOOK_PROFILE=acity
ALTENAR_INTEGRATION=casinoatlanticcity
ALTENAR_ORIGIN=https://www.casinoatlanticcity.com
ALTENAR_REFERER=https://www.casinoatlanticcity.com/apuestas-deportivas
```
**通用项 (除非 bookie 更改国家/地区,否则请勿更改):**
```
ALTENAR_COUNTRY_CODE=PE # Código ISO del país de la cuenta
ALTENAR_CULTURE=es-ES # Idioma de la API (no cambiar)
ALTENAR_TIMEZONE_OFFSET=300 # UTC-5 (Perú). GMT-4=240, GMT-6=360
ALTENAR_NUM_FORMAT=en-GB # VITAL: garantiza decimales con punto (1.50 no 1,50)
ALTENAR_DEVICE_TYPE=1 # 1=Desktop. No cambiar.
ALTENAR_SPORT_ID=0 # 0=todos los deportes. 66=solo fútbol.
```
#### 🔐 Booky 认证变量 (用于真实下注)
这**仅在您希望执行真实下注时**才需要。在模拟交易模式下您可以跳过本节。
**步骤 1 — Bookie 访问 URL:**
```
# DoradoBet:
ALTENAR_BOOKY_URL=https://doradobet.com/deportes-en-vivo
# ACity:
ALTENAR_BOOKY_URL=https://www.casinoatlanticcity.com/apuestas-deportivas#/overview
```
**步骤 2 — 您的账户凭据:**
```
ALTENAR_LOGIN_USERNAME=tu_email_o_usuario
ALTENAR_LOGIN_PASSWORD=tu_contraseña
```
**步骤 3 — 捕获真实的 JWT:**
在 `.env` 中配置好凭据后,运行:
```
# 打开 Chrome,自动登录并等待你关闭窗口
npm run token:booky:wait-close
# 备选方案:使用 90 秒 timeout 的 headless 模式
npm run token:booky:timeout
```
该脚本会自动写入您的 `.env`:
```
ALTENAR_BOOKY_AUTH_TOKEN=Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV...
```
#### 🛡️ 安全变量 — 真实投放置防护
```
BOOKY_REAL_PLACEMENT_ENABLED=false
```
```
BOOKY_TOKEN_MIN_REMAINING_MINUTES=2
```
```
BOOKY_MIN_EV_PERCENT=2
```
```
BOOKY_MAX_ODD_DROP=0.20
```
```
BOOKY_AUTO_TOKEN_REFRESH_ENABLED=true
```
```
BOOKY_KEEP_REAL_PLACEMENT_ON_TOKEN_REFRESH=false
```
#### 🧮 Pinnacle ↔ Altenar 匹配器变量
```
MATCH_DIAGNOSTIC_LOG=1
```
```
MATCH_FUZZY_THRESHOLD=0.77
```
```
MATCH_MIN_ACCEPT_SCORE=0.60
```
```
MATCH_TIME_TOLERANCE_MINUTES=5
```
```
MATCH_TIME_EXTENDED_TOLERANCE_MINUTES=30
```
#### 🧹 内务处理变量 (可选)
```
BOOKY_BALANCE_REFRESH_MS=45000
```
```
BOOKY_HISTORY_REFRESH_MS=60000
```
```
BOOKY_HISTORY_RETENTION_DAYS=30
```
```
BOOKY_PROFILE_HISTORY_MAX_ITEMS=500
```
```
BOOKY_ORPHAN_ACTIVE_GRACE_MS=120000
```
### 步骤 3:摘要 — 哪些是必选项,哪些是可选项?
| 变量 / 步骤 | 模拟交易 | 实时真实下注 |
|---|:---:|:---:|
| 活跃的 Pinnacle 账户 | ✅ | ✅ |
| 具有余额的 DoradoBet 或 ACity 账户 | ❌ | ✅ |
| `BOOK_PROFILE` + `ALTENAR_*` | ✅ | ✅ |
| `ALTENAR_LOGIN_USERNAME` + `PASSWORD` | ❌ | ✅ |
| `ALTENAR_BOOKY_AUTH_TOKEN` | ❌ | ✅ |
| `BOOKY_REAL_PLACEMENT_ENABLED=true` | ❌ | ✅ |
| 防护 (`BOOKY_MIN_EV_PERCENT` 等) | ❌ | ✅ 推荐 |
| `MATCH_*` (匹配器调优) | 可选 | 可选 |
## 📦 安装与部署
### 先决条件
- **Node.js:** v18.0.0 或更高版本
- **npm:** v8.0.0 或更高版本
- **操作系统:** Windows, macOS 或 Linux
- **Chromium:** 由 Puppeteer 自动安装 (首次启动时)
- **RAM:** 建议最少 4GB (Node.js 2GB + Chromium 2GB)
### 快速安装
```
# 1. 克隆仓库
git clone https://github.com/tu-usuario/betsniper-v3.git
cd betsniper-v3
# 2. 安装 Backend 依赖
npm install
# 3. 安装 Frontend 依赖
cd client
npm install
cd ..
# 4. 配置环境变量(可选)
cp .env.example .env
# 如果需要自定义端口或配置,请编辑 .env
```
### 生成的文件结构
系统将在首次启动时自动创建这些目录和文件:
```
data/
├── pinnacle_live.json # Feed en tiempo real de Pinnacle
├── pinnacle_token.json # Headers de autenticación (auto-renovado)
└── pinnacle_stale.trigger # Flag para reinicio de socket (auto-generado)
db.json # Base de datos local (creada por LowDB)
```
### 执行模式:3终端架构
为了进行完整操作,请**并行**执行这些命令 (在 3 个不同的终端中):
#### **终端 1:后端服务器 (必需)**
启动 REST API、数据库和后台扫描器。
```
npm run dev
```
**它的作用是什么?**
- 在 `http://localhost:3000` 上暴露 API
- 每 2 小时执行一次 Pinnacle/Altenar 的自动摄入
- 循环执行 Live 机会扫描器 (根据活动自适应轮询)
- 监控活跃下注并自动清算
**预期日志:**
```
🚀 Servidor BetSniper V3 corriendo en http://localhost:3000
📝 Modo: development
🔄 Background Scanner Iniciado (Modo Seguro Anti-Ban) + AUTO-TRADING ACTIVO
⏰ [CRON] Ejecutando Ingesta Automática de Pinnacle...
```
#### **终端 2:Pinnacle 摄入 (Live 必需)**
保持与 Pinnacle 的 WebSocket 连接并保存实时赔率。
```
node services/pinnacleLight.js
```
**首次启动 (身份验证):**
- 如果不存在 `data/pinnacle_token.json`,脚本将自动打开一个 **Chrome 窗口**。
- **所需操作:** 在该窗口中手动登录 Pinnacle。
- 一旦您导航到 "Live Soccer" 部分,脚本将自动捕获 headers。
- 当您看到消息 `💾 Token actualizado en disco` 时,**关闭 Chrome 窗口**。
- 脚本将继续运行 WebSocket。
**自动续订:**
- 如果 token 过期 (大约每 ~1 小时),脚本会检测到并再次打开 Chrome。
- 重复手动登录过程。
**预期日志:**
```
🚀 Starting Pinnacle Auth Scraper (Direct WS)...
✅ Headers cargados y válidos (Generados: 14:32:15).
🔌 Conectando al WebSocket...
✅ WebSocket Conectado! (Esperando frames...)
📡 FRAME: Straight - Updates: 12
💾 Datos guardados en disco (6 eventos).
```
#### **终端 3:前端 (UI 必需)**
在开发模式下启动 React 界面。
```
cd client
npm run dev
```
**URL:** `http://localhost:5173`
**预期日志:**
```
VITE v5.0.0 ready in 324 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
```
### 可选工具
#### Booky 流程 (配置文件、token、捕获、冒烟测试)
```
# 1) 选择操作配置文件
npm run book:acity
# 或
npm run book:dorado
# 2) 捕获真实 auth token(打开 Chrome)
npm run token:booky:wait-close
# 3) 捕获 placeWidget/betslip payloads
npm run capture:booky
# 4) 检查是否存在最新的捕获(在克隆新设备/新机器上为必填)
# PowerShell / Bash (Git Bash)
curl -sS --compressed http://localhost:3000/api/booky/capture/latest
# 应返回:{"success":true,"found":true,...}
# 4.1) placeWidget 的快速健康检查(无噪音输出)
# 选项 A(如果有 jq):
curl -sS --compressed http://localhost:3000/api/booky/token-health | jq.exe '{success,authenticated:.token.authenticated,jwtValid:.token.jwtValid,expired:.token.expired,remainingMinutes:.token.remainingMinutes,expIso:.token.expIso}'
curl -sS --compressed "http://localhost:3000/api/booky/account?refresh=1" | jq.exe '{success,balance:.balance.amount,currency:.balance.currency,stale:.balance.stale,updatedAt:.balance.updatedAt,source:.balance.source}'
curl -sS --compressed http://localhost:3000/api/booky/capture/latest | jq.exe '{success,found,profile,generatedAt,totalCaptured}'
# 选项 B(如果 jq 不可用):
curl -sS --compressed http://localhost:3000/api/booky/token-health | python -c "import sys,json; d=json.load(sys.stdin); t=d.get('token') or {}; print({'success':d.get('success'),'authenticated':t.get('authenticated'),'jwtValid':t.get('jwtValid'),'expired':t.get('expired'),'remainingMinutes':t.get('remainingMinutes'),'expIso':t.get('expIso')})"
curl -sS --compressed "http://localhost:3000/api/booky/account?refresh=1" | python -c "import sys,json; d=json.load(sys.stdin); b=d.get('balance') or {}; print({'success':d.get('success'),'amount':b.get('amount'),'currency':b.get('currency'),'stale':b.get('stale'),'updatedAt':b.get('updatedAt'),'source':b.get('source')})"
curl -sS --compressed http://localhost:3000/api/booky/capture/latest | python -c "import sys,json; d=json.load(sys.stdin); print({'success':d.get('success'),'found':d.get('found'),'profile':d.get('profile'),'generatedAt':d.get('generatedAt'),'totalCaptured':d.get('totalCaptured')})"
# placeWidget 的最低健康标准:
# - token:authenticated=true, jwtValid=true, expired=false, remainingMinutes > 2
# - wallet:success=true, stale=false, updatedAt 为近期时间
# - 捕获:found=true, totalCaptured > 0 且 generatedAt 为近期时间
# - payload 验证:POST /api/booky/real/dryrun/:id 必须返回 success=true
# 5) 检查 token 健康状况及安全流程(非真实投注)
npm run smoke:booky
# 6) 手动清理孤立的进行中记录(如果 UI 显示幽灵 EN JUEGO 状态)
npm run cleanup:booky:orphans
```
对于受控的真实发送测试 (仅在您启用 `BOOKY_REAL_PLACEMENT_ENABLED=true` 时):
```
npm run smoke:booky:live
```
#### 快速检查清单:在 20 秒内激活真实交易
1. 确认 token 有效:`GET /api/booky/token-health` (无 `expired`,剩余分钟数 > 2)。
2. 确认捕获就绪:`GET /api/booky/capture/latest` 显示 `found: true`。
3. 在发送真实请求之前对机会执行 dry-run:`POST /api/booky/real/dryrun/:id`。
4. 然后启用 `BOOKY_REAL_PLACEMENT_ENABLED=true`。
5. 保持 `BOOKY_KEEP_REAL_PLACEMENT_ON_TOKEN_REFRESH=false` 以进行保守操作。
6. 会话结束时,恢复为 `BOOKY_REAL_PLACEMENT_ENABLED=false`。
有用的清理脚本选项:
```
# 仅输出 JSON
npm run cleanup:booky:orphans -- --json
# 清理特定的 profile
npm run cleanup:booky:orphans -- --profile=acity
# 使用远程 cache(不强制刷新)
npm run cleanup:booky:orphans -- --refresh=false
```
#### 手动扫描器 (观察者模式)
如果您希望实时查看检测到的每个机会的详细日志**而不干扰服务器**:
```
node scripts/scan_live.js --dry-run
```
**重要提示:** 如果服务器已经在运行,`--dry-run` 标志是**必需的**。否则,两个进程将尝试同时注册下注 (存在重复风险)。
**输出:**
```
🟢 INICIANDO LIVE SNIPER (Intervalo: 60s) [MODO: OBSERVADOR (Dry Run)]...
🛡️ Dry Run: No se ejecutarán apuestas, solo detección.
🎯 Pinnacle Live Found: Home=1.155, Away=11.83 -> RealProb(away)=7.7%
🔥 OPORTUNIDADES EN VIVO DETECTADAS 🔥
┌─────┬──────────────────────┬───────┬──────┬──────────┬─────────┬─────┬──────────┬───────┐
│Match│ CS Constantine (F) │ Score │ Time │ Strategy │ Real % │ Odd │ Kelly $ │ EV │
├─────┼──────────────────────┼───────┼──────┼──────────┼─────────┼─────┼──────────┼───────┤
│ ... │ Afak Relizane (F) │ 1-0 │ 62' │ LIVE_VAL │ 7.7% │37.0 │ $12.30 │185.9% │
└─────┴──────────────────────┴───────┴──────┴──────────┴─────────┴─────┴──────────┴───────┘
```
#### 手动数据摄入
如果您希望强制更新赛前数据库而无需等待自动 cron 任务:
```
# 更新 Altenar 事件 (DoradoBet)
node scripts/ingest-altenar.js
# 更新 Pinnacle 事件
node scripts/ingest-pinnacle.js
```
**用法:** 每天执行一次或在赛前交易会话之前执行。
## 🖥️ 用户界面 (仪表板)
### 概览
该仪表板专为专业交易者设计,拥有 6 个专门的选项卡:
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 BetSniper V3 |Balance: S/1,234.56| ROI: +12.3%| │
├─────────────────────────────────────────────────────────────────┤
│ [Pre-Match] [En Vivo] [Activas] [Historial] [Monitor] [Matcher]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ [Contenido dinámico según pestaña seleccionada] │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 1. 赛前 (未来机会)
**目的:** 检测尚未开始的事件中的 value bets。
**列:**
- **比赛:** 事件名称 (主队 vs 客队)。
- **联赛:** 比赛和国家。
- **时间:** 开始的日期和时间 (调整为本地时区)。
- **PIN (Pinnacle):** 计算出的无利润加成的“公平”赔率。
- **ALT (Altenar):** DoradoBet 提供的赔率。
- **EV%:** 期望值。例如:`15.2%` = 每投注 S/1 的预期收益。
- **Kelly:** 建议的投注额 (S/)。
- **操作:** `APOSTAR` 按钮 (在模拟交易中注册)。
**过滤器:**
- 最低 EV:仅显示 EV > 5% 的机会。
- 最长时间:未来 X 小时内的事件。
### 2. 实时 (实时机会)
**目的:** 正在进行的比赛中检测到的机会。
**特殊指示器:**
- **🔥 红色徽章:** "La Volteada" (热门球队落后)。
- **⚡ 绿色徽章:** Value Bet Live (赔率差异)。
- **⚽ 蓝色徽章:** Next Goal (进攻压力)。
**附加信息:**
- **当前比分:** `1-0` (实时更新)。
- **分钟:** `67'` (与 Pinnacle 同步)。
- **红牌:** 🟥 (如果有红牌,"La Volteada" 将被停用)。
**示例:**
```
┌───────────────────────────────────────────────────────────────────────┐
│ 🔥 LIVE SNIPE │ Tigres UANL vs Pumas │ 0-1 │ 42' │ EV: 28% │ S/8 │
│ Favorito perdiendo. Prob Real: 62% | Cuota ALT: 2.20 │
│ [📊 VER STATS] [💰 APOSTAR] │
└───────────────────────────────────────────────────────────────────────┘
```
### 3. 活跃 (进行中的投注)
**目的:** 实时监控待处理下注。
**列:**
- **比赛:** 押注的事件。
- **选择:** `Home` / `Draw` / `Away` (如果是 Total,则为特定盘口)。
- **Stake:** 投注金额 (S/)。
- **赔率:** 下注时的 Odd。
- **当前比分:** 实时比分 (每 5 秒更新一次)。
- **时间:** 比赛分钟数。
- **状态:**
- 🟢 `WINNING` (正在赢)
- 🟡 `PENDING` (结果不确定)
- 🔴 `LOSING` (正在输)
- **潜在:** 赢的利润 / 输的损失。
**操作:**
- 查看详情 (`🔍` 完整的比赛统计数据)。
- 手动兑现 (在模拟交易中禁用)。
### 4. 历史记录 (已清算投注)
**目的:** 历史表现分析。
**汇总指标 (Header):**
```
Total Apostado: S/1,234.00 | Ganado: S/1,421.30 | ROI: +15.2% | Win Rate: 58.3%
```
**投注表格:**
- **日期:** 执行的时间戳。
- **比赛:** 事件。
- **策略:** `PREMATCH` / `LIVE_SNIPE` / `LIVE_VALUE`。
- **结果:** ✅ `WON` / ❌ `LOST`。
- **P&L:** 盈亏。
**过滤器:**
- 按日期 (过去 7 天,30 天,全部)。
- 按策略。
- 按结果 (仅赢 / 仅输)。
**导出:** `📥 Exportar CSV` 按钮,用于外部分析。
### 5. 监视器 (赔率比较器)
**目的:** 所有实时比赛的专业实时视图。
**布局:**
```
┌──────────────────────┬───────────────────────┬───────────────────────┐
│ PARTIDO / TIEMPO │ PINNACLE (Live & Pre) │ ALTENAR (Bookie) │
├──────────────────────┼───────────────────────┼───────────────────────┤
│ Liverpool vs Man Utd │ 1 │ X │ 2 │ 1 │ X │ 2 │
│ PIN: 72' │ 2-1 │ 1.45 │ 4.5 │ 7.2 │ 1.38 │ 4.8 │ 8.5 │
│ ALT: 72' │ 2-1 │ ▲ │ ● │ ▼ │ ● │ ● │ ● │
└──────────────────────┴───────────────────────┴───────────────────────┘
```
**指示器:**
- **▲ 绿色:** 赔率上升 (潜在机会)。
- **▼ 红色:** 赔率下降。
- **● 蓝色:** 赔率稳定 (脉冲 = 新鲜数据)。
- **紫色徽章:** 赛前赔率供参考。
**额外列:**
- **PIN Goals / ALT Goals:** 大小盘市场 (Over/Under 2.5, 1.5, 3.5)。
**用途:** 手动检测自动扫描器可能已过滤掉的机会。
### 6. 匹配器 (手动链接)
**目的:** 供用户强制在系统无法自动关联的 Pinnacle 和 Altenar 事件之间进行匹配的工具。
**用例:**
- 名称差异非常大 (例如:"Man City" vs "Manchester City FC")。
- 具有歧义名称的联赛。
- 未能完全覆盖的小联赛事件。
**流程:**
1. 未匹配的 Altenar 事件列表。
2. 点击 `SUGERIR` 以生成 `High Confidence` 候选。
3. 检查建议并使用 `APLICAR` 或 `APLICAR TOP 20` 应用。
4. 对于特定情况,在 Pinnacle 列表中手动搜索并使用 `VINCULAR`。
5. 系统将映射保存在 `db.json` 中。
6. 未来的检测将使用此保存的匹配和后端的动态别名。
## 🔌 API 端点
服务器暴露了以下 REST 端点:
### **机会**
**`GET /api/opportunities/prematch`**
- **描述:** 返回赛前 value bets。
- **响应:**
```
{
"success": true,
"data": [
{
"eventId": 15234567,
"match": "Real Madrid vs Barcelona",
"league": "La Liga",
"date": "2026-02-20T20:00:00.000Z",
"pinnaclePrice": 2.15,
"altenarPrice": 2.35,
"realProb": 46.5,
"ev": 9.3,
"kellyStake": 12.50,
"selection": "Home"
}
]
}
```
**`GET /api/opportunities/live`**
- **描述:** 返回实时机会。
- **缓存:** 5 秒。
**`GET /api/opportunities/live/placement-provider`**
- **描述:** 返回用于自动投放置的活动 provider。
- **响应:** `{ "success": true, "provider": "booky", "allowed": ["booky", "pinnacle"] }`
**`POST /api/opportunities/live/placement-provider`**
- **Body:** `{ "provider": "booky" }` 或 `{ "provider": "pinnacle" }`
- **描述:** 热切换自动投放置 provider,无需重启后端。
**`POST /api/opportunities/discard`**
- **Body:** `{ "eventId": 123456 }`
- **描述:** 将事件添加到黑名单 (不再显示)。
### **投资组合 (模拟交易)**
**`GET /api/portfolio`**
- **描述:** 资金池的当前状态。
- **响应:**
```
{
"balance": 1234.56,
"initialCapital": 1000.00,
"activeBets": [
{
"id": "1708176543210",
"match": "Tigres vs Pumas",
"selection": "Home",
"stake": 8.00,
"odd": 2.20,
"status": "PENDING",
"score": "0-1",
"liveTime": "42'"
}
],
"history": [...]
}
```
**`POST /api/portfolio/bet`**
- **Body:** 完整的机会对象。
- **描述:** 执行手动下注 (模拟交易)。
**`POST /api/portfolio/reset`**
- **描述:** 将投资组合重置为初始资本。
- **⚠️ 危险:** 删除所有历史记录。
### **监视器**
**`GET /api/monitor/live-odds`**
- **描述:** Pinnacle 与 Altenar 的比较数据流。
- **格式:** 带有嵌套 odds 的事件数组。
- **更新:** 实时 (读取 `pinnacle_live.json` + 查询 Altenar)。
### **匹配器**
**`GET /api/matcher/unlinked`**
- **描述:** 没有 Pinnacle 匹配的 Altenar 事件。
**`POST /api/matcher/link`**
- **Body:** `{ "altenarId": 123, "pinnacleId": 456 }`
- **描述:** 强制手动链接。
### **Booky (半自动 + 受控真实交易)**
**`GET /api/booky/tickets`**
- 返回待处理票据 + booky 历史记录。
**`POST /api/booky/prepare`**
- 从机会中准备草稿票据。
**`POST /api/booky/confirm/:id`**
- 在半自动模式下确认票据 (在投资组合中镜像)。
**`POST /api/booky/cancel/:id`**
- 取消草稿票据。
**`GET /api/booky/token-health`**
- 真实 JWT 的状态 (`exp`,剩余分钟,认证)。
**`POST /api/booky/token/renew`**
- 触发辅助 token 续订。
**`GET /api/booky/account?refresh=1&historyLimit=60`**
- 按配置文件的真实账户快照 (余额 + 已对账的远程历史记录)。
**`GET /api/booky/capture/latest`**
- `data/booky` 中的最新 payload 捕获。
**`POST /api/booky/real/dryrunid`**
- 构建最终的 `placeWidget` payload,而不发送真实下注。
**`POST /api/booky/real/confirm/:id`**
- 标准真实确认 (带有防护)。
**`POST /api/booky/real/confirm-fast/:id`**
- 具有不确定状态处理和受控重试的快速真实确认。
## ⚙️ 高级配置
### 环境变量 (`.env`)
```
# 核心
NODE_ENV=development
PORT=3000
TZ=America/Lima
DISABLE_BACKGROUND_WORKERS=false
# Altenar Profile (set-book-profile.js)
BOOK_PROFILE=doradobet
ALTENAR_INTEGRATION=doradobet
ALTENAR_ORIGIN=https://doradobet.com
ALTENAR_REFERER=https://doradobet.com/deportes-en-vivo
ALTENAR_COUNTRY_CODE=PE
ALTENAR_CULTURE=es-ES
ALTENAR_TIMEZONE_OFFSET=300
ALTENAR_NUM_FORMAT=en-GB
ALTENAR_DEVICE_TYPE=1
ALTENAR_SPORT_ID=0
# 可选 Overrides
# ALTENAR_WIDGET_BASE_URL=https://sb2frontend-altenar2.biahosted.com/api/widget
# ALTENAR_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
# ALTENAR_ACCEPT_LANGUAGE=es-ES,es;q=0.9,en;q=0.8
# Booky / Real Placement
# ALTENAR_BOOKY_URL=https://www.casinoatlanticcity.com/apuestas-deportivas#/overview
# ALTENAR_LOGIN_USERNAME=tu_usuario
# ALTENAR_LOGIN_PASSWORD=tu_password
# ALTENAR_BOOKY_AUTH_TOKEN=Bearer
# BOOKY_REAL_PLACEMENT_ENABLED=false
# BOOKY_KEEP_REAL_PLACEMENT_ON_TOKEN_REFRESH=false
# BOOKY_AUTO_TOKEN_REFRESH_ENABLED=true
# BOOKY_TOKEN_MIN_REMAINING_MINUTES=2
# BOOKY_MIN_EV_PERCENT=2
# BOOKY_MAX_ODD_DROP=0.20
# AUTO_SNIPE(受控推出)
# AUTO_PLACEMENT_PROVIDER=booky
# AUTO_SNIPE_ENABLED=false
# AUTO_SNIPE_DRY_RUN=true
# AUTO_SNIPE_MIN_EV_PERCENT=3
# AUTO_SNIPE_MIN_STAKE_SOL=2
# AUTO_SNIPE_MAX_BETS_PER_HOUR=4
# AUTO_SNIPE_COOLDOWN_PER_PICK_MS=120000
# AUTO_SNIPE_REQUIRE_REAL_PLACEMENT_ENABLED=true
# AUTO_SNIPE_REENTRY_MIN_ODD_IMPROVEMENT_PCT=8
# AUTO_SNIPE_REENTRY_MIN_ODD_POINTS=0.30
# AUTO_SNIPE_MAX_ENTRIES_PER_PICK=2
# Matcher 诊断/调优
# MATCH_DIAGNOSTIC_LOG=1
# MATCH_FUZZY_THRESHOLD=0.77
# MATCH_MIN_ACCEPT_SCORE=0.60
# MATCH_TIME_TOLERANCE_MINUTES=5
# MATCH_TIME_EXTENDED_TOLERANCE_MINUTES=30
# booky 历史记录/账户清理(可选)
# BOOKY_BALANCE_REFRESH_MS=45000
# BOOKY_HISTORY_REFRESH_MS=60000
# BOOKY_HISTORY_RETENTION_DAYS=30
# BOOKY_PROFILE_HISTORY_MAX_ITEMS=500
# prematch endpoint 性能(可选)
# PREMATCH_CACHE_TTL_MS=20000
```
#### 高负载模式 (Sabadazo)
要在比赛量激增时保持仪表板响应迅速:
```
DISABLE_BACKGROUND_WORKERS=false
DISABLE_LIVE_SCANNER=false
DISABLE_PREMATCH_SCHEDULER=true
DISABLE_PINNACLE_INGEST_CRON=true
DISABLE_MONITOR_DASHBOARD=true
# 解锁 LIVE 的 profile(临时)
LIVE_SNIPE_REQUIRE_PINNACLE_LIVE=false
LIVE_VALUE_MIN_EV=0.01
LIVE_VALUE_REQUIRE_SCORE_SYNC=false
LIVE_VALUE_SCORE_SYNC_MAX_GOAL_DIFF=1
LIVE_VALUE_STABILITY_MIN_HITS=1
LIVE_VALUE_STABILITY_MIN_AGE_MS=1500
LIVE_GLOBAL_STABILITY_MIN_HITS=1
```
然后,重启后端。在正常的日子,将两个标志都恢复为 `false`。
### 自定义风险配置
编辑 `src/utils/mathUtils.js`:
```
const RISK_PROFILES = {
'PREMATCH_VALUE': 0.25, // Cambiar a 0.30 para ser más agresivo
'LIVE_VALUE': 0.125,
'LIVE_SNIPE': 0.10, // Cambiar a 0.05 para ser ultra-conservador
};
```
### 调整过滤器 (扫描器)
编辑 `src/services/scannerService.js`:
```
// Línea ~135: Filtro de Stake Mínimo
ops = ops.filter(op => op.kellyStake >= 1.00); // Cambiar a 0.50 para capturar más ops
```
## 📋 脚本与命令指南
BetSniper 包含两种类型的脚本:**npm 命令** (在 `package.json` 中定义,使用 `npm run `) 和**直接脚本** (在 `scripts/` 中,使用 `node scripts/.js`)。本指南按功能对它们进行组织。
### 🧩 npm 命令 (package.json)
#### 主服务器
| 命令 | 描述 |
|---|---|
| `npm start` | 以生产模式启动服务器 (`node server.js`)。无热重载。 |
| `npm run dev` | 使用 **nodemon** 以开发模式启动服务器 (保存文件时重新启动)。推荐用于开发。 |
#### Booky 配置文件管理
| 命令 | 描述 |
|---|---|
| `npm run book:dorado` | 在 `.env` 中将活动配置文件更改为 **DoradoBet** (`BOOK_PROFILE`、`ALTENAR_INTEGRATION`、`ALTENAR_ORIGIN`、`ALTENAR_REFERER`)。无需重启服务器。 |
| `npm run book:acity` | 在 `.env` 中将活动配置文件更改为 **ACity** (Casino Atlantic City)。无需重启服务器。 |
#### JWT Token 提取 (Booky 认证)
| 命令 | 模式 | 描述 |
|---|---|---|
| `npm run token:booky` | 有头模式 + 自动超时 | 打开带有可视化会话的 Chrome。在检测到登录后自动捕获 JWT。超时后自动关闭。 |
| `npm run token:booky:wait-close` | 有头模式 + 手动等待 | 打开 Chrome,捕获 JWT 并**等待用户手动关闭窗口**。如果登录需要 2FA 或 captcha,则非常有用。 |
| `npm run token:booky:dorado` | DoradoBet + 有头模式 | 与 `token:booky` 相同,但强制使用 DoradoBet 配置文件,即使 `.env` 另有指定。 |
| `npm run token:booky:dorado:wait-close` | DoradoBet + 手动 | 组合:DoradoBet 配置文件 + 手动等待关闭。 |
| `npm run token:booky:acity` | ACity + 有头模式 | 强制使用 ACity 配置文件。 |
| `npm run token:booky:acity:wait-close` | ACity + 手动 | ACity + 手动等待。 |
**它内部是如何工作的?**
1. 从 `.env` 中读取 `ALTENAR_BOOKY_URL`、`ALTENAR_LOGIN_USERNAME`、`ALTENAR_LOGIN_PASSWORD`。
2. 打开 Puppeteer,导航到 bookie,自动登录。
3. 拦截包含 JWT 的服务器响应。
4. 验证它是经过身份验证的用户 token (而非访客)。
5. 在您的 `.env` 中写入 `ALTENAR_BOOKY_AUTH_TOKEN=Bearer eyJ...`。
**预期输出:**
```
✅ Login detectado. Capturando JWT...
🔑 JWT válido: usuario antho@ejemplo.com | Expira: 2026-03-03T14:22:00Z
💾 Token escrito en .env
```
#### 捕获 placeWidget Payload
为了理解当您下真实注时 bookie 发送了什么 (用于构建 `confirmRealPlacement` 的 payload) 所必需。
| 命令 | 模式 | 描述 |
|---|---|---|
| `npm run capture:booky` | 有头模式 + 当前配置文件 | 打开 Chrome,导航到 bookie,在点击投注时捕获 `placeWidget`/betslip payload。 |
| `npm run capture:booky:headless` | 无头模式 | 相同但没有可见窗口。 |
| `npm run capture:booky:dorado` | DoradoBet 有头模式 | 强制使用 DoradoBet 配置文件。 |
| `npm run capture:booky:dorado:headless` | DoradoBet 无头模式 | 强制使用 DoradoBet 且无窗口。 |
| `npm run capture:booky:acity` | ACity 有头模式 | 强制使用 ACity 配置文件。 |
| `npm run capture:booky:acity:headless` | ACity 无头模式 | ACity 且无窗口。 |
捕获的 payload 保存在 `data/booky/capture-*.json` 中,并可通过 `GET /api/booky/capture/latest` 获取。
#### Pinnacle 投放置 Payload 捕获 (阶段 1)
在实现 `pinnacleSemiAutoService` 之前,发现 Pinnacle 的真实契约 (端点 + request body + 响应) 所必需。
| 命令 | 模式 | 描述 |
|---|---|---|
| `npm run capture:pinnacle:placement` | 有头模式 + 手动等待 | 使用专用配置文件打开 Pinnacle,捕获可能是 placement 的 `POST/PUT/PATCH` 请求,并等待手动关闭浏览器。 |
| `npm run capture:pinnacle:placement:headless` | 无头模式 + 超时 180 秒 | 在时间窗口内执行自动捕获,无 UI。 |
**捕获输出:**
- `data/pinnacle/capture-placement-.json`
- `data/pinnacle/capture-placement.latest.json`
**推荐流程 (安全):**
1. 执行 `npm run capture:pinnacle:placement`。
2. 在 Pinnacle 窗口中,手动下注直到最后一次确认点击。
3. 关闭浏览器以完成并持久化捕获。
4. 检查 `capture-placement.latest.json` 并提取:
- 确切的端点,
- 必需的 headers,
- payload schema,
- 响应语义 (已确认/已拒绝/重新报价)。
提示:如果未出现任何候选,请使用以下命令重复:
```
node scripts/capture-pinnacle-placement.js --headed --wait-close --all-posts
```
#### 历史和配置文件侦察
| 命令 | 描述 |
|---|---|
| `npm run spy:altenar` | 通过拦截真实流量自动检测 bookie 的集成参数 (integration、countryCode、baseUrl)。如果 bookie 更改其配置,这很有用。保存在 `data/altenar-profile-*.json` 中。 |
| `npm run spy:booky:history` | 打开 Chrome (有头模式),导航到 bookie 历史记录并捕获余额和历史记录端点的完整响应。保存在 `data/booky/spy-history-*.json` 中。 |
| `npm run spy:booky:history:headless` | 与上一个相同,但为无头模式,超时为 120 秒。 |
#### Booky 流程冒烟测试
| 命令 | 描述 |
|---|---|
| `npm run smoke:booky` | 以**安全**模式执行完整的 E2E 流程:`token-health` → `account snapshot` → `prepare ticket` → `confirm-fast`。**不发送任何真实投注**,即使 `BOOKY_REAL_PLACEMENT_ENABLED=true`。 |
| `npm run smoke:booky:live` | 相同,但经过真实投放置路径 (需要 `BOOKY_REAL_PLACEMENT_ENABLED=true` 且 EV ≥ 0%)。仅用于在受控环境中验证完整流程。 |
| `npm run health:latency` | 对关键端点 (`portfolio`、`live`、`prematch`、`booky/account`、`kelly-diagnostics`) 执行基于样本的延迟检查,以检测冻结/事件循环阻塞。 |
### 📥 手动摄入脚本 (`scripts/`)
服务器会自动执行这些摄入,但您可以手动强制执行:
#### `node scripts/ingest-altenar.js`
更新 `db.json` 中 Altenar (DoradoBet) 的 prematch 事件缓存。
- **智能跳过:** 如果数据少于 100 分钟,则自动跳过摄入。
- **强制:** 通过将 `force=true` 更改为内部调用或删除 `db.json` 中的 `altenarLastUpdate` 来强制执行。
- **何时使用:** 在 prematch 交易会话之前,以确保数据新鲜。
#### `node scripts/ingest-pinnacle.js`
将 Pinnacle Arcadia (REST) prematch 事件下载并标准化到 `db.json` 中。
- 将美式赔率转换为小数。
- 计算公平概率 (无利润加成)。
- **何时使用:** 每天一次,或者每当您想刷新 Pinnacle prematch 缓存时。
推荐命令:
```
# 正常模式(带增量 flush)
npm run ingest:pinnacle:force
# OneDrive 安全模式(无增量 flush)
npm run ingest:pinnacle:safe
```
### 🔍 手动扫描器脚本
#### `node scripts/scan_live.js [--dry-run]`
以 60 秒循环的独立模式 (在服务器之外) 运行实时扫描器。
- **`--dry-run` / `-d`:** 观察者模式 — 检测机会但**不注册投注**。如果服务器已在运行,请务必使用。
- 无 `--dry-run`:在 `db.json` 中注册投注 (仅在服务器停止时使用)。
```
# 观察者模式(在服务器运行时推荐)
node scripts/scan_live.js --dry-run
# 主动模式(仅在服务器停止时使用)
node scripts/scan_live.js
```
#### `node scripts/run_linker.js`
以独立模式运行 prematch 扫描器。
- 读取 `db.json` (Pinnacle 和 Altenar 缓存)。
- 交叉比对事件并显示检测到的 prematch 机会。
- 不注册投注。仅用于诊断。
```
node scripts/run_linker.js
```
### 🦵 数据库诊断脚本
#### `node scripts/check_db.js`
显示 `db.json` 状态的快速摘要:每个集合中的记录数量 (pinnacle、matches、scanned_prematch 等)。
```
node scripts/check_db.js
# 输出:Keys: ['config','upcomingMatches','portfolio',...]
# Pinnacle Count: 142
```
#### `node scripts/find_match_in_db.js `
在 `db.json` 和 `data/pinnacle_live.json` 中递归搜索球队或比赛。
```
node scripts/find_match_in_db.js "Liverpool"
node scripts/find_match_in_db.js "Tigres"
```
#### `node scripts/find_live_match.js`
在当前 Pinnacle 实时数据流 (`data/pinnacle_live.json`) 中搜索特定比赛。
#### `node scripts/check_linked_status.js`
检查 `db.json` 中特定记录的链接状态 (用于调试特定比赛未链接的原因)。
#### `node scripts/check_odds.js`
通过交叉比对 Pinnacle 和 Altenar 数据,显示特定比赛的存储赔率。
#### `node scripts/check_mapping.js`
显示 `db.json` 中 `mappedTeams` 字典的条目,以验证保存的别名。
#### `node scripts/generate_full_report.cjs`
生成完整的 CSV (`reporte_completo_partidos.csv`),包含所有 Pinnacle 和 Altenar 的比赛,显示它们是否已链接。可用于审计匹配器的质量。
```
node scripts/generate_full_report.cjs
# 创建:reporte_completo_partidos.csv
```
### 🧹 投资组合维护脚本
#### `node scripts/reset_database.js`
将 `db.json` 恢复为默认值 (资金 100,历史为空)。
```
node scripts/reset_database.js
```
#### `node scripts/force_settle_bets.js`
强制清算卡在 `PENDING` 状态的活跃投注。
- 查询 Altenar 的结果 API (`GetEventResults`) 以获取最终比分。
- 对每种类型的 pick (home、away、draw、over、under) 应用赢/输逻辑。
- 当僵尸协议 无法自动清算时很有用。
```
node scripts/force_settle_bets.js
```
#### `node scripts/purge_invalid_bets.cjs`
从 `db.json` 中手动删除具有无效或损坏事件 ID 的投注。
- 要清除的 ID 列在脚本本身中 (`TARGET_IDS`)。
- 如果发现新的损坏投注,请编辑数组以添加 ID。
```
node scripts/purge_invalid_bets.cjs
```
#### `node scripts/fix_under_2_bets.cjs`
修复行 为 `under_0` (行 解析错误) 的 Under 投注。从 `market` 字段中提取真实的行 并更正 `pick`。
```
node scripts/fix_under_2_bets.cjs
```
#### `node scripts/migrate_bankroll.js`
将 `db.json` 中的资金池缩放至新值 (例如从 1,000 到 10,000),对余额和历史记录按比例应用相同的因子。
- 在执行前编辑脚本内的 `newCapital` 值。
```
node scripts/migrate_bankroll.js
```
### 🧪 测试和 Mock 脚本
#### `node scripts/mock_pinnacle.js`
生成一个包含虚构数据 (如 Man City vs Liverpool 等测试比赛) 的 `data/pinnacle_live.json` 文件。允许在没有真实 Pinnacle 连接的情况下开发和测试扫描器。
```
node scripts/mock_pinnacle.js
# 在 data/pinnacle_live.json 中创建测试数据
```
#### `node scripts/tmp-run-booky-confirm.mjs`
直接执行 booky 票据确认,用于热测试。执行前需要在文件中编辑 `ticketId`。
```
node scripts/tmp-run-booky-confirm.mjs
```
### 🐞 调试脚本 (开发人员工具)
通用诊断脚本。它们不属于正常的操作流程,但对于调查实时问题很有用:
| 脚本 | 何时使用? |
|------|
| `debug_live.js` | 查看 Altenar 实时数据流的原始结构 (`GetLivenow`) |
| `debug_live_event.js` | 检查具有所有详细信息的特定实时事件 |
| `debug_live_markets.js` | 查看实时事件中可用的市场 (开放/关闭) |
| `debug_live_names.js` | 查看 Altenar 返回的原始球队名称 |
| `debug_live_odds.js` | 比较实时事件中 Altenar 与 Pinnacle 的赔率 |
| `debug_live_structure_v3.js` | 探索实时端点的完整 JSON 结构 (当前版本) |
| `debug_matching.js` | 调试为什么特定的一对球队没有链接 |
| `debug_matcher_specific.js` | 使用当前 Pinnacle 缓存 (`data/pinnacle_live.json`) 测试 `findMatch()` |
| `debug_full_scan.js` | 以最大日志记录执行完整的机会扫描 |
| `debug_monitor_link.js` | 诊断监视器为何不显示 Arcadia 赔率 (计算 `linked` 和 `pinnacleFound`) |
| `debug_pinnacle_structure.js` | 查看 Pinnacle Arcadia 响应的原始结构 |
| `debug_pinnacle_raw.js` | 查看 Pinnacle 未经转换的原始 HTTP 响应 |
| `debug_pinnacle_endpoints.js` | 测试不同的 Pinnacle 端点 (matchups、odds 等) |
| `debug_pinnacle_markets.cjs` | 检查 Pinnacle 事件中可用的市场 |
| `debug_pinnacle_match_info.cjs` | 查看 Pinnacle 比赛的完整信息 (球队、赔率、状态) |
| `debug_altenar_markets.js` | 检查 Altenar 事件的市场 (1X2、Totals、BTTS) |
| `debug_totals_structure.js` | 查看 Over/Under 市场结构以验证标准化 |
| `debug_scanner_v2.js` | 逐步分析扫描器的输出 (当前版本) |
| `audit_date.js` | **投资组合历史审计:** 查询 `GetEventResults`,将真实最终比分与 `db.json` 中记录的进行比较,纠正错误的 WON/LOST 状态并调整余额。接受日期作为参数。 |
```
# Matching 诊断:
node scripts/debug_matching.js
node scripts/debug_matcher_specific.js
# Monitor/Linking 诊断:
node scripts/debug_monitor_link.js
node scripts/debug_full_scan.js
# 投资组合历史审计(修正 score/状态):
node scripts/audit_date.js 2026-03-01
```
### 📊 快速摘要:每种情况下该执行什么?
| 情况 | 命令 |
|---|---|
| **每日正常启动** | 终端 1:`npm run dev` \| 终端 2:`node services/pinnacleLight.js` \| 终端 3:`cd client && npm run dev` |
| **更改 bookie** | `npm run book:acity` 或 `npm run book:dorado` |
| **更新 JWT token** | `npm run book:dorado` → `npm run token:booky:wait-close` |
| **验证一切正常** | `npm run smoke:booky` |
| **在无活动服务器的情况下查看机会** | `node scripts/scan_live.js --dry-run` |
| **强制更新 prematch 数据** | `node scripts/ingest-altenar.js` + `node scripts/ingest-pinnacle.js` |
| **投注卡在 PENDING** | `node scripts/force_settle_bets.js` |
| **匹配器未链接球队** | `node scripts/debug_matching.js` 或 `node scripts/debug_matcher_specific.js` → 在 `dynamicAliases.json` 中添加别名 |
| **投注的比分/结果不正确** | `node scripts/audit_date.js YYYY-MM-DD` (更正 `db.json` 中的状态) |
| **审计匹配器质量** | `node scripts/generate_full_report.cjs` |
| **重置数据库** | `node scripts/reset_database.js` |
| **在没有真实 Pinnacle 的情况下进行测试** | `node scripts/mock_pinnacle.js` → `npm run dev` |
## 🚨 故障排除
### 问题:"Chromium 未关闭"
**症状:** Chrome 窗口无限期保持打开。
**原因:** Pinnacle token 未能正确捕获。
**解决方案:**
1. 手动关闭 Chrome 窗口。
2. 删除文件:`rm data/pinnacle_token.json`
3. 重启:`node services/pinnacleLight.js`
4. 当 Chrome 打开时再次登录 Pinnacle。
### 问题:"前端未出现机会"
**症状:** "赛前" 和 "实时" 选项卡为空。
**诊断:**
1. 验证 3 个进程是否都在运行 (终端 1, 2, 3)。
2. 检查终端 1 (服务器) 的日志以查找错误。
3. 直接在浏览器中打开 `http://localhost:3000/api/opportunities/live`。
**解决方案:**
- **如果 API 返回 `[]`:** 匹配器未链接事件。使用 "匹配器" 选项卡强制链接。
- **如果出现 500 错误:** 检查 `data/pinnacle_live.json` 是否存在并包含数据。
- **如果 `pinnacle_live.json` 为空:** 终端 2 进程失败。重启。
### 问题:"间歇性延迟 / 加载时屏幕闪烁"
**症状:** 有时加载快,有时全部超时 (资金、机会、kelly card)。
**快速诊断:**
```
npm run health:latency
```
**如果出现间歇性超时:**
1. 激活高负载模式 (`DISABLE_PREMATCH_SCHEDULER=true`,`DISABLE_PINNACLE_INGEST_CRON=true`;如果监视器已打开,可选 `DISABLE_MONITOR_DASHBOARD=true`)。
2. 重启后端。
3. 重复 `npm run health:latency` 并确认 `portfolio/live` 是否稳定。
### 问题:"数据冻结 (陈旧数据)"
**症状:** 监视器显示过时的时间 (例如:Altenar 在第 75 分钟,Pinnacle 在第 70 分钟)。
**原因:** Pinnacle WebSocket 停止接收帧。
**自动解决方案:** 系统检测到这一点并创建 `data/pinnacle_stale.trigger`,触发 socket 的自动重启。
**手动解决方案:**
1. 停止终端 2:`Ctrl+C`
2. 重启:`node services/pinnacleLight.js`
### 问题:"重复投注"
**症状:** 同一事件在 "活跃" 中出现两次。
**原因:** 您在服务器运行时在没有 `--dry-run` 标志的情况下执行了 `scan_live.js`。
**预防:** 如果服务器处于活动状态,**务必**使用 `--dry-run`。
**清理:**
```
// Abrir db.json y eliminar manualmente la entrada duplicada en activeBets[]
```
### 问题:"Booky token 无效或过期"
**症状:** 端点 `/api/booky/real/*`
**用 ❤️ 为算法交易员构建**
⭐ 如果这个项目对你有帮助,请考虑在 GitHub 上给它点个星
**具备量化风险管理的自动化体育交易系统**
[](https://nodejs.org/)
[](https://reactjs.org/)
[](LICENSE)
## 🔐 网络安全视角
本项目演示了以下实际应用技术:
- WebSocket 流量分析
- 实时系统行为监控
- 事件驱动平台的逆向工程
## 📖 目录
- [概述](#-descripción-general)
- [最近更新 (最新 Commit)](#-cambios-recientes-último-commit)
- [核心功能](#-características-principales)
- [系统架构](#-arquitectura-del-sistema)
- [交易策略](#-estrategias-de-trading)
- [财务管理](#-gestión-financiera-portfolio-theory)
- [预操作配置指南](#-guía-de-configuración-pre-operativa)
- [安装与部署](#-instalación-y-despliegue)
- [用户界面](#-interfaz-de-usuario-dashboard)
- [API 端点](#-api-endpoints)
- [高级配置](#-configuración-avanzada)
- [脚本与命令指南](#-guía-de-scripts-y-comandos)
- [故障排除](#-troubleshooting)
- [路线图](#-roadmap)
## 🌟 概述
**BetSniper V3** 是一款基于量化金融原理设计的高频体育交易系统。该系统作为**算法套利者**,在“ Sharp Bookie ”(Pinnacle/Arcadia - 真实概率来源)和“ Soft Bookie ”(Altenar/DoradoBet - 目标市场)之间交叉比对实时数据,以识别市场低效并执行正期望值 (EV+) 策略。
### 工作原理?
1. **数据接入:** 通过与 Pinnacle 的 WebSocket 连接获取“无利润加成”的公平赔率,代表事件的真实概率。
2. **市场扫描:** 持续分析 Altenar (DoradoBet) 的赔率,以检测其与真实概率的差异。
3. **数学计算:** 应用带有动态风险配置的凯利公式来确定每次投注的最佳规模。
4. **模拟执行:** 模拟交易系统,模拟投注的执行并实时追踪损益 (P&L)。
5. **持续监控:** 追踪活跃投注的状态,并根据真实结果进行自动清算。
## 🆕 最近更新 (最新 Commit)
本节总结了自上次 commit 以来的实现内容,以提供技术和操作上的可追溯性。
### 2026-04-01 更新 (v3.4.24)
- **阶段 1 (ARBITRAGE 单腿半自动):**
- `client/src/App.jsx` 现在允许在每条腿的 provider 为 Altenar 时,从套利卡片中执行单腿操作。
- 如果该腿为 Arcadia/Pinnacle,则显示为**参考**,在阶段 1 不进行自动发送。
- 增加了将套利腿 (`1x2` 和 `DC+opuesto`) 转换为半自动流程可执行机会的功能。
- **真实发送 前的强制试运行:**
- 在真实的 `confirm` 之前,会强制执行 `POST /api/booky/real/dryrun/:id`。
- 如果 dry-run 失败,票据将被取消且不会发送 `placeWidget`。
- dry-run 的摘要 包含在 UI 可见的确认块中。
- **阶段 2 (Arcadia -> Altenar 顺序双重执行):**
- ARBITRAGE 选项卡中的每个机会新增了 `Ejecutar Dual` 按钮。
- 操作顺序:Arcadia (`prepare -> dryrun -> confirm-fast`),然后是 Altenar (`prepare -> dryrun -> confirm-fast`)。
- 根据 provider (Arcadia 和 Altenar) 自动选择腿,优先选择每侧 stake 最大的。
- UI 中的明确结果:
- `CONFIRMED` (双腿均已确认),
- `REJECTED` (Altenar 执行前 Arcadia 失败),
- `HEDGE_REQUIRED` (Arcadia 已确认且 Altenar 被拒绝或状态不确定)。
- **并发执行控制:**
- 为每张套利卡片设置锁,以防止在双重序列期间同时触发。
### 2026-04-01 更新 (v3.4.23)
- Sprint A.1 套利的 `Double Chance + opuesto 1x2` 管道现已投入运行。
- 增强了 DC 市场在摄入、缓存优先和扫描器之间的持久性,以防止数据丢失:
- `scripts/ingest-pinnacle.js` 现在持久化从 1x2 派生的 `odds.doubleChance`。
- `src/services/prematchScannerService.js` 在从 prematch 缓存中补充 `upcomingMatches` 时保留了 `doubleChance`。
- `scripts/ingest-altenar.js` 和 `src/services/altenarPrematchScheduler.js` 现在根据 Altenar 的真实结构 (`desktopOddIds/mobileOddIds`,`typeId` 9/10/11) 映射 DC。
- 调整后的操作验证:
- `upcomingMatches=63`,`pinnacleWithDC=59`
- `altenarUpcoming=81`,`altenarWithDC=79`
- `linked=30`,`linkedAltWithDc=30`
- 结果:双方均已为 2 腿套利机会启用了 DC 技术覆盖;如果在特定快照中没有机会,原因在于定价/市场,而不是缺乏 DC 数据。
### 2026-03-29 更新 (v3.4.22)
- Pinnacle 现在具备完整的历史恢复和本地对账功能:
- `GET /api/pinnacle/history` 同步 Arcadia 的远程投注,并通过 `providerBetId` 与 `portfolio` 进行交叉比对。
- `GET /api/pinnacle/account` 支持用于操作快照的可选刷新/历史记录。
- 新增了锚定到外部现金流 (`/transactions`) 的 Pinnacle 真实 PnL 计算:
- 资本基数基于存款/取款,
- 显示的 PnL = 当前余额 - 外部基数,
- 避免将 Pinnacle 余额与 Booky PnL 混淆。
- 强化的自动投放置 UI:
- 运行时手动选择 provider (`BOOKY`/`PINNACLE`),
- PINNACLE 徽章/按钮,支持历史记录手动同步 (悬停时显示 `SYNC PINNACLE`,进行中显示 `SYNC...`),
- 响应式布局以避免卡片溢出。
- 改进的已结束投注,带有来源可追溯性:
- 来源徽章 (`BOOKY`/`PINNACLE`/`SIM`) 和 provider 过滤器 (`ALL/BOOKY/PINNACLE/SIM`)。
- 新增 Pinnacle 操作/诊断脚本:
- `npm run capture:pinnacle:account`
- `npm run capture:pinnacle:account:headless`
- `scripts/debug-pinnacle-history-endpoints.js`
完整技术详情请见 [CHANGELOG.md](CHANGELOG.md),条目 `v3.4.22`。
### 快速操作 SOP (v3.4.22)
发布后日常操作的推荐检查清单:
1. 验证 Pinnacle 账户:
- `GET /api/pinnacle/account?refresh=1`
- 确认 `balance`、`pnl.total`、`pnl.baseCapital` 和 `pnl.baseCapitalSource`。
2. 验证历史同步:
- `GET /api/pinnacle/history?refresh=1&status=settled&days=180&limit=500`
- 确认 `success=true` 并检查 `reconcileStats.touchedCount`。
3. 验证 UI:
- 在 Auto Placement 中,选择 `PINNACLE`。
- 确认蓝色徽章 (`PINNACLE`) 和同步操作 (悬停时显示 `SYNC PINNACLE`)。
- 执行手动同步并验证状态 `SYNC...` + 成功消息。
4. 验证财务一致性:
- Pinnacle 显示的 PnL 应遵循:`PnL = 当前余额 - 外部资本基数`。
5. 最低监控:
- 每日进行 1 次手动控制同步。
- 检查 `transactions` 或 `history` 中是否有 `error`。
建议在生产环境中调整的变量:
- `PINNACLE_PNL_WINDOW_DAYS`
- `PINNACLE_PNL_BASE_CAPITAL` (仅备用)
- `PINNACLE_HISTORY_DEFAULT_DAYS`
- `PINNACLE_HISTORY_DEFAULT_STATUS`
### 2026-03-29 更新 (v3.4.21)
- `scannerService` 中的运行时自动投放置 provider 选择器 (`booky` 或 `pinnacle`)。
- 新增查询端点:`GET /api/opportunities/live/placement-provider`。
- 新增热切换端点:`POST /api/opportunities/live/placement-provider`。
- 启动/实时决策诊断现在显示活动 provider 和允许的选项。
### 2026-03-29 更新 (v3.4.20)
- 自动投放置现在通过配置 (`AUTO_SNIPE_ALLOWED_TYPES`) 支持多种策略,默认激活 `LIVE_SNIPE`、`LA_VOLTEADA` 和 `LIVE_VALUE`。
- 移除了将 `LIVE_VALUE` 排除在自动引擎之外的硬编码限制。
- 改进的运行时诊断:`GET /api/opportunities/live/diagnostics` 现在包含 `scanner.autoPlacementAllowedTypes`。
- 丢弃原因从 `not-snipe` 演变为 `type-not-enabled`,当类型未启用时。
- 再次扩展了 `src/utils/dynamicAliases.json` 以加强国际匹配 (队伍/球队/选项的名称变体)。
- 完整详情请见 [CHANGELOG.md](CHANGELOG.md),条目 `v3.4.20`。
### 2026-03-26 更新 (v3.4.19)
- 已结束投注现在可以正确区分执行来源:
- `BOOKY` 代表远程,
- `REAL` 代表本地真实,
- `SIM` 仅代表模拟。
- 修复了通过 `eventId`/`match` 进行比分回退导致的同一比赛行中比分视觉不一致的问题 (`1-1` vs `?-?`)。
- 在已结束选项卡中,现在也为本地真实行 (`isRealHistory`) 显示 `Ticket