Fix: SurrealDB v2 compatible record IDs for trade history

This commit is contained in:
2026-03-15 20:14:08 -07:00
parent 04bd2fada6
commit 1999377682

View File

@@ -51,6 +51,34 @@ export class PaperEngine {
return this.accounts.get(strategyName); 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() { async init() {
try { try {
const states = await db.query('SELECT * FROM paper_strategy_state ORDER BY timestamp DESC'); const states = await db.query('SELECT * FROM paper_strategy_state ORDER BY timestamp DESC');
@@ -96,11 +124,8 @@ export class PaperEngine {
return null; 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 = { const trade = {
id: tradeId, id: this._genId(),
strategy: signal.strategy, strategy: signal.strategy,
ticker: signal.ticker, ticker: signal.ticker,
side: signal.side.toLowerCase(), side: signal.side.toLowerCase(),
@@ -127,8 +152,7 @@ export class PaperEngine {
acct.openPositions.set(trade.ticker, list); acct.openPositions.set(trade.ticker, list);
try { try {
// Force SurrealDB to use our exact record link ID await db.create('paper_positions', trade);
await db.create(tradeId, trade);
await this._saveState(acct); await this._saveState(acct);
} catch (e) { } catch (e) {
console.error('[Paper] DB write error:', e.message); console.error('[Paper] DB write error:', e.message);
@@ -176,7 +200,7 @@ export class PaperEngine {
if (won) acct.wins++; if (won) acct.wins++;
else acct.losses++; else acct.losses++;
const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; const recordId = this._recordId(pos.id);
try { try {
const updated = await db.query( const updated = await db.query(
@@ -185,12 +209,13 @@ export class PaperEngine {
); );
const rows = updated[0] || []; const rows = updated[0] || [];
if (rows.length === 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) { } catch (e) {
console.error('[Paper] Settle DB error:', e.message); console.error('[Paper] Settle DB error:', e.message);
try { try {
await db.create(recordId, { ...pos, id: recordId }); await db.create('paper_positions', { ...pos, id: this._rawId(pos.id) });
} catch (e2) { } catch (e2) {
console.error('[Paper] Settle DB fallback error:', e2.message); console.error('[Paper] Settle DB fallback error:', e2.message);
} }
@@ -264,7 +289,7 @@ export class PaperEngine {
acct.totalPnL -= pos.cost; acct.totalPnL -= pos.cost;
acct.losses++; acct.losses++;
const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; const recordId = this._recordId(pos.id);
try { try {
const updated = await db.query( const updated = await db.query(
@@ -273,7 +298,7 @@ export class PaperEngine {
); );
const rows = updated[0] || []; const rows = updated[0] || [];
if (rows.length === 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) { } catch (e) {
console.error('[Paper] Force-expire DB error:', e.message); console.error('[Paper] Force-expire DB error:', e.message);
@@ -302,7 +327,7 @@ export class PaperEngine {
pos.settleTime = Date.now(); pos.settleTime = Date.now();
try { try {
const recordId = pos.id.includes(':') ? pos.id : `paper_positions:${pos.id}`; const recordId = this._recordId(pos.id);
await db.query( await db.query(
`UPDATE ${recordId} SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime`, `UPDATE ${recordId} SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime`,
{ result: 'cancelled', pnl: 0, settleTime: pos.settleTime } { result: 'cancelled', pnl: 0, settleTime: pos.settleTime }