The name: in The Fellowship of the Ring, Galadriel takes Frodo to a silver basin of water in Lothlórien and invites him to look. The Mirror can show things that were, things that are, and things that have not yet come to pass — though even Galadriel cannot always say which is which, and the visions are not guaranteed to happen. Click the title on the main screen to rename the app to anything you like.
Captured from v4.82. Dollar figures and account numbers are replaced with X’s in these images only — your own screen shows the real values.
The main dashboard shows the total portfolio summary at the top with aggregate statistics across all services, followed by individual service cards. Each service card displays position counts, cost, value, current P&L, and closed P&L.
Key elements: - Total Portfolio summary with position counts, cost, value, current P&L, and closed P&L - Current P&L Breakdown showing taxable vs non-taxable - Closed P&L Breakdown showing taxable vs non-taxable - Collapsible Closed P&L Chart - Service cards with individual metrics and “Add Service” button
The service detail view shows the trade entry form at the top and position cards below. The toggle switches between Open and Closed positions. Position cards show symbol, spread type, cost, value,
Expanding a service shows all positions grouped by Open and Closed status. Each position shows the symbol, leg count, cost basis, current value/mark, and P&L.
Key elements: - Service summary statistics - Add Trade form with Thinkorswim paste input - Position number selector (existing or new) - Trade date picker - Open/Closed Positions toggle - Position cards in a responsive grid
Clicking a position card expands it to show full details including trade history, mark-to-market section, and action buttons. The taxable
Clicking a position expands it to show individual legs with detailed trade history, add trade functionality, mark-to-market controls, and Schwab account integration.
Key elements: - Close/Delete buttons for position management - Taxable checkbox for tax tracking - Mark-to-Market section with current mark and set mark button - Net Position showing current leg quantities - Individual trade cards with full details (action, legs, strikes, expirations, price)
The closed positions view shows positions that have been closed, with “Days Held” instead of DTE. Expanded positions show the full trade
The Closed section shows positions that have been fully closed out, with realized P&L and tax status tracking.
Key elements: - Closed Positions toggle selected - Position cards showing closed status - Opened/Closed dates displayed - Days Held metric (instead of DTE) - Reopen/Delete buttons for closed positions - Full trade history in expanded view
The settings panel handles Google account authentication and cloud sync. Data can be synced to Google Sheets for backup and cross-device
The Settings dialog provides Google Sheets sync configuration, Schwab integration management, and data import/export functionality.
Key elements: - Google Account sign in/out - Cloud Sync toggle with spreadsheet name and last sync time - Sync Now and Refresh from Cloud buttons - Direct link to open spreadsheet in Google Sheets - Export/Import for local data backup
A "Service" represents a trading advisory or strategy you're following (e.g., "Alpha Picks", "My Strategy"). Each service contains its own portfolio of positions. You can also rename a service by clicking its title, and tag each one with a Trader Effort (High / Medium / Low) so the summary tables can sort or group by how much attention each strategy demands.
Each service card on the main page has a small note icon (next to the trash icon) that opens an editable description for that service. Use it to record what the service trades, its strategy, risk approach, entry/exit rules — anything you want to remember the next time you revisit it. The icon turns blue when a description is set, and hovering shows the text as a tooltip.
The description is persisted everywhere your data goes:
description column on the
services sheet (existing sheets pick the column up on the next sync)The main dashboard top banner shows app-wide totals plus two new at-a-glance tiles:
Below the banner, the Total Portfolio card shows:
Each service is listed below the totals with the same per-service stats, plus a small Trader Effort badge. See the Reports & Analysis section for the expandable charts and cross-service comparison tables.
The easiest way to add trades is to copy the trade string from Thinkorswim:
Every trade string you've ever pasted into a service is kept in a per-service History log accessed from the History button next to the trade input. Useful for re-pasting an earlier fill onto a new position, or for auditing what was actually entered.
When Schwab is connected and you open a new position, the position's notes are pre-filled with
Open/Close VIX X/ SPX Y/ using the live VIX and SPX quotes. When you close the position,
the matching VIX…/ and SPX…/ tokens are auto-completed with the
closing values, so you have a record of regime conditions at both ends of the trade.
closeDate is missing or invalid by setting it to the position's latest trade date.The app parses trade strings in this format:
BUY +2 VERTICAL SPY 100 17 JAN 25 600/605 CALL @2.50
Components:
BUY or SELL - Action+2 - Quantity (positive for buy, negative for sell)VERTICAL - Spread typeSPY - Underlying symbol100 - Multiplier (100 for standard options)17 JAN 25 - Expiration date600/605 - Strike pricesCALL - Option type@2.50 - Price per contractThe dashboard has two collapsible sections under the Total Portfolio card and per-service expansion areas with the same content scoped to one service.
Toggle Closed P&L Chart to expand a chart of realized P&L over time. Controls let you switch between:
The chart shows the trade-average P&L, average days-in-trade (DIT), and trade count for the selected period at the top. The same chart is available on each service detail page, scoped to that service.
Below the Closed P&L Chart toggle is the Service Performance Summary: a sortable table that compares every service for the selected period. Columns include Service, Trader Effort, Win Rate, Won, Avg Win %, Lost, Avg Loss %, Avg DIT, Total Invested, Total P&L, ROI, and Avg Annual ROI. Click any column header to sort. A "Total" row at the bottom pools all services.
Beneath the table you'll see a High Water Mark for cash deployed in open positions line. This is the peak concurrent capital deployment during the period (computed via a sweep-line over each position's open interval), plus the matching ROI and annualized ROI based on total P&L. The same row appears on each service detail page, scoped to that service.
A second collapsible section shows the same table as the Service Performance Summary but folds in every currently open position alongside the closed ones. Open positions contribute their unrealized P&L (mark + trade P&L), days-held = today − openDate, and are bucketed into the win/loss split by the sign of their current P&L. Useful for "where am I right now, not just what's settled" decisions.
Either Service Summary table can be reorganized by Group by Effort. When active, rows are sorted first by Trader Effort (toggleable High→Low / Low→High), and any other column you click becomes a secondary sort within each Effort bucket. Toggle it off to return to plain single-column sorting.
On each service detail page, click Show Strategy Breakdown to see Win Rate, Avg P&L, and Total P&L grouped by spread type (Vertical, Iron Condor, Butterfly, Custom, etc.) for the selected period.
The Day P&L and Week P&L tiles in the top banner are clickable. Each opens a modal that lists every closed position contributing to that total — grouped by service with a per-service subtotal, with columns for Closed date, DIT, Underlying, Type (structure), and P&L. A grand total appears at the bottom.
The Print PDF button on the main page expands the Closed P&L Chart and All Positions Service Summary, then opens the browser print dialog so you can save the entire dashboard to PDF. The print stylesheet hides UI chrome (sort controls, toggles, the app header) and avoids splitting tables across pages.
On each service detail page, the Export Trades button drops a small menu with
Open / Closed / All. Picking one downloads a JSON file (<service>-trades-<scope>-<date>.json)
of just that service's trades — one entry per position with positions, marks, dates, and full
trade history nested. Designed for handing off to Claude (or another AI) for analysis, not for
re-import.
The Position Analyzer is a Black-Scholes scenario tool for any open position. It requires Schwab integration so it can pull a live option chain and per-leg quotes. To open it, click the chart icon next to a position on the service page.
r, and continuous dividend yield q. Changes to
r and per-symbol q are persisted across sessions.
Different broker analytics tools use slightly different inputs. To force the analyzer's per-leg
delta to match what Thinkorswim displays, type the position's ToS delta into the
ToS Δ field next to the summary header and click Calibrate.
The app bisects on r until our computed delta lands on yours, re-implying σ from
each leg's mark at every candidate rate. The new r is persisted so subsequent loads
of any position pick up the calibrated value.
Under the price chart at the bottom of each Analyze page there is an Open in TradingView link. It opens that position's underlying in a new tab, so the app can stay on one screen and the chart on another.
If you are signed in to tradingview.com in the same browser, the link opens your chart — your layout, your indicators, your drawings. There is nothing to configure and no password to enter here: being signed in to TradingView is what does the work, and the app never sees that session. Without a TradingView account the same link opens a default chart.
The app's own price chart stays where it is. It has the range buttons and the trend lines, and it is drawn from the same Schwab data as every other number on the page.
Futures chart as the continuous series — an option on /GCJ26 opens
GC1! rather than a single short, gappy contract. Settings → TradingView takes
per-symbol overrides (one SYMBOL=TVSYMBOL per line) for anything that resolves to
the wrong instrument, and a layout field for pinning a saved layout that is not your default.
Most people should leave both empty.
Two top-bar tabs cover non-options activity in your taxable Schwab accounts:
Both pages only show accounts you have explicitly marked Taxable in Settings → Schwab Integration. If no account is marked taxable, both pages surface a yellow "no taxable accounts configured" notice.
You can optionally sync your data to Google Sheets for backup and cross-device access.
Connect your Schwab account to automatically fetch real-time Net Liquidation values for your positions. This helps you track the current market value without manually entering marks.
By default, accounts show as "MARGIN ****1234". In Settings → Schwab Integration you can set per-account properties:
Each position carries three checkboxes — Short Term, Long Term and §1256 — which are mutually exclusive: checking one clears the others. With none checked the position is non-taxable, which is the state for anything held in an IRA or Roth.
/ (futures options). These are taxed 60% long term / 40%
short term in the US no matter how long they were held, so the holding-period rules above do not
apply to them.If you have already closed one without an account, reopen it, set the account, and close it again — the selector is available whenever a position is open. The original close date is kept, so the round trip does not move the position to today.
Existing positions were classified once, automatically. Because the rules above fire only when an account is set, positions whose account was linked before this feature existed would have stayed blank forever. A one-time backfill therefore runs on first load and classifies them from their account. It only ever fills a gap — a position that already has a term keeps it, and one with no account is left alone. It is driven entirely by the per-account Taxable checkboxes, so if an account was not ticked Taxable at the moment it ran, its positions came out blank.
These drive the Short Term Taxable / Long Term Taxable / Non-taxable breakdown of Current and Closed P&L on the dashboard. A §1256 position contributes to both term figures — 60% of its P&L to Long Term and 40% to Short Term — so it appears in both drill-downs, each showing its share rather than the whole position.
When Schwab is connected, the live SPX and VIX prices (with today's high / low) appear in the top banner of both the main page and every service page. These same quotes feed the VIX/SPX auto-fill in position notes when you open and close positions.
The app automatically handles these symbol variations:
| Your Position | Schwab Returns | Status |
|---|---|---|
| SPX | $SPX | ✓ Auto-matched |
| SPXW | SPX | ✓ Auto-matched |
| RUTW | RUT | ✓ Auto-matched |
| NDXW / NDXP | NDX | ✓ Auto-matched |
Activity summarises how much trading happened in a period rather than how profitable it was: positions opened and closed, trades placed, contracts traded and average days-in-trade, split taxable / non-taxable and totalled. Use the period selector for the current year, the last 30 days, or other windows.
The Performance tab compares selected services against a benchmark, and answers the question a return on its own cannot: did you take more risk to get it?
For the current selection it reports annualized volatility, maximum drawdown, beta and correlation to the benchmark, and return per unit of volatility — each beside the benchmark’s own figure. Return per unit of volatility is deliberately not a Sharpe ratio: no risk-free rate is subtracted, because the comparison that matters here is the benchmark line next to it, not cash.
These come from value history, not from the cumulative P&L curve, and the reason matters. The P&L curve is realized — it steps only when a position closes — so its variance is the variance of closing events rather than of the book. A position that swung wildly for weeks and closed flat would contribute nothing to it. Volatility taken from that curve therefore understates risk by an unbounded amount: the worse the swing, the more flattering the number. Value-history buckets are marks, so they move while positions are still open, which is where the risk actually lives.
The price of that choice is coverage. Value history only exists from the day the feature shipped, and it samples only while the app is open. So every figure reports how many days actually fed it — “79 of 91 days” — and below twenty paired observations the statistics are withheld rather than displayed, because a beta from six days is noise dressed as a number.
What the book moves per 1%. Each row is a position’s dollars per 1% move in its underlying, and where a beta is known it is also expressed as a benchmark-equivalent, so unlike underlyings can legitimately be added together.
Three things are shown rather than quietly absorbed. The raw total is labelled “if everything moved 1% together”, because that is the only reading it supports. Rows with no beta are left out of the benchmark-equivalent total and counted, instead of silently shrinking it. And positions that could not be priced at all — no Schwab link, a cold quote cache, or a leg with no quote — are counted too, so a small total is never mistaken for a small book.
The Heat Map tab is two maps, deliberately not one, plus a reference map of the market. In all of them a box’s area is a non-negative magnitude and its colour is the P&L on that magnitude. A tile’s size is never a profit and never a loss — a treemap cannot draw “minus forty pixels”, and the usual workarounds make a loss look like a holding.
Every open position, grouped by service. Area is the capital that went out of the door to put those positions on; colour is the unrealized P&L on that capital. Holdings has no timeframe, because a snapshot does not have one.
Every position closed inside the selected window, grouped by service. Area is the peak capital that service had working at one time on those trades — not the sum of their costs, which would count the same dollar twice when one trade follows another. Colour is what they realized.
The two sets are exhaustive and disjoint by construction: a position open now closed in no window, and one closed in the window is not open now. That is what keeps the page from mixing a snapshot with a flow, and it is why a service that closed everything at a profit gets a large green Harvest tile instead of vanishing for having no net liq. The two totals are never added together, and the page says so.
Today, Week, Month, Quarter, YTD and All. The window drives Harvest only — Holdings ignores it. Choosing a window with no closes says so plainly rather than showing an empty box.
A separate panel showing the eleven S&P 500 sectors sized by index weight — so it is a picture of the index, not of your exposure — and the major indexes as equal tiles, because no comparable magnitude exists for an index and inventing one would put a made-up number into an area. It has its own timeframe buttons and follows Harvest by default; if you detach it, it says so, because two windows silently differing is exactly how a reader draws a comparison that is not there.
It carries its own colour ramp, saturating at a level that follows the window — about ±2% for a day, ±20% for a year. Sector days sit inside roughly ±2% while this book’s days swing far wider, so one shared ramp would leave the sectors uniformly grey and imply nothing was happening. These are price returns on notional, a different quantity from your return on capital at risk, and the panel says so. VIX gets a violet/amber ramp of its own: green-is-good reads backwards for volatility, and colouring a spike green would be actively misleading.
Tiles too small to carry a label are drawn at a readability floor and flagged as not to scale, so a small service stays findable without the map lying about its size. Sector weights are published to a tenth of a percent and carry the date they were measured, along with how old that is.
Capital at risk is the most a position ever had committed at one time — the running net cash flow’s largest absolute value. Adding to a position deepens it; a roll nets rather than counting both legs; selling down and buying back reports the peak rather than the sum, because the same dollar redeployed is not two dollars. A credit position reports the premium received.
This used to read only the opening trade, which made every later tranche invisible. On a holding built over six lots that reported the first lot alone — and since the figure is both a tile’s area and an ROI denominator, it drew multi-lot services far too small and overstated their return.
An opt-in colour mode on Holdings. Instead of colouring by P&L over the whole life of each position, it colours by what they have done since the prior year-end close, using the same boundaries the All Positions summary uses. A four-year holding coloured by whole-life P&L is answering a four-year question.
It says when it is mixing bases: a position with no year-end boundary keeps its whole-life figure, which is a different question from the rest of the map, so the count is stated above it.
Closed positions are easy: they closed on a date, so a year filter simply selects them. An open position is the hard case — asking what it earned in 2026 requires knowing what it was worth at the end of 2025, and a mark taken today cannot answer that.
So the app records an immutable year-end boundary per position: what it was worth at the close of 31 December. With one, an open position can report what it earned this year; without one it falls back to its whole-life figure, which is honest but a different question. The summary says which it is showing.
Boundaries do not sync. They are deliberately excluded from the Google Sheets copy, because a build predating them would drop them on parse and an older tab would then erase them. Each machine builds its own — so run the backfill once per device, and until you do, that device will show whole-life figures under every period. If one machine shows different numbers for “All time” and “2026” while another shows the same figure for both, that is the difference.
Shares are included, carefully. Schwab’s price history is split-adjusted and a stored share count may not be, so a window spanning a split would be wrong with nothing to show for it. Shares are admitted only when both halves of a basis test pass: the recorded count must equal what the broker holds now, and every recorded share price must sit near the adjusted close on its own trade date. The backfill needs a signed-in Schwab session for the first of those, and says so if it cannot get one.
Every Schwab refresh snapshots the value of each open position, each service and the portfolio as a whole into daily, weekly, monthly and yearly buckets, each holding a minimum, maximum and last value. “Value” is the effective net liquidation figure — the mark where one is set, otherwise trade P&L — and matches the Open Value shown elsewhere.
An entry rating grades the conditions a position was opened into — not the position's result. It is set per service, because it only means something for a strategy whose entries are supposed to share a trigger.
Grades come from zones you can calibrate against your own closed trades, so the boundaries reflect what has actually worked in your book rather than a generic scale. A spike below the no-trade threshold is reported as such rather than given a poor grade.
While a position is being opened the badge folds in the live VIX and SPX rather than the previous close, so the grade you see before entering is the grade for the conditions you are actually entering into.
PL5 Scenarios projects P&L across a grid of market shocks — SPX moving by a range of percentages against VIX moving by a range of points — at several future dates. It runs for a single position or for an aggregated scope such as a whole service.
It is a decision aid only. It informs; it never recommends, never rates and never acts. Days-in-trade and days-to-60 appear as context, but your exit rules stay yours.
Read the two axes with different confidence. The SPX axis is well grounded. The VIX axis is not: on a PL5 the net vega is a small residual of large opposing leg vegas, so a modest error in any one leg's vega moves the projected volatility response a great deal. Treat the ΔVIX direction as indicative and the ΔSPX direction as meaningful.
example.com is fine; only
http and https addresses are accepted, because the app is putting a
link in front of you to click and where it will send you is checked before it is stored. The
link is stored on the service, so it syncs with everything else.Data is held in your browser and, if you enable it, mirrored to your own Google Sheet. Nothing is stored on anyone else's server.
Browser storage moved from localStorage to IndexedDB, which lifts
a hard 5 MB ceiling that a large book with full trade history and value buckets was
approaching. The migration runs once, automatically, and keeps the old copy until it has
verified the new one. If you ever see a migration prompt, let it finish before closing the tab.
Take a backup from Settings before anything unusual — a large import, a sync you are unsure about, or a first run on a new machine.
ADR fees. Small unexplained debits against foreign holdings are depositary service fees. An ADR is not the foreign share — it is a receipt from a US bank that holds the ordinary shares abroad, and the bank charges a cent or two per share a year for that custody. On a dividend-paying ADR it is usually netted out of the dividend and never seen; otherwise it lands as a standalone debit. They are tagged as an expense rather than filtered out, because unlike an internal sweep the money genuinely left.
The Balances tab tracks account value over time, separately from position P&L, so a rising balance caused by a deposit is never mistaken for a gain.
Reconcile answers one question: what has the broker done that this app does not know about? It compares recent Schwab activity against the contracts your open positions hold, and sorts what it finds into three groups.
Set globally and overridable per service, under Reconcile → Settings:
Recording something here does not close the position. It becomes “Ready to close” and waits for you — recording that the contracts are gone and deciding the position is finished are separate acts.
The Reconcile page checks as soon as you open it, so what the count on the nav button refers to is on screen when you get there. Beyond that, automatic checks can be switched off entirely, leaving the Check now button working. When on, they run anywhere in the app — you do not have to be on the Reconcile page: about 8 seconds after the app loads, when you return to the browser tab after 15 minutes or more away, and every 30 minutes while the app stays open. Nothing runs once the tab is closed. A log records everything that was changed and when.
The PL5 Builder solves a 1 / −2 / 2 SPX put ratio against the live option chain, and can place it.
If a leg lands on a contract an open position already holds, the build is blocked. This is not cosmetic: the broker nets both into one line, so per-position tracking stops matching the account, and a fill on a contract held by two positions cannot be attributed back to either — which makes it impossible to reconcile automatically. Change a strike, or change the expiry, before placing.
The execution panel places the solved trade directly. Two modes:
Guards apply in both: a per-order value ceiling, a per-lane daily order cap, and an account-wide daily cap above them. Every request and response is written to a local audit log.
Rather than one take-it-or-leave-it limit, the order is worked: it starts better than mid and steps toward the cap until it fills.
A GTC closing order is placed automatically at the fill price plus your profit target (2.5 by default; set it to zero to place none), and the position is recorded into the service you chose, with its Schwab account set so auto-mark, fee sync and entry greeks work straight away.
The Eye tab — “PL5 Eye (the eye is always watching, searching…)” — runs the PL5 entry unattended: it watches for a VIX spike and, when eight conditions are all met, builds and places the trade. It is PL5-only and expects a non-IRA account, since the structure needs portfolio margin.
Every condition shows what it currently reads against what it needs, so “why isn't it trading” is answerable at a glance. Anything it could not determine fails its condition — a quote that never arrived or a balance that did not load is a question it cannot answer, not permission to proceed.
Arming is deliberate and per device, and it happens on the Eye page — which is behind the risk acknowledgement, so nothing can start watching without that having been accepted.
Once armed, the bot keeps running as you move around the app. Go to Balances, a service page, the Analyzer — it carries on checking every 30 seconds and the VIX chart keeps filling in. Only two things stop it: disarming it, or closing the tab.
Armed means running. There is no in-between state: a reload disarms the bot, so after refreshing the page it is off and you arm it again. What is remembered is the day — attempts already used, and anything already filled — so re-arming after a refresh does not hand it a fresh set of attempts. An attempt that does not fill costs a stand-down rather than the day, up to a daily attempt limit.
While the bot is armed, a chip appears in the header on every page, so you can see it is still going from anywhere. It is hidden when the bot is disarmed — including after a reload, which is the honest report, since nothing is running then.
Clicking the chip goes to the Eye page.
The daily fill count is taken from recorded positions rather than a local tally, so a refreshed tab — or a second machine — cannot place a second trade on a day that already had one.
Today’s readings are kept, so leaving the app and coming back rebuilds the day’s shape rather than starting blank. They clear themselves at the start of each new trading day, on the Eastern date — an evening session does not lose its afternoon.
The line is drawn from the bot’s own checks, so it begins when the bot starts watching rather than at the opening bell, and a gap in it is a check that failed rather than something smoothed over.
Live mode places real orders. The bot runs the same order path as the Builder — the same guards, the same price ladder, the same closing GTC placed behind the fill, the same record written into your service — with nobody between the conditions being met and the order being sent.
Dry run is the default, and it is not a simulation. It travels the identical path and stops at the moment of committing, so what you watch in a rehearsal is the real thing right up to the last step. Stay in dry run until a full session has behaved the way you would have.
It watches its own order, roughly every two seconds, for as long as it is working it. When the order fills, the position is recorded and the closing GTC placed within the same few seconds — it does not wait for reconciliation, and the trigger for any further trade switches to the VIX at that fill immediately.
Reconciliation is the backstop for what the bot did not do itself: the profit-target GTC filling days later, or an order it lost track of because the tab was closed mid-ladder.
Fills per day counts positions it actually opened, read from what has been recorded. Attempts per day counts every time the conditions opened and it tried, filled or not, and exists so a day where everything fails does not loop.
In dry run nothing is recorded, so nothing ever fills and the fills limit never applies. If you are testing and it stops after a few trades, that is the attempt limit — raise that, and shorten the stand-down between attempts. Raising fills per day will do nothing.
The bot has its own Start, Step, Cap, Interval and after-a-partial interval, separate from the Builder’s and behaving identically: start better than mid, step toward the cap, slow right down once anything fills. The page shows how many rungs and how many minutes your numbers add up to.
It used to weight them, on the reasoning that technology alone is about a third of the S&P, so eight of eleven sectors green with technology red is a market falling rather than rotating. That reasoning was tested against three years of daily closes — 753 days — measured both ways, and it did not survive.
The two readings agree on every day that matters. All eight days the S&P fell 2% or more are blocked by both. All three rotation days — where the index fell over 1% while six or more sectors rose — pass both; each was a technology sell-off with money moving into defensives, which is exactly the case weighting was supposed to catch and counting turns out to catch too. They disagree only on flat-to-mildly-red days, where neither reading is obviously right.
Counting also fixes something weighting made worse. Judging a sector green by the sign of its move leaves the reading hostage to whichever sector sits nearest zero, and with weight that was brutal: technology flipping between +0.01% and −0.01% swung the reading between 3% and 40% on noise. Counted, that same flip moves the number by one sector out of eleven, and no sector can do worse than that.
The deeper reason is that the S&P is already weighted by size. How far the index fell and how many sectors fell with it are two different questions, and asking the breadth figure to answer both was asking one number to do too much.
The weights are still there — they size the sector map and the strip on the Eye page, and the panel tells you which month the table is from. They are a fixed table; the index provider restates them monthly and the app does not follow that automatically.
Since the gate counts sectors, the age of that table no longer changes any verdict — it only affects how large the tiles are drawn. Refreshing it roughly once a year keeps the map honest, and nothing worse than a slightly mis-sized tile happens if you forget.
Where the Builder stops and asks, the bot follows a rule: move the clashing strike if the resulting delta is still in spec; failing that, move to the expiration next-nearest the target and try again; failing that on every candidate, do not trade — and say which rule stopped it.
The bot has its own account, service, size, target DTE, delta band, profit target and order ceiling — the same fields as the Builder with separate values, so the two never interfere. The thresholds and the trade shape sync across devices; the armed state and the log stay local.
All notification settings live in Settings → Notifications (the gear icon). Email is per person — enter your address and tick the switch. Telegram is relayed through the app's own server, so no credentials are stored in the browser, but its destination is a server setting shared by everyone using a deployment.
A dry-run message says DRY RUN — nothing placed before anything else, so it can never be read as a position you hold. A partial fill is reported as “20 of 60” rather than as a smaller fill, because the two call for different responses. A notification failure never affects the bot.
Trade Plan in the top bar, beside Help, opens a password-protected document in its own tab — so it can be read alongside whatever page you are working on. Enter the password once and the button opens it directly from then on.
The unlock lasts as long as the app stays open and is never written to disk, so reloading the app asks again. In normal use that is once a day.
The pages that can place live orders — the PL5 Builder and the Eye — are behind an acknowledgement that must be accepted each time. The page does not load, fetch, or arm until it is. The full terms are on the disclaimer page.
In short: this tool is provided as-is, you use it at your own risk, and you are responsible for every order that reaches your broker — whether you typed it or the bot did. Verify everything against thinkorswim before and after it trades.
This is a client-side web application - all processing happens in your browser:
┌─────────────────────────────────────────────────────────────────┐
│ YOUR BROWSER │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ React App │ │ localStorage │ │ OAuth │ │
│ │ (all logic) │←→│ (JSON data) │ │ Tokens │ │
│ └─────────────┘ └──────────────┘ └─────┬──────┘ │
└───────────────────────────────────────────┼─────────────────────┘
↑ │
│ static files │ API calls
↓ ↓
┌─────────────┐ ┌───────────────────────┐
│ Vercel │ │ Google Sheets API │
│ (host) │ │ Schwab Trader API │
└─────────────┘ └───────────────────────┘
By default, your data is stored in your browser's localStorage:
When you could lose data:
The Schwab integration is designed with security as a top priority. Here's how it works and why your data is safe.
The app uses OAuth 2.0 with PKCE (Proof Key for Code Exchange), the industry standard for secure authentication:
| Feature | Benefit |
|---|---|
| OAuth 2.0 + PKCE | Your Schwab password is never seen or stored by the app |
| Read-Only Access | The app can only VIEW your positions, never place trades or move money |
| Token Stored Locally | Your access token stays in your browser, not on our servers |
| Serverless Token Exchange | API credentials are kept secure on the server, never exposed to browsers |
| Revocable Access | You can disconnect at any time via Settings or your Schwab account |
| No Data Storage | Position data is fetched on-demand and cached temporarily in your browser only |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your Browser │ │ Vercel Server │ │ Schwab API │
│ (Frontend) │ │ (Serverless) │ │ (External) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ 1. Click Sign In │ │
│───────────────────────────────────────────────▶│
│ │ Redirect to Schwab │
│ │ │
│ 2. User logs in & approves │
│◀───────────────────────────────────────────────│
│ Redirect with auth code │
│ │ │
│ 3. Send auth code │ │
│───────────────────────▶│ │
│ │ 4. Exchange for tokens │
│ │───────────────────────▶│
│ │◀───────────────────────│
│ 5. Return tokens │ Access + Refresh │
│◀───────────────────────│ │
│ │ │
│ 6. Fetch positions (with token) │
│───────────────────────────────────────────────▶│
│◀───────────────────────────────────────────────│
│ Position data │
Key Point: The Schwab API credentials (Client ID and Secret) are stored securely on the Vercel server as environment variables. They are NEVER sent to or accessible from your browser. The browser only receives temporary access tokens after you authenticate.
The button in the header cycles three themes: light, dark and Mallorn.
Mallorn is a Middle-earth skin on the light palette — golden parchment for the page and cards, mallorn-leaf green and garnet red for P&L, over a dyed olive ground, with a painting hung in each margin and a gilt frame around the window that the page scrolls underneath. The paintings size themselves to whatever gutter the content column leaves and disappear entirely on a narrow window, where they could only show a sliver rather than a picture.
It is a palette swap and little more: every colour in the app is emitted as a CSS variable and every control reads it, so a theme is a block of overrides that no component knows about. Colour was computed rather than eyeballed — every value was checked against both surfaces that carry text, and all five mid-tones failed on the first pass, between 3.2:1 and 4.1:1, and were re-solved to clear 4.5:1 before shipping.
Click the version number next to the dashboard title (e.g. v2.26) to open an
in-app changelog showing every release since v1.0 and what shipped in each.
Toggle dark mode from the user menu. The app is fully themed (charts, modals, tables); the choice is saved per browser.
Both the dashboard and each service page have a period selector (Last 30 Days / Current Year / Previous Year / All Time) that drives the Closed P&L number, the Service Performance Summary, and the Closed P&L Chart together. Default is Current Year.
For positions not linked to Schwab (or when you want to override), set a "mark" manually:
Your data is stored locally in your browser. To ensure you don't lose it:
Galadriel’s Mirror — Help last updated August 2026 (covers v4.82) · Privacy Policy Disclaimer