Skip to main content
Glama

run_backtest

Backtest a Moving Average Crossover trading strategy with customizable parameters and optional visualization of equity curves.

Instructions

Backtests a Moving Average Crossover strategy.

Args:
    symbol: Ticker symbol
    fast_ma: Fast moving average period
    slow_ma: Slow moving average period  
    start_date: Backtest start date
    end_date: Backtest end date
    visualize: If True, returns equity curve chart

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
symbolYes
fast_maYes
slow_maYes
start_dateNo2020-01-01
end_dateNo2023-12-31
visualizeNo

Implementation Reference

  • Core handler function for the 'run_backtest' tool. Fetches historical price data (stocks via yfinance, crypto via CoinGecko), implements Moving Average Crossover strategy, generates buy/sell signals, computes strategy vs buy-hold returns, Sharpe ratio, max drawdown, and optionally includes equity curve visualization.
    def run_backtest(symbol: str, fast_ma: int, slow_ma: int, start_date: str = "2020-01-01", end_date: str = "2023-12-31", visualize: bool = False) -> str:
        """
        Backtests a Moving Average Crossover strategy.
        
        Args:
            symbol: Ticker symbol
            fast_ma: Fast moving average period
            slow_ma: Slow moving average period  
            start_date: Backtest start date
            end_date: Backtest end date
            visualize: If True, returns equity curve chart
        """
        try:
            logger.info(f"Starting backtest for {symbol} (Fast: {fast_ma}, Slow: {slow_ma}) from {start_date} to {end_date}")
            # Use yfinance directly instead of get_price
            df = _fetch_data(symbol, start_date, end_date)
            
            if df.empty:
                logger.warning(f"Backtest failed: No data for {symbol}")
                return f"No data found for {symbol}"
                
            # Strategy Logic
            df['Fast_MA'] = ta.sma(df['Close'], length=fast_ma)
            df['Slow_MA'] = ta.sma(df['Close'], length=slow_ma)
            
            # Signals
            df['Signal'] = 0
            df.loc[df['Fast_MA'] > df['Slow_MA'], 'Signal'] = 1
            df['Position'] = df['Signal'].diff()
            
            # Calculate Returns
            df['Market_Return'] = df['Close'].pct_change()
            df['Strategy_Return'] = df['Market_Return'] * df['Signal'].shift(1)
            
            # Equity Curves
            df['Strategy_Equity'] = (1 + df['Strategy_Return']).cumprod()
            df['BuyHold_Equity'] = (1 + df['Market_Return']).cumprod()
            
            # Metrics
            total_return = (1 + df['Strategy_Return']).prod() - 1
            buy_hold_return = (1 + df['Market_Return']).prod() - 1
            
            # Max Drawdown
            cum_ret = (1 + df['Strategy_Return']).cumprod()
            peak = cum_ret.expanding(min_periods=1).max()
            dd = (cum_ret / peak) - 1
            max_dd = dd.min()
            
            # Sharpe Ratio (assuming 0% risk free for simplicity or use config)
            sharpe = df['Strategy_Return'].mean() / df['Strategy_Return'].std() * np.sqrt(252)
            
            result = (
                f"Backtest Results for {symbol} ({start_date} to {end_date}):\n"
                f"Strategy: MA Crossover ({fast_ma}/{slow_ma})\n"
                f"------------------------------------------------\n"
                f"Total Return: {total_return:.2%}\n"
                f"Buy & Hold Return: {buy_hold_return:.2%}\n"
                f"Sharpe Ratio: {sharpe:.2f}\n"
                f"Max Drawdown: {max_dd:.2%}"
            )
            
            if visualize:
                try:
                    from tools.visualizer import plot_line
                    df_clean = df.dropna(subset=['Strategy_Equity', 'BuyHold_Equity'])
                    chart = plot_line(
                        {
                            'x': list(range(len(df_clean))),
                            'y': [df_clean['Strategy_Equity'].tolist(), df_clean['BuyHold_Equity'].tolist()]
                        },
                        x_label="Days",
                        y_label="Equity (Initial = $1)",
                        title=f"Backtest: {symbol} MA({fast_ma}/{slow_ma})",
                        labels=["Strategy", "Buy & Hold"]
                    )
                    result += f"\n\n{chart}"
                except Exception as e:
                    logger.error(f"Error generating backtest chart: {e}")
                    result += f"\n(Visualization error: {str(e)})"
            
            logger.info(f"Backtest completed for {symbol}. Return: {total_return:.2%}")
            return result
    
        except Exception as e:
            logger.error(f"Backtest failed for {symbol}: {e}", exc_info=True)
            return f"Error running backtest: {str(e)}"
  • server.py:385-388 (registration)
    Registers run_backtest as an MCP tool using the register_tools helper in the 'Backtesting' category for the FastMCP server.
    register_tools(
        [run_backtest, walk_forward_analysis],
        "Backtesting"
    )
  • app.py:290-290 (registration)
    Groups run_backtest in the 'Backtesting' category within tools_map for the Gradio UI toolbox interfaces.
    "Backtesting": [run_backtest, walk_forward_analysis],
  • Helper function to fetch historical data, detecting crypto symbols via CoinGecko or falling back to yfinance for stocks/crypto.
    def _fetch_data(symbol: str, start: str, end: str):
        """Fetch data from either yfinance (stocks) or CoinGecko (crypto)."""
        # Try to find CoinGecko ID
        coin_id = _get_coingecko_id(symbol)
        if coin_id:
            logger.info(f"Recognized {symbol} as crypto (CoinGecko ID: {coin_id})")
            return _fetch_crypto_data(coin_id, start, end)
        else:
            # Fall back to stock data
            logger.info(f"Treating {symbol} as stock symbol")
            return yf.download(symbol, start=start, end=end, progress=False)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/N-lia/MonteWalk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server