← Back to App

Galadriel’s Mirror Help

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.

Application Screenshots

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.

Main Page

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.

Main page — portfolio totals, tax breakdown and service cards

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

Service View - Open Positions

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,

Service View - Open Positions

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.

Service view — open positions

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

Service View - Expanded Position

Clicking a position card expands it to show full details including trade history, mark-to-market section, and action buttons. The taxable

Service View - Expanded Position

Clicking a position expands it to show individual legs with detailed trade history, add trade functionality, mark-to-market controls, and Schwab account integration.

Service view — an expanded position

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)

Service View - Closed Positions

The closed positions view shows positions that have been closed, with “Days Held” instead of DTE. Expanded positions show the full trade

Service View - Closed Positions

The Closed section shows positions that have been fully closed out, with realized P&L and tax status tracking.

Service view — closed positions

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

Account Settings & Sync

The settings panel handles Google account authentication and cloud sync. Data can be synced to Google Sheets for backup and cross-device

Account Settings & Sync

The Settings dialog provides Google Sheets sync configuration, Schwab integration management, and data import/export functionality.

Account Settings — cloud sync, Schwab accounts and the Taxable flag

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

Getting Started

Creating a Service

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.

  1. Click + Add Service on the main page
  2. Enter a name for your service
  3. Click Create

Service Description

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:

Understanding the Dashboard

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.

Adding Trades

Copying from Thinkorswim

The easiest way to add trades is to copy the trade string from Thinkorswim:

  1. Go to the Activity & Positions tab in Thinkorswim
  2. Find your filled order in the Filled Orders section
  3. Click the icon in the leftmost column of the order row
  4. In the dialog that appears, copy the trade string
  5. Paste it into the Add Trade field in the app
Tip: You can optionally specify a Position # to add the trade to an existing position, or leave it blank to create a new position. You can also override the trade date if you're back-entering a fill.

Trade String History

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.

Notes & VIX/SPX Auto-Fill

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.

Editing Position Numbers, Dates & Moves

Trade String Format

The app parses trade strings in this format:

BUY +2 VERTICAL SPY 100 17 JAN 25 600/605 CALL @2.50

Components:

Reports & Analysis

The dashboard has two collapsible sections under the Total Portfolio card and per-service expansion areas with the same content scoped to one service.

Closed P&L Chart

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.

Service Performance Summary

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.

All Positions Service Summary (Open + Closed)

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.

Group by Effort

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.

Strategy Breakdown (per service)

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.

Day P&L & Week P&L Drill-Down

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.

Print PDF

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.

Per-Service Trade Export

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.

Position Analyzer

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.

What It Shows

Calibrate to ToS Delta

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.

Heads up: The analyzer currently supports SPX/SPXW/XSP, RUT/RUTW/IWM, NDX family/QQQ, DJX/DIA, SPY, and OEX/XEO. VIX options and futures-options (e.g. /ES, /GC) need a different pricing model and are deferred to v2.

Open in TradingView

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.

Dividends & Stock Sales

Two top-bar tabs cover non-options activity in your taxable Schwab accounts:

Dividends Page

Stock Sales Page

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.

Google Sheets Sync

You can optionally sync your data to Google Sheets for backup and cross-device access.

Enabling Sync

  1. Click the Settings (gear) icon
  2. Click Sign in with Google
  3. After signing in, click Enable Cloud Sync
  4. A new Google Sheet will be created in your Google Drive
Privacy: The spreadsheet is created in YOUR Google Drive. Only you can access it. The app never stores your Google password.

Schwab Integration

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.

Setting Up Schwab Integration

  1. Click the Settings (gear) icon
  2. Scroll to Schwab Integration
  3. Click Enable Schwab Integration
  4. Click Sign in to Schwab
  5. You'll be redirected to Schwab's login page
  6. Log in with your Schwab credentials
  7. Review and approve the access request
  8. You'll be redirected back to the app with your accounts connected

Setting Account Nicknames & Flags

By default, accounts show as "MARGIN ****1234". In Settings → Schwab Integration you can set per-account properties:

Linking Positions to Schwab

  1. Expand a position by clicking on it
  2. Find the Schwab dropdown and pick the account that holds this position
  3. Optionally check Auto-mark to market so the position's mark refreshes automatically every time Schwab data refreshes
  4. Use the Refresh from Schwab button in the top bar to pull fresh quotes for every linked account at once
