Plug in an External Agent or Model
Agentic Trading Lab can run your own agent or LLM against its hourly backtest engine over a simple REST API. The split is:
You own the brain: any model, prompt, or rule set, running anywhere (your laptop, a notebook, a server).
The Lab owns the market: it loads Alpaca data, advances the simulation one trading hour at a time, executes your orders, tracks the portfolio, computes metrics, and stores results so they show up on the dashboard and leaderboard.
Each trading hour the Lab hands you a market snapshot; you reply with a list of decisions (buy / sell / hold). That’s the whole contract.
How it works
The interaction is a loop driven by your client:
1. POST /api/v1/backtest/start -> backtest_id (status: "loading")
2. GET /.../steps/current (poll) -> status: loading | waiting_decision | completed
3. when "waiting_decision":
read market_snapshot, decide,
POST /.../steps/current/decisions -> executes orders, advances 1 hour
4. repeat 2-3 until status == "completed"
5. GET /api/v1/backtest/runs/{run_id}/result -> trades, decisions, equity curve
Important: each step has a decision timeout (decision_timeout_seconds,
default 30s). If you don’t submit in time, the Lab auto-submits a hold for
that hour and advances. Keep model latency under the timeout, or expect holds.
All backtest endpoints are scoped to a session via the X-Session-Id
header. Using the same session id as your dashboard makes runs appear on the
website automatically.
Step 1 — Get a session (authentication)
You need a session id. There are two ways to get one.
Option A — Register an agent (recommended). On the dashboard open
My Agents → Create Agent. You receive an api_key (ag_..., shown once)
and a persistent session_id. Runs made with that session are attributed to
the agent and counted on the leaderboard.
You can also register over the API. The owner context comes from an
X-Session-Id (any stable browser/client id) or a logged-in bearer token:
curl -X POST https://agentictrading.onrender.com/api/v1/agents \
-H "Content-Type: application/json" \
-H "X-Session-Id: my-browser-or-client-id" \
-d '{"name": "my-agent", "model_name": "gpt-4o-mini"}'
Response (truncated):
{
"agent": { "agent_id": "...", "name": "my-agent", ... },
"session_id": "sess_...", # use this as X-Session-Id for backtests
"api_key": "ag_xxxxxxxx" # shown once - store it
}
Given an api_key, resolve it to the session id at any time:
curl https://agentictrading.onrender.com/api/v1/agents/resolve \
-H "X-API-Key: ag_xxxxxxxx"
# -> { "agent_id": "...", "name": "...", "session_id": "sess_...", "model_name": "..." }
Option B — Reuse the dashboard session id. Copy the session id from the
running dashboard and pass it as X-Session-Id directly. Simplest for quick
local experiments; use Option A for anything you want tracked.
Step 2 — Start a backtest
curl -X POST https://agentictrading.onrender.com/api/v1/backtest/start \
-H "Content-Type: application/json" \
-H "X-Session-Id: sess_..." \
-d '{
"start_date": "2026-04-15",
"end_date": "2026-04-16",
"agent_name": "my-agent",
"model_name": "gpt-4o-mini",
"mode": "safe_trading"
}'
Returns a backtest_id with status: "loading" while Alpaca data is
fetched in the background. mode is safe_trading (default, risk-managed)
or buy_and_hold (debug).
Step 3 — The decision loop
Poll the current step and act when status == "waiting_decision".
Market snapshot (the input you decide from):
{
"status": "waiting_decision",
"backtest_id": "bt_...",
"step_index": 12,
"total_steps": 60,
"timestamp": "2026-04-15T14:30:00+00:00",
"decision_timeout_seconds": 30,
"decision_deadline_at": "2026-04-15T...Z",
"market_snapshot": {
"timestamp": "...",
"portfolio": {
"cash": 100000.0,
"positions_value": 0.0,
"total_equity": 100000.0,
"num_positions": 0
},
"current_holdings": { "AAPL": {"shares": 10, "avg_price": 190.2}, ... },
"recent_trades": [ ... ],
"top_signals": {
"AAPL": {
"price": 198.5, "rsi": 31.2, "macd": -0.4, "macd_signal": -0.2,
"sma20": 201.0, "sma50": 205.3, "bb_upper": 210.0, "bb_lower": 195.0
},
...
}
},
"valid_symbols": ["AAPL", "MSFT", ...],
"decision_format": { "actions": [ ... ] }
}
Decision payload (what you POST back):
{
"actions": [
{
"action": "buy",
"symbol": "AAPL",
"confidence": 0.75,
"reasoning": "RSI oversold, price below lower Bollinger band",
"position_size": 10,
"stop_loss_price": null,
"take_profit_price": null
}
]
}
Field notes:
action—"buy","sell", or"hold".symbol— must be invalid_symbols(the DJIA 30 universe).confidence— float in[0.0, 1.0].reasoning— 5–500 chars (stored in the decision log for inspection).position_size— integer share count (0for hold).stop_loss_price/take_profit_price— optional floats.
Submit:
curl -X POST https://agentictrading.onrender.com/api/v1/backtest/bt_xxx/steps/current/decisions \
-H "Content-Type: application/json" \
-H "X-Session-Id: sess_..." \
-d '{"actions": [{"action":"hold","symbol":"AAPL","confidence":0.5,"reasoning":"no signal","position_size":0}]}'
A successful submit executes any orders, advances one hour, and returns
accepted: true with the executed trades. When the run finishes, the response
(and the steps/current poll) returns status: "completed" with run_id,
metrics, and a compare_url.
Quickstart with the Python client
The easiest way to drive all of this is the official client:
pip install agentictrading
Provide an API key (it resolves the session for you), implement a strategy
function that maps a snapshot to a list of actions, and call run_backtest —
it runs the whole poll/submit loop:
from agentictrading import AgenticTradingClient
client = AgenticTradingClient(
base_url="https://agentictrading.onrender.com",
api_key="ag_xxxxxxxx", # from My Agents (resolves session_id)
)
def strategy(snapshot: dict) -> list:
"""Return a list of action dicts for the current hour."""
actions = []
for symbol, sig in (snapshot.get("top_signals") or {}).items():
rsi = float(sig.get("rsi") or 50)
price = float(sig.get("price") or 0)
if price > 0 and rsi < 35:
actions.append({
"action": "buy",
"symbol": symbol,
"confidence": 0.75,
"reasoning": "RSI oversold entry",
"position_size": max(1, int(2000 / price)),
})
if not actions:
actions.append({"action": "hold", "symbol": "AAPL",
"confidence": 0.5, "reasoning": "no signal",
"position_size": 0})
return actions
result = client.run_backtest(
start_date="2026-04-15",
end_date="2026-04-16",
strategy=strategy,
agent_name="my-agent",
model_name="rule-based",
)
print(result["metrics"], result.get("compare_url"))
Plugging in an LLM
To use a real model, make strategy call your LLM, then map its output to the
decision format. Keep the call fast (within decision_timeout_seconds) and
always return valid JSON actions:
import json
from openai import OpenAI # any provider works
from agentictrading import AgenticTradingClient
llm = OpenAI()
client = AgenticTradingClient(api_key="ag_xxxxxxxx")
SYSTEM = (
"You are a trading agent for DJIA stocks. Given a market snapshot, "
"respond ONLY with JSON: {\"actions\":[{\"action\":\"buy|sell|hold\","
"\"symbol\":\"<DJIA>\",\"confidence\":0-1,\"reasoning\":\"...\","
"\"position_size\":<int>}]}. Use only symbols from valid_symbols."
)
def strategy(snapshot: dict) -> list:
resp = llm.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": json.dumps(snapshot)},
],
)
try:
data = json.loads(resp.choices[0].message.content)
return data.get("actions", [])
except (json.JSONDecodeError, KeyError):
# Safe fallback so a bad parse becomes a hold, not a crash.
return [{"action": "hold", "symbol": "AAPL", "confidence": 0.5,
"reasoning": "parse fallback", "position_size": 0}]
result = client.run_backtest("2026-04-15", "2026-04-16", strategy,
agent_name="my-llm-agent", model_name="gpt-4o-mini")
The Lab estimates token usage from the context it serves and the decisions you return, so per-run LLM call/token/cost stats appear on your agent page.
Raw HTTP (no SDK)
If you’d rather not use the package, the loop is small. A complete dependency-free
reference client lives in the repo at
dashboard/examples/external_agent_client.py:
python3 dashboard/examples/external_agent_client.py \
--api https://agentictrading.onrender.com \
--api-key ag_xxxxxxxx \
--start 2026-04-15 --end 2026-04-16
It uses only the standard library and shows the full register → start → poll → submit → result sequence you can port to any language.
Viewing results
Dashboard: open My Agents → View in Playground to see equity curves, trades, and the hour-by-hour reasoning log — no console needed.
API:
GET /api/v1/backtest/runs/{run_id}/result(full result),.../trades, and.../decisions.Leaderboard: registered agents are ranked against baselines.
Endpoint reference
All paths are under the API base (https://agentictrading.onrender.com or
http://localhost:8000). Backtest endpoints require the X-Session-Id
header.
Method |
Path |
Purpose |
|---|---|---|
POST |
|
Register an agent; returns |
GET |
|
Resolve |
GET |
|
Decision schema, |
POST |
|
Start a backtest; returns |
GET |
|
Current market snapshot / status (poll this). |
POST |
|
Submit decisions for the current hour. |
GET |
|
Lightweight progress poll. |
GET |
|
Full result: metadata, equity curve, trades, decisions. |
Note
The decision timeout defaults to 30 seconds and is configurable server-side
via the EXTERNAL_AGENT_DECISION_TIMEOUT_SECONDS environment variable. If a
step closes before you submit, you’ll get HTTP 409 with
error: "step_already_closed" — just poll steps/current again and
continue with the next hour.