/* ─────────────────────────────────────────────────────────
components.jsx — reusable bits for the dashboard
Export each to window so the main app.jsx can use them.
───────────────────────────────────────────────────────── */
const { useState, useMemo, useRef, useEffect } = React;
/* ───────── format helpers ───────── */
const fmtUSD = (n, opts = {}) => {
const { showSign = false, decimals = 2 } = opts;
const sign = n > 0 ? "+" : n < 0 ? "−" : "";
const abs = Math.abs(n).toLocaleString("en-US", {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
});
return (showSign ? sign : (n < 0 ? "−" : "")) + "$" + abs;
};
const fmtPct = (n, decimals = 1) => {
const sign = n > 0 ? "+" : n < 0 ? "−" : "";
return sign + Math.abs(n).toFixed(decimals) + "%";
};
const fmtDateShort = (iso) => {
const d = new Date(iso + "T12:00:00");
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
};
const pnlClass = (n) => (n > 0 ? "pos" : n < 0 ? "neg" : "");
/* ───────── Tiny sparkline ───────── */
function Sparkline({ values, color = "var(--text-2)", height = 28, fill = false }) {
if (!values || values.length === 0) return null;
const min = Math.min(...values);
const max = Math.max(...values);
const W = 100, H = 30;
const span = max - min || 1;
const pts = values.map((v, i) => {
const x = (i / (values.length - 1)) * W;
const y = H - ((v - min) / span) * H;
return [x, y];
});
const d = pts.map((p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(2) + "," + p[1].toFixed(2)).join(" ");
const dFill = d + ` L${W},${H} L0,${H} Z`;
return (
{fill && }
);
}
/* ───────── ET clock + market status hook ─────────
Mirrors the timing_optimizer reference:
- 00:00–04:00 ET → AFTER-HRS
- 04:00–09:30 ET → PRE-MKT
- 09:30–16:00 ET → OPEN
- 16:00–24:00 ET → AFTER-HRS
Updates every second.
─────────────────────────────────────────────────── */
function getETParts(d = new Date()) {
// Use Intl to reliably read NY time regardless of viewer's local tz
const fmt = new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
hour: "2-digit", minute: "2-digit", second: "2-digit",
hour12: false, weekday: "short",
year: "numeric", month: "2-digit", day: "2-digit",
});
const parts = fmt.formatToParts(d);
const get = k => parts.find(p => p.type === k)?.value;
const h = parseInt(get("hour"), 10) % 24;
const m = parseInt(get("minute"), 10);
const s = parseInt(get("second"), 10);
const weekday = get("weekday"); // "Mon" "Tue" ...
const isoDate = `${get("year")}-${get("month")}-${get("day")}`;
return { h, m, s, weekday, isoDate, totalMin: h * 60 + m };
}
function getMarketStatus(parts) {
const { totalMin, weekday } = parts;
const isWeekend = weekday === "Sat" || weekday === "Sun";
if (isWeekend) return { label: "WEEKEND", color: "var(--text-3)", isAfterHours: true, isOpen: false };
if (totalMin < 240) return { label: "AFTER-HRS", color: "var(--warn)", isAfterHours: true, isOpen: false };
if (totalMin < 570) return { label: "PRE-MKT", color: "#FF8A3D", isAfterHours: false, isOpen: false };
if (totalMin < 960) return { label: "OPEN", color: "var(--pos)", isAfterHours: false, isOpen: true };
return { label: "AFTER-HRS", color: "var(--warn)", isAfterHours: true, isOpen: false };
}
function useETClock() {
const [now, setNow] = React.useState(() => new Date());
React.useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
const parts = getETParts(now);
const status = getMarketStatus(parts);
return { parts, status, now };
}
function fmtETTime12(parts) {
const { h, m, s } = parts;
const period = h >= 12 ? "PM" : "AM";
const h12 = ((h + 11) % 12) + 1;
const pad = n => String(n).padStart(2, "0");
return `${h12}:${pad(m)}:${pad(s)} ${period}`;
}
/* Next-trading-day label, used in the hero when after hours.
- Friday after hours → Monday
- Sat/Sun → Monday
- Other days after hours → tomorrow's weekday + date */
function nextTradingDayLabel(parts) {
// Walk forward at least 1 calendar day, skip Sat/Sun
const base = new Date(parts.isoDate + "T12:00:00Z");
base.setUTCDate(base.getUTCDate() + 1);
while ([0, 6].includes(base.getUTCDay())) base.setUTCDate(base.getUTCDate() + 1);
return base.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", timeZone: "UTC" });
}
/* ───────── Logo variants ───────── */
function LogoVariant({ variant = "candlesticks" }) {
if (variant === "candlesticks") {
return (
{/* Three OHLC candlesticks, last one a tall green winner */}
{/* Arrow lifting off the winner */}
TRADING
.OPT
);
}
if (variant === "monogram") {
return (
tO
trading-optimizer
);
}
if (variant === "ticker") {
return (
$
TO
TRADING
OPTIMIZER
);
}
if (variant === "target") {
return (
{/* Crosshair ticks */}
/
OPTIMIZER
/
);
}
if (variant === "network") {
return (
{/* Constellation of trade-day nodes connected by a path */}
trading
·
optimizer
);
}
if (variant === "bracket") {
return (
[
10:45 → 13:00
TRADING OPTIMIZER
]
);
}
return null;
}
Object.assign(window, { LogoVariant });
/* ───────── Account switcher tabs ───────── */
function AccountTabs({ accounts, selected, onSelect }) {
return (
Accounts
{accounts.map((acct, i) => {
const isOn = selected === i;
const dayPos = acct.account.day_pnl_dollars >= 0;
return (
onSelect(i)}
style={isOn ? { "--accent": acct.accentColor } : undefined}
>
{acct.label}
{acct.alpacaAccountId}
{acct.strategyKindLabel}
${acct.account.portfolio_value.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
{fmtUSD(acct.account.day_pnl_dollars, { showSign: true })}
);
})}
);
}
function Topbar({ botHealth, asOf, clock, logoVariant = "candlesticks" }) {
const statusDot =
botHealth.last_cron_status === "ok" ? "dot" :
botHealth.last_cron_status === "warn" ? "dot warn" : "dot neg";
const timeStr = clock ? fmtETTime12(clock.parts) : "—";
const status = clock?.status;
return (
Current Time
{timeStr} ET
Market Hours
{status?.label || "—"}
);
}
/* ───────── Hero / TODAY ───────── */
function TodayHero({ plan, account, streakHistory, currentStreak, botHealth, equitySpark, asOf, acct, clock }) {
const isStrategyB = acct && acct.strategyKind === "B";
// Variable central content per state
let badge = null;
let ticker = plan.ticker || "—";
let tickerCls = "tile-hero-ticker";
let context = null;
if (plan.state === "holding") {
badge = Live · Holding ;
const h = Math.floor(plan.closes_in_minutes / 60);
const m = plan.closes_in_minutes % 60;
context = (
Buy {plan.buy_time} · Sell {plan.sell_time}
· closes in {h}h {m}m
);
} else if (plan.state === "closed") {
const won = (plan.realized_pnl ?? 0) >= 0;
badge = Closed · {won ? "win" : "loss"} settled ;
context = (
Bought {plan.buy_time} · Sold {plan.sell_time}
· day complete
);
} else if (plan.state === "pre_market") {
badge = Pre-market · queued ;
const h = Math.floor(plan.fires_in_minutes / 60);
const m = plan.fires_in_minutes % 60;
context = (
Scanner picked {plan.ticker} · order fires in {h}h {m}m at {plan.buy_time}
);
} else if (plan.state === "no_trade") {
ticker = "NO TRADE";
tickerCls = "tile-hero-ticker no-trade";
badge = Scanner · no qualifying mover ;
context = (
{plan.reason}
);
} else if (plan.state === "weekend") {
ticker = "MARKETS CLOSED";
tickerCls = "tile-hero-ticker no-trade";
badge = Weekend · cron paused ;
const h = Math.floor(plan.next_fire_in_minutes / 60);
const m = plan.next_fire_in_minutes % 60;
context = (
Next cron fires {plan.next_fire_at} · in {h}h {m}m
);
} else if (plan.state === "next_day") {
ticker = "AWAITING SCANNER";
tickerCls = "tile-hero-ticker no-trade";
badge = After-hours · tomorrow's plan ;
context = (
Stake
${plan.stake.toLocaleString()}
{plan.stake_basis}
Buy
{plan.buy_time}
market order
Sell
{plan.sell_time}
market order
Formula in effect
{plan.formula}
{isStrategyB ? "always-on compounding" : "win × 1.5 / loss = port × 2%"}
{plan.stake_explanation} {plan.note}
);
}
const isActive = plan.state === "holding" || plan.state === "closed" || plan.state === "pre_market";
const showStake = isActive || plan.state === "next_day";
// Today P&L value (unrealized or realized; 0 otherwise)
const todayPnl = plan.unrealized_pnl ?? plan.realized_pnl ?? 0;
const asOfDate = asOf ? new Date(asOf) : new Date();
return (
{plan.state === "next_day" ? "Today's trading day" : "Today"}
{(() => {
const d = clock ? new Date(clock.parts.isoDate + "T12:00:00Z") : asOfDate;
const opts = { weekday: "long", month: "long", day: "numeric", timeZone: clock ? "UTC" : undefined };
return d.toLocaleDateString("en-US", opts);
})()}
{plan.state === "next_day"
? `cron fires next at 09:20 ET`
: `cron @ 09:20 ET · last fire ${botHealth.last_cron_at}`}
{/* Dominant tile */}
{badge}
{isActive ? plan.formula : ""}
{ticker}
{plan.ticker_long && isActive && (
{plan.ticker_long}
)}
{plan.ticker_sector && isActive && (
{plan.ticker_sector}{plan.ticker_exchange ? ` · ${plan.ticker_exchange}` : ""}
{plan.ticker_industry ? {` · ${plan.ticker_industry}`} : ""}
)}
{context}
{plan.state === "holding" && (
Last · Bought
${plan.last_price.toFixed(2)} / ${plan.bought_at.toFixed(2)}
)}
{plan.state === "closed" && (
Sell · Buy · Shares
${plan.sell_price.toFixed(2)} / ${plan.buy_price.toFixed(2)}
· {plan.shares}sh
)}
{/* Supporting tiles */}
{/* Stake tile (today) OR Today's trade tile (next_day) */}
{plan.state === "next_day" && plan.today_settled ? (
Today's trade
{plan.today_settled.ticker}
Buy
${plan.today_settled.buy_price.toFixed(2)}
{plan.today_settled.buy_time}
→
Sell
${plan.today_settled.sell_price.toFixed(2)}
{plan.today_settled.sell_time}
{plan.today_settled.shares.toLocaleString()} shares of {plan.today_settled.ticker}
) : (
Stake deployed
{showStake && plan.stake_basis && (
{plan.stake_basis}
)}
{showStake ? "$" + plan.stake.toLocaleString() : — }
{showStake
? (plan.stake_explanation || "Stake set from current streak formula.")
: "No position deployed today."}
)}
Streak
last 8
{isStrategyB
? —n/a
: currentStreak.count === 0
? 0reset
:
{currentStreak.count}{currentStreak.kind}
}
{streakHistory.map((s, i) => )}
{!isStrategyB && currentStreak.count === 0 && (
broken 5/15 from 4W by CSCO loss
)}
{isStrategyB && (
Strategy B always deploys full portfolio — no streak formula.
)}
{plan.state === "next_day" ? "Today's result" : "Today P&L"}
{plan.state === "next_day" && plan.today_settled && (
{plan.today_settled.ticker}
)}
{plan.state === "next_day" && plan.today_settled
? (() => {
const pnl = plan.today_settled.realized_pnl;
const pct = plan.today_settled.realized_pnl_pct;
const cls = pnl > 0 ? "value-pos" : pnl < 0 ? "value-neg" : "value-neutral";
return (
<>
{fmtUSD(pnl, { showSign: true })}
{fmtPct(pct, 2)}
· settled at 13:00 ET · {plan.today_settled.shares}sh of {plan.today_settled.ticker}
>
);
})()
: (
<>
0 ? "value-pos" : todayPnl < 0 ? "value-neg" : "value-neutral")}>
{isActive ? fmtUSD(todayPnl, { showSign: true }) : $0.00 }
{plan.state === "holding" ? "unrealized · marks every tick"
: plan.state === "closed" ? "realized · settled"
: "no trade today"}
>
)}
Portfolio value
${account.portfolio_value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
Last cron fire
{botHealth.last_cron_at}
status OK · {botHealth.consecutive_successful_fires} successful in a row · backup {botHealth.backup_age_hours}h old
Open positions
{account.open_positions_count === 0
? 0
: account.open_positions_count}
{account.open_positions_count > 0 && plan.ticker && (
· {plan.ticker}
)}
cash ${account.cash.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })} available
);
}
/* ───────── Stats row ───────── */
function StatsRow({ perf }) {
return (
Total return
since 2026-03-31
0 ? "value-pos" : "value-neg")}>
{fmtPct(perf.totalReturnPct, 2)}
0 ? "value-pos" : "value-neg"}>
{fmtUSD(perf.totalReturnDollars, { showSign: true })}
on ${perf.startingBalance.toLocaleString()} starting equity
Win rate
{perf.winRate.toFixed(1)}%
{perf.wins}W
/
{perf.losses}L
of {perf.totalTrades} trades · Sharpe {perf.sharpe.toFixed(1)}
Best · Worst day
Best
{fmtUSD(perf.bestDay.pnl_dollars, { showSign: true })}
{perf.bestDay.ticker} · {fmtDateShort(perf.bestDay.date)}
Worst
{fmtUSD(perf.worstDay.pnl_dollars, { showSign: true })}
{perf.worstDay.ticker} · {fmtDateShort(perf.worstDay.date)}
);
}
/* ───────── Main chart — tabbed (Strategy-A-style) ─────────
Tabs: Portfolio Value · Daily P&L ($) · Daily P&L (%) · Cumulative P&L
Range: 3D · 7D · 14D · 1M · All
─────────────────────────────────────────────────────────── */
const TAB_DEFS = [
{ id: "portfolio", label: "Portfolio Value",
tip: "Account value at each daily close.\nThe headline strategy curve — answers \"is this making money over time?\"" },
{ id: "daily", label: "Daily P&L ($)",
tip: "Per-day realized profit/loss in dollars.\nBars colored green positive, red negative." },
{ id: "dailypct", label: "Daily P&L (%)",
tip: "Per-day P&L as a percent of the stake deployed that day.\nNormalizes for variable position sizing — apples-to-apples across streak-scaled days." },
{ id: "cumulative", label: "Cumulative P&L",
tip: "Running sum of daily P&L from the start of the window.\nSame trajectory as Portfolio Value minus starting balance." },
];
const RANGE_DEFS = [
{ id: "3D", label: "3D", days: 3 },
{ id: "7D", label: "7D", days: 7 },
{ id: "14D", label: "14D", days: 14 },
{ id: "1M", label: "1M", days: 22 },
{ id: "3M", label: "3M", days: 66 },
{ id: "6M", label: "6M", days: 126 },
{ id: "1Y", label: "1Y", days: 252 },
{ id: "5Y", label: "5Y", days: 1260 },
{ id: "All", label: "All", days: Infinity },
];
function MainChart({ equityCurve, dailyPnl, tradeByDate, tickerMeta, accentColor = "#00E5A0" }) {
const [tab, setTab] = useState("portfolio");
// View window expressed as data-array indices [start, end). Range pills are
// shortcuts that snap this window; pan/pinch/wheel mutate it directly.
const total = equityCurve.length;
const defaultDays = Math.min(35, total);
const [view, setView] = useState({ start: total - defaultDays, end: total });
const [isDragging, setIsDragging] = useState(false);
// Refs for pointer interaction (mutating per-event without re-rendering)
const bodyRef = useRef(null);
const pointersRef = useRef(new Map());
const dragRef = useRef(null);
const pinchRef = useRef(null);
function snapToRange(rangeId) {
const def = RANGE_DEFS.find(r => r.id === rangeId);
if (!def) return;
const N = !isFinite(def.days) ? total : Math.min(def.days, total);
setView({ start: total - N, end: total });
}
function clampWindow(start, end) {
let s = start, e = end;
if (s < 0) { e -= s; s = 0; }
if (e > total) { s -= (e - total); e = total; }
s = Math.max(0, Math.round(s));
e = Math.min(total, Math.round(e));
if (e - s < 2) e = Math.min(total, s + 2);
return { start: s, end: e };
}
// Which range pill (if any) matches the current window exactly + is end-anchored
const activeRange = (() => {
if (view.end !== total) return null;
const win = view.end - view.start;
for (const r of RANGE_DEFS) {
const target = !isFinite(r.days) ? total : Math.min(r.days, total);
if (win === target) return r.id;
}
return null;
})();
/* ── Pointer interaction handlers (mouse + touch + pen via Pointer Events) ── */
function onPointerDown(e) {
bodyRef.current?.setPointerCapture?.(e.pointerId);
pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
const n = pointersRef.current.size;
if (n === 1) {
const rect = bodyRef.current.getBoundingClientRect();
dragRef.current = { startX: e.clientX, startView: { ...view }, width: rect.width };
pinchRef.current = null;
} else if (n === 2) {
const [a, b] = [...pointersRef.current.values()];
pinchRef.current = {
startDist: Math.hypot(a.x - b.x, a.y - b.y),
startMidX: (a.x + b.x) / 2,
startView: { ...view },
rectLeft: bodyRef.current.getBoundingClientRect().left,
width: bodyRef.current.getBoundingClientRect().width,
};
dragRef.current = null;
}
setIsDragging(true);
}
function onPointerMove(e) {
if (!pointersRef.current.has(e.pointerId)) return;
pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
const n = pointersRef.current.size;
if (n === 1 && dragRef.current) {
const { startX, startView, width } = dragRef.current;
const dx = e.clientX - startX;
const win = startView.end - startView.start;
const dIdx = -(dx / width) * win;
setView(clampWindow(startView.start + dIdx, startView.end + dIdx));
} else if (n === 2 && pinchRef.current) {
const [a, b] = [...pointersRef.current.values()];
const dist = Math.hypot(a.x - b.x, a.y - b.y);
const { startDist, startMidX, startView, rectLeft, width } = pinchRef.current;
const scale = startDist / Math.max(1, dist); // >1 zooms out, <1 zooms in
const startWin = startView.end - startView.start;
const newWin = Math.max(2, Math.min(total, startWin * scale));
// Anchor zoom around the midpoint of the two fingers (in data-index space)
const midT = (startMidX - rectLeft) / width;
const midIdx = startView.start + midT * startWin;
setView(clampWindow(midIdx - midT * newWin, midIdx + (1 - midT) * newWin));
}
}
function onPointerUp(e) {
if (pointersRef.current.has(e.pointerId)) {
pointersRef.current.delete(e.pointerId);
}
if (pointersRef.current.size === 0) {
dragRef.current = null;
pinchRef.current = null;
setIsDragging(false);
} else if (pointersRef.current.size === 1) {
// Transition from pinch back to drag — restart drag anchor
const [pid] = [...pointersRef.current.keys()];
const pt = pointersRef.current.get(pid);
const rect = bodyRef.current.getBoundingClientRect();
dragRef.current = { startX: pt.x, startView: { ...view }, width: rect.width };
pinchRef.current = null;
}
}
function onWheel(e) {
e.preventDefault();
const rect = bodyRef.current.getBoundingClientRect();
const cursorT = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
const win = view.end - view.start;
const factor = e.deltaY > 0 ? 1.18 : 1 / 1.18;
const newWin = Math.max(2, Math.min(total, win * factor));
const cursorIdx = view.start + cursorT * win;
setView(clampWindow(cursorIdx - cursorT * newWin, cursorIdx + (1 - cursorT) * newWin));
}
// Suppress default browser wheel scrolling over the chart, but only while
// the user is actually hovering us. Attach as non-passive so preventDefault sticks.
useEffect(() => {
const el = bodyRef.current;
if (!el) return;
const handler = (e) => onWheel(e);
el.addEventListener("wheel", handler, { passive: false });
return () => el.removeEventListener("wheel", handler);
}, [view.start, view.end, total]);
/* ── Derived data slices for current view window ── */
const eqSlice = equityCurve.slice(view.start, view.end);
const pnlSlice = dailyPnl.slice(view.start, view.end);
const pnlPctSlice = pnlSlice.map(d => {
const tr = tradeByDate[d.date];
const stake = (tr && tr.stake_dollars) || 15000;
return { date: d.date, pnl_dollars: d.pnl_dollars, pct: stake ? (d.pnl_dollars / stake) * 100 : 0, stake };
});
const cumulativeSlice = (() => {
let sum = 0;
return pnlSlice.map(d => { sum += d.pnl_dollars; return { date: d.date, cumulative: sum }; });
})();
const lastEq = eqSlice[eqSlice.length - 1]?.portfolio_value;
const firstEq = eqSlice[0]?.portfolio_value;
// ─── Sub-line under tabs: summary of current tab+range ───
let summaryLeft = "";
let summaryRight = "";
if (tab === "portfolio") {
summaryLeft = `${eqSlice.length} trading days · daily close`;
summaryRight = `$${firstEq?.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} → $${lastEq?.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
} else if (tab === "daily") {
const wins = pnlSlice.filter(d => d.pnl_dollars > 0).length;
const losses = pnlSlice.filter(d => d.pnl_dollars < 0).length;
const net = pnlSlice.reduce((a, d) => a + d.pnl_dollars, 0);
summaryLeft = `${pnlSlice.length} days · ${wins}W / ${losses}L`;
summaryRight = `net ${fmtUSD(net, { showSign: true })}`;
} else if (tab === "dailypct") {
const winRows = pnlPctSlice.filter(d => d.pct > 0);
const lossRows = pnlPctSlice.filter(d => d.pct < 0);
const avgWin = winRows.length ? winRows.reduce((a, d) => a + d.pct, 0) / winRows.length : 0;
const avgLoss = lossRows.length ? lossRows.reduce((a, d) => a + d.pct, 0) / lossRows.length : 0;
summaryLeft = `avg win ${fmtPct(avgWin, 2)} · avg loss ${fmtPct(avgLoss, 2)}`;
summaryRight = `% of daily stake deployed`;
} else if (tab === "cumulative") {
const t = cumulativeSlice[cumulativeSlice.length - 1]?.cumulative ?? 0;
summaryLeft = `running sum · ${cumulativeSlice.length} days`;
summaryRight = `total ${fmtUSD(t, { showSign: true })}`;
}
// ─── Choose data + renderer per tab ───
let chartEl = null;
if (tab === "portfolio") {
chartEl = "$" + (v / 1000).toFixed(1) + "k"}
tooltipPrimary={v => "$" + v.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
tooltipPrimaryLabel="EQUITY"
tooltipDeltaLabel="Δ START"
tradeByDate={tradeByDate}
tickerMeta={tickerMeta}
interactive={!isDragging}
accentColor={accentColor}
/>;
} else if (tab === "daily") {
chartEl = ({ ...d, value: d.pnl_dollars }))}
yFmt={v => (v < 0 ? "−" : "") + "$" + Math.abs(v / 1000).toFixed(1) + "k"}
tooltipPrimary={v => fmtUSD(v, { showSign: true })}
tooltipPrimaryLabel="P&L"
tradeByDate={tradeByDate}
tickerMeta={tickerMeta}
interactive={!isDragging}
/>;
} else if (tab === "dailypct") {
chartEl = ({ ...d, value: d.pct }))}
yFmt={v => (v > 0 ? "+" : v < 0 ? "−" : "") + Math.abs(v).toFixed(1) + "%"}
tooltipPrimary={v => fmtPct(v, 2)}
tooltipPrimaryLabel="P&L %"
extraRows={(d) => [["STAKE", "$" + d.stake.toLocaleString()], ["P&L $", fmtUSD(d.pnl_dollars, { showSign: true })]]}
tradeByDate={tradeByDate}
tickerMeta={tickerMeta}
interactive={!isDragging}
/>;
} else if (tab === "cumulative") {
chartEl = (v < 0 ? "−" : "") + "$" + Math.abs(v / 1000).toFixed(1) + "k"}
tooltipPrimary={v => fmtUSD(v, { showSign: true })}
tooltipPrimaryLabel="CUM P&L"
tradeByDate={tradeByDate}
tickerMeta={tickerMeta}
anchorAtZero={true}
interactive={!isDragging}
accentColor={accentColor}
/>;
}
return (
{TAB_DEFS.map(t => (
setTab(t.id)}
>
{t.label}
))}
{RANGE_DEFS.map(r => (
snapToRange(r.id)}
>{r.label}
))}
{summaryLeft}
{summaryRight} · drag to pan · pinch / scroll to zoom
{chartEl}
);
}
/* ───────── Generic line view (Portfolio / Cumulative) ───────── */
function LineView({ data, yKey, yFmt, tooltipPrimary, tooltipPrimaryLabel, tooltipDeltaLabel,
tradeByDate, tickerMeta, anchorAtZero = false, height = 300, interactive = true,
accentColor = "#00E5A0" }) {
const wrapRef = useRef(null);
const [hover, setHover] = useState(null);
const [width, setWidth] = useState(900);
useEffect(() => {
const el = wrapRef.current;
if (!el) return;
const ro = new ResizeObserver(() => setWidth(el.clientWidth));
ro.observe(el);
setWidth(el.clientWidth);
return () => ro.disconnect();
}, []);
useEffect(() => { if (!interactive) setHover(null); }, [interactive]);
const pad = { l: 56, r: 16, t: 8, b: 28 };
const W = width, H = height;
const iw = W - pad.l - pad.r;
const ih = H - pad.t - pad.b;
if (!data || data.length === 0) return
;
const values = data.map(d => d[yKey]);
let min = Math.min(...values);
let max = Math.max(...values);
if (anchorAtZero) {
min = Math.min(0, min);
max = Math.max(0, max);
}
const yPadV = (max - min) * 0.1 || 1;
const yMin = min - yPadV;
const yMax = max + yPadV;
const x = i => pad.l + (data.length === 1 ? iw / 2 : (i / (data.length - 1)) * iw);
const y = v => pad.t + ih - ((v - yMin) / (yMax - yMin)) * ih;
const linePath = data.map((d, i) => (i === 0 ? "M" : "L") + x(i).toFixed(2) + "," + y(d[yKey]).toFixed(2)).join(" ");
const areaPath = linePath + ` L${x(data.length - 1).toFixed(2)},${pad.t + ih} L${x(0).toFixed(2)},${pad.t + ih} Z`;
const yTicks = 4;
const yVals = Array.from({ length: yTicks + 1 }, (_, i) => yMin + ((yMax - yMin) * i) / yTicks);
const xTickCount = Math.min(5, data.length);
const xIdx = xTickCount <= 1 ? [0] :
Array.from({ length: xTickCount }, (_, i) => Math.round((i * (data.length - 1)) / (xTickCount - 1)));
// Color (green for non-negative trend / positive zero-anchor, red for negative)
const lastVal = data[data.length - 1][yKey];
const lineColor = anchorAtZero
? (lastVal >= 0 ? accentColor : "#FF5C6F")
: accentColor;
const isNeg = anchorAtZero && lastVal < 0;
function onMove(e) {
if (!interactive) return;
const rect = e.currentTarget.getBoundingClientRect();
const px = e.clientX - rect.left;
const t = (px - pad.l) / iw;
const i = Math.max(0, Math.min(data.length - 1, Math.round(t * (data.length - 1))));
setHover({ i, x: x(i), y: y(data[i][yKey]) });
}
function onLeave() { setHover(null); }
const startVal = data[0][yKey];
const hovered = hover ? data[hover.i] : null;
const hoveredTrade = hovered ? tradeByDate[hovered.date] : null;
const hoveredMeta = hoveredTrade ? tickerMeta[hoveredTrade.ticker] : null;
const tooltipDelta = hovered ? hovered[yKey] - startVal : 0;
return (
{yVals.map((v, i) => (
))}
{/* zero baseline */}
{anchorAtZero && yMin < 0 && yMax > 0 && (
)}
{yVals.map((v, i) => (
{yFmt(v)}
))}
{xIdx.map((i, k) => (
{fmtDateShort(data[i].date)}
))}
{hover && (
)}
{hover && (
DATE {fmtDateShort(hovered.date)} · {new Date(hovered.date).getFullYear()}
{tooltipPrimaryLabel} = 0 ? "pnl-pos" : "pnl-neg") : "v"}>{tooltipPrimary(hovered[yKey])}
{tooltipDeltaLabel && (
{tooltipDeltaLabel} = 0 ? "pnl-pos" : "pnl-neg"}>{fmtUSD(tooltipDelta, { showSign: true })}
)}
{hoveredTrade && (
{hoveredTrade.ticker}{hoveredMeta ? " · " + hoveredMeta.n : ""}
{hoveredMeta && {hoveredMeta.s} · {hoveredMeta.x} }
)}
)}
);
}
/* ───────── Generic bars view (Daily $ / Daily %) ───────── */
function BarsView({ data, yFmt, tooltipPrimary, tooltipPrimaryLabel, extraRows,
tradeByDate, tickerMeta, height = 300, interactive = true }) {
const wrapRef = useRef(null);
const [width, setWidth] = useState(900);
const [hover, setHover] = useState(null);
useEffect(() => {
const el = wrapRef.current;
if (!el) return;
const ro = new ResizeObserver(() => setWidth(el.clientWidth));
ro.observe(el);
setWidth(el.clientWidth);
return () => ro.disconnect();
}, []);
useEffect(() => { if (!interactive) setHover(null); }, [interactive]);
const pad = { l: 56, r: 16, t: 14, b: 28 };
const W = width, H = height;
const iw = W - pad.l - pad.r;
const ih = H - pad.t - pad.b;
if (!data || data.length === 0) return
;
const max = Math.max(...data.map(d => Math.abs(d.value))) || 1;
const zero = pad.t + ih / 2;
const yScale = (ih / 2) / max;
// With very dense data (1000+ bars in ~800px) bars can go sub-pixel; clamp to 1.
const bw = Math.max(1, Math.min(20, (iw / data.length) - (data.length > 200 ? 0 : 2)));
const xTickCount = Math.min(5, data.length);
const xIdx = xTickCount <= 1 ? [0] :
Array.from({ length: xTickCount }, (_, i) => Math.round((i * (data.length - 1)) / (xTickCount - 1)));
const yTickVals = [max, max / 2, 0, -max / 2, -max];
const yTickY = v => zero - v * yScale;
const hovered = hover ? data[hover.i] : null;
const hoveredTrade = hovered ? tradeByDate[hovered.date] : null;
const hoveredMeta = hoveredTrade ? tickerMeta[hoveredTrade.ticker] : null;
return (
{yTickVals.map((v, i) => (
))}
{yTickVals.map((v, i) => (
{yFmt(v)}
))}
{xIdx.map((i, k) => (
{fmtDateShort(data[i].date)}
))}
{data.map((d, i) => {
const xCenter = pad.l + (data.length === 1 ? iw / 2 : (i / (data.length - 1)) * iw);
const x = xCenter - bw / 2;
const v = d.value;
const h = Math.abs(v) * yScale;
const yTop = v >= 0 ? zero - h : zero;
const fill = v >= 0 ? "var(--pos)" : "var(--neg)";
const isHovered = hover && hover.i === i;
return (
interactive && setHover({ i, x: xCenter, yTop })}
onMouseLeave={() => interactive && setHover(null)}
/>
);
})}
{hover && (
)}
{hover && hovered && (
DATE {fmtDateShort(hovered.date)}
{tooltipPrimaryLabel} = 0 ? "pnl-pos" : "pnl-neg"}>{tooltipPrimary(hovered.value)}
{extraRows && extraRows(hovered).map(([k, v], idx) => (
{k} {v}
))}
{hoveredTrade && (
{hoveredTrade.ticker}{hoveredMeta ? " · " + hoveredMeta.n : ""}
{hoveredMeta && {hoveredMeta.s} · {hoveredMeta.x} }
)}
)}
);
}
/* ───────── Recent trades ───────── */
function TradesTable({ trades, tickerMeta }) {
return (
Recent trades
last {trades.length} closed positions · newest first
read-only · source: alpaca
Date
Ticker
Company
Sector
Stake
P&L $
P&L %
{trades.map((t, i) => {
const meta = tickerMeta[t.ticker] || { n: t.ticker, s: "—", x: "" };
return (
{fmtDateShort(t.date)}
{t.ticker}
{meta.x && · {meta.x} }
{meta.n}
{meta.s}
${t.stake_dollars.toLocaleString()}
{fmtUSD(t.pnl_dollars, { showSign: true })}
{fmtPct(t.pnl_percent, 2)}
);
})}
);
}
/* ───────── About / How it works ───────── */
function AboutSection({ strategy, acct }) {
const isB = acct && acct.strategyKind === "B";
const steps = [
{ time: "09:00", name: "Optimizer", desc: isB
? "No formula search for Strategy B — stake is always the full portfolio. Optimizer still picks buy/sell timing."
: "Brute-force search over 14,700 combinations picks today's win/loss formula and buy/sell time." },
{ time: "09:20", name: "Scanner", desc: "Pulls pre-market movers from a curated watchlist. Picks the top % gainer above 4%." },
{ time: strategy.buy_time, name: "Buy", desc: isB
? "Deploys the ENTIRE portfolio into the picked ticker. Today's stake = yesterday's close."
: `Submits a market buy on Alpaca. Today's stake: $${strategy.base_stake_dollars.toLocaleString()} (base).` },
{ time: strategy.sell_time, name: "Sell", desc: "Closes the position at the chosen sell time. Realized P&L lands." },
{ time: "13:15", name: "Log", desc: "Writes logs/YYYY-MM-DD.json. Updates streak state for tomorrow." },
{ time: "13:30", name: "Notify", desc: "SNS email with the day's summary. CloudWatch records the run." },
];
return (
How it works
{isB ? "Compound everything, every day." : "One stock, one day, one schedule."}
read-only · alpaca · v0.1
{isB ? (
<>
{acct.label} runs Strategy B — full compounding .
Each day the bot deploys the entire portfolio balance into one stock
rather than a fixed $15k stake. The trade execution is identical to Account 1, but the
sizing changes every day.
Both gains and losses compound exponentially. A 5% winning day on a $130,000 portfolio
returns $6,500 — but a 7.9% losing day (like CSCO on 5/15) wipes out
$10,200+ in one trade. There is no streak formula —
stake is always 100% of available capital.
Backtest over the last {strategy.backtest_days} days returned
{" "}+{strategy.backtest_pnl_percent.toFixed(2)}%
{" "}at a {(strategy.backtest_win_rate * 100).toFixed(1)}% win rate.
>
) : (
<>
This bot trades one stock per day on Alpaca. Each morning a
scanner pulls pre-market movers from a curated watchlist of 570 tickers and picks the top
percent gainer above 4%. An optimizer decides what time to buy and sell based on what has
worked historically.
Stake size scales up after winning streaks and resets after a loss. The
current win formula is {strategy.win_formula_expression}
{" "}({strategy.win_formula_label}); the loss formula is
{" "}{strategy.loss_formula_expression}
{" "}({strategy.loss_formula_label}). Both were chosen by {strategy.chosen_via}.
Backtest over the last {strategy.backtest_days} days returned
{" "}+{strategy.backtest_pnl_percent.toFixed(2)}%
{" "}at a {(strategy.backtest_win_rate * 100).toFixed(1)}% win rate. The dashboard reads
the daily logs the bot writes to disk after each run.
>
)}
AWS EC2
Alpaca Markets API
Python 3.12
cron @ 09:20 ET
SNS email alerts
CloudWatch
github.com / trading-optimizer
{steps.map((s, i) => (
{s.time} ET
{s.name}
{s.desc}
))}
);
}
/* ───────── Export ───────── */
Object.assign(window, {
Sparkline, Topbar, TodayHero, StatsRow, MainChart, TradesTable, AboutSection,
AccountTabs,
fmtUSD, fmtPct, fmtDateShort, pnlClass,
});