Skip to content
Conduit

mcp server

tradersagent-plugin

Unofficial Hermes Desktop plugin — a chart-first Vela console with the LuxAlgo Library (LuxAlgo MCP) behind it

godzillacode0000/tradersagent-plugincommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 21, 2026

A Hermes Desktop plugin that opens a chart-first trading console: Vela-rendered candlesticks, and the LuxAlgo Library on top of them.

licence: MIT + vendored AGPL-3.0 python: 3.11+ hermes desktop: 0.21.3+ kind: desktop plugin

A sidebar entry that docks a full trading console beside your conversation: Vela-rendered candlesticks and the LuxAlgo Library (800+ concepts and indicators, served live by LuxAlgo's own MCP server) in one local web app — your chat session is left exactly where it was.

  • Chart-first by design. Vela gets the whole pane. The Library and the item detail panel are two small toggles in the top bar, never permanent columns.
  • Everything local. One Python process on 127.0.0.1:8787 proxies LuxAlgo's MCP server and serves the frontend. No account, no key, no telemetry. The chart engine (Vela, Apache-2.0) and the Pine runtime (pinets, AGPL-3.0 — a patched build) are vendored under console/frontend/vendor/ and served from this machine; no engine code is fetched at run time (market bars come from Binance and Library scripts from LuxAlgo's MCP — both over the network by design, and the console says which provider answered). The patches and their published chain: THIRD-PARTY.md and console/frontend/vendor/pinets/PROVENANCE.md.
  • The agent can drive the chart. console/bin/trader-chart reads what the chart is showing and puts indicator scripts on it through a small file bridge, so you prompt your agent instead of clicking around. console/bin/library-indicator pulls an indicator's Pine source by name.
  • Edge Stats — how often did it actually happen? An optional, local port of LuxAlgo's open-source Edge Stats engine: ask "how often does a gap fill by the close?" and get the rate with its sample size and a 95% interval — never a bare percentage — plus the sessions behind it, drawn on a Vela chart. ./install.sh --with-edge installs it; the ⋯ menu opens it.

Unofficial. This project is not affiliated with, endorsed by, or sponsored by LuxAlgo. It uses the LuxAlgo name descriptively ("compatible with LuxAlgo MCP") and ships no LuxAlgo logo. See THIRD-PARTY.md and NOTICE for the licences, the attribution each piece requires and the source offer for the patched engine (the distribution as a whole carries AGPL-3.0 parts).

Requirements

Hermes Desktopv0.21.3 or newer (the plugin uses the sidebar.nav + routes contribution areas)
Python3.11+ with the mcp client: pip install mcp (a venv is fine — point PY= at it)
Networkfor LuxAlgo's MCP endpoint and the Binance data feed; with no market feed the chart stays empty (chart_state says bars None). The chart engine and Pine runtime are served locally
Node 20+ (optional)for Edge Stats (./install.sh --with-edge, ~300 MB) and the prop-firm simulator (./install.sh --with-prop, ~10 MB) — both LuxAlgo engines, both local. Without them everything else works and each sheet says what is missing
Preview resizer (optional)libvips (or ImageMagick, or ffmpeg) — see Catalogue previews. With none of them the console still shows every preview, just at the catalogue's original size
OSLinux (built and verified on Omarchy / Arch + Hyprland) or macOS — Macs need the one-time venv + the launchd service; see docs/MACOS.md. Windows is out of scope.

Install

git clone https://github.com/godzillacode0000/tradersagent-plugin
cd tradersagent-plugin

./console/start.sh        # terminal 1 — the console on http://127.0.0.1:8787/
./install.sh              # the plugin -> ~/.hermes/desktop-plugins/traders-desk/
                          #   and the desk skill -> ~/.hermes/skills/trading/trader-desk/

