Skip to content

feature: add lean ui terminal UI with live backtest results - #675

Open
jaredbroad wants to merge 8 commits into
masterfrom
LEAN-CLI-UI
Open

jaredbroad wants to merge 8 commits into
masterfrom
LEAN-CLI-UI

Conversation

@jaredbroad

Copy link
Copy Markdown
Member

Adds lean ui, a keyboard-driven terminal UI (in the style of btop) for browsing projects, running backtests and watching their results, local or in the cloud.

Completed cloud backtest of the Intraday HMM Simplified Regime Detection project

While a backtest runs, the equity chart fills in from left to right over the backtest's period and refreshes every 15 seconds:

Running cloud backtest at 42%

What it does

  • Projects: lists local projects and, when logged in, the organization's cloud projects, marked local, cloud or synced. ↑↓/j k to move, / to filter, enter for actions.
  • b / c backtest locally / in the cloud and open the results right away. A local backtest is written to an output directory the UI picks so it can be read while running; a cloud backtest switches over as soon as lean cloud backtest prints its url. The command's output is the Logs tab while it runs.
  • v view results of every local and cloud backtest of the project, newest first. [ ] step through them, o opens a filterable list.
  • Results screen: runtime statistics, the Strategy Equity chart spanning the backtest's start to end, and Statistics, Orders and Logs tabs.
  • Orders load 100 at a time as they are scrolled to (Orders (100 of 3,555)), so backtests with millions of orders stay fast.
  • Push, pull and cloud status run after a confirmation, with their output in the UI. x stops a running command the way Ctrl+C would.

How it works

  • lean ui runs CLI commands as child processes (python -m lean), so their console output can't draw over the UI and no command needed refactoring.
  • Local result files and the cloud API share the same JSON format; lean/ui/results.py reads both. In the cloud only the Strategy Equity chart is read, with orders (100 per request) and logs (200 per request) loaded when their tab opens.
  • The chart, orders and log endpoints answer {"status": "loading"} while they prepare a backtest's data, and a running backtest's orders come back empty with a length of 100. Both are handled.
  • Textual is only imported by lean ui, so other commands start as before. textual and textual-plotext are added to install_requires (about 1.9 MB of wheels).

Testing

  • 2,700+ tests pass, including pilot-driven tests of the UI (tests/commands/test_ui.py), the result readers (tests/test_ui_results.py) and axis ticks (tests/test_ui_ticks.py). Static analysis passes.
  • Checked by hand against real data: a local backtest run from the UI over the sample data, refreshing while it ran, and 129 cloud backtests of the HMM project including paging through 3,555 orders.
  • Not yet done by hand: starting a cloud backtest with c from the UI end to end.

🤖 Generated with Claude Code

jaredbroad and others added 8 commits October 8, 2026 15:49
…commands

Adds a keyboard-driven terminal UI built on Textual. It lists local projects
under the Lean CLI root and, when logged in, the organization's cloud
projects, labelled local, cloud or synced. Projects can be filtered and
navigated with the arrow keys or j/k.

The selected project offers backtest, cloud backtest, logs, push, pull and
cloud status actions. Each one shows the exact `lean` command for
confirmation, runs it as a child process (`python -m lean`) and streams its
output into the UI. `x` stops a running command via Ctrl+C (Ctrl+Break on
Windows), and pressing it again kills the process.

Textual is only imported by `lean ui`, so other commands start as fast as
before. textual-plotext is added for the upcoming backtest charts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pressing `v` on a project with local backtests opens a results screen modeled
on the QuantConnect.com backtest page: a list of the project's backtests,
the runtime statistics, equity against a scaled benchmark, drawdown, and tabs
for statistics, orders, every other chart (series in a second unit go on a
right-hand axis) and the log.

Results are parsed from the result file LEAN writes, which uses the same
chart, order and statistics format as the cloud API, so the parser can serve
cloud backtests too. Running backtests report progress from their last
equity point. Chart axes use round values and round times.

Also fixes the projects screen being queried while another screen was on
top, which failed when cloud projects finished loading behind a dialog, and
stops project action keys from firing on other screens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…testing

The results screen now shows a project's cloud backtests next to its local
ones, newest first, read through the API: the backtest summary and its
Strategy Equity chart, with orders (100 per request) and logs (200 per
request) loaded when their tab is opened. Backtests that are still running
are refreshed every 15 seconds in the cloud and every 10 seconds locally.

`b` and `c` now open the results right away instead of asking to confirm.
A local backtest is written to an output directory the UI chooses, so its
results can be read while it runs; its progress comes from the date range in
LEAN's log, since the result file only gets it at the end. A cloud backtest
switches to the backtest as soon as `lean cloud backtest` prints its url.
The command's output is the backtest's log while it runs.

The results screen uses the full width: `[` and `]` step through backtests
and `o` opens a filterable list of all of them. Only the Strategy Equity
chart is shown. The latest logs action is replaced by the Logs tab.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The x axis now runs from the backtest's start to its end date when they are
known, so a running backtest's equity grows from left to right instead of
being stretched over the full width. Local backtests take the period from
LEAN's log while running and cloud backtests from the API.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The orders tab loaded every order before showing any, which for a cloud
backtest with millions of orders meant tens of thousands of requests. It now
loads 100 orders when the tab opens and the next 100 as the cursor or view
nears the end, showing "Orders (100 of 3,555)". While a backtest runs only
orders placed since the last refresh are requested, and the orders are
loaded again once it completes so their final status shows.

The cloud chart, orders and log endpoints answer {"status": "loading"}
while they prepare a backtest's data, which left the orders tab empty after
a backtest completed. Those answers are now retried, and if the statistics
count orders the endpoint does not return yet, loading them is retried too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The test added the orders while retries ran every 0.1 seconds, and waiting
for the app's workers could last through all of them, so it sometimes
failed. The fake endpoint now returns the orders from its third request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cloud orders endpoint answers a running backtest with no orders and a
length of 100, which showed as "Orders (0 of 100)". The length is now only
used when a page has orders, and a running backtest without any shows
"Orders (none yet)".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A completed and a running cloud backtest of the Intraday HMM Simplified
Regime Detection project, for the pull request description.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jaredbroad

Copy link
Copy Markdown
Member Author

🍿 #vibed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant