Skip to main content

Agent-native Python trading engine for research, backtesting, and live execution

Project description

TradeLab for Python

TradeLab is an agent-native trading toolkit for research, deterministic backtesting, paper trading, and permission-gated live execution.

The distribution is named tradelab-python; the import package and command are both tradelab.

pip install tradelab-python

Python 3.11 or newer is required.

Why TradeLab

  • One signal contract across bar backtests, async signals, tick replay, portfolios, walk-forward validation, paper sessions, and live adapters.
  • Deterministic execution with explicit slippage, spreads, commissions, carry, funding, risk sizing, stops, targets, partial exits, and daily circuit breakers.
  • Yahoo and CSV ingestion with normalization, chunking, retry control, throttling, and atomic local caches.
  • Research statistics for Monte Carlo analysis, Deflated Sharpe, PBO, CPCV, and persistent hypothesis logs.
  • Self-contained JSON, CSV, Markdown, and offline HTML reports.
  • A 25-tool MCP server for research and paper-session control, with an injectable, fail-closed live broker boundary.
  • Strict typing, finite JSON outputs, and no implicit live-trading permission.

Quick start

import asyncio

from tradelab import backtest, get_historical_candles
from tradelab.reporting import export_backtest_artifacts
from tradelab.strategies import get_strategy


async def main() -> None:
    candles = await get_historical_candles(
        source="yahoo",
        symbol="SPY",
        interval="1d",
        period="2y",
        cache=True,
    )
    signal = get_strategy("ema-cross")({"fast": 10, "slow": 30, "rr": 2})
    result = backtest(
        candles=candles,
        symbol="SPY",
        interval="1d",
        equity=10_000,
        risk_pct=1,
        warmup_bars=50,
        costs={"slippageBps": 1, "commissionBps": 0.5},
        signal=signal,
    )
    print(result["metrics"])
    export_backtest_artifacts(result, out_dir="output")


asyncio.run(main())

BacktestResult behaves like an immutable mapping and can be converted to an independent plain dictionary with result.to_dict().

Signal contract

A strategy receives a context mapping and returns None or an order intent:

def signal(context: dict[str, object]) -> dict[str, object] | None:
    bar = context["bar"]
    if context["openPosition"] is not None:
        return None
    close = float(bar["close"])
    return {"side": "long", "entry": close, "stop": close * 0.97, "rr": 2}

Common fields are side, entry, stop, takeProfit, rr, qty, size, riskPct, and riskFraction. Snake-case Python options are accepted by the Python API; canonical camel-case result payloads remain compatible with the original TradeLab contracts.

Data

from tradelab.data import get_historical_candles, load_candles_from_csv

yahoo = await get_historical_candles(
    source="yahoo", symbol="QQQ", interval="1d", period="1y"
)
csv_rows = load_candles_from_csv("data/btc.csv")

All candles normalize to time, open, high, low, close, and volume. Cache writes use atomic replacement and strict JSON. The Yahoo client accepts an injected httpx.AsyncClient, clock, and sleeper for deterministic integration tests.

Validation and portfolios

from tradelab import backtest_portfolio, grid, walk_forward_optimize
from tradelab.strategies import get_strategy

factory = get_strategy("ema-cross")
walk_forward = walk_forward_optimize(
    candles=yahoo,
    train_bars=180,
    test_bars=60,
    parameter_sets=grid({"fast": [8, 10], "slow": [21, 30], "rr": [1.5, 2]}),
    signal_factory=lambda params: factory(params),
)

portfolio = backtest_portfolio(
    equity=100_000,
    max_daily_loss_pct=3,
    systems=[
        {"symbol": "SPY", "candles": spy, "signal": spy_signal, "weight": 2},
        {"symbol": "QQQ", "candles": qqq, "signal": qqq_signal, "weight": 1},
    ],
)

Portfolio equity points expose lockedCapital and availableCapital. Daily loss controls reset at actual New York midnight, including DST boundaries.

Paper and live sessions

from tradelab.live import SessionManager

manager = SessionManager()
session = await manager.create(id="paper-spy", symbol="SPY", mode="paper", equity=25_000)
await session.push_bar(
    {"time": 1_735_828_200_000, "open": 100, "high": 101, "low": 99, "close": 100}
)
await session.place_order(side="long", risk_pct=1, stop=98, target=104)
print(session.get_status())
await manager.halt_all()

Paper mode needs no credentials. Live mode requires all four conditions:

  1. TRADELAB_ALLOW_LIVE=true
  2. confirm_live=True
  3. a connected, credentialed non-paper broker adapter
  4. genuine streamed order updates for fills, cancellations, and reconnects

The process-level halt_all() operation flattens and stops every managed session.

Command line

