MadMarket Low-Latency Sports Betting Terminal by Maksim ZubovMadMarket Low-Latency Sports Betting Terminal by Maksim Zubov

MadMarket Low-Latency Sports Betting Terminal

Maksim Zubov

Maksim Zubov

MadMarket (Mollybet) Low-Latency Courtsiding Terminal

A high-frequency, modular CLI execution terminal built on Node.js tailored for semi-automated sports betting on the MadMarket platform (powered by the Mollybet API engine). Engineered specifically for high-stress, time-critical trading environments such as courtsiding and after-goal execution.

1. Architectural Philosophy & Performance Constraints

Zero Runtime Bloat:
Built exclusively on native Node.js core modules (node:http2, node:readline, node:crypto, node:perf_hooks, node:events).
The only permitted external dependency is ws for low-overhead Boost.Beast WebSocket streaming.
Pre-Warmed HTTP/2 Multiplexing:
Persistent http2.connect session maintained against https://edge.madmarket.io.
Background HTTP/2 PING frames dispatched every 15 seconds to keep connection warm and measure sub-millisecond RTT.
Order execution simply serializes a minimal JSON buffer into an already active HTTP/2 stream, achieving local package construction latency of < 1 ms.
Adapter Design Pattern (BaseAdapter $\rightarrow$ MollybetAdapter):
Isolates broker-specific endpoints, headers, payload serializations, and bet-type resolvers.
Ready for plug-and-play direct broker connectors (Pinnacle Direct API, BetInAsia, Singbet, Betfair Exchange) without touching execution engine or UI core.
Strict 3-Betslip Session Quota:
Mollybet restricts accounts to a maximum of 3 concurrent active betslips per session.
The engine manages an intelligent pre-armed pool, actively closing unused slips via HTTP DELETE /v1/betslips/<id>/ when switching matches or market modes.
Tmux & SSH-Safe Raw Mode UI:
Single-key instant trigger execution via process.stdin.setRawMode(true).
Complete UTF-8 escape-code flicker-free differential HUD rendering.

2. Project File Structure


3. Mollybet Protocol & API Specifications

3.1 Base Headers & Authentication

All HTTP/2 requests to https://edge.madmarket.io must transmit:

3.2 Key Endpoints

Pre-Arm Betslip: POST /v1/betslips/

Delete Betslip (Quota Release): DELETE /v1/betslips/<betslipId>/
Closes the slip on broker servers to stay within the 3-slip limit.
Dispatch Combat Order: POST /v1/orders/

Order Status Polling: GET /v1/orders/<orderId>/
Returns { status: "done" | "in_flight" | "failed", closed: true, close_reason: "order_filled" | "timed_out", ... }.

3.3 Bet-Type String Encoding Rules

Mollybet encodes handicap quarter-lines as integers multiplied by 4 (lineIndex = Math.round(line * 4)):

Football ("fb" / "fb_ht")

Full-Time Total Over: for,ir,<h>,<a>,ahover,<lineIndex> (e.g. Total Over 2.5 at 0-0 $\rightarrow$ for,ir,0,0,ahover,10)
Full-Time 1X2 Moneyline: for,h (Home), for,a (Away)
Individual Team Totals (ITB):
ITB1 (Team 1 / Home Over): for,ir,<h>,<a>,tahover,h,<lineIndex>
ITB2 (Team 2 / Away Over): for,ir,<h>,<a>,tahover,a,<lineIndex>
1st Half (HT) Markets:
Sent with "sport": "fb_ht".
HT Total Over 0.5 at 0-0 $\rightarrow$ for,ir,0,0,ahover,2.

Ice Hockey ("ih")

Always sent with "sport": "ih" in POST /v1/betslips/ (for both Regular Time 60m and Period markets), and without ir,<h>,<a> in bet_type:
Regular Time 60m (FT, prefix tp,reg):
OVER (Default line 5.5 $\rightarrow$ 22): for,tp,reg,ahover,<lineIndex>
HOME / AWAY (1X2 60m): for,tp,reg,wdw,h / for,tp,reg,wdw,a
ITB1 / ITB2 (Default line 2.5 $\rightarrow$ 10): for,tp,reg,tahover,h,<lineIndex> / for,tp,reg,tahover,a,<lineIndex>
Current Period $N \in {1, 2, 3}$ (HT, prefix tperiod,<N>):
Automatically tracks active period $N$ from ir_status.period_scores:
HT_OVER (Default line 1.5 $\rightarrow$ 6): for,tperiod,<N>,ahover,<lineIndex>
HT_HOME / HT_AWAY: for,tperiod,<N>,wdw,h / for,tperiod,<N>,wdw,a
HT_ITB1 / HT_ITB2 (Default line 0.5 $\rightarrow$ 2): for,tperiod,<N>,tahover,h,<lineIndex> / for,tperiod,<N>,tahover,a,<lineIndex>

4. Key Engineering Features & Fixes

4.1 Strict Bookmaker Filtering (getAllowedBookies())

Problem: Third-party aggregate bookmakers (such as Parimatch pmk or betdex) send distorted quotes (e.g. quote 93.257 with limit €1218 on closed lines). When using Math.max(), these quotes polluted Pinnacle's (pin88) real market prices.
Solution: Both MollybetAdapter and StateManager strictly filter quotes using getAllowedBookies() derived from config.accounts and config.adaptiveBookies. Any PMM frame or bookies_with_offers item from non-configured bookmakers (pmk, betdex, sbo, etc.) is immediately rejected. Only pin88 quotes and stake limits are accepted.

4.2 Honest Closed Line Reset & Zero-Price Display

Problem: When a moneyline or total market was suspended or closed by Pinnacle (e.g. 1=0, 2=0 on blowout or danger moments), previous logic kept stale prices because it ignored zero values (if (odds.winHome > 0)).
Solution:
WsFeed emits updatedMarkets: { wdw, ahou, itb1, itb2 }.
StateManager.updateOdds() explicitly zeroes out prices when the market has no offers.
TerminalUI.formatSlipStatus() displays closed lines in bold bright red: 0.00 (ЗАКРЫТО 38s) or 0.00 (NO SLIP).
ExecutionEngine.fire() aborts instantly on closed lines (livePrice <= 1.0) with [ABORT] Линия БК закрыта (кэф: 0.00) to prevent failed orders.

4.3 Async Order Lifecycle Tracker

When an order is placed, ExecutionEngine.trackOrder(orderId, targetKey, result) tracks its execution asynchronously without blocking the UI:
State IN_FLIGHT: Displayed in yellow in the permanent HUD combat banner.
State DONE: Displayed in green [СТАВКА ПРИНЯТА (DONE) pin88] OVER @ кэф 1.35 | €5 | ID: 2290691511 (0.8s).
State REJECTED / TIMED_OUT: Displayed in red with rejection reason.
Sanitized stake output (€5 instead of €EUR,5).

4.4 Market Modes & Quota Isolation

Mode MAIN: Pre-arms [OVER, HOME, AWAY].
Mode ITB: Pre-arms [OVER, ITB1, ITB2].
Toggled seamlessly with key I. Unused slips are deleted from Mollybet via HTTP DELETE to remain strictly within the 3-slip limit.

4.5 Multi-Period Combat (FT vs HT)

Toggled with key T between Full-Time (FT) and 1st Half (HT).
Dedicated instant trigger keys: 3 (HT Over), 4 (HT Home/ITB1), 5 (HT Away/ITB2), 6 (Direct ITB1), 7 (Direct ITB2).

4.6 Bet History & Settlement Normalization

Centralized tracking via StateManager.getOrdersHistory(limit=200) parsing /v1/orders/.
orderNormalizer.js extracts partial fills, true stakes (effStake), true odds (effPrice), and actual PnL (profit/loss).
Handles API quirks where Mollybet returns close_reason: "order_filled" on a failed bet.
UI renders an interactive, scrollable History Modal (Key B) and a concise 3-bet preview block directly in the Combat HUD.

