Skip to content

Bot API Reference

tradingbot.utils.botclass.Bot(name: str, symbol: Optional[str] = None, interval: str = '1m', period: str = '1d', **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 Optional[str]

Trading symbol (e.g., "EURUSD=X", "^XAU", "QQQ") Optional for multi-asset bots that override makeOneIteration()

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'
**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
def __init__(self, name: str, symbol: Optional[str] = None, interval: str = "1m", period: str = "1d", **kwargs):
    """
    Initialize a trading bot.

    Args:
        name: Unique name for the bot (used for database identification)
        symbol: Trading symbol (e.g., "EURUSD=X", "^XAU", "QQQ")
                Optional for multi-asset bots that override makeOneIteration()
        interval: Data interval (e.g., "1m", "5m", "1h", "1d") - default: "1m"
        period: Data period (e.g., "1d", "5d", "1mo", "1y") - default: "1d"
        **kwargs: Arbitrary hyperparameters that will be stored in self.params
                 and can be accessed by subclasses for flexible parameterization
    """
    self.bot_name = name  # Store name separately to avoid DetachedInstanceError
    self.dbBot = BotRepository.create_or_get_bot(name)
    self.symbol = symbol
    self.interval = interval
    self.period = period

    # Store hyperparameters in a dictionary for flexible access
    self.params = kwargs.copy() if kwargs else {}

    # Initialize services
    self._data_service = DataService()
    self._bot_repository = BotRepository()
    self._portfolio_manager = PortfolioManager(
        bot=self.dbBot,
        bot_name=self.bot_name,
        data_service=self._data_service,
        bot_repository=self._bot_repository,
    )

    # Maintain backward compatibility for data caching
    self.data: Optional[pd.DataFrame] = None
    self.datasettings: Tuple[Optional[str], Optional[str]] = (None, None)

addPdDFToDb(df: pd.DataFrame) -> None

Add DataFrame rows to database, skipping duplicates.

Only inserts rows with timestamps newer than the latest in database.

Parameters:

Name Type Description Default
df DataFrame

DataFrame with columns: symbol, timestamp, open, high, low, close, volume

required
Source code in tradingbot/utils/botclass.py
def addPdDFToDb(self, df: pd.DataFrame) -> None:
    """
    Add DataFrame rows to database, skipping duplicates.

    Only inserts rows with timestamps newer than the latest in database.

    Args:
        df: DataFrame with columns: symbol, timestamp, open, high, low, close, volume
    """
    self._data_service.add_pd_df_to_db(df)

buy(symbol: str, quantityUSD: float = -1) -> None

Buy a quantity of the specified symbol.

Parameters:

Name Type Description Default
symbol str

Trading symbol to buy

required
quantityUSD float

Amount in USD to spend (-1 means use all available cash)

-1
Source code in tradingbot/utils/botclass.py
def buy(self, symbol: str, quantityUSD: float = -1) -> None:
    """
    Buy a quantity of the specified symbol.

    Args:
        symbol: Trading symbol to buy
        quantityUSD: Amount in USD to spend (-1 means use all available cash)
    """
    self._portfolio_manager.buy(symbol, quantity_usd=quantityUSD, cached_data=self.data)
    # Refresh dbBot reference after portfolio update
    self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)

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
def convertToWideFormat(
    self,
    data_long: pd.DataFrame,
    value_column: str = "close",
    fill_method: str = "both",
) -> pd.DataFrame:
    """
    Convert long-format DataFrame to wide format for portfolio optimization.

    Args:
        data_long: DataFrame in long format with columns: symbol, timestamp, open, high, low, close, volume
        value_column: Column name to use as values (default: "close")
        fill_method: How to handle missing values - "forward", "backward", "both", or None

    Returns:
        DataFrame with timestamp as index, symbols as columns, and specified value column as values
    """
    return self._data_service.convert_to_wide_format(
        data_long=data_long,
        value_column=value_column,
        fill_method=fill_method,
    )

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
def decisionFunction(self, 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

    Args:
        row: 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"]

    Returns:
        -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
    """
    raise NotImplementedError("You need to overwrite the decisionFunction!!!!")

getDataFromDB(symbol: str, start_date: Optional[pd.Timestamp] = None, end_date: Optional[pd.Timestamp] = None) -> pd.DataFrame

Load data from database for a symbol.

Parameters:

Name Type Description Default
symbol str

Trading symbol to query

required
start_date Optional[Timestamp]

Optional start date (timezone-aware UTC)

None
end_date Optional[Timestamp]

Optional end date (timezone-aware UTC)

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
def getDataFromDB(
    self,
    symbol: str,
    start_date: Optional[pd.Timestamp] = None,
    end_date: Optional[pd.Timestamp] = None,
) -> pd.DataFrame:
    """
    Load data from database for a symbol.

    Args:
        symbol: Trading symbol to query
        start_date: Optional start date (timezone-aware UTC)
        end_date: Optional end date (timezone-aware UTC)

    Returns:
        DataFrame with columns: symbol, timestamp, open, high, low, close, volume
        Empty DataFrame if no data found
    """
    return self._data_service.get_data_from_db(symbol, start_date, end_date)

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
def getLatestDecision(self, data: pd.DataFrame, nrMedianLatest: int = 1) -> int:
    """
    Get the latest trading decision by applying decisionFunction to data.

    Args:
        data: DataFrame with market data
        nrMedianLatest: Number of latest rows to average (default: 1)

    Returns:
        Averaged decision signal (-1, 0, or 1)
    """
    if not isinstance(data, pd.DataFrame):
        raise ValueError("Data must be a pandas DataFrame")
    if len(data) == 0:
        return 0  # No data, hold

    # Work on a copy to avoid mutating the original DataFrame
    data_copy = data.copy()
    data_copy["signal"] = data_copy.apply(self.decisionFunction, axis=1)

    # Ensure we don't try to access more rows than available
    nrMedianLatest = min(nrMedianLatest, len(data_copy))
    if nrMedianLatest <= 0:
        return 0

    # Get the last nrMedianLatest signals and return their mean
    latest_signals = data_copy["signal"].iloc[-nrMedianLatest:]
    return int(latest_signals.mean())

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
def getLatestPrice(self, symbol: str) -> float:
    """
    Get the latest price for a symbol, using TTL cache and checking DB first.

    Args:
        symbol: Trading symbol to get price for

    Returns:
        Latest price as float

    Raises:
        ValueError: If no price data is available
    """
    return self._data_service.get_latest_price(symbol, cached_data=self.data)

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
def getLatestPricesBatch(self, symbols: list[str]) -> dict[str, float]:
    """
    Get latest prices for multiple symbols in a single DB query.

    Args:
        symbols: List of trading symbols to get prices for

    Returns:
        Dictionary mapping symbol to latest price
    """
    return self._data_service.get_latest_prices_batch(symbols)

getYFData(symbol: Optional[str] = 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 Optional[str]

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
def getYFData(
    self,
    symbol: Optional[str] = None,
    interval: str = "1m",
    period: str = "1d",
    saveToDB: bool = False,
) -> pd.DataFrame:
    """
    Fetch market data from Yahoo Finance, checking database first.

    Args:
        symbol: Trading symbol (defaults to self.symbol)
        interval: Data interval (e.g., "1m", "5m", "1h", "1d")
        period: Data period (e.g., "1d", "5d", "1mo", "1y")
        saveToDB: Whether to save fetched data to database

    Returns:
        DataFrame with columns: symbol, timestamp, open, high, low, close, volume
    """
    if not symbol:
        if self.symbol is None:
            raise ValueError("symbol parameter is required when self.symbol is None (multi-asset bot)")
        symbol = self.symbol

    data = self._data_service.get_yf_data(
        symbol=symbol,
        interval=interval,
        period=period,
        save_to_db=saveToDB,
        use_cache=True,
    )

    # Update cache for backward compatibility
    if (interval, period) == self.datasettings:
        self.data = data

    return data

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
def getYFDataMultiple(
    self,
    symbols: list[str],
    interval: str = "1d",
    period: str = "3mo",
    saveToDB: bool = True,
) -> pd.DataFrame:
    """
    Fetch market data for multiple symbols efficiently, checking database first.

    Args:
        symbols: List of trading symbols to fetch
        interval: Data interval (e.g., "1m", "5m", "1h", "1d")
        period: Data period (e.g., "1d", "5d", "1mo", "3mo", "1y")
        saveToDB: Whether to save fetched data to database for each symbol

    Returns:
        DataFrame with columns: symbol, timestamp, open, high, low, close, volume
        Combined data from all symbols in long format
    """
    return self._data_service.get_yf_data_multiple(
        symbols=symbols,
        interval=interval,
        period=period,
        save_to_db=saveToDB,
    )

getYFDataWithTA(symbol: Optional[str] = None, interval: str = '1m', period: str = '1d', saveToDB: bool = False) -> 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 across multiple runs.

Parameters:

Name Type Description Default
symbol Optional[str]

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

Returns:

Type Description
DataFrame

DataFrame with market data and technical analysis features

Source code in tradingbot/utils/botclass.py
def getYFDataWithTA(
    self,
    symbol: Optional[str] = None,
    interval: str = "1m",
    period: str = "1d",
    saveToDB: bool = False,
) -> 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 across multiple runs.

    Args:
        symbol: Trading symbol (defaults to self.symbol)
        interval: Data interval (e.g., "1m", "5m", "1h", "1d")
        period: Data period (e.g., "1d", "5d", "1mo", "1y")
        saveToDB: Whether to save fetched data to database. Set to True for
                 historical backtests to enable data reuse.

    Returns:
        DataFrame with market data and technical analysis features
    """
    if not symbol:
        if self.symbol is None:
            raise ValueError("symbol parameter is required when self.symbol is None (multi-asset bot)")
        symbol = self.symbol

    data = self._data_service.get_yf_data_with_ta(
        symbol=symbol,
        interval=interval,
        period=period,
        save_to_db=saveToDB,
    )

    # Update cache for backward compatibility
    if (interval, period) == self.datasettings:
        self.data = data

    return data

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
def local_backtest(self, initial_capital: float = 10000.0) -> Dict[str, Any]:
    """
    Local-only helper: run a backtest with current instance parameters.

    Args:
        initial_capital: Starting capital for backtest

    Returns:
        Backtest results dictionary
    """
    from .backtest import backtest_bot

    results = backtest_bot(self, initial_capital=initial_capital)
    print(f"\n--- Backtest Results: {self.bot_name} ---")
    print(f"Yearly Return: {results['yearly_return']:.2%}")
    print(f"Sharpe Ratio: {results['sharpe_ratio']:.2f}")
    print(f"Number of Trades: {results['nrtrades']}")
    print(f"Max Drawdown: {results['maxdrawdown']:.2%}")
    return results

local_development(param_grid: Optional[Dict[str, List[Any]]] = None, objective: str = 'sharpe_ratio', initial_capital: float = 10000.0, n_jobs: Optional[int] = None) -> 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 Optional[Dict[str, List[Any]]]

Optional parameter grid to use. If None, uses self.param_grid or class attribute.

None
objective str

Metric to maximize ("sharpe_ratio" or "yearly_return")

'sharpe_ratio'
initial_capital float

Starting capital for backtests

10000.0
n_jobs Optional[int]

Number of parallel jobs (None = auto-detect)

None

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
def local_development(
    self,
    param_grid: Optional[Dict[str, List[Any]]] = None,
    objective: str = "sharpe_ratio",
    initial_capital: float = 10000.0,
    n_jobs: Optional[int] = None,
) -> 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.

    Args:
        param_grid: Optional parameter grid to use. If None, uses self.param_grid or class attribute.
        objective: Metric to maximize ("sharpe_ratio" or "yearly_return")
        initial_capital: Starting capital for backtests
        n_jobs: Number of parallel jobs (None = auto-detect)

    Returns:
        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
    """
    # Step 1: Optimize
    opt_results = self.local_optimize(
        param_grid=param_grid,
        objective=objective,
        initial_capital=initial_capital,
        n_jobs=n_jobs,
    )

    # Step 2: Backtest with best parameters
    print("\n" + "=" * 60)
    print("Backtesting with best parameters...")
    print("=" * 60)
    best_bot = self.__class__(**opt_results["best_params"])
    best_bot.local_backtest(initial_capital=initial_capital)

    return opt_results

local_optimize(param_grid: Optional[Dict[str, List[Any]]] = None, objective: str = 'sharpe_ratio', initial_capital: float = 10000.0, n_jobs: Optional[int] = None) -> 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 Optional[Dict[str, List[Any]]]

Optional parameter grid to use. If None, uses self.param_grid or class attribute.

None
objective str

Metric to maximize ("sharpe_ratio" or "yearly_return")

'sharpe_ratio'
initial_capital float

Starting capital for backtests

10000.0
n_jobs Optional[int]

Number of parallel jobs (None = auto-detect)

None

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
def local_optimize(
    self,
    param_grid: Optional[Dict[str, List[Any]]] = None,
    objective: str = "sharpe_ratio",
    initial_capital: float = 10000.0,
    n_jobs: Optional[int] = None,
) -> 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.

    Args:
        param_grid: Optional parameter grid to use. If None, uses self.param_grid or class attribute.
        objective: Metric to maximize ("sharpe_ratio" or "yearly_return")
        initial_capital: Starting capital for backtests
        n_jobs: Number of parallel jobs (None = auto-detect)

    Returns:
        Full optimization results dictionary

    Raises:
        ValueError: If no param_grid is defined
    """
    from .hyperparameter_tuning import tune_hyperparameters

    # Use provided grid, or fall back to class attribute
    grid = param_grid or getattr(self, "param_grid", None) or self.__class__.param_grid
    if not grid:
        raise ValueError(
            f"No param_grid defined for {self.__class__.__name__}. "
            f"Either define param_grid as a class attribute or pass it to local_optimize()."
        )

    print("=" * 60)
    print(f"Hyperparameter optimization for {self.__class__.__name__}")
    print("=" * 60)

    results = tune_hyperparameters(
        self.__class__,
        grid,
        objective=objective,
        initial_capital=initial_capital,
        verbose=True,
        n_jobs=n_jobs,
    )

    print("\n" + "=" * 60)
    print("Best parameters (paste into __init__ defaults):")
    print("=" * 60)
    for key, value in results["best_params"].items():
        print(f"    {key}: {value},")
    print()

    return results

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
def makeOneIteration(self) -> 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:
        -1: Sold
         0: No action
         1: Bought

    Raises:
        NotImplementedError: If self.symbol is None (multi-asset bot) and method not overridden
    """
    # For multi-asset bots, this method must be overridden
    if self.symbol is None:
        raise NotImplementedError(
            "Multi-asset bots must override makeOneIteration(). "
            "Single-asset bots must provide a symbol in __init__()."
        )

    # Refresh dbBot to ensure it's attached to a session
    self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)
    data = self.getYFDataWithTA(saveToDB=True, interval=self.interval, period=self.period)
    decision = self.getLatestDecision(data)
    cash = self.dbBot.portfolio.get("USD", 0)
    holding = self.dbBot.portfolio.get(self.symbol, 0)
    if decision == 1 and cash > 0:
        self.buy(self.symbol)
        return 1
    elif decision == -1 and holding > 0:
        self.sell(self.symbol)
        return -1
    else:
        print("doing nothing!")
    return 0

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
def rebalancePortfolio(self, targetPortfolio: dict[str, float], onlyOver50USD: bool = False) -> None:
    """
    Rebalance portfolio to match target weights.

    Args:
        targetPortfolio: Dictionary mapping symbols to target weights (e.g., {"VWCE": 0.8, "GLD": 0.1, "USD": 0.1})
                       Weights must sum to 1.0 (100%)
        onlyOver50USD: If True, filter out assets with target value <= $50 and redistribute weights equally
                      among remaining assets (default: False)

    Raises:
        ValueError: If weights don't sum to 1.0 (within tolerance)
    """
    self._portfolio_manager.rebalance_portfolio(targetPortfolio, only_over_50_usd=onlyOver50USD)
    # Refresh dbBot reference after portfolio update
    self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)

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
def run(self) -> None:
    """
    Execute one iteration of the bot and log results.

    Catches exceptions and logs them to the database before re-raising.
    """
    # Refresh dbBot to ensure it's attached to a session
    self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)
    bot_name = self.bot_name
    decision = -2
    try:
        decision = self.makeOneIteration()
        # Refresh again after makeOneIteration in case portfolio was updated
        self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)
        cash = self.dbBot.portfolio.get("USD", 0)

        # Handle multi-asset bots gracefully
        if self.symbol:
            holding = self.dbBot.portfolio.get(self.symbol, 0)
            holding_info = f"Holding: {holding}"
        else:
            # For multi-asset bots, show portfolio summary
            non_usd_holdings = {k: v for k, v in self.dbBot.portfolio.items() if k != "USD" and v > 0}
            holding_info = f"Holdings: {len(non_usd_holdings)} assets"

        print(f"Decision: {decision}")
        with get_db_session() as session:
            run = RunLog(
                bot_name=bot_name,
                success=True,
                result=f"Decision: {decision}, Cash: {cash}, {holding_info}, portfolio: {str(self.dbBot.portfolio)}",
            )
            session.add(run)
            # Context manager will commit automatically
    except Exception as e:
        print(f"Error in makeOneIteration: {e}")
        with get_db_session() as session:
            run = RunLog(
                bot_name=bot_name,
                success=False,
                result=str(e),
            )
            session.add(run)
            # Context manager will commit automatically
        raise e

sell(symbol: str, quantityUSD: float = -1) -> None

Sell a quantity of the specified symbol.

Parameters:

Name Type Description Default
symbol str

Trading symbol to sell

required
quantityUSD float

Amount in USD to sell (-1 means sell all holdings)

-1
Source code in tradingbot/utils/botclass.py
def sell(self, symbol: str, quantityUSD: float = -1) -> None:
    """
    Sell a quantity of the specified symbol.

    Args:
        symbol: Trading symbol to sell
        quantityUSD: Amount in USD to sell (-1 means sell all holdings)
    """
    self._portfolio_manager.sell(symbol, quantity_usd=quantityUSD, cached_data=self.data)
    # Refresh dbBot reference after portfolio update
    self.dbBot = self._bot_repository.create_or_get_bot(self.bot_name)