Metadata-Version: 2.4
Name: tradelab-python
Version: 1.3.1
Summary: Agent-native Python trading engine for research, backtesting, and live execution
Project-URL: Homepage, https://github.com/ishsharm0/tradelab-python
Project-URL: Repository, https://github.com/ishsharm0/tradelab-python
Project-URL: Issues, https://github.com/ishsharm0/tradelab-python/issues
Author: Ish Sharma
License-Expression: MIT
License-File: LICENSE
Keywords: algorithmic-trading,backtesting,mcp,quantitative-finance,trading
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: cryptography>=44
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.27
Requires-Dist: jinja2>=3.1
Requires-Dist: mcp>=1.9
Requires-Dist: pyjwt>=2.10
Requires-Dist: rich>=13.9
Requires-Dist: typer>=0.15
Requires-Dist: tzdata>=2025.2
Requires-Dist: uvicorn>=0.34
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: hypothesis>=6.130; extra == 'dev'
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.11; extra == 'dev'
Requires-Dist: twine>=6.1; extra == 'dev'
Provides-Extra: ib
Requires-Dist: ib-insync>=0.9.86; extra == 'ib'
Description-Content-Type: text/markdown

# 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`.

```bash
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

```python
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:

```python
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

```python
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

```python
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

```python
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

```bash
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:

```json
{
  "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:

```bash
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:

```bash
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:

```python
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

```json
{
  "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

```bash
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](https://github.com/ishsharm0/tradelab-python/blob/main/docs/architecture.md),
[API map](https://github.com/ishsharm0/tradelab-python/blob/main/docs/api.md),
[live safety](https://github.com/ishsharm0/tradelab-python/blob/main/docs/safety.md), and
[MCP tools](https://github.com/ishsharm0/tradelab-python/blob/main/docs/mcp.md).

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