4.7 Smart ITB (Individual Total) Line Selection

The system employs a selectionMode in parseTotalMarket.
For Main Totals (ahou), it picks the line closest to 1.95 (even money) to offer the most balanced handicap.
For Individual Totals (tahou), it uses lowest mode to automatically latch onto the lowest available line (e.g. 0.5) with odds > 1.0, and holds it until suspended (sticky line).

4.8 Alphanumeric Match Searching & Multi-Sport Support (FB + IH)

Supports Football (fb / fb_ht) and Ice Hockey (ih) simultaneously in the live feed and Match Selection Menu ([FB] green / [IH] cyan badges).
Automatic period detection (P1 / P2 / P3) for Ice Hockey via ir_status.period_scores, with automatic betslip teardown and re-arming when a period ends and the next period begins (P1 -> P2 -> P3).
In the Match Selection menu, direct typing triggers an instant local filter by home or away team name, while H/Ctrl+H and F/Ctrl+F filter by sport.

5. Configuration (config.json)


Parameter Type DescriptionsessionToken string Active Mollybet authentication token. instanceId string Whitelabel routing instance identifier. accounts Array<[string, string]> List of [bookmaker, account_id] credentials. adaptiveBookies string[] Authorized bookmakers for pricing and routing (strictly ["pin88"]). defaultStake number Combat stake amount in active currency. stakeStep number Incremental step when pressing + / -. slippagePct number Max allowable price slippage (0.03 = 3%). limitPrice = livePrice * (1 - slippagePct). defaultDuration number Order queue lifespan in seconds at Pinnacle. Default: 15. debounceMs number Hardware anti-double-click guard period (ms). dryRun boolean When true, executes full pipeline locally without sending HTTP order frame.

6. Keybindings Reference

Combat HUD Mode (Default)

Key ActionSPACE FIRE TOTAL OVER on active match & active period. 1 FIRE HOME (П1) in MAIN mode, or FIRE ITB1 in ITB mode. 2 FIRE AWAY (П2) in MAIN mode, or FIRE ITB2 in ITB mode. 6 DIRECT FIRE ITB1 (Home Individual Total Over). 7 DIRECT FIRE ITB2 (Away Individual Total Over). 3 DIRECT FIRE 1st Half (HT) Total Over. 4 DIRECT FIRE 1st Half (HT) Home / ITB1. 5 DIRECT FIRE 1st Half (HT) Away / ITB2. I Toggle Market Mode: MAIN (1X2 + Total) $\leftrightarrow$ ITB (ITB1 + ITB2 + Total). T Toggle Active Period: Full-Time (FT) $\leftrightarrow$ 1st Half (HT). + / - Increase / Decrease active stake by stakeStep. S Open manual stake entry modal (with broker max-limit enforcement). [ / ] Cycle between armed matches in the pool. M Open Live Match Selection Menu. C Open Pre-Armed Coupons & Matches Manager. TAB Toggle Full-screen Live Log Viewer (terminal.log). D Disarm and release betslips for active match. Shift+D Disarm ALL matches and purge betslip pool. Q / Ctrl+C Graceful shutdown and release of all resources.

Match Selection Menu (M)

Key ActionUP / DOWN Navigate match list. ENTER / SPACE Select match and pre-arm 3 slips in combat pool. H / Ctrl+H Filter list: Toggle Ice Hockey Only ([IH]). F / Ctrl+F Filter list: Toggle Football Only ([FB]). L / Ctrl+L Filter list: Show Live Only. A / Ctrl+A Filter list: Show All Matches (all sports). 1-9 + ENTER Fast select by match row number. ESC / M Clear search query or return to Combat HUD.

7. Verification & Architectural Unit Tests

Run the test suite:

Outputs:

Like this project

Posted Oct 26, 2026

Developed a Node.js-based CLI terminal for semi-automated sports betting.