Skip to main content

Backlight

Automated stock and options trading platform: pipelines of parameterized nodes pick securities, decide how to trade them through tagged decision trees, refine those decisions, and hand orders to a connector. One runner drives the same pipeline in backtest, paper, or live mode. Built on the same stack as proxer: pywebview + FastAPI + Vue 3 (Vuetify).

Quick start

pip install py-backlight     # or, from a checkout: make install (pip install -e .[dev] + build the UI)
backlight hub                # opens the desktop window (or: backlight hub --headless)

The PyPI distribution is py-backlight (backlight there is an old, empty project); the package, the import and the command are all backlight. Extras: py-backlight[agent] (the pipeline agent) and py-backlight[databento]. make check builds the wheel with the UI bundled and runs twine check; make deploy uploads it.

Upgrading from trader, the old name: the first run moves ~/.config/trader, ~/.trader (and trader.db in it) and ~/.cache/trader to their backlight names, TRADER_* environment variables still work where no BACKLIGHT_* one is set, and agent nodes are rewritten to import backlight.

Without the window, the API is at http://127.0.0.1:8770/docs.

Deployed on a cluster, headless, behind a Cloudflare tunnel and with sign-in on: see docs/deploy.md (Dockerfile, Helm chart in charts/backlight). Sign-in is optional — off on the desktop unless you add a user with backlight auth add-user NAME.

From the command line:

backlight catalog                                   # node types and connectors
backlight backtest preset-sma-cross --symbols SPY,QQQ --start 2022-01-01 --end 2023-12-31
backlight runs --mode backtest
backlight report <run_id>                           # markdown run report
backlight report <run_id_a> <run_id_b>              # compare
backlight purge --older-than-days 30 --vacuum       # delete old backtest runs
backlight fetch --provider massive --symbols SPY --start 2020-01-01 --end 2024-12-31
backlight flatfiles --start 2024-01-01 --end 2024-12-31   # Massive option minute files, once
backlight backtest preset-opt-0dte-condor --symbols SPY --provider alpaca --option-provider massive_files

The first launch seeds fifteen preset pipelines (stock, options, and two branching examples) and four decision trees into ~/.config/backlight/, and the synthetic data connector needs no network, so the Backtest Lab works out of the box.

Layout

Path What
src/backlight/types.py The objects that cross node ports: SecurityId, Bar, Quote, OptionContract, Universe, Signal, Intent, OrderRequest, OrderEvent, Position.
src/backlight/pipeline/ params.py (parameter schemas, from proxer), nodes/ (trigger, universe, selector, strategy, refiner, sizer, execution, output, flow), tree/ (decision tree model, safe expression language, action templates), graph/ (pipeline docs, validation, compile, catalog, plugins, stores, presets), engine.py (the tick).
src/backlight/connectors/ base.py (the three interfaces), sim (fill model, brackets, option expiry), synthetic (offline random-walk bars, Black-Scholes chains, paper-mode market data), csv, schwab, alpaca, massive, massive_files (option flat files over S3), kalshi (plan only).
src/backlight/runner/ portfolio.py (ledger), feed.py (bar views for nodes and the sim), backtest.py, live.py (paper and live), recorder.py.
src/backlight/db/ SQLite schema (every run-scoped table cascades from runs, which carries a mode column), migrations, run repo with purge, batched record writer, report queries and metrics.
src/backlight/hub/ FastAPI routers (api/), background jobs, WebSocket event bus, hub config, secrets, pywebview shell.
gui/ Vue 3 + Vuetify + VueFlow UI: Dashboard, Pipelines workbench, Decision Trees, Backtest Lab, Runs, Live, Data, Settings (with the Connectors tab), and an in-app illustrated guide (gui/public/guide.html).
charts/backlight/, Dockerfile Container image and Helm chart: one hub pod on a PVC, optional sign-in, optional cloudflared tunnel.
docs/ The proposal, architecture notes, connector setup, Kalshi plan.

How a tick works

trigger -> universe -> selector -> strategy (decision tree) -> refiners -> sizer -> execution -> final

Every node has PARAMETERS rendered by the GUI, typed ports checked at compile time, and tags. A strategy emits Signals carrying the DecisionPath that produced them; refiners veto or reshape and leave a note; the sizer turns the signal into an Intent; execution builds OrderRequests (with bracket children); output.final applies buying power and per-underlying caps. Every hop is recorded, so a fill walks back to the tree leaf and selector rule that caused it, and reports attribute P&L per tag, per leaf, and per tree edge.

Modes

Mode Clock Data Execution
backtest bar iterator historical connector + Parquet cache sim
paper wall clock market connector sim or a venue's paper account
live wall clock market connector venue (Schwab)

Backtest runs can be purged from the Runs view or with backlight purge; paper and live runs are kept unless purged by mode explicitly.

Writing a node

from backlight.pipeline.nodes.base import RefinerNode, register_node
from backlight.pipeline.params import Parameter

