/* ───────────────────────────────────────────────────────── app.jsx — main dashboard composition + Tweaks integration ───────────────────────────────────────────────────────── */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "todayState": "auto", "density": "regular", "logo": "candlesticks" }/*EDITMODE-END*/; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [selectedAcct, setSelectedAcct] = React.useState(0); const D = window.DATA; const acct = D.accounts[selectedAcct]; const clock = useETClock(); // Inject the active account's accent into a CSS var so the chart line / fill follow document.documentElement.style.setProperty("--acct-accent", acct.accentColor); // Decide which "today" plan to show. Tweak takes precedence (for demo + // testing). When tweak is "auto" we use the REAL position state from // samples.today.position_state (set by generate_dashboard_data.py based // on the bot's actual log), with clock-based overrides only for: // - weekend (cron paused, show weekend tile) // - after-hours of a market day (show tomorrow's plan) // - log isn't from today (bot hasn't fired yet -> pre_market) // This replaces the old hardcoded 10:45-13:00 "holding window" which // was wrong when the bot's actual buy/sell times differ from those slots. const todayDateET = (() => { // Today's ET date as YYYY-MM-DD const d = new Date(); const opts = { timeZone: "America/New_York" }; const y = d.toLocaleString("en-US", { ...opts, year: "numeric" }); const m = d.toLocaleString("en-US", { ...opts, month: "2-digit" }); const day = d.toLocaleString("en-US", { ...opts, day: "2-digit" }); return `${y}-${m}-${day}`; })(); const logIsToday = acct.actualDate === todayDateET; const isFirstRun = !!acct.firstRun; const resolvedState = (() => { if (t.todayState !== "auto") return t.todayState; // A LIVE open position (current_position_ticker from GET /accounts) means the // account is holding RIGHT NOW — show it immediately, before the hourly // generator refreshes the blob. Same real-time source as the Settings strategy // lock, so the "Live · Holding" badge never lags. Checked before firstRun so a // brand-new account's very first buy shows "holding", not "awaiting first trade". if (acct.livePositionTicker) return "holding"; // Brand-new account (never traded): always show the awaiting-first-trade // plan — in EVERY clock window, not just after-hours — so a pilot who just // linked their account sees their balance + "here's what happens next", // not a bare "no trade today" tile. if (isFirstRun) return "next_day"; if (clock.parts.weekday === "Sat" || clock.parts.weekday === "Sun") return "weekend"; if (clock.status.isAfterHours) return "next_day"; if (!logIsToday) return "pre_market"; // bot hasn't fired yet today // Use actual position state from samples return acct.actualPositionState || "no_trade"; })(); const plan = (() => { const base = acct.todayPlans[resolvedState] || acct.todayPlans.closed; // For next_day, embed today's settled result so the hero can surface it if (resolvedState === "next_day") { // First-run: no settled trade exists yet — flag it so the hero shows the // welcome copy instead of an empty "today's result" tile. if (isFirstRun) return { ...base, first_run: true }; const today = acct.todayPlans.closed; return { ...base, today_settled: { ticker: today.ticker, ticker_long: today.ticker_long, buy_time: today.buy_time, sell_time: today.sell_time, buy_price: today.buy_price, sell_price: today.sell_price, shares: today.shares, realized_pnl: today.realized_pnl, realized_pnl_pct: today.realized_pnl_pct, }, }; } return base; })(); const equitySpark = acct.equityCurve.slice(-15).map(d => d.portfolio_value); // Account/position visibility: no_trade / weekend / next_day zeros out today's P&L + positions const flatState = ["no_trade", "weekend", "next_day"].includes(resolvedState); const account = flatState ? { ...acct.account, day_pnl_dollars: 0, open_positions_count: 0 } : resolvedState === "holding" ? { ...acct.account, open_positions_count: 1 } : acct.account; const shellStyle = t.density === "compact" ? { padding: "20px 32px 56px" } : {}; // Bot status pill (ACTIVE green / STANDING BY red) — derived from the resolved // today-state + live open position + clock. Holding/open position ⇒ mid-cycle // ⇒ ACTIVE; everything else (after the sell, no-trade, weekend, overnight, // pre-cron) ⇒ STANDING BY. See botState() in components.jsx. const botStatus = botState({ resolvedState, openPositions: account.open_positions_count, livePositionTicker: acct.livePositionTicker, }, clock.parts); return (