Skip to main content
Glama

BotTrade Python SDK

Backtest any Python trading agent on a versioned historical-market benchmark.

CI PyPI Python MIT MCP Registry

Quick start

python -m pip install 'bottrade==0.2.0'
export BOTTRADE_API_KEY="bt_your_key_here"

Create my_agent.py:

import bottrade


def decide(observation: bottrade.Observation):
    symbol = observation.scenario.benchmark_symbol or observation.scenario.universe[0]
    bars = observation.bars[symbol]

    if observation.position(symbol):
        return bottrade.hold("Position is open")

    if len(bars) >= 2 and bars[-1].close > bars[-2].close:
        return bottrade.buy(symbol, quantity=10, reasoning="Positive one-bar momentum")

    return bottrade.hold("Waiting for momentum")


result = bottrade.backtest(
    decide,
    scenario="sandbox-nov-2024",
    agent_info=bottrade.AgentInfo(
        name="My momentum agent",
        framework="python",
        version="1.0",
    ),
)

print(result.run_id)
print(result.return_pct)
print(result.sharpe)
print(result.max_drawdown)

Run it:

python my_agent.py

backtest() calls the agent, submits its orders, advances the scenario, computes final metrics, and returns a typed BacktestResult.

Get an API key at bot-trade.org/account.

Related MCP server: freqtrade-mcp-server

Agent decisions

An agent receives one Observation and returns an order, a list of orders, or hold().

return bottrade.buy("AAPL", quantity=10, reasoning="Breakout")
return bottrade.sell("AAPL", quantity=5, reasoning="Reduce exposure")
return bottrade.short("TSLA", quantity=2, reasoning="Bearish signal")
return bottrade.cover("TSLA", quantity=2, reasoning="Close short")
return bottrade.hold("No signal")

Multiple orders:

return [
    bottrade.buy("AAPL", quantity=10),
    bottrade.buy("MSFT", quantity=5),
]

Each order owns its symbol, side, quantity, and reasoning.

Observation reference

observation.scenario       # Scenario metadata and universe
observation.sim_time       # Current simulated timestamp
observation.cash           # Available cash
observation.positions      # Current positions
observation.bars           # Visible OHLCV bars by symbol
observation.step_number    # Current runner step
observation.position("SPY")

Bars are typed objects:

latest = observation.bars["SPY"][-1]
print(latest.open, latest.high, latest.low, latest.close, latest.volume)

Stateful agents

import bottrade


class MovingAverageAgent:
    def decide(self, observation: bottrade.Observation):
        symbol = "SPY"
        closes = [bar.close for bar in observation.bars[symbol]]
        average = sum(closes) / len(closes)

        if closes[-1] > average and observation.position(symbol) is None:
            return bottrade.buy(symbol, quantity=10)

        return bottrade.hold()


result = bottrade.backtest(
    MovingAverageAgent(),
    scenario="sandbox-nov-2024",
    lookback=20,
)

Async agents

import asyncio
import bottrade


async def decide(observation: bottrade.Observation):
    signal = await get_model_signal(observation)
    if signal == "buy":
        return bottrade.buy("SPY", quantity=10)
    return bottrade.hold()


async def main():
    result = await bottrade.backtest_async(decide, scenario="sandbox-nov-2024")
    print(result.return_pct)


asyncio.run(main())

Agent provenance

Attach reproducible identity to every run:

info = bottrade.AgentInfo(
    name="AI Hedge Fund technical",
    framework="ai-hedge-fund",
    model="gpt-4.1",
    version="2026.7.10",
    source_url="https://github.com/virattt/ai-hedge-fund",
    source_revision="09dd33167bd6b4ea63ae32e7246e70e80632cc81",
    config={"analysts": ["technical_analyst"], "lookback": 180},
)

result = bottrade.backtest(agent, scenario="tech-2024-q2", agent_info=info)

Published run pages display this identity with the benchmark evidence.

Runner options

result = bottrade.backtest(
    agent,
    scenario="tech-2024-q2",
    lookback=50,
    decide_every=1,
    max_steps=10_000,
    resume_run_id=None,
    publish=False,
)

Option

Meaning

scenario

Ready scenario slug

lookback

Visible bars per symbol at each decision

decide_every

Call the agent every N bars

max_steps

Maximum simulator steps for this invocation

resume_run_id

Continue an existing active run

publish

Publish the completed run and trades

Command line

Export a function or agent object from a module:

bottrade backtest my_agent:decide --scenario sandbox-nov-2024
python -m bottrade backtest my_agent:decide --scenario sandbox-nov-2024

Add provenance:

bottrade backtest my_agent:decide \
  --scenario tech-2024-q2 \
  --name "My momentum agent" \
  --framework python \
  --agent-version 1.0 \
  --source-revision abc123

Run bottrade backtest --help for the complete command reference.

Explicit reference strategy

import bottrade
from bottrade.strategies import buy_and_hold

result = bottrade.backtest(
    buy_and_hold(quantity=10, symbol="SPY"),
    scenario="sandbox-nov-2024",
)

Here, quantity configures the selected buy-and-hold agent.

Integrations

Integration

Example

Plain Python

Custom momentum agent

OpenAI Agents SDK

Streamable HTTP MCP agent

LangChain / LangGraph

MultiServerMCPClient agent

OpenAI, Gemini, Grok

Multi-provider agent

AI Hedge Fund

AI Hedge Fund adapter

Result object

result.run_id
result.agent_info
result.scenario
result.return_pct
result.final_equity
result.sharpe
result.sortino
result.max_drawdown
result.trade_count
result.published

Publish a result with publish=True, then embed its evidence badge:

[![Tested on BotTrade](https://bot-trade.org/run/RUN_ID/badge.svg)](https://bot-trade.org/run/RUN_ID)

Low-level client

Use session() for explicit observation, submission, and stepping:

import bottrade

info = bottrade.AgentInfo(name="My manual agent", framework="python")

with bottrade.session("sandbox-nov-2024", agent_info=info) as run:
    while run.active:
        observation = run.observe()
        run.submit(decide(observation))
        run.step()

    results = run.results()

BotTradeClient exposes scenario discovery, run creation, observations, orders, stepping, results, publication, public runs, URLs, and badges. AsyncBotTradeClient provides the same operations with async methods.

import bottrade

with bottrade.BotTradeClient.from_env() as client:
    scenarios = client.list_scenarios()
    print([scenario.slug for scenario in scenarios])

Development

python -m pip install -e '.[dev]'
ruff check .
mypy
pytest
python -m build
twine check dist/*

BotTrade is designed for software evaluation, education, and research.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Local-first backtesting engine with built-in overfitting detection (PBO, deflated Sharpe, bootstrap CI, walk-forward) and a native MCP server for AI agents to validate trading strategies.
    4
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server for cryptocurrency trading via Freqtrade, enabling trade management, balance checks, strategy configuration, backtesting, and bot lifecycle control from any MCP-compatible AI agent.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that lets an AI agent backtest, risk-check, and audit trading strategies, determining if a strategy is overfit or actually works.
    MIT