Skip to content

UI Intricacies & Pitfalls

The UI reference tells you what every button does. This page is about how the window behaves at runtime — the things that trip people up the first time they hit them. Nothing here will crash the app; the failure mode is almost always "the chart silently does something less useful than you expected."


1. Tutorial overlay

The first time the app runs is a lot of controls to scan. A guided tour narrates each one with a dimmed-screen + spotlight callout:

  • End user (Windows installer) — Start menu → Compare My Stocks (Tutorial).
  • Developer — pass --tutorial (manual: Next/Skip buttons) or --tutorial-auto (auto-advances every ~5s). See CLI → Tutorial.

The tour is also the canonical answer to "what's the currency checkbox actually doing" — it documents the tri-state (§ 4 below) that the plain control reference glosses over.


2. Status bar (bottom edge)

Six pills along the bottom edge tell you at a glance which data sources the app can actually reach:

Pill Green ("connected" / "set") means
IB IB sidecar is up and the Pyro5 connection is alive.
Flex TransactionHandlers.IB.FlexToken is configured.
RAPID StockPrices StockPricesHeaders.X_RapidAPI_Key is set (yfinance via RapidAPI).
RAPID SeekingAlpha SeekingAlphaHeaders.X_RapidAPI_Key is set.
RAPID YFinance Jupyter.RapidYFinanaceKey is set.
Polygon Sources.PolySource.Key is set.

If anything is red, a ⚠ Select Config/Help → Help to fix hint appears next to the pills. The bar auto-hides ~4 s after startup to save vertical space; move the cursor to the bottom edge to bring it back (stays visible for ~3 s past the last hover). State refreshes every 2 s.

For the underlying config fields, see CONFIGURATION → Sources, CONFIGURATION → TransactionHandlers, and the config_help.md walkthrough.


3. Auto-update, the update queue, and the "Update is waiting" hint

With Auto Update on (UI § 13), almost every control change posts an update onto a background worker. The worker is single-threaded and buffers up to 3 pending updates. Beyond that, additional changes are dropped and the status line shows Update is waiting (generating graph probably).

What this means in practice:

  • Don't spam controls during a long redraw. Wait for the previous update to land, or toggle Auto Update off, make all your changes, then toggle it back on (it will recompute once).
  • A change that "didn't take" is almost always dropped, not buggy. Toggle the control off and on again — that re-posts the request.
  • Programmatic widget rewrites (saved-graph load, currency conversion, setting controls from Parameters) are protected by a reentrancy guard so they don't fire spurious updates. If you see "ignored (ignore_updates_for_now)" in the debug log, that's the guard doing its job — it's not an error.

4. Adjust Currency is tri-state, not on/off

adjust_currency looks like a normal checkbox but Qt renders three states; the tutorial labels them as:

State Effect
Checked (✓) Convert everything to the currency in Current Currency (e.g. ILS).
Partially checked (■, "square") Show everything in Symbols.Basecur (usually USD). FX is still applied where needed to normalize to the base.
Unchecked Leave each ticker in its native trading currency. No FX conversion at all.

The control reference (UI § 11) only describes the on/off poles — click the box twice if a single click doesn't get you where you want. The "square" state is the difference between "USD everywhere" and "each stock in its own currency".

Underlying config is Symbols.Basecur and the currency-pair fetch logic documented in CONFIGURATION → Symbols.


5. Chart interactions (pan / zoom)

The chart pane is a pyqtgraph canvas. Mouse interactions are:

  • Scroll wheel — zoom on the cursor.
  • Drag — pan.
  • Right-click — pyqtgraph's built-in context menu (auto-range, axis options, export).
  • Hover — tooltip with the ticker and y-value at the cursor.

Important behavior: zooming the chart updates the Start / End date pickers visually so you can see the new range, but it does not re-run filters, re-query data, or write back to Parameters. The chart is a free interaction surface. If you want the zoom to stick — apply filters at the new range, persist it to a saved graph, fetch missing data — pick the dates explicitly (UI § 10) or drag the Date range slider (daterangepicker). Only the date controls trigger the data path.


6. Refresh: how it actually fetches

