Bot API Reference
tradingbot.utils.botclass.Bot(name: str, symbol: str | None = None, tickers: list[str] | None = None, interval: str = '1m', period: str = '1d', benchmark_tickers: list[str] | None = None, **kwargs)
Base class for trading bots.
Provides common functionality for data fetching, trading operations, portfolio management, and database interactions.
Data Caching: - Each Bot instance has its own DataService instance with per-instance caching. - For cross-run data reuse (e.g., hyperparameter tuning), data is persisted to the database when saveToDB=True. Subsequent fetches check the database first and only call yfinance if data is missing or stale. - Best practice: Use saveToDB=True for historical backtests to enable efficient data reuse across multiple runs or parameter combinations.
Subclasses should implement either: - decisionFunction(row) -> int: Returns -1 (sell), 0 (hold), or 1 (buy) - makeOneIteration() -> int: Custom iteration logic
Initialize a trading bot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique name for the bot (used for database identification) |
required |
symbol
|
str | None
|
Trading symbol (e.g., "EURUSD=X", "^XAU", "QQQ") Optional for multi-asset bots that override makeOneIteration() |
None
|
tickers
|
list[str] | None
|
List of trading symbols for multi-ticker strategies. If provided, symbol is ignored and set to None. e.g. tickers=["SPY", "QQQ", "GLD"] |
None
|
interval
|
str
|
Data interval (e.g., "1m", "5m", "1h", "1d") - default: "1m" |
'1m'
|
period
|
str
|
Data period (e.g., "1d", "5d", "1mo", "1y") - default: "1d" |
'1d'
|
benchmark_tickers
|
list[str] | None
|
Subset of |
None
|
**kwargs
|
Arbitrary hyperparameters that will be stored in self.params and can be accessed by subclasses for flexible parameterization |
{}
|
Source code in tradingbot/utils/botclass.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
backtest_type: str
property
Classify the bot's backtesting mode.
Note this counts self.tickers, NOT tradeable_tickers: a bot with one tradeable ticker plus a benchmark must still take the multi-ticker path, because the single-asset path fetches only self.symbol (None for a tickers= bot) and would never load the benchmark's data.
Precedence is targetWeights > decisionFunction > makeOneIteration, and it is deliberate: targetWeights is strictly more expressive than a -1/0/1 signal, so a bot defining both is one whose weights function calls its own decisionFunction as a helper. Ticker count does not enter into it — a single-ticker targetWeights bot is legal, because sizing a lone position between 0 and 1 is a strategy in its own right.
Returns:
| Type | Description |
|---|---|
str
|
"target_weights" — targetWeights overridden (any ticker count) → backtestable |
str
|
"single_asset" — decisionFunction overridden + single ticker → backtestable |
str
|
"multi_asset" — decisionFunction overridden + multiple tickers → backtestable |
str
|
"event_driven" — only makeOneIteration overridden → NOT backtestable |
str
|
"unknown" — neither method overridden → NOT backtestable |
can_backtest: bool
property
True if this bot can be backtested with local_backtest() / local_optimize().
Only data-driven bots that implement decisionFunction() or targetWeights() are backtestable. Event-driven bots (makeOneIteration only) must use run() for live execution.
dbBot: BotSnapshot
property
This bot's row, read fresh on every access, as plain data.
It used to be the ORM row itself, re-fetched after every trade by hand. Any row outlives its session and goes stale or detached, and a missed re-fetch surfaced later as DetachedInstanceError or as last run's cash. Reading on access makes both impossible. Bind it to a local when a method needs one consistent view.
tradeable_tickers: list[str]
property
Tickers this bot may actually hold: self.tickers minus benchmark_tickers.
Benchmarks are fetched into self.datas (a strategy may need one as a relative-strength baseline) but are excluded from the equal-weight divisor and are never bought or sold. Defaults to self.tickers, so bots that declare no benchmarks are unaffected.
Written defensively because tests stub out Bot.init entirely and backtest_bot() accepts arbitrary instances. It is also called from init during validation, before the attributes are guaranteed set.
addPdDFToDb(df: pd.DataFrame, interval: str | None = None) -> None
Add DataFrame rows to database, skipping duplicates.
Only inserts rows newer than the latest stored for this (symbol, interval).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
DataFrame with columns: symbol, timestamp, open, high, low, close, volume |
required |
interval
|
str | None
|
Bar size these rows were fetched at; defaults to this bot's own interval. Filing rows under the wrong bar size corrupts every later read (see HistoricData's docstring). |
None
|
Source code in tradingbot/utils/botclass.py
buy(symbol: str, quantity_usd: float = -1, option: bool | str | None = None, *, delta: float | None = None, dte: int | None = None) -> None
Buy a quantity of the specified symbol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
Trading symbol to buy |
required |
quantity_usd
|
float
|
Amount in USD to spend (-1 means use all available cash) |
-1
|
option
|
bool | str | None
|
True / "call" / "put" buys a ~OPTION_TARGET_DTE-day at-the-money
contract on |
None
|
delta
|
float | None
|
Option strike by delta (0.70 = a 70-delta call) instead of ATM. Defaults to OPTION_TARGET_DELTA. |
None
|
dte
|
int | None
|
Minimum days to expiry. Defaults to OPTION_TARGET_DTE. |
None
|
Source code in tradingbot/utils/botclass.py
close_options(underlying: str, include_stock: bool = False) -> float
Close every option leg on underlying (long and short); include_stock
also flattens the shares held beside them. Returns net cash.
Source code in tradingbot/utils/botclass.py
convertToWideFormat(data_long: pd.DataFrame, value_column: str = 'close', fill_method: str = 'both') -> pd.DataFrame
Convert long-format DataFrame to wide format for portfolio optimization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_long
|
DataFrame
|
DataFrame in long format with columns: symbol, timestamp, open, high, low, close, volume |
required |
value_column
|
str
|
Column name to use as values (default: "close") |
'close'
|
fill_method
|
str
|
How to handle missing values - "forward", "backward", "both", or None |
'both'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with timestamp as index, symbols as columns, and specified value column as values |
Source code in tradingbot/utils/botclass.py
decisionFunction(row: pd.Series) -> int
Decision function that determines trading action based on market data row.
Must be overridden by subclasses (unless using makeOneIteration() instead).
This is the preferred approach for most bots. The base class will: 1. Apply this function to each row in the DataFrame 2. Average the last N decisions (default: 1) 3. Execute trades based on the final decision
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
Series
|
Pandas Series containing: - Market data: symbol, timestamp, open, high, low, close, volume - Technical indicators: ~150+ indicators (e.g., momentum_rsi, trend_macd, etc.) - Access via: row["indicator_name"] |
required |
Returns:
| Type | Description |
|---|---|
int
|
-1: Sell signal (will sell holdings if any exist) 0: Hold (no action taken) 1: Buy signal (will buy if cash available) |
Example
def decisionFunction(self, row): if row["momentum_rsi"] < 30: return 1 # Oversold, buy elif row["momentum_rsi"] > 70: return -1 # Overbought, sell return 0 # Hold
Source code in tradingbot/utils/botclass.py
delta_hedge(underlying: str, band_usd: float, ww: tuple[float, float] | None = None) -> float
Trade shares to bring the options-plus-shares delta back to ~0 beyond band_usd.
ww=(cost_frac, risk_aversion) switches to a Whalley-Wilmott band, rehedged to its edge.
Source code in tradingbot/utils/botclass.py
dividend_threatened_calls(book: options.OptionBook) -> list[str]
Short ITM calls in book likely to be assigned early before the next
ex-dividend date (time value below the dividend, within
option_rules.EX_DIVIDEND_CLOSE_BDAYS sessions). Each is logged.
Source code in tradingbot/utils/botclass.py
getDataFromDB(symbol: str, start_date: pd.Timestamp | None = None, end_date: pd.Timestamp | None = None, interval: str | None = None) -> pd.DataFrame
Load data from database for a symbol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
Trading symbol to query |
required |
start_date
|
Timestamp | None
|
Optional start date (timezone-aware UTC) |
None
|
end_date
|
Timestamp | None
|
Optional end date (timezone-aware UTC) |
None
|
interval
|
str | None
|
Bar size to read; defaults to this bot's own interval. Rows stored at other bar sizes are never returned. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns: symbol, timestamp, open, high, low, close, volume |
DataFrame
|
Empty DataFrame if no data found |
Source code in tradingbot/utils/botclass.py
getLatestDecision(data: pd.DataFrame, nrMedianLatest: int = 1) -> int
Get the latest trading decision by applying decisionFunction to data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
DataFrame with market data |
required |
nrMedianLatest
|
int
|
Number of latest rows to average (default: 1) |
1
|
Returns:
| Type | Description |
|---|---|
int
|
Averaged decision signal (-1, 0, or 1) |
Source code in tradingbot/utils/botclass.py
getLatestPrice(symbol: str) -> float
Get the latest price for a symbol, using TTL cache and checking DB first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
Trading symbol to get price for |
required |
Returns:
| Type | Description |
|---|---|
float
|
Latest price as float |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no price data is available |
Source code in tradingbot/utils/botclass.py
getLatestPricesBatch(symbols: list[str]) -> dict[str, float]
Get latest prices for multiple symbols in a single DB query.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbols
|
list[str]
|
List of trading symbols to get prices for |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Dictionary mapping symbol to latest price |
Source code in tradingbot/utils/botclass.py
getYFData(symbol: str | None = None, interval: str = '1m', period: str = '1d', saveToDB: bool = False) -> pd.DataFrame
Fetch market data from Yahoo Finance, checking database first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str | None
|
Trading symbol (defaults to self.symbol) |
None
|
interval
|
str
|
Data interval (e.g., "1m", "5m", "1h", "1d") |
'1m'
|
period
|
str
|
Data period (e.g., "1d", "5d", "1mo", "1y") |
'1d'
|
saveToDB
|
bool
|
Whether to save fetched data to database |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns: symbol, timestamp, open, high, low, close, volume |
Source code in tradingbot/utils/botclass.py
getYFDataMultiple(symbols: list[str], interval: str = '1d', period: str = '3mo', saveToDB: bool = True) -> pd.DataFrame
Fetch market data for multiple symbols efficiently, checking database first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbols
|
list[str]
|
List of trading symbols to fetch |
required |
interval
|
str
|
Data interval (e.g., "1m", "5m", "1h", "1d") |
'1d'
|
period
|
str
|
Data period (e.g., "1d", "5d", "1mo", "3mo", "1y") |
'3mo'
|
saveToDB
|
bool
|
Whether to save fetched data to database for each symbol |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns: symbol, timestamp, open, high, low, close, volume |
DataFrame
|
Combined data from all symbols in long format |
Source code in tradingbot/utils/botclass.py
getYFDataWithTA(symbol: str | None = None, interval: str = '1m', period: str = '1d', saveToDB: bool = False, features: list[str] | None = None) -> pd.DataFrame
Fetch market data with technical analysis indicators.
Data fetching strategy: - Checks database first for existing data - Only fetches from yfinance if data is missing or stale - If saveToDB=True, saves fetched data to database for future reuse
Note: For repeated backtests or hyperparameter tuning, set saveToDB=True to enable efficient data reuse.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str | None
|
Trading symbol (defaults to self.symbol) |
None
|
interval
|
str
|
Data interval (e.g., "1m", "5m", "1h", "1d") |
'1m'
|
period
|
str
|
Data period (e.g., "1d", "5d", "1mo", "1y") |
'1d'
|
saveToDB
|
bool
|
Whether to save fetched data to database. Set to True for historical backtests to enable data reuse. |
False
|
features
|
list[str] | None
|
Optional list of specific TA indicator column names to keep. If provided, drops other TA columns to save memory. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with market data and technical analysis features |
Source code in tradingbot/utils/botclass.py
get_ai_tools() -> list[Any]
Return custom LangChain tools for this bot. Override in subclasses to add bot-specific tools (e.g. get_tradeable_symbols, run_optimization). These are merged with the base tools when calling run_ai().
Source code in tradingbot/utils/botclass.py
local_backtest(initial_capital: float = 10000.0) -> dict[str, Any]
Local-only helper: run a backtest with current instance parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_capital
|
float
|
Starting capital for backtest |
10000.0
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Backtest results dictionary |
Source code in tradingbot/utils/botclass.py
local_development(param_grid: dict[str, list[Any]] | None = None, objective: str = 'alpha_t', initial_capital: float = 10000.0, n_jobs: int | None = None, param_sample_ratio: float = 1.0) -> dict[str, Any]
Convenience wrapper for the typical local development workflow:
1) Optimize hyperparameters for this bot's class 2) Backtest a bot instance constructed with the best parameters
Does NOT modify init defaults; you still paste them manually.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
param_grid
|
dict[str, list[Any]] | None
|
Optional parameter grid to use. If None, uses self.param_grid or class attribute. |
None
|
objective
|
str
|
Metric to maximize, one of hyperparameter_tuning.OBJECTIVES (default "alpha_t": t-stat of alpha vs QQQ) |
'alpha_t'
|
initial_capital
|
float
|
Starting capital for backtests |
10000.0
|
n_jobs
|
int | None
|
Number of parallel jobs (None = auto-detect) |
None
|
param_sample_ratio
|
float
|
Fraction of param combinations to test (0.0–1.0). 1.0 = all (default). e.g. 0.2 = randomly test 20% of the grid. |
1.0
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Optimization results dictionary with 'best_params' and performance metrics |
Example
bot = MyBot() results = bot.local_development()
Prints best parameters in copy-paste format
Then backtests with those parameters
Copy the printed parameters into init defaults
Source code in tradingbot/utils/botclass.py
local_optimize(param_grid: dict[str, list[Any]] | None = None, objective: str = 'alpha_t', initial_capital: float = 10000.0, n_jobs: int | None = None, param_sample_ratio: float = 1.0) -> dict[str, Any]
Local-only helper: run hyperparameter optimization for this bot's class.
Uses either the provided param_grid or self.param_grid (if defined as class attribute). Prints the best combination in a format easy to copy-paste into init defaults.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
param_grid
|
dict[str, list[Any]] | None
|
Optional parameter grid to use. If None, uses self.param_grid or class attribute. |
None
|
objective
|
str
|
Metric to maximize, one of hyperparameter_tuning.OBJECTIVES (default "alpha_t": t-stat of alpha vs QQQ) |
'alpha_t'
|
initial_capital
|
float
|
Starting capital for backtests |
10000.0
|
n_jobs
|
int | None
|
Number of parallel jobs (None = auto-detect) |
None
|
param_sample_ratio
|
float
|
Fraction of param combinations to test (0.0–1.0). 1.0 = all (default). |
1.0
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Full optimization results dictionary |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no param_grid is defined |
Source code in tradingbot/utils/botclass.py
1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 | |
makeOneIteration() -> int
Execute one iteration of the trading bot.
Default implementation: 1. Fetches data with technical indicators 2. Gets decision by applying decisionFunction() to data 3. Executes buy/sell based on decision
When to override: - Multi-asset bots (must override if self.symbol is None) - External data sources (e.g., Fear & Greed Index API) - Portfolio optimization strategies - Custom data processing beyond row-by-row logic
When NOT to override: - Simple single-asset strategies (just implement decisionFunction() instead) - Strategies that only need different timeframes (set interval/period in init)
Returns:
| Type | Description |
|---|---|
int
|
-1: Sold 0: No action 1: Bought |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If self.symbol is None (multi-asset bot) and method not overridden |
Source code in tradingbot/utils/botclass.py
open_credit_spread(underlying: str, side: str, short_delta: float = 0.3, width: float = 10.0, dte: int = 35, max_risk_usd: float | None = None, view: options.ChainView | None = None) -> int
Sell a vertical credit spread: side="put" / "bull" is a bull put spread
(short put at short_delta, long put width lower), side="call" /
"bear" a bear call spread. Sized so that total max loss stays within
max_risk_usd (default: all free cash). Returns the number opened.
view reuses a chain the bot already loaded (options.load_chain).
Source code in tradingbot/utils/botclass.py
open_iron_condor(underlying: str, short_delta: float = 0.16, width: float = 10.0, dte: int = 35, max_risk_usd: float | None = None, view: options.ChainView | None = None, call_delta: float | None = None) -> int
Sell a put spread and a call spread on one expiry (calls at call_delta if given). Returns the number opened.
Source code in tradingbot/utils/botclass.py
open_structure(pick: options.StructurePick, max_risk_usd: float | None = None) -> int
Open as many units of any options.select_* pick (butterfly, straddle,
collar, calendar, diagonal, covered call...) as max_risk_usd of
worst-case loss allows (default: all free cash). Returns units opened.
Source code in tradingbot/utils/botclass.py
option_book(underlying: str) -> options.OptionBook
Held options on underlying: legs, marks, entry value, P&L, DTE and net greeks.
portfolio_value() -> float
Total book value: cash plus every holding at the latest price (short legs negative).
rebalancePortfolio(targetPortfolio: dict[str, float], onlyOver50USD: bool = False) -> None
Rebalance portfolio to match target weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targetPortfolio
|
dict[str, float]
|
Dictionary mapping symbols to target weights (e.g., {"VWCE": 0.8, "GLD": 0.1, "USD": 0.1}) Weights must sum to 1.0 (100%) |
required |
onlyOver50USD
|
bool
|
If True, filter out assets with target value <= $50 and redistribute weights equally among remaining assets (default: False) |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If weights don't sum to 1.0 (within tolerance) |
Source code in tradingbot/utils/botclass.py
run() -> None
Execute one iteration of the bot and log results.
Catches exceptions and logs them to the database before re-raising.
Source code in tradingbot/utils/botclass.py
run_ai(system_prompt: str, user_message: str, model: str | None = None, max_tool_rounds: int = 5, extra_tools: list[Any] | None = None, tool_names: list[str] | None = None) -> str
Run the AI with tools (market data, portfolio, recent trades, plus any custom tools) bound to this bot. Uses the main LLM (OPENROUTER_MAIN_MODEL, default deepseek/deepseek-v3.2). Pass model= to override. Merge custom tools via get_ai_tools() or extra_tools=. Optional tool_names= whitelists which base tools to include. Requires OPENROUTER_API_KEY.
Source code in tradingbot/utils/botclass.py
run_ai_simple(system_prompt: str, user_message: str, model: str | None = None) -> str
Run the AI for a single-turn, no-tools task (summarization, extraction, classification, rewriting). Uses the cheap LLM (OPENROUTER_CHEAP_MODEL, default openrouter/free). Use run_ai() when you need tool access.
Source code in tradingbot/utils/botclass.py
run_ai_simple_with_fallback(system_prompt: str, user_message: str, sanity_check: Callable[[str], bool] | None = None, fallback_to_main: bool = True) -> str
Run a simple (no-tools) task with cheap LLM first; verify output for sanity; if validation fails, retry with main LLM. Prefer this over run_ai_simple when you want to save cost but still guarantee sane results.
Optional callable(response) -> bool. If None, uses a default
check (non-empty, no refusal/error prefix).
fallback_to_main: If True and sanity check fails, retry with main model.
Source code in tradingbot/utils/botclass.py
sell(symbol: str, quantity_usd: float = -1, option: bool | str | None = None) -> None
Sell a quantity of the specified symbol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
Trading symbol to sell |
required |
quantity_usd
|
float
|
Amount in USD to sell (-1 means sell all holdings) |
-1
|
option
|
bool | str | None
|
True sells the option contracts held on |
None
|
Source code in tradingbot/utils/botclass.py
targetWeights(rows: dict[str, pd.Series]) -> dict[str, float] | None
Cross-sectional alternative to decisionFunction: return the target book.
Override this instead of decisionFunction when the strategy sizes positions rather than merely signalling them — vol targeting, risk parity, cross-sectional ranking. decisionFunction can only say -1/0/1, which the framework turns into equal weights; that is a different strategy from "hold each leg at constant ex-ante volatility", and expressing the latter used to require makeOneIteration and thereby forfeit backtesting entirely.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
dict[str, Series]
|
The current bar for EVERY ticker in self.tickers, benchmarks included — cross-sectional strategies need to see the whole universe before weighting any of it. For history, read self.datas[ticker], which the caller has already truncated to bars at or before the current one (so there is no way to peek ahead by accident). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float] | None
|
Long-only weights over TRADEABLE tickers, summing to <= 1.0. |
dict[str, float] | None
|
|
dict[str, float] | None
|
|
dict[str, float] | None
|
|
dict[str, float] | None
|
|
dict[str, float] | None
|
|
Example
def targetWeights(self, rows): longs = [t for t in self.tradeable_tickers if rows[t]["close"] > self.datas[t]["close"].iloc[-200:].mean()] return {t: 1.0 / len(longs) for t in longs} if longs else {}
Source code in tradingbot/utils/botclass.py
trade_option_legs(legs: list[tuple[str, float]]) -> float
Change several legs (contracts in share-equivalents, or the underlying's shares) atomically.