A copy-trading–first Terminal UI for analysing personal investments on eToro.
Built with Textual, backed by local SQLite history.
Status: v0.1 — all three phases from PRD.md implemented + UX polish pass.
A guided tour of every panel — with screenshots and what-the-keys-do — lives in docs/USAGE.md. All screenshots in this README and in the manual are produced from synthetic data via
scripts/generate_screenshots.py; no real account numbers leak into the repo.
A multi-panel TUI with 20 tabbed panels plus 4 drill-down panels grouped into 8 categories (Overview / Portfolio / Performance / History / Risk / Discover / Notes / Plan):
- Summary — persistent header with equity, cash, invested, P&L, delta arrows, and a 30-snapshot trend sparkline.
- Dashboard (new) — at-a-glance landing page: KPIs, trend, top 5 winners / losers, top exposures, recent activity.
- Stocks — open self-managed positions, sortable, filterable,
Enterdrills into the instrument. - Mirrors — copy-trading dashboard with compound monthly return per trader.
- Monthly — month-by-month gain %, cumulative compounded, realized $ split (stocks vs copies).
- Look-through — the product differentiator: aggregates positions across every copy + self-managed into one virtual portfolio per ticker ("what am I actually exposed to?").
- Equity Curve — line of local equity history + monthly gain bars, with drawdown overlay.
- Instrument Detail — open lots (self + via-mirror), closed trades, win rate.
- Mirror Detail — trader's monthly history + your current holdings via them.
- Settings & Help — editable config, live keybinding map.
- Trade History — paginated closed trades with category (self/copy) and win-rate stats.
- Allocation — by industry, asset class, and the stock-vs-copy ratio.
- Copy Risk — per-mirror concentration flag (HIGH/ELEVATED/OK).
- Mirror Compare — months × traders heatmap of monthly gain %.
- Trader Scout — search
/user-info/people/searchby period + filters; save candidates locally. - Trader Profile — drill-down from Trader Scout with public metrics, monthly gain, and visible holdings.
- Watchlists — saved candidates + eToro-native watchlists (fetch on demand).
- Heatmap — calendar-style daily P&L grid.
- Journal — timestamped notes per instrument/mirror/global.
- Alerts — local rules engine (concentration, mirror drawdown, position drawdown) evaluated on every refresh.
- Attribution — splits realized returns into stocks vs copies per month, with a winner column.
- Sector Allocation Detail — per-sector P&L and return %.
- Cash & Orders — cash breakdown plus pending orders table.
- What-if — project months-to-target based on local equity curve's avg-monthly trend.
- Candle Chart — OHLC sub-pane launched from Instrument Detail via
c(1D / 1h / 1W toggles).
- Manual refresh (
r) — fetches portfolio, P&L, trade history, and monthly gain concurrently. - Every refresh persists to SQLite → enables equity curves, drawdowns, and longitudinal analytics.
- Read-only: trading actions exist as placeholders that always raise
TradingDisabledError. - Mouse + keyboard (vim-style
hjkl, arrows, and number hotkeys). - Discoverability: tabs are prefixed with
[N]numeric hotkeys + a category icon; a contextual status bar between the panel and the footer always shows the current panel's hint and panel-specific keys; the command palette groups all 20 panels by category for fuzzy search.
See PRD.md for the full spec, schema, and panel specifications.
Selected panels — see docs/USAGE.md for the full tour.
| Stocks — open self-managed positions | Mirrors — copy-trading dashboard |
|---|---|
| Look-through — real exposure across self + every copy | Monthly — gain %, cumulative compound, $ split |
|---|---|
| Equity Curve — local history + drawdown overlay | Heatmap — daily P&L calendar grid |
|---|---|
| Copy Risk — concentration + riskScore + drawdown | Mirror Compare — months × traders heatmap |
|---|---|
| Trade History — every closed trade, stocks vs copy | Help overlay — live keybindings + panel map |
|---|---|
- Python 3.12+
- An eToro account with API access enabled
- Generate keys at: eToro → Settings → Trading → API Key Management
- Choose Real environment and Read permission for MVP
git clone https://github.com/madpin/etorotui.git
cd etorotui
# 1. venv + install
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
# 2. credentials
cp .env.example .env
# edit .env and paste ETORO_API_KEY and ETORO_USER_KEY
# 3. config (optional)
cp etorotui.toml.example etorotui.toml
# edit etorotui.toml to taste
# 4. run
etorotui # real account
etorotui --mode demo # demo account
uv run etorotui # alternative| Key | Action |
|---|---|
r |
Refresh snapshot |
: |
Command palette — type to jump to any panel or action |
t |
Toggle theme (dark / light) |
1–9 |
Jump to the Nth tab (titles are numbered for clarity) |
Tab / Shift+Tab |
Next / previous panel |
Enter |
Drill into selected row |
Esc / Backspace |
Back from drill-down / close filter |
s / Shift+S |
Cycle sort column / reverse (arrows in header show direction) |
/ |
Filter rows (live substring match + row counter) |
j |
Journal note (selected row) |
? / F1 |
Categorized help overlay |
Ctrl+, |
Settings |
q / Ctrl+C |
Quit |
All keys are remappable in etorotui.toml → [keybindings].
./etorotui.toml — see etorotui.toml.example for an annotated reference. Highlights:
[ui].theme—dark(default) orlight[ui].default_months— window for Monthly P&L panel[ui.layout].panels— order of panels in the main screen[history].db_path— where SQLite snapshots are stored[features].trading— must remainfalsein MVP[keybindings]— remap any default key
ruff check . # lint (clean)
ruff format . # format
pytest -q # 71 tests, all passingCoding rules and architecture: AGENTS.md. Per-area conventions in .cursor/rules/.
The test suite is grounded in a redacted snapshot of a real account (tests/fixtures/etoro_snapshot.json — username/CIDs zeroed out). Aggregation math and SQLite round-trips are verified against this fixture.
pytest -q
# ======================== 71 passed in 0.9s =========================source .venv/bin/activate
python scripts/generate_screenshots.py # writes docs/screenshots/*.svgThe script builds a fully synthetic SnapshotView, populates a throwaway SQLite database with ~30 historical snapshots, and drives the Textual app via App.run_test(...) to capture SVGs. No credentials, no network calls, no real account data — the constants at the top of the script are the entire fixture.
src/etorotui/
cli.py app.py config.py theme.tcss
api/ # async eToro client + pydantic models
services/ # portfolio, mirrors, market, history
db/ # schema.sql, migrations, repository
ui/
screens/ # main, detail, settings, help
panels/ # one file per panel
widgets/ # pnl_cell, sparkline, sortable_table, toast
actions/ # placeholder for future trading actions
utils/ # format/math helpers
tests/ # JSON-fixture-based smoke tests
- Aggregation math and rendering helpers are ported from a working single-file Rich script (
portfolio.py) — the predecessor to this project. - eToro Public API documentation: https://api-portal.etoro.com/.
See LICENSE.