Refresh Stocks (UI § 13) is the only control that talks to the data source. Three behaviors worth knowing:

  1. End date is ignored during fetch. The display filter's End date does not cap the request — the app fetches up to now, then re-applies the End filter for display. You can't "stop fetching at 2023" by setting the End date.
  2. Click Refresh a second time to cancel. While a refresh is in flight, the button label changes to Stop Refreshing. Clicking it sets cancel_requested = True; the worker checks between symbols and breaks out cleanly. The button becomes Stopping… until the current symbol completes.
  3. Min Refresh (default on) only fetches the missing tail — fast but skips correcting silent gaps inside the existing cache. Turn it off for a "full re-pull" when you suspect cached data is wrong.

Symbol resolution is async: typing a ticker and hitting Add to / < Add to queues it for background processing. The chart updates once the queue drains.


7. Saved graphs: load behavior and persistence

The Saved graphs list (UI § 12) persists more than it looks like.

  • Auto-load at startup — controlled by Running.LoadLastAtBegin and Running.LastGraphName in config. When on (default), the app silently reloads the last graph you used on launch. To start clean, clear those fields in myconfig.yaml.
  • Transient fields are stripped. Internal control flags (reset_ranges, …) are not written to graphs.json so the on-disk format stays stable across app versions.
  • Loading a graph is reentrancy-heavy. The compare-with combo can re-emit currentTextChanged after items get reseated; the load path has three layered guards to prevent that from re-triggering an update. Symptom of a bug here: loading a graph "forgets" Compare With or flips Do Comparison off. If this ever happens, save a different graph and reload — but please file an issue first.

8. Embedded notebook fallback

If Voila / Jupyter aren't installed (or fail to start), the notebook pane is replaced by an explanatory frame with the failure reason, instead of crashing the app. On a clean Windows install you can run install\installvoila.bat to fix it. Switching to No Jupyter mode (UI § 14) hides the area entirely.


9. High-DPI and scaling

The startup code sets QT_SCALE_FACTOR automatically based on screen size. If the UI looks broken (controls clipped, fonts the wrong size), set Running.TryToScaleDisplay: false in myconfig.yaml — see CONFIGURATION → Running. After turning it off you can either rely on the OS-level DPI setting or set QT_SCALE_FACTOR yourself in the environment before launch.


10. Dev-only: module auto-reload

Setting Running.AutoReload: true (or running under PyCharm — the PYCHARM_HOSTED=1 env var enables it as a convenience) polls loaded modules every Running.CheckReloadInterval seconds and hot-swaps any that changed on disk. Set CheckReloadInterval: 0 to disable polling even when AutoReload is on. Don't ship with this on — it significantly increases idle CPU usage.


11. Pitfalls — silent "did the wrong thing"

The control reference is permissive; the engine isn't. A few combinations are accepted by the UI but quietly degrade. None crash — they just produce a less-useful chart.

  • A group name in the Reference list is not expanded. Putting FANG there treats it as a literal ticker — you'll get an unrelated stock or a fetch error. To use a group as a reference line, put it in the Groups list with Sum unite (UI § 9) instead.
  • A group name in addstock / Selected (with Use Groups off) — same thing, treated as a literal ticker.
  • Compare With set to a group works only if the same group is in your active groups list with Sum or Average unite. The app will additionally try to fetch a stock by that name; it'll fail and log a warning — harmless but noisy.
  • Extending the date range backwards doesn't auto-pull older transactions. Hit Refresh Stocks (UI § 13) after extending the range.
  • Filter rows (UI § 6) ignore Reference lines, so loading up the Reference list defeats the "top-N" effect on the primary tickers.
  • Operations that need transactions (Value, Total Profit, Unrealized Profit, Realized) silently produce empty lines when you have no transaction data imported. Set up an IB Activity Statement or My Stocks CSV — see QUICKSTART § 4.
  • P/E and Price/Sells need an earnings/fundamentals data source. The status bar (§ 2 above) tells you whether RAPID StockPrices / SeekingAlpha are configured. Missing keys → empty lines.
  • Symbols that share a name across exchanges are best added through Lookup (UI § 1) so the correct contract is pinned. Typing a bare ticker and pressing Add to uses the data source's default resolution, which can pick the wrong listing for non-US names.
  • Refreshing while disconnected (red IB pill — § 2 above) silently leaves the cache untouched for IB-sourced symbols. Connect TWS / IB Gateway first.
  • Display modes (UI § 14) affect layout only — they never change what the engine computes. If switching modes "fixes" or "breaks" the chart, the underlying issue is in another control.

See also