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:
TRADELAB_ALLOW_LIVE=trueconfirm_live=True- a connected, credentialed non-paper broker adapter
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f55436caa55e4a011c2bc62a161d9ef0b8b2f31579a68b850f142a2b841a6bd
|
|
| MD5 |
839535acac4f32d1a94c15523b8e0c54
|
|
| BLAKE2b-256 |
7fb68cb20bafa0cc3e968d7b45ae23a293d53fa1b3dca267bdfd828ba69c44ba
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4270b7e38ff61a57710bbf9b6e20b98cc7660d43f2ad9abe30639d8068877ced
|
|
| MD5 |
a25806a586ef96d8eb102f1a42e4fe97
|
|
| BLAKE2b-256 |
09d58b3c04a04e92120b8470f4342c236c50c01802ceaee9f3beb09861a7688e
|