@register_node
class NoFridays(RefinerNode):
    """Veto new entries on Fridays."""
    kind = "refiner.no-fridays"
    label = "No Fridays"
    PARAMETERS = [Parameter("enabled_days", "number", "Days", default=4, min=0, max=6, step=1)]

    def applies(self, signal, ctx):
        return signal.action.value.startswith("open")

    def apply(self, signal, ctx):
        return self.veto("friday") if ctx.now.weekday() == 4 else signal

The docstring is the node's documentation in the workbench: its first paragraph is the palette description, the whole of it shows under More. PORT_DOCS = {"port": "..."} says what a port means on this node. Text is plain: blank lines split paragraphs, - starts a list item, backticks mark code.

Port types (run, universe, signal, intent, order) live in a registry in backlight.pipeline.ports; clicking a port in the workbench opens its documentation. A plugin registers its own from the same module:

from backlight.pipeline.ports import PortType, register_port_type

register_port_type(PortType(
    "alert", "Alert", "A message for the operator.",
    doc="Longer explanation for the port-type dialog.",
    carries=Alert,                       # a dataclass: its fields are listed
    field_docs={"text": "What to say."}, color="#e11d48"))

The engine only moves values along the ports each node category handles, so a plugin type is documented and wired in the editor, but nothing produces or consumes it yet.

Drop the file in ~/.config/backlight/plugins/nodes/ or register it under the backlight.nodes entry-point group. Connectors work the same way under plugins/connectors/ and the backlight.connectors group.

Pipeline agent

In the pipeline editor, Agent opens a chat that builds and edits the open pipeline: it reads the node catalog and other pipelines, adds and wires nodes, sets parameters and validates, and the canvas follows each edit (nothing is saved until you press Save). Chats are per user and kept per pipeline.

When no node fits, the agent can write one. A written node loads only after: a static check (allowed imports and calls, no private attributes or routes to connectors, never an execution or output node), an import test in a separate process with an empty environment, and approval by a reviewer agent. Approved nodes live in ~/.config/backlight/plugins/agent-nodes/ and are listed (with their code and review) under Admin → Settings → Agent, where an admin can remove them.

Setup: pip install -e ".[agent]" (the Docker image includes it), then Admin → Settings → Agent: provider (OpenAI or Anthropic), its API key, model, Enable. OpenAI also covers any OpenAI-compatible endpoint through the base URL. Anthropic is spoken natively (Claude's thinking is kept across turns, an effort setting, prompt caching, and on Anthropic's own API a declined request is retried on Anthropic's recommended fallback model). One hub-wide key per provider; every signed-in user's chats use the active one.

The tools (backlight.agent.tools) are plain name + JSON schema + handler over a server-side working pipeline, so another front end (an MCP server) can expose the same set.

Paths

What Where Override
pipelines, trees, hub config, secrets, plugins ~/.config/backlight/ BACKLIGHT_CONFIG_DIR
SQLite database ~/.backlight/backlight.db BACKLIGHT_DATA_DIR, BACKLIGHT_DB
Parquet bar cache, logs ~/.cache/backlight/ BACKLIGHT_CACHE_DIR

Moving a cache to another instance

A provider's cached bars, option blocks and downloaded store (Massive flat files, Databento's dataset) export to one tar and import anywhere, from the Data tab (Export / import cache) or the CLI:

backlight cache list                                          # what each provider holds
backlight cache export --provider massive --provider massive_files -o massive.tar
backlight cache export --provider massive_files --timeframe 1d -o day.tar   # only some timeframes
backlight cache import massive.tar                            # on the other instance
ssh a backlight cache export --provider massive -o - | backlight cache import -   # or piped

An import never deletes. Files already there are kept unless the archive's copy is newer (--mode newer, the default; bar files merge instead), or always replaced (--mode replace), or always kept (--mode skip). Store files land in the importing instance's own data_dir for that connector. The Data tab uploads in 32 MB pieces, so a proxy's body limit (Cloudflare: 100 MB) doesn't get in the way.

Development

make test      # pytest
make smoke     # every preset through a two-year synthetic backtest
make gui       # rebuild the UI after editing gui/src
BACKLIGHT_HUB_DEBUG=1 backlight hub   # WebKit inspector in the window

Metadata

Release files for py-backlight 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-backlight 0.1.0
File Size Uploaded
py_backlight-0.1.0.tar.gz 3.3 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-backlight 0.1.0
File Interpreter ABI Platform
py_backlight-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 6.7 MB

Release files / py_backlight-0.1.0.tar.gz

Download URL py_backlight-0.1.0.tar.gz
Size 3.3 MB
Tags Source
SHA-256 checksum
How to use checksums
ea02a63e9a8d945f244e7051c88673ab17a4bd976a9e24516f1bfc512867938f
BLAKE2b-256 checksum
How to use checksums
0e5af1ae3ef18734f4f547171d9d005d571dd896cca5fec51f37405eb3122454
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / py_backlight-0.1.0-py3-none-any.whl

Download URL py_backlight-0.1.0-py3-none-any.whl
Size 3.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0447ebf9db54e4324004425a0a65a97fedcaf6f365e2a16ff5ff70f3be213344
BLAKE2b-256 checksum
How to use checksums
35abdc33870ae1b3b32611a5dad9c430d9503ae9f0ff85db81dcda42d843a00f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page