tradelab backtest --source yahoo --symbol SPY --interval 1d --period 1y
tradelab backtest --source csv --csv-path ./data/spy.csv --strategy buy-hold
tradelab walk-forward --source yahoo --symbol QQQ --period 2y
tradelab run ema-cross --source yahoo --symbol SPY --params '{"fast": 8}'
tradelab prefetch --symbol SPY --interval 1d --period 1y
tradelab import-csv ./data/spy.csv --symbol SPY --interval 1d
tradelab paper --csv-path ./data/spy.csv --symbol SPY --strategy ema-cross
tradelab paper --config ./paper-portfolio.json --dashboard --dashboard-port 4317 --watch
tradelab status --state-dir ./output/live-state

paper --config and live --config run several systems through one LiveOrchestrator. Strategy and CSV paths in the config are resolved relative to the config file; registered strategy names work too. Both snake-case Python options and the documented camel-case JSON aliases are accepted for engine settings:

{
  "allocation": "weighted",
  "equity": 50000,
  "maxDailyLossPct": 3,
  "systems": [
    {
      "id": "spy",
      "symbol": "SPY",
      "interval": "1m",
      "strategy": "ema-cross",
      "params": {"fast": 8, "slow": 21},
      "weight": 2,
      "csvPath": "./data/spy.csv"
    },
    {
      "id": "qqq",
      "symbol": "QQQ",
      "strategy": "./strategies/qqq.py",
      "weight": 1
    }
  ]
}

Use at most one configured system per symbol. Broker order events are symbol-scoped, so the CLI rejects duplicate symbols instead of allowing one strategy's fills to corrupt another's state. csvPath is paper-only; live configs reject historical replay sources.

The optional dashboard binds to loopback and closes with its engine or orchestrator. --watch runs until interruption; cleanup closes the dashboard before stopping every runtime. Live trading requires --watch so a successful command cannot submit an order and immediately disconnect. On live shutdown the CLI cancels identified pending entry orders and requests position flattening before disconnect. The environment opt-in, explicit confirmation, credentials, and certified streaming-order-update gates still apply, so bundled REST-only adapters continue to fail closed.

The CLI prints the ephemeral dashboard command token to stderr beside the URL. Supply it on every dashboard request without mixing it into JSON stdout, for example:

curl -H "X-Tradelab-Token: $TOKEN" http://127.0.0.1:4317/state

For example, this bundled-adapter live command is intentionally refused until its adapter provides certified authenticated streaming order updates:

TRADELAB_ALLOW_LIVE=true tradelab live \
  --broker alpaca --config ./live-portfolio.json \
  --confirm-live --watch --dashboard

--strategy accepts either a registered name or a local Python file. A strategy file defines signal(context) directly, or create_signal(params) returning that callable:

def signal(context):
    bar = context["bar"]
    return {"side": "long", "qty": 1, "stop": bar["close"] * 0.98, "rr": 2}

Run it with tradelab backtest --source csv --csv-path bars.csv --strategy ./strategy.py.

MCP server

{
  "mcpServers": {
    "tradelab": {
      "command": "tradelab-mcp"
    }
  }
}

The server exposes research, robustness, persistent research-session, and paper-session tools over stdio. The default stdio graph intentionally has no live broker factory. Applications that inject a certified broker factory through create_server(dependencies=...) remain subject to the same environment, confirmation, credential, and streamed-order-update gates as the Python API. The bundled REST-only adapters fail closed for managed live protection until they gain authenticated reconnecting order streams.

Development

uv sync --all-extras
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy --strict src examples scripts
uv build
uv run twine check dist/*

Additional references: architecture, API map, live safety, and MCP tools.

The original JavaScript repository is used as an immutable parity oracle. Python adds stricter finite-number, path-containment, atomic-write, concurrency, and timezone safety where preserving a JavaScript defect would be dangerous.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tradelab_python-1.3.1.tar.gz (360.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tradelab_python-1.3.1-py3-none-any.whl (191.9 kB view details)

Uploaded Python 3

File details

Details for the file tradelab_python-1.3.1.tar.gz.

File metadata

  • Download URL: tradelab_python-1.3.1.tar.gz
  • Upload date:
  • Size: 360.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for tradelab_python-1.3.1.tar.gz
Algorithm Hash digest
SHA256 2f55436caa55e4a011c2bc62a161d9ef0b8b2f31579a68b850f142a2b841a6bd
MD5 839535acac4f32d1a94c15523b8e0c54
BLAKE2b-256 7fb68cb20bafa0cc3e968d7b45ae23a293d53fa1b3dca267bdfd828ba69c44ba

See more details on using hashes here.

File details

Details for the file tradelab_python-1.3.1-py3-none-any.whl.

File metadata

  • Download URL: tradelab_python-1.3.1-py3-none-any.whl
  • Upload date:
  • Size: 191.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for tradelab_python-1.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4270b7e38ff61a57710bbf9b6e20b98cc7660d43f2ad9abe30639d8068877ced
MD5 a25806a586ef96d8eb102f1a42e4fe97
BLAKE2b-256 09d58b3c04a04e92120b8470f4342c236c50c01802ceaee9f3beb09861a7688e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page