feat(oneshot): add Anti-Flip 5m microstructure execution engine

Introduces a complete, event-driven execution engine for 5-minute
Polymarket UP/DOWN markets, implementing the Anti-Flip strategy spec.

Architecture
────────────
• EventBus          — central pub/sub bus connecting all services
• MarketFeedService — discovers 5m/15m markets via Gamma API, polls
                      CLOB orderbooks every 200–500ms, emits snapshots
• FeatureEngine     — maintains a 15s rolling buffer per market and
                      computes midSlope6s, retrace3s, imbalance, spread,
                      depthTop3 for both UP and DOWN sides
• SignalEngine      — hard gate checks (TTE, spread, depth, stale) then
                      weighted score (imbalance 35%, slope 35%,
                      spread 20%, retrace 10%) + trend confirmation
• ExecutionEngine   — limit-marketable FOK buy, market-sell FOK exit,
                      GTC limit-sell for TP; dry-run short-circuits
• RiskEngine        — consecutive loss cooldown, daily USDC loss cap,
                      session halt; all via explicit canTrade() gate
• PositionEngine    — per-market position state, TP/adverse/slope/time
                      exit evaluation on every snapshot tick
• StateMachine      — explicit state graph with guarded transitions:
                      IDLE → SETUP_READY → ORDER_PENDING → POSITION_OPEN
                      → REDUCE_ONLY → IDLE | COOLDOWN | HALTED
• Telemetry         — structured JSONL logger (data/oneshot_telemetry.jsonl)
                      recording decisions, orders, exits, and transitions

Runtime sequence (per market, per tick)
────────────────────────────────────────
A  Ingest snapshot (MarketFeedService)
B  Build features — rolling slope, retrace, imbalance (FeatureEngine)
C  Hard gate check — TTE [25,120]s, spread ≤ 0.02, depth ≥ minTopSize
D  Score + trend confirm → emit ENTER_LONG / ENTER_SHORT / NO_TRADE
E  Submit FOK limit-marketable at bestAsk
F  Fill handling — full fill / partial (reduce if TTE ≤ 25s) / timeout
G  Position management — TP, adverse (2-tick), slope drop (4s), time exits
H  Risk enforcement — P&L accounting, cooldown, daily halt

New scripts
───────────
  npm run oneshot      — live trading  (DRY_RUN=false)
  npm run oneshot-sim  — simulation    (DRY_RUN=true)
  npm run oneshot-dev  — sim + nodemon

New .env variables
──────────────────
  ONESHOT_ASSETS, ONESHOT_DURATION, ONESHOT_POLL_INTERVAL_MS,
  ONESHOT_BASE_RISK_USDC, ONESHOT_TP_TICKS, ONESHOT_SCORE_THRESHOLD,
  ONESHOT_MIN_TOP_SIZE, ONESHOT_MAX_CONSEC_LOSSES,
  ONESHOT_COOLDOWN_ROUNDS, ONESHOT_DAILY_LOSS_CAP, ONESHOT_FILL_TIMEOUT_MS

Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
This commit is contained in:
direkturcrypto
2026-02-24 13:00:19 +07:00
parent 526076fe6e
commit f074ca9ecb
13 changed files with 1858 additions and 1 deletions
+109
View File
@@ -0,0 +1,109 @@
/**
* Telemetry.js
* Structured JSONL logger for the OneShot engine.
*
* Every decision tick, order lifecycle event, position exit, and state
* transition is recorded to data/oneshot_telemetry.jsonl — one JSON object
* per line — for offline analysis and strategy tuning.
*/
import fs from 'fs';
import path from 'path';
import logger from '../utils/logger.js';
const DATA_DIR = path.resolve('data');
const LOG_FILE = path.join(DATA_DIR, 'oneshot_telemetry.jsonl');
export class Telemetry {
constructor() {
// Ensure data/ directory exists
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
}
// ── Public log methods ───────────────────────────────────────────────────
/**
* Log a per-decision-tick evaluation record.
* Called for every signal evaluation, whether entry is taken or not.
*
* @param {Object} d
* @param {string} d.marketSlug
* @param {number} d.ts
* @param {number} d.tteSec
* @param {number} d.spread
* @param {number} d.imbalance
* @param {number} d.slope
* @param {number} d.retrace
* @param {number} d.depth
* @param {boolean} d.gatePass
* @param {string} d.reasonCode
* @param {number} d.score
* @param {string} d.action
*/
logDecision(d) {
this._write({ type: 'decision', ...d });
}
/**
* Log an order lifecycle event (submit → ack → fill / cancel).
*
* @param {Object} d
* @param {string} d.clientOrderId
* @param {string} d.side
* @param {string} d.marketSlug
* @param {number} d.px
* @param {number} d.qty
* @param {number} d.ackMs
* @param {number} d.fillMs
* @param {string} d.status
*/
logOrder(d) {
this._write({ type: 'order', ...d });
}
/**
* Log a position exit event.
*
* @param {Object} d
* @param {string} d.marketSlug
* @param {string} d.exitReason
* @param {number} d.entryPx
* @param {number} d.exitPx
* @param {number} d.pnl
* @param {number} d.shares
*/
logExit(d) {
this._write({ type: 'exit', ...d });
const pnlStr = d.pnl >= 0
? `+$${d.pnl.toFixed(4)}`
: `-$${Math.abs(d.pnl).toFixed(4)}`;
logger.money(`[Telemetry] exit ${d.marketSlug} | ${d.exitReason} | pnl=${pnlStr}`);
}
/**
* Log a state machine transition.
*
* @param {Object} d
* @param {string} d.marketSlug
* @param {string} d.from
* @param {string} d.to
* @param {string} d.reason
* @param {number} d.ts
*/
logTransition(d) {
this._write({ type: 'transition', ...d });
}
// ── Internal ─────────────────────────────────────────────────────────────
_write(record) {
const line = JSON.stringify({ ...record, ts: record.ts ?? Date.now() }) + '\n';
fs.appendFile(LOG_FILE, line, (err) => {
if (err) logger.warn(`[Telemetry] write error: ${err.message}`);
});
}
}