From 199937768208e420e7de4c530c120bd11d488556 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sun, 15 Mar 2026 20:14:08 -0700 Subject: [PATCH] Fix: SurrealDB v2 compatible record IDs for trade history --- lib/paper/engine.js | 51 +++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/lib/paper/engine.js b/lib/paper/engine.js index 04fed73..f4369e2 100644 --- a/lib/paper/engine.js +++ b/lib/paper/engine.js @@ -51,6 +51,34 @@ export class PaperEngine { return this.accounts.get(strategyName); } + /** + * Generate a short unique ID safe for SurrealDB v2 record keys. + * Avoids colons so we control the full `table:id` format ourselves. + */ + _genId() { + return `pt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + } + + /** + * Extract the raw record key from a SurrealDB id. + * e.g. "paper_positions:pt_123_abc" -> "pt_123_abc" + * "pt_123_abc" -> "pt_123_abc" + */ + _rawId(id) { + if (!id) return id; + const str = typeof id === 'object' && id.id ? String(id.id) : String(id); + const idx = str.indexOf(':'); + return idx >= 0 ? str.slice(idx + 1) : str; + } + + /** + * Build the full `table:id` string for use in raw queries. + */ + _recordId(id) { + const raw = this._rawId(id); + return raw.startsWith('paper_positions:') ? raw : `paper_positions:⟨${raw}⟩`; + } + async init() { try { const states = await db.query('SELECT * FROM paper_strategy_state ORDER BY timestamp DESC'); @@ -96,11 +124,8 @@ export class PaperEngine { return null; } - // FIX: Pre-format the ID exactly as SurrealDB expects it to ensure perfect sync - const tradeId = `paper_positions:pt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; - const trade = { - id: tradeId, + id: this._genId(), strategy: signal.strategy, ticker: signal.ticker, side: signal.side.toLowerCase(), @@ -127,8 +152,7 @@ export class PaperEngine { acct.openPositions.set(trade.ticker, list); try { - // Force SurrealDB to use our exact record link ID - await db.create(tradeId, trade); + await db.create('paper_positions', trade); await this._saveState(acct); } catch (e) { console.error('[Paper] DB write error:', e.message); @@ -176,8 +200,8 @@ export class PaperEngine { if (won) acct.wins++; else acct.losses++; - const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; - + const recordId = this._recordId(pos.id); + try { const updated = await db.query( `UPDATE ${recordId} SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime`, @@ -185,12 +209,13 @@ export class PaperEngine { ); const rows = updated[0] || []; if (rows.length === 0) { - await db.create(recordId, { ...pos, id: recordId }); + // Record vanished from DB — re-insert the full settled trade + await db.create('paper_positions', { ...pos, id: this._rawId(pos.id) }); } } catch (e) { console.error('[Paper] Settle DB error:', e.message); try { - await db.create(recordId, { ...pos, id: recordId }); + await db.create('paper_positions', { ...pos, id: this._rawId(pos.id) }); } catch (e2) { console.error('[Paper] Settle DB fallback error:', e2.message); } @@ -264,7 +289,7 @@ export class PaperEngine { acct.totalPnL -= pos.cost; acct.losses++; - const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; + const recordId = this._recordId(pos.id); try { const updated = await db.query( @@ -273,7 +298,7 @@ export class PaperEngine { ); const rows = updated[0] || []; if (rows.length === 0) { - await db.create(recordId, { ...pos, id: recordId }); + await db.create('paper_positions', { ...pos, id: this._rawId(pos.id) }); } } catch (e) { console.error('[Paper] Force-expire DB error:', e.message); @@ -302,7 +327,7 @@ export class PaperEngine { pos.settleTime = Date.now(); try { - const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; + const recordId = this._recordId(pos.id); await db.query( `UPDATE ${recordId} SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime`, { result: 'cancelled', pnl: 0, settleTime: pos.settleTime }