AI Agents Quick Start

This page shows the core patterns for creating and running AI trading agents inside a LumiBot strategy. Whether you want to backtest an AI trading agent with external MCP tools or build an agentic backtesting workflow, these examples get you started in minutes. For background on why LumiBot is the only framework that puts the LLM inside the backtest loop, see AI Trading Agents and Agentic Backtesting.

The pattern is simple:

  • Create the agent once in initialize()

  • Run the agent from lifecycle methods like on_trading_iteration()

  • The agent reasons, calls tools, and executes trades on each bar

  • The same code works in backtests and live trading

Imports

from lumibot.components.agents import MCPServer, agent_tool
from lumibot.strategies import Strategy

Built-in tools are included by default – no import needed for those.

Minimal Example (Built-in Tools Only)

This strategy creates an agent that uses only the default built-in tools. No external MCP servers, no explicit tool list.

from lumibot.strategies import Strategy


class SimpleAgentStrategy(Strategy):
    parameters = {"symbol": "SPY"}

    def initialize(self):
        self.sleeptime = "1D"
        self.agents.create(
            name="research",
            default_model="gpt-4.1-mini",
            system_prompt=(
                "Analyze the current portfolio and market conditions. "
                "Trade conservatively. If the evidence is weak, do nothing."
            ),
        )

    def on_trading_iteration(self):
        result = self.agents["research"].run(
            context={"symbol": self.parameters["symbol"]}
        )
        self.log_message(f"[research] {result.summary}", color="yellow")

The agent has access to all built-in tools (positions, portfolio, prices, history, DuckDB, orders, docs) without listing them.

External MCP Server Example

Add an external MCP server by passing a URL. This example connects to Alpha Vantage for news data:

import os
from lumibot.components.agents import MCPServer
from lumibot.strategies import Strategy


class NewsSentimentStrategy(Strategy):
    def initialize(self):
        self.sleeptime = "1D"
        self.agents.create(
            name="news_research",
            default_model="gpt-4.1-mini",
            system_prompt=(
                "Search for recent US stock news. Find stocks with strong "
                "catalysts and buy the best opportunities. Hold up to 4 "
                "equity positions. Park idle capital in SHV when nothing qualifies."
            ),
            mcp_servers=[
                MCPServer(
                    name="alpha-vantage",
                    url=f"https://mcp.alphavantage.co/mcp?apikey={os.environ['ALPHAVANTAGE_API_KEY']}",
                    exposed_tools=["NEWS_SENTIMENT"],
                ),
            ],
        )

    def on_trading_iteration(self):
        result = self.agents["news_research"].run()
        self.log_message(f"[news_research] {result.summary}", color="yellow")

MCP Server with Auth Headers

Some MCP servers require authentication via HTTP headers. This example uses a Smithery-hosted FRED server with a Bearer token:

import os
from lumibot.components.agents import MCPServer
from lumibot.strategies import Strategy


class MacroRiskStrategy(Strategy):
    def initialize(self):
        self.sleeptime = "1D"
        self.agents.create(
            name="macro_research",
            default_model="gpt-4.1-mini",
            system_prompt=(
                "Use economic data to decide whether capital should be in "
                "TQQQ or a defensive asset like SHV. Check interest rates, "
                "inflation, and growth conditions. This is a binary allocator."
            ),
            mcp_servers=[
                MCPServer(
                    name="fred-macro",
                    url="https://server.smithery.ai/@kablewy/fred-mcp-server/mcp",
                    headers={"Authorization": f"Bearer {os.environ['SMITHERY_API_KEY']}"},
                    exposed_tools=["search_series", "get_series_observations"],
                ),
            ],
        )

    def on_trading_iteration(self):
        result = self.agents["macro_research"].run()
        self.log_message(f"[macro_research] {result.summary}", color="yellow")

Custom Strategy Tools with @agent_tool

If your strategy needs a custom helper that the agent can call, decorate a method with @agent_tool:

from lumibot.components.agents import agent_tool
from lumibot.strategies import Strategy


class CustomToolStrategy(Strategy):

    @agent_tool(
        name="get_watchlist_bias",
        description="Return a structured bias payload for one symbol.",
    )
    def get_watchlist_bias(self, symbol: str) -> dict:
        return {"symbol": symbol, "bias": "neutral"}

    def initialize(self):
        self.sleeptime = "1D"
        self.agents.create(
            name="research",
            default_model="gpt-4.1-mini",
            system_prompt="Analyze watchlist bias before trading.",
            tools=[self.get_watchlist_bias],
        )

    def on_trading_iteration(self):
        result = self.agents["research"].run()
        self.log_message(f"[research] {result.summary}", color="yellow")

When you pass a tools=[...] list, those tools are added alongside the default built-in tools. You only need to list your custom tools.

Passing Context

Pass point-in-time context into the agent run. LumiBot automatically injects positions, cash, and datetime, but you can add strategy-specific context:

result = self.agents["research"].run(
    context={
        "symbol": "SPY",
        "signal_state": self.vars.get("signal_state", "unknown"),
    }
)

Working with the Result

run(...) returns an AgentRunResult with these useful fields:

  • result.summary – the agent’s concluding summary

  • result.text – full text output from the agent

  • result.cache_hit – whether the result was replayed from cache

  • result.warning_messages – list of observability warnings

  • result.tool_calls – list of tool call events

  • result.tool_results – list of tool result events

  • (result.payload or {}).get("trace_path") – path to the full JSON trace

result = self.agents["research"].run()
self.log_message(f"summary={result.summary}", color="yellow")

trace_path = (result.payload or {}).get("trace_path")
if trace_path:
    self.log_message(f"trace={trace_path}", color="blue")

if result.warning_messages:
    for warning in result.warning_messages:
        self.log_message(f"WARNING: {warning}", color="red")

Running a Backtest

Use the standard LumiBot backtest pattern. The agent runs on every bar just like it would in live trading:

if __name__ == "__main__":
    IS_BACKTESTING = True
    if IS_BACKTESTING:
        from datetime import datetime
        NewsSentimentStrategy.backtest(
            datasource_class=None,
            backtesting_start=datetime(2025, 9, 1),
            backtesting_end=datetime(2026, 3, 1),
            benchmark_asset="SPY",
        )

Set datasource_class=None to use the data source configured in your .env file via BACKTESTING_DATA_SOURCE.

Best Practices

  • Create the agent once in ``initialize()``. Do not recreate it on every iteration.

  • Keep system prompts short. 2-3 sentences about your strategy intent. LumiBot handles the rest.

  • Do not list built-in tools. They are included by default.

  • MCP servers are just URLs. No local scripts, no npm installs.

  • Log the summary. Always log result.summary so you can understand agent decisions.

  • Inspect traces when surprised. The JSON trace is the source of truth for debugging agent behavior.

Where to Go Next