Trading infrastructure library with backtesting
Project description
tradedesk
event-driven trading framework for building, running, and evaluating systematic trading strategies across both backtesting and live broker environments.
Tradedesk provides:
- Event-based strategy execution
- Unified backtest and live broker runtime model
- Market data aggregation and indicator framework
- Portfolio orchestration and risk management
- Trade recording, metrics, and reporting
The framework is designed so that strategies react to events --- not broker implementations --- enabling the same strategy code to run unchanged in both backtest and live environments.
Core Concepts
Event-Driven Architecture
All major subsystems communicate via events:
- Market data events (ticks, candles)
- Strategy events (signals)
- Execution events (order completions and broker fills)
- Portfolio events (position updates and lifecycle transitions)
- Recording events (trade lifecycle, equity, and reporting)
As a user, you primarily:
- Implement a strategy that reacts to candle updates
- Optionally subscribe to events for custom analytics or logging
Architecture Overview
For a concise public map of the system, see ARCHITECTURE.md. tradedesk is built around an event-driven core that wires together market data, strategy logic, portfolio management, execution adapters (IG for live trading, Dukascopy-backed backtests), and a recording layer for metrics and reports. The public interfaces expose reusable building blocks (marketdata, strategy, portfolio, recording) with clear data-flow guarantees across backtest and live paths.
Basic Strategy Structure
A strategy derives from the base strategy class and implements candle handling logic.
Typical flow:
- Market data arrives (tick or candle)
- Aggregation produces candles
- Strategy receives
on_candle_close - Strategy emits order requests
- Execution layer processes orders
- Portfolio updates positions
- Recording captures trade lifecycle
Running a Backtest
Backtesting uses the same event model as live trading.
High-level flow:
- Dukascopy cache data is loaded via
BacktestClient.from_dukascopy_cache(...) run_backtest(...)drives the event loop and recording pipeline- Strategy code executes unchanged
- Portfolio and recording operate identically to live mode
See docs/backtesting_guide.md for the current cache-backed workflow.
Live Trading (IG)
The IG execution module provides:
- REST client for order management
- Streaming price integration
- Position synchronization
- Retry and resilience handling
Your strategy remains unchanged --- only the execution configuration differs.
Orders placed through request_order(...) continue to flow through
OrderExecutionHandler in both backtest and live sessions. For clients such as
IG that do not publish their own position-open callbacks, tradedesk emits a
PositionOpenedEvent immediately after a confirmed opening fill. That keeps
recording subscribers and custom event consumers aligned across backtest,
DEMO, and LIVE runs without double-publishing for clients that already emit
their own lifecycle events.
IG Credentials
Live IG runs read credentials from environment variables:
IG_API_KEY(required)IG_USERNAME(required)IG_PASSWORD(required)IG_ENVIRONMENT(optional, defaults toDEMO, valid values areDEMOandLIVE)IG_ACCOUNT_ID(required for strategies that construct tick-levelMarketSubscriptionitems)
Example:
IG_API_KEY=... \
IG_USERNAME=... \
IG_PASSWORD=... \
IG_ENVIRONMENT=DEMO \
IG_ACCOUNT_ID=... \
python your_live_runner.py
tradedesk authenticates with IG and captures the short-lived session headers
(CST and X-SECURITY-TOKEN) from the login response automatically. You do not
configure those session tokens yourself.
Strategies that subscribe to tick-level MarketSubscription updates on IG also
need to include the IG account identifier in each subscription item name. In
practice that usually means reading an IG_ACCOUNT_ID environment variable in
your strategy code and passing it as account_id=... when you construct each
MarketSubscription.
When live sessions ask IG for historical candles, IG enforces a separate
account-level historical-data allowance. tradedesk treats that 403 response as
a distinct failure mode instead of retrying it as an authentication problem, so
embedding runtimes can back off or warn explicitly when warmup/history fetches
run out of quota.
IG spread gate and scalingFactor
When OrderExecutionHandler is configured with a spread limit, it checks the
current spread from the IG market snapshot before submitting each order. IG
returns bid and offer in broker-scaled units (for example, EURUSD bid≈11715.5
instead of 1.17155), so tradedesk divides by the instrument.scalingFactor
from the snapshot before computing the spread. Non-FX instruments (indices,
gold) use scalingFactor=1, leaving their prices unchanged. If you configure
max_spread thresholds, set them in the decimal price units your strategy uses
— the normalization is transparent.
Portfolio & Risk
The portfolio subsystem:
- Tracks positions
- Applies risk policies
- Reconciles fills
- Emits portfolio events
Risk controls such as spread limits and portfolio-level order gates can reject orders before broker submission.
Recording & Reporting
The recording subsystem:
- Tracks trades and equity curves
- Computes excursions and performance metrics
- Generates structured reports from position lifecycle events and fills
Users can subscribe to recording events for custom reporting pipelines.
Typical Project Structure
my_strategy/
strategy.py
run_backtest.py
config.py
Installation
Python 3.11+ is required.
Install the published package:
pip install tradedesk
To enable the optional machine-learning building blocks in tradedesk.ml
(feature engineering, walk-forward CV, XGBoost direction classifier — see
the Phase 6 sprint), install the [ml] extra:
pip install 'tradedesk[ml]'
For local development:
pip install -e '.[dev]'
Machine Learning (tradedesk.ml)
The tradedesk.ml subpackage hosts the building blocks for ML-driven
strategies. ML extras (xgboost, scikit-learn, joblib) install via the
optional [ml] extra:
pip install 'tradedesk[ml]'
Phase 6 ships:
-
FeatureBuilder(tradedesk.ml.features) — feature engineering for 1-minute OHLC(V) bid/ask bars. Built-in feature families:- Lagged log returns over a fan of horizons (1, 5, 15, 60, 240 bars).
- Rolling realised volatility, skew and kurtosis of 1-min log returns.
- Time-of-day (cyclical sin/cos) and weekday.
- Outputs from a configurable indicator stack — by default the full
tradedesk.marketdata.indicatorsset (ADX, ATR, Bollinger Bands, CCI, EMA, Keltner Channel, MACD, MFI, OBV, RSI, SMA, Stochastic, VWAP, Williams %R) driven by the same streaming indicator classes used in live trading, so backtest and live features are bit-identical. - Microstructure ratios: body/range, upper/lower wick, plus bid/ask spread when those columns are present.
Strict no-look-ahead — every column at bar
tdepends only on data up to and includingt. Forward-return labels live intradedesk.ml.labelsand the walk-forward splitter intradedesk.ml.cvenforces the embargo/purge that guards against label leakage at fold boundaries.
from tradedesk.ml import FeatureBuilder, FeatureConfig
builder = FeatureBuilder() # default stack + default config
features = builder.transform(bars) # bars: DatetimeIndex, OHLC(V) [+ bid/ask]
Pass indicators={...} to swap or shrink the indicator stack and
config=FeatureConfig(...) to tune the rolling-window fan or toggle
optional feature families.
Phase 6 quickstart — features → labels → model → walk-forward CV
The full Phase 6 stack lives behind tradedesk[ml] and is exposed via
tradedesk.ml and tradedesk.strategy.MLDirectionStrategy. The
end-to-end workflow:
import pandas as pd
from tradedesk.ml import (
FeatureBuilder, FeatureConfig,
LabelConfig, forward_return_labels,
WalkForwardConfig, WalkForwardSplitter, walk_forward_evaluate,
)
from tradedesk.ml.model import DirectionClassifier, DirectionClassifierConfig
# 1. Features --------------------------------------------------------------
builder = FeatureBuilder(config=FeatureConfig())
X = builder.transform(bars) # bars indexed by UTC DatetimeIndex
# 2. Forward-return labels (binary up/down) --------------------------------
y_raw = forward_return_labels(bars, LabelConfig(horizon=15)).reindex(X.index)
valid = y_raw.notna()
X = X.loc[valid]
y = (y_raw.loc[valid] > 0).astype(int); y.index = X.index
# 3. Walk-forward CV with embargo/purge ------------------------------------
splitter = WalkForwardSplitter(
WalkForwardConfig(train_window=200_000, test_window=50_000, embargo=15, purge=15)
)
def make_clf() -> DirectionClassifier:
return DirectionClassifier(DirectionClassifierConfig(n_estimators=200, n_jobs=4))
metrics = walk_forward_evaluate(X, y, splitter, make_clf)
print(metrics[["fold", "accuracy", "auc", "sharpe", "trade_count"]])
# 4. Train + persist a final model -----------------------------------------
model = DirectionClassifier(DirectionClassifierConfig()).fit(X, y)
model.save("artefacts/direction_eurusd.joblib")
A runnable Phase 6 walk-forward driver sits at
docs/examples/phase6_walk_forward_eurusd.py. The 8-yr Dukascopy
bid/ask cache lives at /paperclip/tradedesk/marketdata.
Leakage gate (CI merge-blocker)
Look-ahead bugs are silent killers; Phase 6 guards against them with two independent gates:
-
tradedesk.ml.cvenforces an embargo + purge at every fold boundary. The defaultembargo=horizon, purge=horizonmatches the forward-label horizon so test rows can never overlap with train labels. -
tests/ml/test_cv.pycarries the leakage canary — a synthetic feature column whose value equals the next bar's return. With the embargo/purge in place the canary must produce <0.55 OOS accuracy / AUC; without it the canary scores ~1.0. CI promotes this canary to a separate top-level step:- name: Phase 6 leakage gate (tradedesk/ml/) run: pytest -m leakage --no-cov
Any code change that breaks the embargo/purge contract trips the gate and the build fails. Add new feature columns? Re-run
pytest -m leakagelocally and confirm the canary still scores at chance level.
Walk-forward reporting & feature importance
tradedesk.ml.reporting renders a markdown report (per-fold metrics,
feature-importance gain, leakage sanity panel, equity curve) from a
walk_forward_collect run. See scripts/render_sample_report.py for a
reproducible synthetic example.
Streaming integration
tradedesk.strategy.MLDirectionStrategy plugs a trained
predict_proba model into the live event loop: rolling 1-min history
buffer → FeatureBuilder.transform → probability → Signal →
SignalGeneratedEvent. The [ig_trader] repository hosts a worked
example wired into the existing portfolio CLI.
Documentation
See the docs/ directory for:
- Backtesting guide
- Strategy guide
- Portfolio guide
- Indicator guide
- Aggregation guide
- Risk management guide
- Metrics guide
- Settings and operational tunables
- Operational resilience and monitoring
- ML label engineering (
docs/ml_labels_guide.md)
Public package entry points are grouped under:
tradedesk.marketdatatradedesk.executiontradedesk.execution.backtesttradedesk.portfoliotradedesk.recordingtradedesk.strategy
tradedesk is designed for clarity, determinism, and event-level transparency.
See Also
- docs/backtesting_guide.md
- docs/strategy_guide.md
- docs/indicator_guide.md
- docs/crash-recovery.md
Contributing
See CONTRIBUTING.md for guidelines on contributing to tradedesk.
License
Licensed under the Apache License, Version 2.0. See: https://www.apache.org/licenses/LICENSE-2.0
Copyright 2026 Radius Red Ltd. | Contact
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 tradedesk-1.2.0.tar.gz.
File metadata
- Download URL: tradedesk-1.2.0.tar.gz
- Upload date:
- Size: 742.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
226432c11bf67da1d4e6ad57bcbc076c985a1903f607ee4fd72d2660b11b2d2d
|
|
| MD5 |
29bb84568dde448c0af61851ec2efa05
|
|
| BLAKE2b-256 |
959b1c7d1c4415ba9976ac30ef68b2880800bf9a8b33b2f438135f325aff6faa
|
Provenance
The following attestation bundles were made for tradedesk-1.2.0.tar.gz:
Publisher:
publish.yml on radiusred/tradedesk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tradedesk-1.2.0.tar.gz -
Subject digest:
226432c11bf67da1d4e6ad57bcbc076c985a1903f607ee4fd72d2660b11b2d2d - Sigstore transparency entry: 1441071643
- Sigstore integration time:
-
Permalink:
radiusred/tradedesk@568cd6c48cc68512dab2c5c993c4a4c5167ad5c9 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/radiusred
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@568cd6c48cc68512dab2c5c993c4a4c5167ad5c9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tradedesk-1.2.0-py3-none-any.whl.
File metadata
- Download URL: tradedesk-1.2.0-py3-none-any.whl
- Upload date:
- Size: 186.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
616c2a2ebc5b223258db6c3c6540ab9289bcd4f14635d84fc0d92f4196d26a42
|
|
| MD5 |
722f743b288364be038bab9eb4706c72
|
|
| BLAKE2b-256 |
05590f21e80d8c6fc21d5ba78042f109e3a193f8da79f3a297a6273a41eb75b0
|
Provenance
The following attestation bundles were made for tradedesk-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on radiusred/tradedesk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tradedesk-1.2.0-py3-none-any.whl -
Subject digest:
616c2a2ebc5b223258db6c3c6540ab9289bcd4f14635d84fc0d92f4196d26a42 - Sigstore transparency entry: 1441071711
- Sigstore integration time:
-
Permalink:
radiusred/tradedesk@568cd6c48cc68512dab2c5c993c4a4c5167ad5c9 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/radiusred
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@568cd6c48cc68512dab2c5c993c4a4c5167ad5c9 -
Trigger Event:
release
-
Statement type: