Skip to main content

Fix: Open Positions Not Showing (Missing cTrader Secrets + Timestamp Bug)

Date: 2026-05-05
Affected: Positions page, Overview metrics (Capital Consumed / Capital at Risk)


Root Cause #1 — Missing CTRADER_CLIENT_ID / CTRADER_CLIENT_SECRET in Production (Primary)​

What was happening​

The GET /api/bots/:id/positions handler (bots-positions-handlers.ts) calls resolveBotCTraderCreds() which checks:

const clientId = c.env.CTRADER_CLIENT_ID;
const clientSecret = c.env.CTRADER_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new AppError('Server is missing CTRADER_CLIENT_ID / CTRADER_CLIENT_SECRET.', 500);
}

These two environment variables were never pushed as Wrangler secrets to production. The worker was throwing a 500 error on every positions request, returning {"error": "..."} — which the frontend treats as an empty positions array.

Fix​

  1. Pushed both secrets to Cloudflare via wrangler secret put:
    • CTRADER_CLIENT_ID
    • CTRADER_CLIENT_SECRET
  2. Added both to services/backend/.dev.vars for local development.

Root Cause #2 — Frontend Timestamp × 1000 Bug (Secondary)​

What was happening​

In BotPositionsPanel.tsx, openTimestamp from the cTrader API is already in unix milliseconds. The code was incorrectly multiplying by 1000 in two places:

Bug 1 — scopedPositions filter:

// BEFORE (wrong): pushed timestamps to year ~56000
const atMs = position.openTimestamp ? position.openTimestamp * 1000 : null;
if (atMs == null) return false; // also dropped positions with no timestamp

With a scope window like "Today" active, every position would be filtered out because atMs was ~1000× in the future. Positions with null timestamps were also hidden.

Bug 2 — openedAt column renderer:

// BEFORE (wrong): displayed year ~56000
const d = new Date(ts * 1000);

Fix​

// Fix 1: scopedPositions — no * 1000, keep null-timestamp positions
const atMs = position.openTimestamp ?? null;
if (atMs == null) return true; // include positions with no timestamp

// Fix 2: column renderer — no * 1000
const d = new Date(ts);

Additional: Debug Logging Added to Backend​

Added console.log statements to ctrader-positions.ts to help diagnose future issues:

  • [cTrader-positions] Requesting TraderInfo + SymbolsList for ctid=...
  • [cTrader-positions] TraderRes: balance=... moneyDigits=... currency=...
  • [cTrader-positions] SymbolsListRes: symbolCount=...
  • [cTrader-positions] ReconcileRes received: positions=... keys=...

Also added fallback for openPosition field name in ReconcileRes (some cTrader API versions use openPosition instead of position):

const rawPositions = (
msg.payload?.position ??
msg.payload?.openPosition ??
[]
) as Array<Record<string, unknown>>;

Deployments​

  • Backend: npx wrangler deploy — secrets registered, debug logging live
  • Frontend: FRONTEND_PAGES_PROJECT=quantago-app FRONTEND_BRANCH=main bash scripts/deploy-frontend.sh — timestamp fix live at https://9418c777.quantago-app.pages.dev