Skip to main content

Bot Paper Mode Fix, Activity Version Bump, and Monitor Crash

Date: 2026-05-05


Summary​

Three issues addressed in this update:

  1. TICK FLIM DEMO bot stuck in paper mode — InstrumentDO had stale cached is_paper=true; added admin endpoint to force reload.
  2. Activity events bumping bot version unnecessarily — play/stop/budget/schedule/name changes no longer increment bots.version.
  3. MonitorPanel crash — price.toFixed() called on string values returned by Neon's serverless driver.

1. Bot Paper Mode Fix​

Root Cause​

  • bots.is_paper = true in the DB (already fixed last session via UPDATE bots SET is_paper = false)
  • The InstrumentDO caches the bot row in memory (sub.bot). When is_paper=true is cached, evaluateBot calls the paper broker adapter even after the DB row was patched.
  • The only way to force a DO reload is via notifyInstrumentBotConfigChanged, which sends POST /instrument/bot-updated to the DO, causing it to re-fetch from Neon.

Changes​

New admin endpoint — POST /api/bots/:id/runtime-refresh

Protected by x-ingestion-admin-secret header via requireIngestionAccess middleware.

// bots-router.ts — registered BEFORE botsRouter.use('*', requireAuth)
botsRouter.post('/:id/runtime-refresh', requireIngestionAccess, handleAdminBotRuntimeRefresh);
// bots-handlers.ts
export const handleAdminBotRuntimeRefresh = async (c: Context<AppEnv>) => {
const botId = c.req.param('id' as never);
const sql = getSql(c.env);
const rows = await sql`SELECT * FROM bots WHERE id = ${botId} LIMIT 1`;
const bot = rows[0];
if (!bot) throw new AppError('Bot not found', 404);

await syncBotRuntimeRegistrations(c.env, botId, bot);
await notifyInstrumentBotConfigChanged(c.env, botId, bot);

const refreshed = await findAccessibleBotById(c.env, botId, bot.user_id);
return c.json({ ok: true, botId, status: bot.status, is_paper: bot.is_paper, bot: refreshed ?? bot });
};

Invocation (after deploy)​

curl -X POST https://api.quantago.co/api/bots/f7657e75-dbfb-4854-9af6-67f538c1362b/runtime-refresh \
-H "x-ingestion-admin-secret: <secret>"

Response confirmed: is_paper: false, status: active, broker_provider: pepperstone, feed_provider: ctrader


2. Activity Version Bump Logic​

Problem​

Every bot activity event (status change, budget update, schedule change, name change) was bumping bots.version via versionMode: 'increment'. This creates meaningless version history for non-structural changes.

Design: versionMode Values​

  • 'increment' — bumps bots.version (for strategy/parameter changes)
  • 'ensure-current' — logs activity without bumping version

Changes​

FileChange
bots-service.ts · updateBotStatusversionMode: 'increment' → 'ensure-current'
bots-service.ts · updateBot optionsAdded versionMode?: 'ensure-current' | 'increment' to options type
bots-service.ts · updateBot callAuto-detects: 'ensure-current' when only name/description fields change, otherwise 'increment' (or explicit from caller)
bots-budget-handlers.ts · handleUpsertBotBudgetversionMode: 'increment' → 'ensure-current'
bots-chat-handlers.ts · set_trading_scheduleAdded versionMode: 'ensure-current' to updateBot call

Version bump still happens for:​

  • Strategy code changes
  • Parameter changes (non-schedule/non-name)
  • Manual rollbacks

Version bump no longer happens for:​

  • launchBot / stopBot (status changes)
  • Budget allocation updates
  • Trading schedule updates
  • Bot name/description renames

3. MonitorPanel price.toFixed Crash​

Root Cause​

Neon PostgreSQL serverless driver returns NUMERIC/DOUBLE PRECISION columns as strings, not numbers. The BotTickDecisionSlim.price field is typed as number in TypeScript but arrives as string from polling DB queries. Calling .toFixed() on a string throws:

Uncaught TypeError: t.price.toFixed is not a function

The live WebSocket path handled this correctly but the DB polling path did not.

Changes in MonitorPanel.tsx​

Fix 1 — at mapping time (line 93):

// Before
price: item.price,
// After
price: typeof item.price === 'number' ? item.price : Number(item.price) || 0,

Fix 2 — mini bar display:

// Before
{decision.price.toFixed(5)}
// After
{(typeof decision.price === 'number' ? decision.price : Number(decision.price) || 0).toFixed(5)}

Fix 3 — decision list:

// Before
{d.price.toFixed(5)}
// After
{(typeof d.price === 'number' ? d.price : Number(d.price) || 0).toFixed(5)}

Files Changed​

  • projects/quantago/services/backend/src/routes/bots/bots-handlers.ts — added handleAdminBotRuntimeRefresh
  • projects/quantago/services/backend/src/routes/bots/bots-router.ts — registered admin route, imported requireIngestionAccess
  • projects/quantago/services/backend/src/routes/bots/bots-service.ts — fixed updateBotStatus + updateBot versionMode
  • projects/quantago/services/backend/src/routes/bots/bots-budget-handlers.ts — fixed budget versionMode
  • projects/quantago/services/backend/src/routes/bots/bots-chat-handlers.ts — fixed trading schedule versionMode
  • projects/quantago/services/frontend/src/routes/bots/MonitorPanel.tsx — fixed 3x price.toFixed crashes

Deployment​

  • Backend: npx wrangler deploy → version 76e83b71-c701-4535-9b99-d211f11dfef4
  • Admin endpoint called → { ok: true, is_paper: false } ✅
  • Frontend: deployed to https://001a0b85.quantago-app.pages.dev