v1.9.20260904a
2026-09-04 · a capped ticker no longer ends the tick
- A name at its concentration cap blocked every tick it stayed top-ranked. The stock path committed to a single ticker before any cap was checked, and the concentration guard then ended the whole tick with a return. The bot proposed the same capped name, refused it, and bought nothing — repeatedly — while lower-ranked candidates were never examined. Structurally the same reject-and-abandon mistake as the entry-DTE floor in July, which blocked 125 decisions over one unlucky expiry.
- Selection now filters first. Candidates already at or above the per-ticker cap are skipped and the next-best is considered, with the skipped names logged. If every candidate is capped, the tick is blocked once with that reason recorded in the journal, rather than silently repeating.
- The block message now distinguishes current from projected exposure. Reaching the downstream guard means current exposure was under the cap but this order's size would cross it — a different situation from a name already over, and the log now says which.
- Verified by simulation: with a capped name top-ranked, selection moves to the next candidate; with every candidate capped, the entry is still refused and the reason recorded; and with nothing capped, the top pick is chosen exactly as before.
- No cap was loosened. The per-ticker limit, the collateral caps and every other guard are unchanged — this only stops one blocked name from starving the whole selection.
v1.9.20260829a
2026-08-29 · duplicate bot loops
- The bot could run several trading loops at once. An activity log showed the Brain completing twice within the same second, alongside doubled data fetches — the signature of more than one
botTick loop running concurrently.
- Cause: START was not idempotent.
startBot() assigned fresh timers over the existing handles without clearing them, so the originals were orphaned — still firing, and no longer stoppable, because stopBot() can only clear the handles it currently holds. Calling start twice therefore left two loops running permanently, and stop only ended one.
- Why this matters beyond wasted API calls: every duplicate-order guard in the bot —
isCloseRecent, markCloseSubmitted, hasOpenOrderFor, the tick cash reservation — assumes a single loop. Two ticks can both pass the same check before either marks its intent, which is the most plausible origin of the repeated closes of one contract, and of the long put created by a double buy-to-close, that were investigated in July.
- Re-entrancy guard added. A tick makes many awaited network calls and can exceed the 30-second interval, so even a single timer can overlap itself. Ticks now refuse to start while one is in flight, and report how many were skipped.
- Cross-tab detection. A second browser tab is a separate page with its own timers, which no in-page guard can see. Running sessions now write a heartbeat, and a session that finds a recent heartbeat from another one raises a loud warning.
- Activity log spam reduced. The earnings cache-hit message logged on every call — every few seconds — crowding useful lines out of the 150-entry log, which is the main diagnostic surface. Now at most hourly.
v1.9.20260818a
2026-08-18 · cash transfers are not losses
- The bot had no concept of deposits or withdrawals. Every change in equity was treated as trading performance. On a second account a cash withdrawal was therefore read as a large loss: it fired a sticky CB3, and then pinned the rolling monthly drawdown past its limit — blocking new entries for a month. Journaled trading over the same three weeks was roughly flat and slightly positive.
- Cash movements are now tracked. The activities endpoint the bot already polls for assignments is also queried for CSD (deposit), CSW (withdrawal) and JNLC (cash journal), on the same cadence. Movements are recorded by date, logged when first seen, and retained for 120 days — longer than the 90-day equity log they correct.
- Drawdown is measured on transfer-adjusted equity. Each daily snapshot now stores cumulative net transfers alongside equity, and the rolling calculation subtracts them, so the result reflects performance rather than account size. Entries written before this field existed default to zero, reproducing the previous behaviour for that period.
- The circuit breaker excludes same-day cash movements before computing the daily loss percentage, so moving money can no longer trip a breaker.
- New panel: Safety → ROLLING DRAWDOWN HALT. Shows both windows with their limits, sample counts and peak dates, flags which is breached, notes when a figure has been transfer-adjusted, and lists recent equity and excluded cash movements. A RESET BASELINE button clears a distorted equity history without needing a browser console.
- Verified against the affected account's real series: the monthly drawdown reads −29.16% on raw equity and −3.21% once the transfer is excluded, and the 08/04 breaker reads −26.00% raw against +0.81% adjusted. Controls confirm the protection still works: a genuine −15% loss still halts, and a deposit can no longer mask a real loss — a raw +20% that conceals a −13.33% trading loss now correctly halts.
v1.9.20260808a
2026-08-08 · portfolio options cap measured in one unit
- The CSP portfolio cap was comparing mixed units. Its budget was 25% of equity minus the option positions' market value — the premium, a couple of thousand dollars — but that budget was then divided by
notionalPer, which is collateral (strike × 100, tens of thousands). A premium-denominated budget divided by a collateral-denominated cost rounds to zero for any strike above roughly a quarter of equity.
- It was wrong in both directions. Expensive strikes were blocked while nowhere near any real limit, and the message reported it as "($2137/$26593 used)" — which reads as 8% of the budget consumed, so the block looked inexplicable. Cheap strikes were meanwhile waved through: on an account already at 77% collateral, the old arithmetic would still have permitted four contracts of a $50 strike. The same comparison also fed contract sizing.
- Both sides now measure collateral, consistent with
checkCollateralCaps, so there is one portfolio limit expressed in one unit. The message now names what is actually short: collateral required, collateral remaining, and how much of the cap is committed.
- Verified against the live account's own numbers: at 77% collateral against a 70% cap the remaining budget is negative, so every entry is correctly refused. The fix makes that refusal honest rather than accidental, and is stricter here, not looser.
v1.9.20260804a
2026-08-04 · circuit-breaker false trigger
- A CB3 sticky lockout fired on a reading that could not have been real. It triggered pre-market on a second account that was 95% cash: a −10% equity loss requires a drop several times larger than the total value of every position held, and cash does not fall when markets fall. The trigger followed a stock sale, which points at the settlement window — the position has left the positions list but the proceeds have not yet landed in cash, so equity reads low for a moment.
- The percentage used the wrong denominator. Daily loss was computed as
(equity − last_equity) / equity, dividing by the current value rather than the starting one. Two consequences: the thresholds did not mean what they said — a CB3 labelled −10% actually fired at a true −9.09% loss — and a temporarily understated equity reading was amplified rather than damped, because the same shortfall is divided by a smaller number. Now measured against last_equity.
- CB3 requires confirmation across two consecutive ticks. It is the one tier that disables the manual RESUME button for the rest of the trading day, so it should not act on a single reading. On the first breach the bot halts at CB2 — recoverable, entries stopped — and states that it is confirming. If the next tick agrees, CB3 engages; if equity has recovered, the pending state clears with a log line naming the likely cause. A transient artifact resolves in about 30 seconds; a real loss does not. CB1 and CB2 still fire immediately, since both can be resumed.
- No baseline, no guess. If the account reports a missing or zero previous-close equity, the breaker check is skipped for that tick with a throttled warning rather than deriving a percentage from an absent number.
- Verified by simulation: the settlement-artifact sequence halts at CB2 and then clears without a lockout; a genuine −12% day confirms and goes sticky on the second reading; a real −6% day is unaffected and behaves exactly as before.
v1.9.20260728a
2026-07-28 · collateral caps are now adjustable
- Correction: the collateral caps shipped in v1.9.20260726a were described as configurable, but no Safety-tab controls were ever built for them. The values existed only in code and could not be changed from the interface. This adds the missing panel.
- New panel: Safety → OPTION COLLATERAL CAPS. Three settings, all as a percentage of equity: the portfolio cap across all short options (default 50%), the per-trade sizing target (12%), and the hard ceiling for a single contract (25%). Persisted to
gus_collateral_caps, so they survive updates like the other safety settings.
- Live usage display. The panel shows collateral currently committed in dollars and as a share of equity, a colour-coded bar that turns amber near the cap and red at it, remaining headroom, and the largest strike that headroom still fits. This makes a COLLATERAL CAP log line self-explanatory: you can see how close to the line the account already was.
- Guardrail: the single-contract ceiling cannot be saved below the per-trade target. Inverting them would block entries the sizing logic considers acceptable, which is how a smaller account gets locked out of every liquid strike.
- No change to trading behaviour — the defaults are identical to what was already running. This build only makes them visible and editable.
v1.9.20260727b
2026-07-27 · ex-dividend awareness for covered calls
- Bug fix — the earnings fallback asked for data Alpaca does not publish. The corporate-actions request used
types=earnings, but that database covers dividends, splits, mergers and spin-offs, not earnings. The call therefore always returned empty, and the failure was reported to the user as a probable data-subscription problem. The dead fallback is removed and the log now states the real position: earnings dates come from Finnhub or from manual entry in the Safety tab.
- Ex-dividend awareness for short calls. The same corporate-actions endpoint does serve cash dividends with an ex-date, so the bot now fetches them for the underlyings it holds short calls on. An in-the-money short call is most likely to be assigned early on the day before an ex-dividend date, when the dividend exceeds the call's remaining time value and exercising to capture it becomes rational for the holder. The result is shares called away, the covered-call leg ended early, and the dividend missed. The bot previously had no idea ex-dividend dates existed.
- The guard is deliberately narrow. It acts only on short CALLS that are in the money, only within 2 days of an ex-date, and only when remaining time value is below the dividend per share. Short puts are untouched — there is no dividend-capture incentive to exercise them early. Long calls are untouched — there the holder is you. An unknown ex-date means no action, the same best-effort stance the earnings blackout takes, since Alpaca warns corporate actions may be delayed by its data providers.
- Closes use a marketable limit priced through the touch, consistent with the other scheduled exits. Events are journaled as
ex_div_close with the ex-date, the amount in the money, and the time value at the moment of the decision, so the reasoning is auditable afterwards.
- Verified by simulation before shipping across eight cases: deep ITM with thin time value closes; ITM with time value above the dividend correctly holds, because early exercise would not be rational; OTM holds; an ex-date outside the window is ignored; short puts, long calls and unknown ex-dates all take no action.
- Reviewed but not adopted: Alpaca's index options, released to paper trading this month, are cash-settled — there are no shares to be assigned, so the wheel cannot function on them, and index market data is not yet available to select strikes from.
v1.9.20260727a
2026-07-27 · regime integrity + price-context instrumentation
- Regime is now pinned for each option-management pass. The exit thresholds were read once at the top of the function, but the loop awaits on every position — so a Brain refresh completing mid-loop could change the regime while the old thresholds stayed in use. A second account's journal recorded a close at "22 DTE (threshold 28)" alongside regime NEUTRAL, a combination impossible within one consistent tick. The pass now uses a single pinned regime and the journal records which regime was actually applied.
- Fail-safe regime on data failure. When the market-data fetch failed, all four signals were assigned random values and a regime was derived from them, logged as "local HMM fallback" — wording that reads like a model but was
Math.random(). Regime is not cosmetic: it sets the delta target, the IV-rank floor, the profit threshold and the DTE-close threshold, so an invented regime silently changed real trading parameters. The bot now retains the last known regime, marks it stale, and suspends new option entries until live data returns. Exits, stops, breaker closes and all defensive management continue to run.
- 20-day price context recorded in the journal — measurement only. The bot had no per-stock price history at all; every signal was a same-day move on market-wide ETFs, so it could not tell whether an individual name sat high or low within its own recent range. Stock decisions now record the 20-day average, the percentage above or below it, and distance from the 30-day high and low. Nothing reads these values — no entry is gated on them. They are exported in the CSV so the question "did entries above the 20-day average do worse?" can be answered from real trades instead of argued from intuition. Cached once per symbol per day.
- Why measurement rather than a filter: a moving average is a reference line, not a signal. "Only buy below it" is a mean-reversion bet that also turns the bot into one that exclusively averages down; "only buy above it" is a trend bet that would have permitted the ladder that prompted the question. The two are opposite strategies and the data to choose between them does not exist yet.
v1.9.20260726c
2026-07-26 · entry quality guards
- Stale-quote guard on stock buys. A second account placed five stock orders across two days at a byte-identical price, every one of them outside regular hours — the quote was frozen and the bot was sizing real orders against it. Two of those orders came back as broker rejections. Entries now read the trade timestamp from the snapshot and refuse a buy when the last trade is older than the configured limit (default 15 minutes). Options already refused a stale chain feed; stock entries had no equivalent.
- Post-stop re-entry price rule. The 24-hour cooldown prevents immediate whipsaw but expires. That account was stopped out of a name and then bought it back five days later above the entry price that had just failed, across five orders. After a stop-loss the bot now records the failed entry price and refuses to re-buy that underlying above it until the window expires (default 30 days). Buying back below the failed entry is still allowed, since that is a better price than the trade that lost money. Active bars are listed in the Safety tab.
- Bug fix — price fetch failure defaulted to $100. The stock entry path initialised
livePrice to 100 and kept that value if the snapshot threw or returned no trade. A failed fetch therefore sized and priced the order as though the stock cost $100, and the cash check passed against the wrong number. It now aborts the entry and records the reason in the journal.
- Verified by simulation before shipping against the sequences in that account's journal: all five re-buys above the failed entry are refused; a dip below the failed entry is still allowed; the rule expires on schedule; and a live in-session quote passes the freshness check while a prior-day price does not.
- Note: the stale-quote guard will refuse most pre-market and after-hours stock entries, because the last trade is usually hours old at those times. That is the intended behaviour. Both guards apply to stock BUYs only — defensive sells, stops, breaker closes and option management are never blocked — and both are configurable in Safety → ENTRY QUALITY GUARDS.
v1.9.20260726b
2026-07-26 · fixes surfaced by a second account's journal
- The stock concentration cap has never run.
exposureForTicker() was defined only inside openOptionCandidate() and openOptionCandidateElite(). The stock-path cap added in v1.9.20260627b is guarded by typeof exposureForTicker === 'function', which evaluates to false at botTick scope — so the guard silently skipped the whole check on every tick since it shipped. A second account's journal showed one underlying reaching roughly 27% of equity against a 10% cap with no CONCENTRATION record. Now defined globally; the nested copies still shadow it locally with identical behaviour.
- Collateral cap recalibrated for smaller accounts. The 12% per-trade cap shipped in v1.9.20260726a was calibrated against a six-figure account. On a ~$40K account it caps strikes at about $47, which blocks every liquid wheel candidate — the same class of mistake as the entry-DTE floor. New
maxSingleContractPct (25%) lets a single contract through above the sizing target while the portfolio cap still binds. Verified against both account sizes: normal wheel strikes pass, genuine over-concentration is still refused.
- Stock entries now check buying power, not just cash. Cash can look ample while buying power is nearly exhausted by option collateral, producing a broker 403 the bot could have predicted. Two such rejections appear in the second journal on a day with five figures of cash available.
- Confirmed by the second journal, already fixed in v1.9.20260720a / v1.9.20260726a: the DTE open-and-close loop (entries landing at exactly the close threshold, with long runs of duplicate closes on the same contract), and earnings-blackout over-blocking measured against full DTE rather than planned hold.
- Note on settings: churn and safety settings persist in localStorage and survive updates, so shipping new defaults does not change an existing installation. The second account was running a per-ticker daily cap of 5 rather than the current default of 2. Review the Safety tab after updating.
v1.9.20260726a
2026-07-26 · unblock entries, cut drag, real risk units, macro blackout
- Driven by the 07/26 journal. The 07/20 churn fixes worked — zero cycling events after the version switch — but the bot then took no action at all: two thirds of decisions were blocked by the new entry-DTE floor, and the remainder by a chain that arrived without Greeks.
- T1.1 —
targetDTE 30 → 45. Paired with closeAtDTE 21, a 30-DTE entry lived only ~9 days: little theta captured, spread paid on both ends, and an entry window that collided with the close trigger. 45 DTE gives roughly 24 days of hold, the standard wheel parameterization.
- T1.2 — chain is DTE-filtered before strike selection. Previously the selector ran across every expiry and the DTE gate rejected the winner afterwards, abandoning the candidate. On 07/20 the best-delta contract sat one day under the floor while a valid further expiry was never considered. Filtering first means the selector only ever sees qualifying expiries.
- T1.3 — Black-Scholes delta fallback. When a chain arrives with no Greeks populated, delta is now estimated from spot, strike, DTE and IV rather than discarding the candidate. Estimated contracts are flagged in the log.
- T2.1 — marketable limit orders on scheduled closes. Take-profit and DTE closes now submit a limit priced through the touch instead of a market order, so the full spread is not conceded on every exit. Defensive closes (stop-loss, breaker, pre-earnings, rolls) deliberately keep market orders — fill certainty outweighs spread there.
- T3 — option risk caps now measure collateral.
maxTotalPct and perTickerMaxPct summed option market value (the premium) rather than collateral at risk (strike × 100). Measured that way the portfolio cap would need dozens of short puts before binding, and maxPctPerTrade never bound at all because sizing ends in Math.max(1, …) — when the budget was smaller than one contract the floor forced one anyway. New maxCollateralPctPerTrade (12%) and maxTotalCollateralPct (50%) measure collateral directly and reject rather than override.
- T4.1 — earnings blackout measured against planned hold. The check tested earnings against the full DTE even though positions close at
closeAtDTE, so it blocked candidates over earnings the bot would never hold through. It now tests DTE − closeAtDTE. Without this, moving to 45 DTE would have blocked nearly every candidate. preEarningsCloseDays remains the backstop.
- T4.3 — macro event blackout. New Safety-tab panel blocks new option entries within N days (default 2) of FOMC decisions and CPI releases — the market-wide analogue of the earnings blackout. Ships with the 2026 schedule built in and a user-editable date list, since built-in dates go stale.
- Bug fix — the approximate-entry fallback never ran.
openOptionCandidate() referenced an undeclared contract object, so every invocation threw ReferenceError before submission and surfaced only as a generic error. Now uses its actual local variables, and carries the same macro, collateral and churn gates as the elite path.
- Unchanged: every safety layer from June and July — churn gates, cooldowns, concentration cap, kill switch, breaker, no-short guard, PDT protection, cash floor, and full journal instrumentation.
v1.9.20260720a
2026-07-20 · DTE churn loop fixes
- Driven by the 07/17 journal. The bot entered a self-inflicted loop: it sold CSPs on contracts at exactly 21 DTE, the DTE-close logic (threshold 21) immediately bought them back, and the Evaluator re-entered the same contract — ~60 open↔close cycles across two underlyings in one afternoon (189 orders), paying spread and fees each time. A third contract entered at 25 DTE never churned, confirming the mechanism.
- Fix 1: regime-aware entry-DTE floor. New
getMinEntryDTE() = max(minDTE, effective closeAtDTE + 5), where effective closeAtDTE mirrors the manager's regime adjustment (28 in CRASH/BEAR, else 21). Applied in both the elite and fallback entry paths — the bot can no longer open a contract its own closer would immediately kill.
- Fix 2: churn gates now bind option entries. Before any auto-CSP/CC submission (elite and fallback), the bot checks the per-underlying daily cap, total daily cap, and post-close cooldown via
checkChurnPreventionGates(getUnderlying(symbol), 'buy'). Previously these gates were wired only into the stock path. Also, countTotalOrdersToday() now counts option orders against the 10/day total cap (it previously excluded them entirely).
- Fix 3: DTE close hardened. The DTE force-close branch now has the same duplicate-close guard as the other close paths (
isCloseRecent + hasOpenOrderFor + markCloseSubmitted) — it previously had none, and fired repeated closes 30 seconds apart on the same contract (the same double-close family that once created a rogue long put). After a successful DTE close, the underlying enters the post-close cooldown, blocking same-day Evaluator re-entry.
- Net effect: Friday's pattern is structurally impossible on four independent axes — can't enter below closeDTE+5, can't exceed 2 orders/underlying/day or 10 total, can't double-fire the closer, can't re-enter a just-closed underlying for 24h.
- Validation note: the 07/08 fixes were confirmed working in this journal (no stop-loss retry floods, full outcome stamping, earnings blackout active).
v1.9.20260708a
2026-07-08 · stock stop must skip options + retry backoff
- Driven by the 07/08 journal (first full-fidelity export). A long option position tripped the -8% stock stop-loss, and the resulting sell order was rejected by the broker and retried every 30 seconds for two hours — hundreds of rejected orders that consumed half the journal.
- Fix A:
checkStockStops now skips option positions entirely — checks both asset_class === 'us_option' and the OCC symbol pattern (belt and suspenders). Options are managed exclusively by their own logic (2× credit stop, take-profit, DTE force-close, pre-earnings close). Stock stops never touch OCC symbols again.
- Fix B: retry backoff for failed stop-loss closes — after 3 consecutive broker rejections for the same symbol, the bot backs off for 30 minutes and raises a loud
🛑 CHECK THIS POSITION MANUALLY alert instead of hammering the API every tick. A successful close clears the failure counter.
- Journal fidelity validation: the 07/02 fixes are confirmed working in production — zero pending records, every decision stamped with a concrete reason, no after-hours flood, earnings blackout actively skipping entries, and the options-buying-power constraint now visible with exact numbers per tick.
- Note: the rogue long put itself requires manual review in the Alpaca dashboard (position may be real from a duplicate buy-to-close, or a stale cache entry). The DTE force-close path does handle long options and remains a backstop.
v1.9.20260702a
2026-07-02 · journal fidelity (silent exits + ext-hours)
- Driven by the 07/02 journal review. The export showed 96% of records logged during the after-hours session (Evaluator kept picking OPTION every 30s from 4-8 PM ET, when options can't trade) — rotating the entire regular trading day out of the 500-record cap. And 100% of OPTION decisions were "pending" because the option-entry path exits silently at ~24 different points without stamping an outcome.
- Ext-hours journaling gate: during pre/after-market (with EXT-HRS enabled), only actionable STOCK verdicts are journaled and pursued. OPTION and SKIP verdicts outside regular hours are neither journaled nor pursued (throttled log message instead). Regular-session behavior unchanged.
- Catch-all outcome stamp for the option path: every silent exit in
openOptionCandidateElite/openOptionCandidate logs its reason immediately before returning; botTick now captures that most-recent log line and stamps the decision no_action with it. Covers all ~24 exit points (IVR too low, no affordable CSP, wheel cap, stale feed, earnings blackout, per-underlying caps, etc.) without touching each one.
- Submission stamping: a successful elite option submission now stamps the originating Evaluator decision as
submitted (previously only the separate EVENT record was created, leaving the decision pending).
- Stock-path silent exits stamped: duplicate-order guard, insufficient-cash check, and nothing-to-sell now each stamp
no_action with their specific reason.
- Net effect: "pending" should effectively disappear from the journal. Every decision now records what happened and why — including why nothing happened, which was the missing half of the story.
v1.9.20260627d
2026-06-27 · breaker state cleanup
- Bug fix: the Safety tab was showing "BREAKER CB1 ACTIVE" with a trigger date from weeks ago, while simultaneously showing "✓ MANUALLY UNLOCKED". Three coordinated fixes resolve the stale-state problem.
- Fix 1:
resumeFromBreaker() now deletes the breaker state when the user clicks Resume Trading, instead of just flagging unlocked: true. The flag-only approach left the state record alive in localStorage, so the renderer kept treating it as "active" with the original trigger data displayed.
- Fix 2:
getBreakerState() auto-clears any record from a previous trading day (previously only cb3 had this check; cb1 and cb2 records could persist indefinitely). It also defensively clears any record with unlocked: true so the UI never shows a contradictory state.
- Fix 3: on bot startup, the breaker-state cleanup runs automatically — so users with stale records in localStorage (like the historical CB1 from weeks ago) get them cleared on next page load.
- Behavior unchanged for live breaker events: when a current-day breaker fires, all the original protective actions still run (cancel open orders, close breached stocks if cb2+, sticky cb3, etc.). Only the UI/state-management bug is fixed.
v1.9.20260627c
2026-06-27 · changelog policy: no P&L amounts
- Policy change: changelog entries no longer reference specific dollar amounts of realized loss or gain from account history. Configuration values (thresholds, caps, defaults) remain since they document how the bot works, not what the account did.
- Cleaned six prior changelog entries to remove account-specific P&L figures (e.g., MU round-trip damage, CRM churn losses, BAC whipsaw losses, AMZN cash/OBP example values). Replaced with qualitative descriptions that preserve the diagnostic meaning without exposing account history.
- This is a documentation-only change. No behavior modified. All Tier A bug fixes and Tier B default tightenings from v1.9.20260627b remain in effect.
v1.9.20260627b
2026-06-27 · post-mortem fixes (Tier A bugs + Tier B defaults)
- Driven by the 06/25-06/26 post-mortem. Alpaca activity log showed material realized losses from an MU round-trip and CRM put churn within a 90-second window.
- A1 (bug fix): Evaluator loser-bias now gated on
rotationEnabled. The scoring code added +2 to stock score per underperformer regardless of the rotation toggle. This drove the 06/25 mass-liquidation despite rotation being "off." Bias now respects the toggle.
- A2 (bug fix): Underlying-level post-stop cooldown. Cooldown was keyed on exact OCC contract symbol — closing CRM 143 put didn't block CRM 142/144. Now also tracks the underlying ticker; option close blocks ALL options on the same underlying for the cooldown window. New
getUnderlying() helper parses OCC symbols.
- A3 (bug fix): Per-underlying daily order cap for options. Order count was per-exact-symbol — 11 CRM fills across 4 strikes counted as 4 separate symbols. Now counts all options on same underlying together.
- B1 (default):
maxPctPerTrade halved from 5% to 2.5%. Reduces per-order concentration. For a $100K account, each trade now ≤ ~$2,500.
- B2 (new check): Concentration cap on STOCK BUYs. The 10% per-underlying cap existed but was only checked during CSP/CC candidate selection — stock-buy path was missing it. Now any stock BUY that would push the underlying above 10% of equity is refused with a
CONCENTRATION: log line. Records to decision journal as blocked.
- B3 (default): Daily kill switch tightened from -$500 to -$300. Smaller threshold halts the bot earlier in adverse intraday conditions.
- B4 (default): Total daily orders cap reduced from 20 to 10. 18-order MU buying spree wouldn't happen — would hit cap at order 10.
- B5 (default): Per-ticker daily orders cap reduced from 3 to 2. Wheel needs at most open+close per day for one underlying.
- Net effect: the 06/25-06/26 patterns (18-order concentration into a single name + 11-order option churn) are structurally blocked under the new rules. Safety layers are stricter; legitimate wheel activity still possible at much smaller scale.
v1.9.20260627a
2026-06-27 · extend journal to defensive events
- Decision journal now captures defensive actions, not just Evaluator decisions. The 06/26 mystery (positions vanished but journal showed only OPTION/pending records) exposed a real gap: stops, breaker closes, pre-earnings exits, rolls, and other defensive sells all bypass
recordDecision(). This build closes that gap.
- New helper:
recordEvent({event_type, trigger, action, outcome, reason}) creates a structured record for any non-Evaluator event. Records use decision: "EVENT" so they coexist cleanly with existing STOCK/OPTION/SKIP records in DECISIONS array, the CSV export, and the JSON export.
- Six convenience wrappers for the common patterns:
recordStopLossEvent, recordBreakerCloseEvent, recordOptionCloseEvent (handles pre_earnings_close / option_stop / option_tp / option_dte_close), recordRollEvent (legs 1 and 2), recordAutoOptionEntry (handles auto_cc / auto_csp / spread_entry), recordRotationEvent.
- 11 defensive sell paths instrumented: stock stop-loss (success + rejection), circuit-breaker close, pre-earnings close, option 2× credit stop, option take-profit / scale-out, option DTE force-close, rotation sell (when enabled), auto-CC entry, elite auto-CSP entry, roll leg 1 (BTC), roll leg 2 (STO new).
- What you can now answer from the journal: "what closed my positions today" — stop-losses, breaker closes, expiry-forced exits, rolls, all visible. Each record includes the trigger data (P&L%, DTE, breaker level, etc.) so you can see why the bot acted, not just that it acted.
- Unchanged: 500-record cap, localStorage persistence, CSV/JSON export buttons, render stats panel. Existing Evaluator records still produced identically — defensive event records are additive.
- Known limitation: events fired by the broker side (option assignments, expirations) still aren't recorded — those happen in Alpaca, not in the bot's code paths. For those, the Orders tab in Alpaca remains the source of truth.
v1.9.20260610d
2026-06-10 · structured decision journal
- New: Decision Journal — every Evaluator decision is now recorded as a structured record (not just a free-text log line). Each record captures: decision verdict, scores breakdown (stock/option/skip), reasons list, signals (VIXY, SPY, QQQ), regime, cash, options_buying_power, equity, exposure, open positions/options, and downstream outcome.
- Outcome stamping: after each decision, the bot records what actually happened —
submitted, blocked (and which gate blocked it: no-short, cash floor, PDT, churn, kill switch), rejected (with the Alpaca error message), or no_action.
- Export buttons in Safety tab → DECISION JOURNAL: CSV (flat, Excel-friendly) and JSON (full nested structure). Each row covers a full decision with all inputs and outcome — you can pivot on regime, filter by outcome, correlate score thresholds with success rates.
- Persisted to
gus_decisions localStorage. Capped at 500 records (auto-rotates). Survives page reload.
- Live stats panel shows total decisions, breakdown by verdict (STOCK / OPTION / SKIP), breakdown by outcome.
- Use case: after a week of running, export the CSV and ask questions like "what percentage of OPTION decisions actually submitted?", "which signals correlated with the best outcomes?", "did the cash floor block valid trades or only bad ones?"
v1.9.20260610c
2026-06-10 · cash floor
- New: cash floor (ON by default, threshold $0). The bot refuses any stock BUY order whose projected post-order cash would drop below the configured threshold.
- Operates silently — no modal, no automation break. Logs
⛔ CASH FLOOR: SYMBOL BUY refused — would leave cash at $X (floor $Y).
- Why stock BUYs only: option SELL orders (CSPs, CCs) receive credit and increase cash. Option BUY orders only happen during defensive closes (stops, rolls, expiry-forced exits) which must never be blocked or you'd be left with uncovered short positions.
- Configurable in Safety tab → SAFETY CONTROLS → CASH FLOOR. Default $0 (don't go negative). Set to e.g. $500 to keep a cash buffer for fees and assignments.
- Independent of Alpaca's server-side margin check — this is an additional client-side rule for users who want a strict no-margin policy. Alpaca will still reject orders that exceed buying_power as a backstop.
- Settings persist to
gus_cash_floor localStorage.
v1.9.20260610b
2026-06-10 · options buying power fix
- Critical fix: CSP entries were being rejected with "insufficient options buying power" because the bot was reading
ACCOUNT.cash as available funds. Alpaca's cash field doesn't reflect collateral already reserved by existing short options — that's what options_buying_power is for.
- Example: the bot read
cash as available funds, but the true ceiling was options_buying_power (significantly lower). The gap = collateral already locked by an existing CSP that the bot wasn't accounting for.
- New helper
availableOptionsCash() reads ACCOUNT.options_buying_power (Alpaca's purpose-built field for new option positions), takes the min of that and cash to be safe, falls back to cash if the field is missing.
- All 4 option-entry call sites updated to use the new function: openOptionCandidate, openOptionCandidateElite, and 2 outer wrappers.
- Stock entry path UNCHANGED — it still reads raw cash directly, since stocks have different margin rules and use Alpaca's separate
buying_power field via its own path.
v1.9.20260610a
2026-06-10 · better API error messages
- Bug fix: alpacaFetch lumped 401 and 403 together as "Authentication failed — check your API Key" — but 401 = bad credentials while 403 = valid credentials lacking the required permission (e.g. options trading level). The misleading message led people to chase API-key issues when the real problem was account permissions.
- 403 now surfaces Alpaca's actual error message: parses the response body for
message or error field and shows it in the log. Example: Forbidden (403) — options trading level insufficient instead of the generic auth message.
- All 4xx errors now show their actual message from Alpaca's response, not just the HTTP status code.
- 4xx errors no longer fall through to the CORS proxy retry — they're definitive, not CORS-related. Saves a wasted round-trip and prevents the confusing "Connection failed" wrapper around a real Alpaca error.
v1.9.20260526a
2026-05-26 · no-short fix + signal-driven entries
- CRITICAL FIX — bot no longer shorts stock. The stock-entry side (buy/sell) was decided by
Math.random() — an 80/20 coin flip in NEUTRAL regime. ~1 in 5 ticks rolled SELL on a stock not held, opening a naked short (e.g. the AMD short you spotted). Shorting has unlimited risk and has no place in a wheel strategy.
- Ticker selection now signal-driven: instead of a random pick from the universe, the bot now selects the highest-scored Brain pick (score = momentum × 2 + liquidity). Random selection removed.
- Side selection now rule-based: the bot SELLS only positions it already holds long — specifically winners up ≥ +8% (take profit) or losers ≤ -8% (safety exit). Otherwise it BUYS the strongest pick. No coin flip.
- Two hard no-short guards added: (1) a stock SELL is refused outright if the ticker isn't held long; (2) SELL quantity is capped at the actual held share count, so the bot can never oversell into a short.
- Net effect: the bot can only buy stocks or sell stocks it owns. It is now structurally incapable of opening a short stock position.
v1.8.20260509f
2026-05-09 · reword churn prevention
- Churn Prevention panel description reworded to neutral/informational style, matching the rotation toggle update in v1.8.e. Removes specific historical-loss references that had appeared in the live UI.
- Behavior unchanged — cooldown, per-ticker daily cap, and total daily cap all still active with same defaults (24h / 3 per ticker / 20 total).
v1.8.20260509e
2026-05-09 · reword rotation message
- Rotation logic toggle message reworded from warning (with specific dollar loss figure) to neutral informational text.
- Recommendation on default: remains OFF — wheel strategy doesn't use rotation; the toggle takes one click to enable if desired.
- v1.7.20260509a changelog entry also updated to remove the now-stale dollar figure.
v1.8.20260509d
2026-05-09 · light theme pass 2
- Comprehensive coverage of inline color styles that v1.8.c missed. Audit of all hard-coded hex values found ~30+ uncovered patterns.
- Tinted panel backgrounds now mapped to subtle pastels: green-tinted → mint (#ecf7ee), blue-tinted → soft cyan (#eef2fa), purple → lavender (#f5eef5), orange → cream (#fdf7e8), red → pink (#fdecec).
- Border colors: 30+ dark-tinted border patterns mapped to light counterparts (mint, cyan, pink, cream, lavender borders).
- Near-black panel backgrounds (#0a0e0a, #0e0e0e, #0c0c0c, etc.) all now render as white.
- Dashboard hero cards (Equity/Cash/Day P&L): row labels darken to #6a7280, values to #1a1d24 for high contrast.
- Hover states: tbody rows + tabs hover to #f0f4f8 instead of pure-white flash.
- Toast messages (.omsg): proper light backgrounds with status colors (.ok green, .err red).
- Progress bars in trade quality breakdown use darkened accents on white.
- Breaker badge + version chip: light backgrounds with colored text and matching borders.
- Webkit scrollbars: light gray track + thumb instead of jarring dark scrollbars on white.
- Splash logo: opacity 0.85 in light mode (slightly less faded than dark mode's 0.7).
- Dark theme remains the default — only the light variant is affected by these changes.
v1.8.20260509c
2026-05-09 · proper light theme
- Bugfix: the "light theme" shipped in v1.8.a was a lazy CSS
filter: invert() hack. Replaced with a proper data-driven theme using [data-theme="light"] attribute selectors.
- Real color tokens: warm near-white backgrounds (#f7f8fa), near-black text (#1a1d24), darkened accents (green #0a8e3a, red #c41e3a, yellow #b8860b, blue #1969b9) for proper contrast on light backgrounds.
- Panels: white with subtle 1px shadow, light gray borders. Inputs: white backgrounds with focus-green border. Tables: white rows with hover highlight.
- Hard-coded dark hex values throughout the bot (#0a0a0a, #1a1a1a, #bbb, etc.) are auto-overridden via attribute selectors — no per-element refactor needed.
- Logo/images render without inversion (no more weird colors on the splash screen).
- Toggle is the same button (Safety tab → UI / UX FEATURES → Theme).
v1.8.20260509b
2026-05-09 · audit fixes (duplicate functions)
- Bug fix discovered by audit: three duplicate function definitions had been silently overriding each other in the file. Resolved by removing the simpler (later) definitions and keeping the richer (earlier) ones.
- updPBtn (manual order panel button): now uses the version with order-type badge + estimated cost + cash sufficiency indicator (was previously falling back to a simpler version without these features).
- renderPicks (Brain top picks cards): now uses the version with IVR/MOM/LIQ badges per pick (was previously falling back to plain cards without badges).
- enrichPicksWithIVR: duplicate definition removed (was functionally identical, just dead code).
- No behavior regressions: the bot was already running these features via the simpler versions; now the richer UI elements will show. All 22 trading-safety layers and 35 v1.8.a feature wirings verified intact after the fix.
v1.8.20260509a
2026-05-09 · cat 2 + cat 3
- 2.1 Daily Realized-Loss Kill Switch (opt-in, OFF by default): independent of circuit breaker. Halts new entries when today's closed-trade realized loss exceeds threshold (default −$500). Configurable in Safety tab.
- 2.2 Entry-Reason Tagging: every new trade now records an
entry_reason in TRADE_JOURNAL (regime_signal, auto_cc_after_assignment, elite_options_brain, bull_put_spread). Stock entries now also persist to journal (previously only options did). Foundation for future signal-quality analysis.
- 2.4 Wheel vs Churn Breakdown: new Dashboard panel showing closed trades grouped by category (🎯 wheel / 🛡 spread / 📈 stock / ❓ other) with count, P&L, and average per bucket. Warns if stock-bucket count exceeds wheel-bucket count.
- 3.1 Theme toggle (button in Safety tab): page-level light/dark via CSS invert filter. Persists across reload.
- 3.2 Mobile-responsive CSS: media queries at 768px and 480px breakpoints reduce padding, hide topbar logo, stack panels, shrink inputs.
- 3.3 Audible alerts (opt-in): Web Audio API generates short beeps on stops/PDT/kill-switch/breaker events. Pure browser, no external assets.
- 3.4 Tooltips: native HTML
title attributes auto-applied to NLV, DTE, IV, CSP, CC, PDT, ACB, Theta, Delta, IVR, BTC, STO, CB1/CB2/CB3.
- 3.5 PDF Summary export: button in Safety tab opens a printable trade-summary report (use browser's Print > Save as PDF).
- 3.6 Trade Replay: button in Safety tab prompts for a date, opens chronological list of all orders that day in a new tab. Useful for diagnosing churn days like 05/06.
v1.7.20260509b
2026-05-09 · cooldown + per-ticker cap + daily cap
- Post-stop cooldown (24h default): after a stop-loss or breaker-triggered close, the bot blocks re-entry of the same ticker for N hours. Prevents whipsaw: historical analysis showed stop-then-immediate-re-buy patterns generated repeated losses. Persisted in
gus_stop_cooldowns localStorage key.
- Per-ticker daily order cap (3/day default): bot refuses to fire a new stock order if that ticker already has 3+ orders today. Addresses the 14-INTC-orders-on-05/06 fragmentation pattern.
- Total daily order cap (20/day default): bot stops placing stock orders for the day once 20 stock orders have been placed across all tickers. Addresses the ~50-order-day pattern seen on 05/06.
- New CHURN PREVENTION sub-panel in Safety tab → SAFETY CONTROLS, with editable cooldown hours + per-ticker cap + daily cap + live status showing today's order count and active cooldowns.
- All three gates fire BEFORE the order submission, so blocked attempts don't consume the order budget. Log entries:
⛔ CHURN GATE: SYMBOL — reason
- Settings persist to
gus_churn_settings localStorage. Survive page reload.
v1.7.20260509a
2026-05-09 · churn fixes (PDT + rotation + limit)
- PDT protection (ON by default): blocks new entries that would create a 4th day-trade in 5 business days. Prevents Pattern Day Trader flag (90-day account restriction) for accounts under $25K. Auto-disables above threshold.
- Rotation logic disabled by default (
OPT_CONFIG.rotationEnabled = false). Sells losing positions to rotate into higher-scoring picks; not used in pure wheel strategy. Can be re-enabled in Safety tab.
- Limit orders for stock entries (ON by default): BUY orders now use limit price at mid + 0.1% slippage cushion. Saves bid-ask spread on every entry. Falls back to market if no live quote available.
- New SAFETY CONTROLS panel in Safety tab with toggles + live PDT day-trade counter showing recent activity by date.
- All settings persist to localStorage (
gus_safety_controls) across page reloads.
v1.6.20260509d
2026-05-09 · orders buffer to max
- Orders fetch buffer increased from 200 → 500 (the Alpaca API maximum per request)
- Other order endpoints unchanged: FIFO/tax fetch was already at 500; duplicate-check fetch stays at 100 (open orders only, kept small for speed)
v1.6.20260509c
2026-05-09 · orders buffer + date
- Orders fetch buffer increased from 50 → 200 (Alpaca API max is 500; 200 covers ~weeks of activity without overwhelming render)
- Orders table TIME column now shows date + time (MM/DD HH:MM:SS AM/PM) instead of just time, matching the activity log format
v1.6.20260509b
2026-05-09 · logo opacity
- Login/connect screen: splash logo fades to 70% opacity for a more muted look
- (Affects whole logo image — JPEG format can't isolate just the eyes)
v1.6.20260509a
2026-05-09 · merged risk limits
- Merged the two duplicate breaker panels: deleted the OLD "CIRCUIT BREAKERS" panel (which had misleading labels — "Weekly Drawdown", "Monthly Drawdown", etc. all wrote to the same daily threshold).
- New unified risk-limits panel in Safety tab → ⛔ CIRCUIT BREAKER STATUS with 6 properly-implemented controls:
- Daily CB1/CB2/CB3 (−3 / −5 / −10%): existing tiered system
- Weekly limit (−7% default): blocks NEW entries when 7-day rolling drawdown hits threshold. Existing positions managed normally. Uses real date-stamped daily snapshots.
- Monthly limit (−12% default): same mechanism, 30-day window
- Per-trade multiplier (2.0× default): sets
OPTION_STOP_MULT — short option closes if loss reaches Nx the original credit. Already wired into manageOptionPositions().
- Live drawdown displays show current 7-day and 30-day DD with peak date, plus ARMED / BLOCKING badge.
- New
gus_equity_daily localStorage key persists one equity snapshot per trading day (rolling 90-day history) for accurate week/month windows.
- New
gus_limits localStorage key persists weekly/monthly/per-trade settings across page reloads.
- Single SAVE ALL button validates all 6 values together. Validation enforces: daily ordering (CB1 < CB2 < CB3), weekly less severe than monthly, per-trade between 1.2x and 10x.
v1.5.20260509b
2026-05-09 · breaker threshold inputs
- Bugfix: circuit breaker manual threshold entry was broken — the
saveCB() function existed in JS but the input fields and SAVE button were never added to the UI. Hard-coded -3% / -5% / -10% were the only values used.
- New UI in Safety tab → ⛔ CIRCUIT BREAKER STATUS: three input fields (CB1 / CB2 / CB3 in %) + SAVE and RESET DEFAULTS buttons.
- Validation: rejects values outside 0.1–50% range; rejects orderings where CB1 ≥ CB2 ≥ CB3 (escalation must be strictly increasing severity).
- Persistence: saved values persist across page reloads via
gus_cb_thresholds localStorage key. RESET DEFAULTS clears the override.
- UI feedback: green "✓ Saved at HH:MM:SS" message shown for 6 seconds after save; red error if validation fails; activity log records the change.
v1.5.20260509a
2026-05-09 · tiered breaker
- Tiered circuit breaker system: three levels of escalating defensive response.
- CB1 (−3%): cancels all pending orders via Alpaca API, halts new entries. Manual unlock required via "RESUME TRADING" button in Safety tab.
- CB2 (−5%): all of CB1 + closes stock positions that have already breached their per-position stop loss. Short options are NEVER auto-closed (wheel philosophy: let IV crush work).
- CB3 (−10%): all of CB2 + STICKY for the rest of the trading day. Cannot be manually unlocked until next trading day. Designed to prevent revenge-trading on a catastrophic day.
- Durable event log: breaker triggers persist in localStorage (last 50 events) and survive page reloads. Visible in Safety tab → ⛔ CIRCUIT BREAKER STATUS panel → EVENT HISTORY.
- State persistence: if you reload the page while a breaker is active, the bot remembers and stays halted.
- Old behavior removed: the bot will no longer auto-clear the breaker just because daily P&L recovers — explicit user action is now required.
v1.4.20260509a
2026-05-09 · complete export
- Bot vs SPY benchmark panel added to Dashboard. Tracks realized wheel P&L only (excludes unrealized stock appreciation) vs. equivalent SPY return since baseline date.
- Verdict banner: BEATING SPY / TRACKING SPY / LAGGING SPY / INSUFFICIENT DATA. Requires 30+ closed trades before drawing conclusions.
- Time-in-market metric: shows % of equity deployed vs. cash. Warns if <40% (cash drag killing returns).
- Trade quality breakdown: visual bar showing % of closed trades that hit profit-target / time-exit / stop-loss / pre-earnings close. Warns if stop-loss rate exceeds 25%.
- Annualized return calculation for both bot and SPY (rough — uses actual days elapsed × 365).
- SPY baseline auto-set on first run; resettable via "RESET BASELINE" button. Stored in localStorage.
v1.3.20260509b
2026-05-09 · audit
- Audit-only build: verified the existing "BOT vs SPY BENCHMARK" panel on the Dashboard is fully functional (tracks realized wheel P&L vs SPY total return).
- No new code added — panel was already shipped in earlier builds. This entry documents the audit and confirms everything is wired.
v1.3.20260509a
2026-05-09 · 4 priority improvements
- Pre-earnings position closing: short options auto-close 1 day before earnings (configurable via OPT_CONFIG.preEarningsCloseDays, set to 0 to disable). Highest-priority exit trigger — runs before stop-loss, profit-take, and 21-DTE checks.
- Defensive rolling (manual): click any short option in Positions detail modal → "PREVIEW ROLL" button appears for challenged positions. Bot finds further-OTM, longer-DTE candidates and only proceeds if net credit is available (industry rule: never roll for debit). User confirms before execution.
- Wheel position cap: max 10 concurrent active wheels by default (OPT_CONFIG.maxOpenWheels). New entries blocked if cap reached. Counts unique underlyings (stock or short options).
- True Adjusted Cost Basis tracking: position detail modal now shows broker basis vs. ACB-after-premiums-collected, with warning to only sell CCs above adjusted basis to lock in profit. Sourced from TRADE_JOURNAL closed-trade history.
- Standing rule active: every build auto-bumps version + appends changelog entry
v1.1.20260509a
2026-05-09
- Established standing versioning rule: every future build automatically bumps version + appends a new changelog entry
- Restructured changelog modal as append-only history (newest first)
- Topbar logo doubled in size (52px height, max 220px width)
v1.0.20260509
2026-05-09 · initial versioned build
- Earnings refresh button label now reflects active source (Finnhub or fallback)
- Click any position row to see detailed summary (contract details, DTE, P&L, market data, earnings context)
- Version badge added to topbar — click anytime to view this changelog
- Esc key closes modals
Single-file HTML · self-contained · no external dependencies · drop directly onto gusautobot.com