Matching: The app matches your tracked positions to Schwab data by comparing symbol, strike price, expiration date, and option type. If a match is found, the Net Liq value is displayed and (if auto-mark is enabled) written to the position's mark.

Tax Term Per Position

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.

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.

SPX / VIX Index Quotes

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.

Supported Symbols

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 Page

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.

Performance

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?

Risk against the benchmark

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.

Current exposure

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.

Heat Map

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.

Holdings — a snapshot

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.

Harvest — a flow

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.

Timeframe

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.

The market — reference, on its own scale

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.

What the area actually measures

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.

Badges

Moved this year

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.

What a Year Filter Means

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.

Where boundaries come from

Two things to know

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.

Value History

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.

Entry Ratings

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

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.

Organising Services & Positions

Where Your Data Lives

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.

Account Balances

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

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.

Modes

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.

When it checks by itself

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.

PL5 Trade Builder

The PL5 Builder solves a 1 / −2 / 2 SPX put ratio against the live option chain, and can place it.

Building

Strike collisions

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.

Placing the order

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.

The price ladder

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.

After the fill

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

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.

The eight conditions

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, and where it runs

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.

The header indicator

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.

The VIX chart

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.

Dry run and live

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.

How it knows a fill happened

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.

Two limits that are not the same

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.

Working the price

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.

The build preview

Why it counts sectors instead of weighting them

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.

About the sector weights

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.

Collisions, handled automatically

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.

Settings

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.

Alerts & Notifications

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

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.

Why it is not just a file: anything served as an ordinary file is fetchable by anyone who knows its address, whether or not a page asks for a password first — so a password prompt in front of a static file would protect nothing. The document is held by the server instead and is only ever sent in answer to a request carrying the right password. Changing the password is a settings change, with no file to re-cut or resend.

Risk Acknowledgement

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.

Application Architecture

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    │
   └─────────────┘              └───────────────────────┘

Key Points

Data Storage & Limitations

By default, your data is stored in your browser's localStorage:

What persists:
Limitations - data is isolated to:

When you could lose data:

Recommendation: Either enable Google Sheets sync or regularly use the Export function in Settings to create backup files.

Schwab Security & Architecture

The Schwab integration is designed with security as a top priority. Here's how it works and why your data is safe.

How Authentication Works

Your Browser
→
Schwab Login
→
Vercel Server
→
Schwab API

The app uses OAuth 2.0 with PKCE (Proof Key for Code Exchange), the industry standard for secure authentication:

  1. You click "Sign in to Schwab" - The app redirects you to Schwab's official login page
  2. You log in at Schwab - Your password is entered directly on Schwab's website, never in our app
  3. Schwab asks for your approval - You explicitly authorize read-only access to your accounts
  4. Schwab sends a temporary code - This code is exchanged for access tokens via our secure server
  5. Tokens are stored locally - Access tokens are stored in your browser's localStorage, not on any server

Security Features

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

What the App CAN'T Do

What the App CAN Do

Revoking Access: You can revoke the app's access at any time by:
  1. Going to Settings in the app and clicking "Sign Out" from Schwab, or
  2. Visiting your Schwab account settings and removing the app's authorization

Technical Architecture

┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│   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.

Appearance

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.

Tips & Tricks

Keyboard Shortcuts

Changelog & Versioning

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.

Dark Mode

Toggle dark mode from the user menu. The app is fully themed (charts, modals, tables); the choice is saved per browser.

Period Selectors

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.

Mark-to-Market

For positions not linked to Schwab (or when you want to override), set a "mark" manually:

  1. Expand a position
  2. Click Set Mark or Update in the Mark-to-Market section
  3. Enter the current Net Liq value — or check Auto-mark to market to let the linked Schwab account drive it on every refresh
  4. Click Save
Tip: If you have Schwab connected and have fetched positions, the mark input will pre-populate with the Schwab Net Liq value for easy one-click updates. The mark source (manual vs schwab) and the timestamp are tracked per position.

Position Status Colors

Data Backup

Your data is stored locally in your browser. To ensure you don't lose it:

Technology Stack


Galadriel’s Mirror — Help last updated August 2026 (covers v4.82) · Privacy Policy Disclaimer