112 lines
3.3 KiB
JavaScript
112 lines
3.3 KiB
JavaScript
/**
|
|
* 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 == null
|
|
? 'pending(on-chain)'
|
|
: 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}`);
|
|
});
|
|
}
|
|
}
|