Commit History
Yesterday
-
fix(quotes): pair a closed-session price by its trade time, not its broadcast time Running
Awannit7a57e8c
4 days ago
-
feat(stream): admit by feed age and client backlog; slow the update rate as clients grow
Awannitc7d3d86
-
feat(nepse): live NEPSE feed, one request for the whole market
Awannitb2a48d6
-
feat(alerts): trigger on ticks instead of polling every 2 minutes
AwannitPrice alerts waited up to 120s for a timer to ask Vercel to look. The tick that satisfies the condition was already in this process's memory, so the poll was asking on a schedule about an event being delivered continuously. watch-live.ts pulls the live-tier thresholds from Vercel every few minutes (no user ids, no state), checks them against every tick, and on a local false->true transition asks Vercel to run its normal evaluation pass now. Latency goes from up to 120s to about 2s. It triggers; it does not decide. Edge detection, cooldowns, mute windows, dedupe and the write all stay on Vercel, because a second copy of the firing rules in a second repo is how a user gets notified twice. Being only a trigger means being wrong cheaply, so ambiguous cases err toward poking: a condition mixing live and daily predicates triggers on its live half and Vercel judges the whole thing. Transitions do not send requests, they set a flag, and one debounced request follows. A thousand users watchi…5634d87
-
feat(capacity): admit clients by measured health, not a guessed cap
AwannitAdmission was governed by MAX_CLIENTS, a constant picked by intuition (200). A constant is wrong in both directions: too low and it turns away tabs while the box sits idle, too high and it admits everyone right up until the event loop stalls and *every* connected user loses their feed. Client count was never what determined whether this server could cope. The load that matters is fan-out work (clients x symbols x tick rate), and event-loop delay measures that directly. - load.ts: samples monitorEventLoopDelay + RSS on a 5s reset cycle and reports ok / strained / critical. - Admission keys on that. While healthy there is no client-count ceiling at all. Under strain, anonymous arrivals shed first so signed-in users keep the sessions they have work in. Critical stops new work entirely; existing clients are never dropped for load. - MAX_CLIENTS survives only as a runaway backstop at 20k, far above real load. STRIX_MAX_CLIENTS=0 disables it. Also adds the live-tier alert heartb…d3911cc
-
Add /quotes batch endpoint and opt-in OHLCV to /history
AwannitTwo additive read endpoints over state the engine already holds, plus the freshness rules that make them safe to trust. /quotes?syms= returns latest price + prevClose for up to 50 symbols straight from memory. It never calls getBaseline (which falls through to fetchHistory), so a 50-symbol request cannot fan out into 50 upstream provider calls. This removes ~30 Finnhub /quote calls per Market Intelligence run and up to 50 per portfolio refresh — the two largest consumers of a 60/min shared allowance. Freshness is classified, never synthesised. asOf is the conflated tick-broadcast time (engine.getLatestTickAt, which over-states age by at most one conflation interval and never under-states it) or the 1D bar's own start time. session_close REQUIRES a closed session. A stored daily bar is not a current price while a market is trading — it is that morning's open, and the market has been moving against it ever since. Serving one whenever a tick was merely absent meant the most likely mome…f6456c2
-
Add instrument identity boundary (validFromUtc) — fix SPCX phantom pre-listing candles
Awannit"SPCX" is a recycled ticker: a 2021 SPAC ETF (~$28.85) traded under it before delisting, so raw-ticker history queries returned the old instrument's bars and rendered phantom candles before SpaceX's 2026-06-12 Nasdaq debut. Users could also scroll into that meaningless span (the chart's left edge anchors to bars[0]). - symbols.ts: optional SymbolMeta.validFromUtc (ISO date, parsed as UTC midnight per ECMA-262) — instrument identity boundary, registry-owned; fail-fast authoring tripwires (1990 floor, +10y ceiling); SPCX set to 2026-06-12; getValidFromSec() runtime accessor (epoch sec). - history.ts: central per-branch filter in fetchInitialUncached (upstream of the LRU cache; Alpaca→Yahoo fallback decides on the FILTERED count), floorGuard backfill wrapper (zero-copy fast path, terminates at the boundary, propagates inner errors), per-provider cursor clamps (Alpaca/Yahoo window clamps incl. Yahoo exclusive-period1 -1s slack; Binance/OANDA cursor stops). - engine.ts: aut…069e91a
-
Strix Server V2.1.4 — cold-start hardening, memory optimisation, compression test
Awannit +1: Claude Sonnet 4.6Cold-start thundering-herd guard - Add FIFO counting semaphore (SEED_MAX_CONCURRENT=8, env-overridable via STRIX_SEED_MAX_CONCURRENT) in ClientHub.handleSubBars. At market open hundreds of clients subscribe within a second; without this each fires an independent provider REST call. Same-(sym,tf) subs already coalesce via the seeding flag, but the real fan-out (distinct symbols × timeframes) can still produce 429 storms on Alpaca free IEX (~200 req/min). The semaphore caps in-flight fetches to 8 while beginSeed() is called BEFORE acquiring a slot so coalescing still works correctly while a slot waits. try/finally guarantees the slot is released even if fetchHistory() throws. Memory optimisation - MAX_BARS_PER_BUCKET default 1,000,000 → 150,000 bars. This sits above the deepest legitimate single-bucket backfill (BACKFILL_PAGE_SAFETY_LIMIT×~1000 bars/page ≈ 100,000 for a 24/7 market) so no user hit the trim, but drops worst-case per-bucket footprint ~6.7× (≈48 MB → …d1a22ba
-
Strix Server V2.1.3 (Session-open baseline fix, forex tick volume, /history endpoint for Chronos, memory hardening)
AwannitCorresponds to Strix Charts V2.1.3. Baseline / CHG correctness at the session boundary - Fix wrong CHG/CHG% the instant a market opens (watchlist/header showed the move vs the wrong prior session until a page refresh). New session-aware derivePrevClose() (baseline.ts) handles the "session-open lag" window where the provider hasn't emitted today's daily bar yet: when the market is open and the last 1D bar starts before the current session bucket, that last bar IS the previous close. Off-hours/weekend behavior (prevClose = bars[len-2]) preserved. - ClientHub now corrects prevClose instantly and event-driven: detectSessionCross() watches the live tick stream and re-derives + re-pushes the moment a tick crosses into a new daily session (the same instant the chart's 1D bar rolls) — no timer, no network (prior close is already cached). sub_ticker arms the trigger per symbol (seeds 0 when the market is closed so the first tick of the next session fires); only the first su…7d92bb6
-
Corresponds to Strix Charts/Streaming V2.1.2
Awannitf30ccd2
-
Strix Streaming V2.1
Awannit1af30de
-
Strix Streaming - V2
11f6676