Optional engines (each is LuxAlgo's own, MIT, fetched once): ./install.sh --with-prop (prop-firm simulator, ~10 MB) and ./install.sh --with-edge (Edge Stats, ~300 MB) — both need Node 20+.

Then in Hermes Desktop:

  1. Ctrl+K → Reload desktop plugins
  2. Capabilities → Plugins → enable Trader's Agent
  3. Click Trader's Agent in the left sidebar — the chart docks on the right, beside your conversation (the plugin declares that position as a standing invariant, so the row never needs arranging by hand)

The agent's chart tools are registered by install.sh itself (the traders-chart MCP server). ./bin/register-mcp.sh does that one job — it resolves the interpreter the same way start.sh does (repo .venv, then a python3.11+ with the mcp client, then uvx), prints the exact command with --print, and leaves an existing registration alone unless you ask for --force. A new session (or /reload-mcp) picks the tools up; ./install.sh --doctor checks the wiring.

Step 2 is not optional, and it is the step that gets skipped. Hermes Desktop ships every desktop plugin disabled by default: an installed plugin is inventoried but contributes nothing — no sidebar row, no pane, no error — until you switch it on. Enabling is a decision the app stores itself, so install.sh --doctor cannot see it. To ask directly:

./bin/is-enabled.sh          # files ok? decision on/off? what to do about it

If the pane is enabled but still does not dock, that is a different, app-side interaction — see docs/INSTALL-ENABLE.md for both failure modes side by side.

The browser builds are committed under console/frontend/vendor/ — the console loads them from there. Their checksums are in console/frontend/vendor/SHA256SUMS (tools/vendor-sums.py --check; the test suite runs it), and console/frontend/vendor/VENDORING.md says how to refresh them (see the licence note in THIRD-PARTY.md).

Verify it worked

curl -s localhost:8787/api/health                    # {"ok": true, "data": {"status": "ok", ...}}
node tools/verify-plugin.mjs traders-desk/desktop/plugin.js        # OK — 8 contributions
console/bin/trader-chart state                       # symbol, timeframe, price, bars, indicators on

Then, in the app: the Trader's Agent row is in the left sidebar, the console frame shows Vela bars with the ▲ mark, and the status-bar chip reads Trader's Agent. If the frame is blank, the console process is not running — start it again.

Run the console as a service (optional)

# ~/.config/systemd/user/traders-agent.service
[Unit]
Description=Trader's Agent console (LuxAlgo MCP proxy + Vela chart)
After=network-online.target

[Service]
ExecStart=%h/tradersagent-plugin/console/start.sh
Restart=always
WorkingDirectory=%h/tradersagent-plugin
# Catalogue previews want an image converter (vips / ImageMagick / ffmpeg) and a service PATH
# usually lacks one — name it here, or thumbnails are served at full size: slow, not wrong.
# Environment=TRADERS_AGENT_RESIZER=/usr/bin/magick

[Install]
WantedBy=default.target
systemctl --user enable --now traders-agent.service

On macOS the same job is a launchd agent instead — one command, and it manages the venv interpreter itself (details in docs/MACOS.md):

./console/service-macos.sh install        # status · uninstall

Using it

WhereWhat it does
Left sidebar → Trader's Agentreveals the chart pane beside your chat — and, when no chat of yours is in front, opens Trader's Agent's own desk chat; a chat you are already in is not switched
Status bar → Trader's Agent chipsame, from anywhere in the app
Ctrl+K → Trading: open Trader's Agentsame, from the palette
Ctrl+K → Trading: toggle chart reveal on launchstop the console opening by itself at app start
Ctrl+K → Trading: toggle the desk chat on row clickstop the row opening the desk chat when nothing else is in front (it stays reveal-only, exactly as it was before 16 Oct)
Chart top bar → ⌗ Indicatorsopens the drawer: search the 806-row LuxAlgo catalogue, star favourites, mount Vela's built-ins, or open a Library row's Details (write-up, full Pine source, licence badge, Run PineTS / Add to chart)
Chart top bar → LayoutVela's own grid picker — presets and custom g<cols>x<rows> grids. The console boots at 2h (two side by side); the same door is chart_set_layout / trader-chart layout
Chart top bar → <> Scriptthe Pine script pane: write or paste Pine, Run, tweak the script's own Settings, save and reopen scripts
Chart top bar → ⛶ Full screenGives the chart the whole pane — and the whole display: the console's chrome, panels and statusbar step aside and the page asks the browser for fullscreen. Esc (or the floating ✕, or the same button) comes back. Docked on Vela's own toolbar row beside <> Script and ☰ catalogue; the door is chart_fullscreen / trader-chart fullscreen [--off]
Chart ⋯ menu → Edge Statsthe Edge Stats sheet: ask a question, run one of the 42 catalogue reports, group by weekday / month / year, open any historical session on a chart. Details below
Chart ⋯ menu → Trader's deskthe desk sheet: Paper (equity, cash, realised, open P&L, positions, fills, Reset), Journal (net P&L, win rate, profit factor, avg win/loss, the realised-equity curve, the closed trades) and Prop firms (the LuxAlgo directory + what is on offer, and Simulate on any challenge — LuxAlgo's own Monte-Carlo engine plays 10,000 journeys through that firm's exact ruleset and the card shows its verdict: pass odds with a 95% CI, failures, cost, days, payout odds and EV). A read surface — the only write is Reset, and approving an order still happens on its card, never here
The chart's own bottom barVela's range buttons, timezone clock and settings (not ours)

Chat beside the chart

The chart is a pane docked to the right of the conversation (traders-desk:chart, 620px, collapsed until you ask for it — your call, 19 Sep: it does not open with the app), so the app's own composer keeps the left and the chart reads on the right — drag the divider to re-balance, or collapse the pane with the app's own control. Clicking the sidebar row re-opens it (host.revealPane, the app's door for "an explicit user action happened") and a chat you are in is never switched: it used to jump to a dedicated desk chat on every click, which pulled you out of whatever you were doing, and that is still the failure this must not repeat. Since 16 Oct the row also opens Trader's Agent's own desk chat — but only when nothing of yours is in front (a cold boot, Settings, a first click), and the whole behaviour is one palette command away from off. Any session can drive the chart through the tools — the pane is chart, not conversation.

That chat is the "connected, sees, understands" part, and it needs no bespoke composer: the tools below answer over the console's push channel, so the round trip is local.

Run PineTS executes the script over the chart's live bars with the vendored, patched Pine runtime and paints it as a native series; it says plainly when a script uses something the runtime refuses (import is the one). Add to chart asks Vela's own Pine engine, which here is the workspace's PineEngine — built on that same patched runtime. (vela-pinets' PineWorkerEngine would run its copy inlined in vela-pinets, without the patches; the console therefore does not use it.) The app reports what actually happened rather than pretending. (A heavy script can be run off the page's main thread instead with ?engine=worker on the console URL, or localStorage luxalgo-web:pine-engine = worker — at the cost of running vela-pinets' copy, which does not carry the patches.)

Strategy backtests — the intrabar broker emulator

LuxAlgo rebuilt Vela's broker emulator on an intrabar model (19 Oct): a Pine strategy() script's fills and exits are resolved inside the bar, with commission, slippage, margin, trailing stops and TradingView's tick-grid snapping, instead of "the close of the signal bar". This console ships that engine (Vela 0.8.5 · vela-pinets 0.2.17 · our PineTS fork of 0.12.0) and reads its ledger:

  • The tester strip appears at the bottom of the pane when a strategy() script runs: net profit, trades, win rate (with its N), max drawdown (money and %), profit factor — plus a curve of the closed-trade P&L and, on Trades, the engine's own round trips (side, size, entry/exit fills, commission, P&L, per-trade excursion).
  • The agent reads the same ledger: chart_apply_pine on a strategy answers with a ledger: line (net −189.55 USD · 11 trades · win 27.3% (n=11) · max DD 194.15 (2.38%) · PF 0.08 · commission 21.73 paid · intrabar fills), so "backtest this on BTC 30m" returns numbers in chat.
  • Honesty rules: a rate with no sample prints —, a profit factor with no losing trade prints ∞, and the strip says whether the ratio came from the engine's totals or from the closed trades. When the worker's smaller summary drops the declaration, the commission is reported as what the fills actually paid — never as "none". The curve is labelled as cumulative closed-trade P&L, not an intra-bar equity path.

Bars served to the chart come from this store first (fxticks.py → fxdata._bars): mid-price OHLC, volume = ticks in the bar, spread = median bid/ask. /api/fx/bars labels the answer source: "dukascopy-ticks" and includes the coverage it used. Yahoo is disabled unless TRADERS_FX_ALLOW_YAHOO=1.

Tick data for the seven instruments

tools/fx-fetch.py walks Dukascopy's v1 tick API and keeps one gzipped JSON per instrument-hour under ~/.local/share/traders-agent/fx/ticks-v1/<SYMBOL>/<YYYY-MM-DD>/<HH>.json.gz (plus a .empty marker for shut hours, so a resume does not re-ask). It is resumable, retries resets/429s, and reports ETA:

python3 tools/fx-fetch.py --years 2 --workers 5      # GBPUSD EURUSD GBPJPY XAUUSD XAGUSD NAS100 SP500
python3 tools/fx-fetch.py --days 3 --instruments GBPUSD,XAUUSD
python3 tools/fx-fetch.py --report                    # hours, empty hours, MB, coverage per symbol

Two conventions worth knowing before touching it: the v1 API's month is 1-based (unlike the older /datafeed/ paths, which are 0-based), and times/bids/asks are cumulative deltas — decode with a running sum (decode_hour, pinned by test_fx_ticks.py). Codes carry a dash and the class: GBP-USD, XAU-USD, USATECH.IDX-USD (Nasdaq-100), USA500.IDX-USD (S&P 500).

Forex pairs — GBPUSD, EURUSD, and the SMT pair

A strategy() for forex cannot use the chart's bars (the pane charts Binance crypto), and PineTS answers request.security("EURUSD", …) with the chart's own series when it has no other source — a comparison of a market with itself. Two lines at the top of a script fix that:

//@version=5
//@fx GBPUSD
//@tf 60

The runner sees //@fx, routes the run to the console's FX provider, and request.security("EURUSD") fetches a different symbol — verified: GBPUSD 1.33 vs EURUSD 1.12 in the same run.

Data comes from Dukascopy's free tick feed (no key, ~20 KB per instrument-hour, decoded from its LZMA >IIIff records in console/backend/fxdata.py), cached under ~/.local/share/traders-agent/fx/. When Dukascopy throttles (it does: 429s, and its free window lags ~two weeks) the loader falls back to Yahoo FX and the answer's source says yahoo — the numbers are never relabelled. GET /api/fx/bars?pair=GBPUSD&timeframe=30&limit=500 is the door; …/api/fx/status says what is cached.

Your own strategy is the script itself: session filter, SMT via request.security, a sweep test, a resting limit at the 50% of the leg, and risk-sized qty — no plugin code changes. Strategies are personal, so none ship in this repository; the directive is the whole integration.

Edge Stats — how often did it actually happen?

LuxAlgo's open-source Edge Stats engine measures P(outcome | conditions) on historical sessions: "of the sessions that gapped up, how many filled the gap that day?" This plugin runs it locally, as a sidecar the console starts the first time you ask a question (no engine process runs until then). It keeps running if the console restarts, and is paused automatically while a data download holds the store.

./install.sh --with-edge        # needs Node 20+ and git; clones the pinned engine and installs it

Then open the chart's ⋯ menu → Edge Stats. First run: one button loads demo data (synthetic bars, about 10 seconds, clearly labelled) or downloads free history from Binance (crypto) or Dukascopy (FX, metals, indices) for the symbols you choose. After that:

  • Start here — six plain questions (does a gap fill? does the opening range break? which weekdays close green?) over the engine's 42-report catalogue, which is grouped by theme below them. Or ask in the engine's own query language, with suggestions as you type — gapFill WHERE gapDirection = up.
  • Read the answer honestly — one big number, with its N, its 95% range, and two chips: is it stable over time and does it match recent sessions. Fewer than 10 matching sessions gives no rate at all, only the counts; fewer than 30 is flagged as thin. Summary (the default) stops there plus the breakdown and the days behind it; Detailed adds the evidence — the two halves, recent vs all, year by year, how long it took. Your choice is remembered.
  • Group by weekday, month, year or any registry field, under Adjust; or narrow by date or parameter.
  • Open a day — any matching session on a chart with its prior high / low / close, open and gap drawn as levels (a level far outside the day's candles is listed as off-chart instead of squashing the chart).
  • Dark or light — the sun/moon in the sheet's header switches the whole console, chart included.
The answer — one number, its N and range, and whether it holds upOne day — the levels behind the number
Group by weekday, month or year — shown in the light theme

(Shown on the demo data.) The same questions are open to your agent: edgestats_query, edgestats_report, edgestats_session, edgestats_show (puts the answer on your screen) and friends (see the tool table below), or trader-chart edge ask "gapFill" from a shell. The engine does all the arithmetic; the plugin never invents or rounds a number of its own.

Where the data lives. Everything stays on this machine, under ~/.local/share/traders-agent/edge/ (override with TRADERS_EDGE_HOME). Downloads go straight from Binance / Dukascopy to your disk and are not redistributed; their terms apply to the data. The engine is MIT; its calendar data is CC BY 4.0 ("Calendar data from Edge Stats by LuxAlgo") — see THIRD-PARTY.md. Historical frequencies are not predictions and not advice.

Letting your agent drive the chart

Two ways in, and they share one path underneath. Preferred: the MCP server — Hermes (or any MCP client) gets the chart as real tools. ./install.sh already ran this step; do it by hand only when you skipped it (--no-mcp) or want a different interpreter:

./bin/register-mcp.sh                  # the one job: register traders-chart (idempotent)
./bin/register-mcp.sh --print          # show the exact command for THIS machine first
hermes mcp test traders-chart          # start a new session afterwards
ToolWhat it does
chart_viewsis a view attached to push into? (0 = the console is not open)
chart_capswhat the attached page will actually execute — read it before drawing
chart_nativeswhat Vela can put on this chart from its own side (the built-ins for this market)
chart_studieseverything on the chart, each row labelled with the reader that saw it — ask this before calling a chart clean
chart_statesymbol, timeframe, last price, bars, indicators on the chart
chart_shotone PNG of the chart (returned as an image, plus the path)
chart_apply_pinerun Pine over the chart's live bars and paint a matching native; a strategy() script backtests on the intrabar emulator and answers with its ledger; optional inputs sets the script's own settings by label ({"Length": 50})
chart_add_indicatoradd a Vela native (ema, supertrend, donchian-channels, …)
chart_remove_indicatortake indicators off the chart — one by name, or all for every study (reports the chart's before → after list)
chart_set_marketswitch symbol / timeframe
chart_set_layoutread or set the workspace grid — 1, 2h, 2v, 4, 8, or g<cols>x<rows> (no argument reads it)
chart_indicatorsthe Indicators drawer — sections favorites/builtins/library, q for the search box, family to open one group of the catalogue, star/unstar KIND:ID, mount a built-in; answers with the rows it painted
chart_fullscreengive the chart the whole pane / the whole screen (on=False comes back) — hides the console chrome and asks the browser for fullscreen; the answer says whether the display was taken or only the page
chart_reloadremount every attached console (picks up new frontend files)
chart_palettewhat colours the chart is wearing (try_apply asserts the console theme)
chart_themeread or set the console theme — light / dark (no argument reports what is worn); the console palette, Vela's chrome and the chart's own colours move together, and the choice survives a reload
chart_replaydrive Vela's replay — rewind to older bars and walk forward (op = start/step/play/pause/stop/state; bars back, or an exact from_ms); while replay is on, a paper fill uses the replay cursor price, not the live one
chart_drawingdraw with Vela's own drawing tools — 76 types (trend line, channel, fib, Gann, Elliott, XABCD …), placed by bars_ago + price, real objects the operator can drag; a wrong anchor count is refused before drawing
chart_viewread or set a view setting — chart type, log / invert / percent scale, countdown, time zone, session, watermark, status line, grid sync, the ? shortcuts panel; every write is read back. Vela cannot create a price alert, so alerts only reads / clears the indicator-alert inbox
chart_marksevent marks on the time axis, in one "Trader's Agent" group (clear only touches its own)
chart_objectsthe Object tree: what is ON the chart (drawings back-to-front, then the indicators) and acting on it by id — list/select/show/hide/lock/unlock/front/back/duplicate/delete/settings/price, by ids or by type; every op reads the stack back
chart_data_windowthe Data window: the chart's own readout — time, OHLC + volume, one section per indicator — at the cursor's bar (or the latest bar when the cursor is off the plot)
chart_browseopen the catalogue in the drawer (optional family)
broker_statethe paper (simulated Binance) account: cash, equity, positions, orders waiting for you, and the journal its fills keep (closed trades, win rate, profit factor)
broker_proposepropose a paper order — it only puts an Approve/Reject card on the chart; the agent cannot approve it
chart_drawrun Pine and paint the boxes/lines/labels it builds on the chart overlay (optional inputs, as above)
chart_pine_inputslist a Pine script's settings — label, type, default, range/options — without running it or touching the chart
chart_clearclear the overlay and the indicators our paint layer added (then report what is left)
chart_batchrun several chart actions in ONE call, in order ([{"action": "market", …}, …]), stopping at the first failure unless told otherwise
chart_snapshotremember the chart's market + indicators as a restore point
chart_undoput the chart back to that point, reporting the chart's own before → after lists
chart_watchwatch for a spell and answer with a diff (what changed), not another snapshot
chart_alertwait for price to move (by a move_pct, or at all) and report the move
library_searchsearch the LuxAlgo Library
library_indicatorone indicator's write-up, licence and Pine source
library_listbrowse indicators with filters and paging (family, text, concept, tier, sort, page)
library_taxonomythe Library's own families or concepts — read before filtering, so a name is the Library's
library_conceptone concept by slug: what it means and which indicators implement it
library_sourcePine source by exact slug — no name resolution to get wrong
edge_presetsLuxAlgo's measured edge presets and their categories
edge_reportone preset's measured edge on one symbol
edge_symbolswhich symbols the edge reports cover
edgestats_statusis the local Edge Stats engine installed, what data it holds, whether a download is running
edgestats_fieldsthe query language's vocabulary — every outcome, condition and field, with definitions
edgestats_presetsthe 42-report catalogue (gap fill, opening-range break, weekday effects, …) with parameters
edgestats_queryP(outcome | conditions) on your own bars, with N and a 95% interval — or no rate when too few sessions matched
edgestats_reportrun one catalogue report on one symbol
edgestats_sessionone historical session behind a result: OHLC, prior levels, gap, opening ranges
edgestats_showput an answer (or a session with its levels) on the operator's screen
edgestats_setupload the demo data or download free history (Binance, Dukascopy) in the background
propfirmsprop-firm directory, with an optional filter
bt_runrun one vectorbt MA-cross backtest; returns metrics
bt_optimizesweep MA pairs at once and rank by return
bt_statusread a saved backtest result (run_id or newest)
bt_data_listlist the operator's local OHLC files
propfirm_offerscurrent prop-firm offers from LuxAlgo's own tracker

Same surface from a shell: trader-chart remove MACD | --all, add ema, apply file.pine, draw file.pine, shot, market SYMBOL TF, layout 2h, layout (read the grid), state. Which of those actually paint in this Vela build — and which calls return cleanly while doing nothing — is written down in docs/vela-chart-api-notes.md, measured from the running app.

Every tool carries MCP annotations (title, readOnlyHint, destructiveHint, openWorldHint), and the mutating ones answer with what the chart looks like after the call — a request echoed back is not a painted pane. Failures come back as a code beside the prose (NOT_RUNNABLE[import], RUNTIME_CRASH[pinets-get_v], TOO_FEW_BARS, ENGINE_UNAVAILABLE, TIMEOUT), so a caller can branch without regex-matching a sentence.

Or the CLIs, if you would rather shell out:

console/bin/trader-chart state                      # symbol, timeframe, price, bars, indicators on
console/bin/trader-chart shot --out /tmp/chart.png   # capture the chart for vision
console/bin/trader-chart apply script.pine           # run Pine over the chart's bars and paint it
console/bin/library-indicator "killzone"             # fetch an indicator's Pine source

Both paths go through the console's push channel (Server-Sent Events): the chart page holds one long-lived connection open, so a command lands in tens of milliseconds instead of waiting for a poll tick — measured 37-156 ms on the wire for add / market / apply, ~65-80 ms for a capture, against ~1.0 s on the old 2-second poll. Polling stays on as a safety net (every 15 s while the stream is healthy, 2 s if it drops) and the file bridge is untouched, so an older view still works. The bridge is still not a headless renderer: with no view attached, commands fail fast and say so.

Screenshots

In Hermes Desktop — the sidebar row docks the chart beside the chatChart-first — Vela gets the whole pane; panels are opt-in
Library open — search across the LuxAlgo LibraryDetail panel — write-up, licence badge, Run PineTS / Add to chart

Library text and Pine sources shown in these screenshots are LuxAlgo's — Source: LuxAlgo Library.

Update / Uninstall

cd tradersagent-plugin && git pull     # update
./install.sh                           # redeploy the plugin (hot-reloads in ~3 s)
systemctl --user restart traders-agent.service   # only if you run the console as a service

Uninstall: delete ~/.hermes/desktop-plugins/traders-desk/ in Hermes Desktop (Capabilities → Plugins), then remove the console's service (systemctl --user disable --now traders-agent.service). Outside the repo the console writes: ~/.local/state/traders-agent/ (console.token, the paper account paper.json), ~/.local/share/traders-agent/ (preview cache thumbs/, Edge Stats edge/, the optional backtest venv bt/), ~/.hermes/skills/trading/trader-desk/ (the skill, copied by install.sh) and, for shots the MCP server saves, /tmp. console/agents/ holds the runtime state. Delete those to remove every trace.

Repository layout

traders-desk/      the Hermes Desktop plugin package: plugin.yaml + desktop/plugin.js (+ its harness
                   expectations beside it). This is the dir the catalog installs (subdir), so the
                   installed folder keeps the plugin's id; install.sh copies the desktop half into
                   $HERMES_HOME/desktop-plugins/traders-desk/
console/frontend/  the web app: Vela chart, Library panel, detail panel, PineTS paint layer
console/backend/   one stdlib HTTP server proxying LuxAlgo's MCP + the chart bridge + the push channel;
                   edgestats.py runs the optional Edge Stats engine as a sidecar
console/mcp/       trader-chart-mcp — the chart as MCP tools (stdio, FastMCP); edge_text.py renders Edge Stats answers
console/bin/       agent-side CLIs (trader-chart, library-indicator)
tools/             verify-plugin.mjs — runs a plugin in Node against SDK stubs (used by CI)
tools/             verify-plugin-hook.mjs — the resolve hook it registers; only the three allowed specifiers resolve
tools/             verify-plugin-selftest.mjs — crafted-copy battery for the import gate (evasions fail, lookalikes pass)
install.sh         installs the plugin into $HERMES_HOME/desktop-plugins/, copies the skill, registers the MCP server
bin/               register-mcp.sh (the MCP registration on its own) · is-enabled.sh (is the plugin switched on?)

Configuration

EnvDefaultMeaning
PYpython3the interpreter for console/start.sh (must have mcp)
PORT8787console port. ./install.sh bakes PORT into the installed plugin's frame (re-run it after changing); the MCP server and trader-chart follow LUXALGO_CONSOLE
LUXALGO_CONSOLEhttp://127.0.0.1:8787where the MCP server, trader-chart and library-indicator find the console (set it with hermes mcp add … --env LUXALGO_CONSOLE=http://127.0.0.1:9000 when PORT is not 8787)
TRADER_CONSOLE_TOKEN_FILE~/.local/state/traders-agent/console.tokenthe POST token the console mints and the MCP server and CLI read
TRADERS_AGENT_WORKDIR~/Projects/luxalgo-web if it exists, else console/agentsthe folder the in-app study bridge runs the agent CLI in (never taken from a request)
TRADERS_AGENT_ALLOW_LANunsetwith --host 0.0.0.0, non-loopback peers may receive the POST token — i.e. they can DRIVE the console, not just read it. Unset, a non-loopback peer can read but every write is refused
TRADERS_AGENT_ALLOWED_HOSTSunsetcomma-separated extra hostnames/IPs treated as "this machine" by the Origin check (e.g. a tailscale name, so a page served over it can drive the console). It widens who may act through a browser page — add only names you control
TRADER_PAPER_FILE~/.local/state/traders-agent/paper.jsonthe paper account (use another path for a second test console)
LUXALGO_AGENTS_DIRconsole/agentsruntime state: chart bridge files, study threads, shots
HERMES_CLIhermes on PATHCLI the in-app study bridge shells out to
LUXALGO_CHART_INLINE_WAIT8seconds a command holds its request open waiting for the chart's pushed answer
TRADERS_AGENT_THUMB_WARM10 turns off warming the catalogue's previews in the background at start-up
TRADERS_AGENT_THUMB_WARM_PAGES0 (all)how many catalogue pages to warm; set a small number on a metered link
TRADERS_AGENT_THUMBS~/.local/share/traders-agent/thumbswhere the shrunk previews live
TRADERS_EDGE_HOME~/.local/share/traders-agent/edgewhere the Edge Stats engine, its workspace and downloaded data live
EDGESTATS_ENGINE$TRADERS_EDGE_HOME/engineuse an existing checkout of LuxAlgo's engine instead
TRADERS_EDGE_PORT8789loopback port the engine sidecar listens on
LUXALGO_EDGESTATSunsettalk to an engine you already run (base URL) instead of starting one
--mcp-urlhttps://mcp.luxalgo.com/mcppoint at a different (or local) MCP server

Catalogue previews are kept on your machine

The Indicators drawer shows each card the way LuxAlgo's own library does, with a chart preview. Those pictures are 1600×1000 PNGs on LuxAlgo's S3 (~29 KB each) and they are slow to fetch one by one — about 1–2 s each from a home connection, and a browser only opens six connections per host, so sixty cards used to fill in while you watched. The console therefore fetches each picture once, shrinks it to the width it is actually painted at (480 px for a card, 960 px for the Details pane), and serves it from http://127.0.0.1:8787/api/library/thumb: ~5 ms warm instead of ~2 s.

  • Every machine keeps its own copy. A fresh install warms the whole catalogue (806 rows, roughly 5 MB) in the background on first start — a few minutes on a slow link, invisible while it works — and every start after that finds the files already there. Nothing is shared between installs and nothing is downloaded twice.
  • The resizer is a CLI, not a Python package: vips, else ImageMagick, else ffmpeg. If none is installed the console serves the catalogue's original PNG — bigger, exactly as slow as upstream, but never a broken or blank tile.
  • A replaced picture is never stale: the cache key carries a tag of the source URL, and the response is immutable, so new artwork is a new file.
  • curl localhost:8787/api/library/thumbs reports what is cached and what the warmer is doing. TRADERS_AGENT_THUMB_WARM=0 switches warming off entirely.

Limits

  • The Pine runtime is a subset of Pine. import is refused outright; while and for…in run. Anything heavier only runs in TradingView's own Pine engine.
  • "Add to chart" is experimental. It hands the script to Vela's Pine engine; some scripts paint nothing. Run PineTS is the reliable path.
  • The bridge is not a headless renderer. trader-chart and the MCP tools only take effect while a chart view is mounted (the Hermes pane or a browser tab); with none attached they fail fast with "no chart view attached" rather than hanging. Commands are pushed, so a view answers in tens of ms.
  • One console per machine on one port. No real orders and no brokerage account exist: the only trading is a simulated Binance spot paper account (broker_*), where the agent can only propose and you approve. The account and the journal its fills keep (win rate, profit factor, the realised curve) are one surface — ⋯ → Trader's desk.
  • The Library is non-commercial. Its content is CC BY-NC-SA 4.0 — fine to read and cite here, not to resell or ship inside a paid product.
  • Linux and macOS. Built and verified on Omarchy (Arch + Hyprland) and on a Mac mini with Hermes Desktop (macOS; setup in docs/MACOS.md — a repo venv plus console/service-macos.sh, the launchd counterpart of the systemd unit above). Everything here is stdlib Python and static JS, so nothing else is platform-specific. Windows is out of scope.

Development

What belongs in a commit: design changes and system upgrades that make the plugin better for anyone who installs it — engines, data sources, the agent surface, the sheet, the docs that describe them. Personal material stays out: a strategy you trade, a study of someone else's app, scratch data, experiments that did not become a feature. If a change only makes sense with your own account, your own pair or your own opinions in it, it is not a plugin change.

node tools/verify-plugin.mjs traders-desk/desktop/plugin.js                                      # plugin harness (same check CI runs)
python3 -m unittest discover -s console/backend/tests -t console/backend/tests -v   # the unit suite (no dependencies)
uv run --with fastmcp python -m unittest discover \
    -s console/backend/tests -t console/backend/tests -p 'test_mcp_server.py' -v     # the MCP tool layer
TRADERS_EDGE_HOME=/path/with/engine python3 -m unittest discover -s console/backend/tests -t console/backend/tests -p 'test_edgestats_real_engine.py' -v   # against the real engine (skips without one)
python3 console/backend/server.py --port 8899 --no-mcp                             # console without the MCP backend
python3 -m compileall console/backend                                              # syntax pass

The suite is stdlib-only on purpose — the study store, the chat bridge, the chart bridge and the push channel are covered without a browser, a network, or the app — and the MCP tool layer runs against a stub console. about 1,130 tests; the MCP ones skip themselves when fastmcp is absent (CI sets TRADER_CHART_REQUIRE_MCP=1 so they cannot silently skip there).

CI (.github/workflows/ci.yml) has three jobs: the plugin harness (Node 20 — the real plugin first, then the crafted-copy battery verify-plugin-selftest.mjs), the backend (compile, boot and a /api/health smoke test on Python 3.11), and the unit suite on Python 3.11 and 3.14 (the MCP step installs fastmcp). Workflow when editing: change the repo, run ./install.sh to deploy; the app re-registers the plugin about 3 seconds after plugin.js changes. Console-only changes need the frame remounted (switch session and back).

Troubleshooting

  • The sidebar row does nothing / the pane shows an old layout. The frame keeps the copy it loaded. Switch to another session and back, or restart the app.
  • start.sh: 'python3' has no 'mcp' client. pip install mcp, or PY=/path/to/venv/bin/python ./console/start.sh.
  • The plugin never appears in Capabilities → Plugins. Ctrl+K → Reload desktop plugins; if it still does not show up, the folder was dropped after a failed load — rename ~/.hermes/desktop-plugins/traders-desk and set id: inside plugin.js to the same new name.
  • The chart is empty. Binance's data feed was unreachable (chart_state says bars None); the console does not invent bars. Check the network, then chart_reload.
  • Port 8787 busy. PORT=9000 ./console/start.sh, then PORT=9000 ./install.sh (it points the installed plugin at that port) and set LUXALGO_CONSOLE=http://127.0.0.1:9000 for the MCP server and CLI.

Licence & credits

This project's own code is MIT (that is what the licence badge means — the combined distribution is AGPL where it redistributes PineTS; see THIRD-PARTY.md) — see LICENSE. It builds on LuxAlgo's work: Vela (Apache-2.0, with its own attribution requirement — the ▲ mark on the chart stays), vela-pinets and pinets (AGPL-3.0; pinets is vendored here as a patched build whose patches are published on the fork, and vela-pinets is vendored unmodified), the LuxAlgo MCP server (MIT, @luxalgo/mcp) and the LuxAlgo Library (free with attribution — Source: LuxAlgo Library — luxalgo.com/library/…). Full details, quotes and links: THIRD-PARTY.md.

Charts are not financial advice; the Library is an encyclopedia, not a signal service.

Known Hermes Desktop interaction: a collapsed pane can stay hidden

A pane contributed with defaultCollapsed: true is adopted into a layout group carrying "minimized": true (persisted under hermes.desktop.layoutTree.v2). On Hermes Desktop 0.17.0 revealPane() — the documented call for an explicit user action — reveals the pane and its zone but does not clear that group flag, so after a layout reset the sidebar row can land with the pane still hidden: the route page then renders the console itself in the main zone instead of docking the chart beside the chat.

Measured 19 Sep 2026 on Omarchy + Hermes Desktop 0.17.0: after Layouts -> Reset, the row's revealPane call left {"id":"g-...","panes":["traders-desk:chart"],"minimized":true} in the store and the chart opened as the main page. Clearing the flag and reloading the window docks the pane beside the chat as intended.

The plugin handles it as far as it can: the page asks for adoption and reveal, retries once a beat later (the app may still be rebuilding the tree when a page mounts), and when the pane is still not visible it renders the console itself under a short note that says why — with Ask again in reach. The note only appears when the pane API exists and reports the pane hidden, so a build without panes gets the plain console and no nagging.

Recovery for a user who hits it: un-minimize the pane's group in hermes.desktop.layoutTree.v2 (DevTools / a CDP session on --remote-debugging-port) or pick a layout template that re-adopts contributed panes, then reload the window. The plugin asks for the documented reveal first and cannot clear the flag itself — the renderer's layout atoms own that state.

↑↓ navigate openesc close