Repository Public
1// v8 hardening (D4): the simulated positions of a spot agent, for the agent page. READ-ONLY: one `select` on the paper book the2// engine keeps (`agent_paper`, engine.ts) — nothing is written, nothing is seeded (engine.ts `paperHoldings` seeds the book on the3// first paper tick; this view never does), and the engine is not touched. The real vault is a different field of the answer4// (`vault`): the two are never added together.5import { sql } from '../db.ts';67export interface PaperView {8 /** simulated holdings other than the cash leg, units > 0, in the order of the book's symbols; `usd` is null when no price is known */9 positions: { symbol: string; units: string; usd: number | null }[];10 /** the simulated cash leg, in USD (one unit of the cash leg = 1 USD, as in the engine and in the vault view) */11 cashSymbol: string; cashUsd: number;12 /** when the book was read and priced (ms) */13 asOf: number;14}1516/** a float of the paper book as a plain decimal string (no exponent, at most 12 decimals, no trailing zeros) */17export const unitsText = (n: number): string => { const s = n.toFixed(12).replace(/0+$/, '').replace(/\.$/, ''); return s === '-0' || s === '' ? '0' : s; };1819/**20 * The paper book of an agent, or null when it has none (the agent never ran in simulation). `priceOf` returns USD prices21 * for market symbols; a symbol without a price is listed with `usd: null`, never dropped and never valued at 0.22 */23export async function paperBookView(agentId: string, cashSymbol: string, order: readonly string[], priceOf: (symbols: string[]) => Promise<Record<string, number>>, now: () => number = Date.now): Promise<PaperView | null> {24 const rows = await sql<{ symbol: string; units: number }[]>`select symbol, units from agent_paper where agent_id = ${agentId}`;25 if (!rows.length) return null;26 const held = rows.filter((r) => r.symbol !== cashSymbol && r.units > 0);27 const rank = (s: string): number => { const i = order.indexOf(s); return i < 0 ? order.length : i; };28 held.sort((a, b) => rank(a.symbol) - rank(b.symbol) || a.symbol.localeCompare(b.symbol));29 const px = held.length ? await priceOf(held.map((r) => r.symbol)).catch(() => ({} as Record<string, number>)) : {};30 return {31 positions: held.map((r) => ({ symbol: r.symbol, units: unitsText(r.units), usd: px[r.symbol] === undefined ? null : r.units * px[r.symbol]! })),32 cashSymbol, cashUsd: Math.max(0, rows.find((r) => r.symbol === cashSymbol)?.units ?? 0), asOf: now(),33 };34}35