Spaces:
Paused
Paused
File size: 1,767 Bytes
5545e4d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | // Per-trader client state. The chart is seeded once from the stored time series,
// then grows a point on every poll so the line keeps moving while you watch.
import type { Holding, TraderDetail, TraderInfo } from "./api";
export const CHART_MAX_POINTS = 5000;
export interface ChartPoint {
t: number; // unix seconds
value: number;
}
function toUnixSeconds(stamp: string): number {
// Stored timestamps are "YYYY-MM-DD HH:MM:SS" local time.
return new Date(stamp.replace(" ", "T")).getTime() / 1000;
}
export class TraderState {
readonly info: TraderInfo;
detail: TraderDetail | null = null;
chart: ChartPoint[] = [];
previousPrices: Record<string, number> = {};
private seeded = false;
constructor(info: TraderInfo) {
this.info = info;
}
recordDetail(detail: TraderDetail): void {
this.detail = detail;
if (!this.seeded) {
this.chart = detail.time_series.map((p) => ({ t: toUnixSeconds(p.datetime), value: p.value }));
this.seeded = true;
}
this.chart.push({ t: Date.now() / 1000, value: detail.portfolio_value });
if (this.chart.length > CHART_MAX_POINTS) {
this.chart.splice(0, this.chart.length - CHART_MAX_POINTS);
}
}
priceDirections(): Record<string, "up" | "down" | "same"> {
const out: Record<string, "up" | "down" | "same"> = {};
if (!this.detail) return out;
for (const h of this.detail.holdings) {
const prev = this.previousPrices[h.symbol];
out[h.symbol] =
prev === undefined || prev === h.price ? "same" : h.price > prev ? "up" : "down";
}
return out;
}
rememberPrices(): void {
if (!this.detail) return;
this.previousPrices = Object.fromEntries(this.detail.holdings.map((h: Holding) => [h.symbol, h.price]));
}
}
|