mirror of
https://github.com/multipleof4/KalBot.git
synced 2026-03-16 21:41:02 +00:00
Fix: Reset preserves open positions, fixes balance race
This commit is contained in:
@@ -41,6 +41,7 @@ export class PaperEngine {
|
|||||||
constructor(initialBalancePerStrategy = 1000) {
|
constructor(initialBalancePerStrategy = 1000) {
|
||||||
this.initialBalancePerStrategy = initialBalancePerStrategy;
|
this.initialBalancePerStrategy = initialBalancePerStrategy;
|
||||||
this.accounts = new Map(); // strategyName -> StrategyPaperAccount
|
this.accounts = new Map(); // strategyName -> StrategyPaperAccount
|
||||||
|
this._resetting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getAccount(strategyName) {
|
_getAccount(strategyName) {
|
||||||
@@ -87,6 +88,8 @@ export class PaperEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async executeTrade(signal, marketState) {
|
async executeTrade(signal, marketState) {
|
||||||
|
if (this._resetting) return null;
|
||||||
|
|
||||||
const acct = this._getAccount(signal.strategy);
|
const acct = this._getAccount(signal.strategy);
|
||||||
const cost = signal.size;
|
const cost = signal.size;
|
||||||
|
|
||||||
@@ -177,12 +180,24 @@ export class PaperEngine {
|
|||||||
else acct.losses++;
|
else acct.losses++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.query(
|
// Use upsert-style: try UPDATE first, if no rows matched, INSERT the settled record
|
||||||
|
const updated = await db.query(
|
||||||
`UPDATE paper_positions SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime WHERE id = $id`,
|
`UPDATE paper_positions SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime WHERE id = $id`,
|
||||||
{ id: pos.id, result, pnl: pos.pnl, settleTime: pos.settleTime }
|
{ id: pos.id, result, pnl: pos.pnl, settleTime: pos.settleTime }
|
||||||
);
|
);
|
||||||
|
// Check if UPDATE matched anything; if not, re-insert the settled position
|
||||||
|
const rows = updated[0] || [];
|
||||||
|
if (rows.length === 0) {
|
||||||
|
await db.create('paper_positions', { ...pos });
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Paper] Settle DB error:', e.message);
|
console.error('[Paper] Settle DB error:', e.message);
|
||||||
|
// Fallback: try inserting the settled record directly
|
||||||
|
try {
|
||||||
|
await db.create('paper_positions', { ...pos });
|
||||||
|
} catch (e2) {
|
||||||
|
console.error('[Paper] Settle DB fallback error:', e2.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emoji = won ? '✅' : '❌';
|
const emoji = won ? '✅' : '❌';
|
||||||
@@ -252,10 +267,14 @@ export class PaperEngine {
|
|||||||
acct.losses++;
|
acct.losses++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.query(
|
const updated = await db.query(
|
||||||
`UPDATE paper_positions SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime WHERE id = $id`,
|
`UPDATE paper_positions SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime WHERE id = $id`,
|
||||||
{ id: pos.id, result: 'expired', pnl: pos.pnl, settleTime: pos.settleTime }
|
{ id: pos.id, result: 'expired', pnl: pos.pnl, settleTime: pos.settleTime }
|
||||||
);
|
);
|
||||||
|
const rows = updated[0] || [];
|
||||||
|
if (rows.length === 0) {
|
||||||
|
await db.create('paper_positions', { ...pos });
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Paper] Force-expire DB error:', e.message);
|
console.error('[Paper] Force-expire DB error:', e.message);
|
||||||
}
|
}
|
||||||
@@ -270,8 +289,42 @@ export class PaperEngine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all strategy stats and clear trade history.
|
* Reset all strategy stats and clear trade history.
|
||||||
|
* Open positions are settled as cancelled (refunded) before clearing.
|
||||||
*/
|
*/
|
||||||
async resetAll() {
|
async resetAll() {
|
||||||
|
this._resetting = true;
|
||||||
|
|
||||||
|
// First, cancel all open positions by refunding their cost
|
||||||
|
for (const [name, acct] of this.accounts) {
|
||||||
|
for (const [ticker, positions] of acct.openPositions) {
|
||||||
|
for (const pos of positions) {
|
||||||
|
// Refund the cost back to balance before we reset it
|
||||||
|
acct.balance += pos.cost;
|
||||||
|
pos.settled = true;
|
||||||
|
pos.result = 'cancelled';
|
||||||
|
pos.pnl = 0;
|
||||||
|
pos.settleTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updated = await db.query(
|
||||||
|
`UPDATE paper_positions SET settled = true, result = $result, pnl = $pnl, settleTime = $settleTime WHERE id = $id`,
|
||||||
|
{ id: pos.id, result: 'cancelled', pnl: 0, settleTime: pos.settleTime }
|
||||||
|
);
|
||||||
|
const rows = updated[0] || [];
|
||||||
|
if (rows.length === 0) {
|
||||||
|
// Row was already gone, that's fine for a reset
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore during reset
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Paper:${name}] Cancelled open position ${pos.id} for ${ticker} (refunded $${pos.cost})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acct.openPositions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now reset all stats
|
||||||
for (const [name, acct] of this.accounts) {
|
for (const [name, acct] of this.accounts) {
|
||||||
acct.balance = acct.initialBalance;
|
acct.balance = acct.initialBalance;
|
||||||
acct.totalPnL = 0;
|
acct.totalPnL = 0;
|
||||||
@@ -293,6 +346,8 @@ export class PaperEngine {
|
|||||||
for (const [, acct] of this.accounts) {
|
for (const [, acct] of this.accounts) {
|
||||||
await this._saveState(acct);
|
await this._saveState(acct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._resetting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user