Skip to main content
GlowBack provides a flexible strategy framework for building and testing trading algorithms. Strategies implement the Strategy trait and receive market events to make trading decisions.

Strategy trait

All strategies must implement the Strategy trait, which provides hooks for market events, order events, and lifecycle management:
pub trait Strategy: Send + Sync {
    fn initialize(&mut self, config: &StrategyConfig) -> Result<(), String>;
    fn on_market_event(&mut self, event: &MarketEvent, context: &StrategyContext) -> Result<Vec<StrategyAction>, String>;
    fn on_order_event(&mut self, event: &OrderEvent, context: &StrategyContext) -> Result<Vec<StrategyAction>, String>;
    fn on_day_end(&mut self, context: &StrategyContext) -> Result<Vec<StrategyAction>, String>;
    fn on_stop(&mut self, context: &StrategyContext) -> Result<Vec<StrategyAction>, String>;
    fn get_config(&self) -> &StrategyConfig;
    fn get_metrics(&self) -> StrategyMetrics;
}

Strategy context

The StrategyContext provides access to market data, portfolio state, and pending orders:
context.get_position(&symbol)        // Get current position for a symbol
context.get_current_price(&symbol)   // Get latest market price
context.get_market_data(&symbol)     // Get market data buffer
context.get_available_cash()         // Get available cash
context.get_portfolio_value()        // Get total portfolio value

Strategy configuration

Configure strategies using StrategyConfig:
let mut config = StrategyConfig::new(
    "my_strategy".to_string(),
    "My Trading Strategy".to_string()
);

config.add_symbol(Symbol::equity("AAPL"));
config.set_parameter("short_period", 10);
config.set_parameter("long_period", 20);
config.set_parameter("position_size", 0.95);
config.initial_capital = Decimal::from(100000);

Example: Moving average crossover strategy

1

Define the strategy struct

Create a struct to hold your strategy state:
use gb_types::strategy::*;
use rust_decimal::Decimal;

#[derive(Debug, Clone)]
pub struct MovingAverageCrossoverStrategy {
    config: StrategyConfig,
    initialized: bool,
    short_period: usize,
    long_period: usize,
    position_size: Decimal,
    last_signal: Option<Signal>,
}

#[derive(Debug, Clone, PartialEq)]
enum Signal {
    Buy,
    Sell,
}
2

Implement the constructor

Create a constructor with default parameters:
impl MovingAverageCrossoverStrategy {
    pub fn new(short_period: usize, long_period: usize) -> Self {
        let mut config = StrategyConfig::new(
            "ma_crossover".to_string(),
            "Moving Average Crossover".to_string()
        );
        config.set_parameter("short_period", short_period);
        config.set_parameter("long_period", long_period);
        config.set_parameter("position_size", 0.95);

        Self {
            config,
            initialized: false,
            short_period,
            long_period,
            position_size: Decimal::new(95, 2),
            last_signal: None,
        }
    }
}
3

Calculate technical indicators

Implement helper methods for calculating indicators:
impl MovingAverageCrossoverStrategy {
    fn calculate_sma(&self, prices: &[Decimal], period: usize) -> Option<Decimal> {
        if prices.len() < period {
            return None;
        }
        
        let sum: Decimal = prices.iter().rev().take(period).sum();
        Some(sum / Decimal::from(period))
    }
    
    fn get_recent_prices(&self, context: &StrategyContext, symbol: &Symbol) -> Vec<Decimal> {
        if let Some(buffer) = context.get_market_data(symbol) {
            buffer.get_bars(self.long_period + 1)
                .iter()
                .map(|bar| bar.close)
                .collect()
        } else {
            Vec::new()
        }
    }
}
4

Implement the Strategy trait

Implement the required trait methods:
impl Strategy for MovingAverageCrossoverStrategy {
    fn initialize(&mut self, config: &StrategyConfig) -> Result<(), String> {
        self.config = config.clone();
        self.short_period = self.config.get_parameter("short_period").unwrap_or(10);
        self.long_period = self.config.get_parameter("long_period").unwrap_or(20);
        self.position_size = self.config.get_parameter::<f64>("position_size")
            .map(Decimal::from_f64_retain)
            .flatten()
            .unwrap_or(Decimal::new(95, 2));
        self.initialized = true;
        Ok(())
    }
    
    fn on_market_event(
        &mut self,
        event: &MarketEvent,
        context: &StrategyContext,
    ) -> Result<Vec<StrategyAction>, String> {
        if !self.initialized {
            return Ok(vec![]);
        }
        
        let symbol = event.symbol();
        let prices = self.get_recent_prices(context, symbol);
        
        if prices.len() < self.long_period {
            return Ok(vec![]);
        }
        
        let short_ma = self.calculate_sma(&prices, self.short_period);
        let long_ma = self.calculate_sma(&prices, self.long_period);
        
        if let (Some(short), Some(long)) = (short_ma, long_ma) {
            let current_signal = if short > long {
                Some(Signal::Buy)
            } else if short < long {
                Some(Signal::Sell)
            } else {
                None
            };
            
            // Check for signal change
            if current_signal != self.last_signal {
                let mut actions = Vec::new();
                
                match current_signal {
                    Some(Signal::Buy) => {
                        // Open long position
                        let available_cash = context.get_available_cash();
                        if let Some(price) = context.get_current_price(symbol) {
                            let quantity = (available_cash * self.position_size) / price;
                            if quantity > Decimal::ZERO {
                                let order = Order::market_order(
                                    symbol.clone(),
                                    Side::Buy,
                                    quantity,
                                    self.config.strategy_id.clone()
                                );
                                actions.push(StrategyAction::PlaceOrder(order));
                            }
                        }
                    },
                    Some(Signal::Sell) => {
                        // Close long position
                        if let Some(position) = context.get_position(symbol) {
                            if position.quantity > Decimal::ZERO {
                                let order = Order::market_order(
                                    symbol.clone(),
                                    Side::Sell,
                                    position.quantity,
                                    self.config.strategy_id.clone()
                                );
                                actions.push(StrategyAction::PlaceOrder(order));
                            }
                        }
                    },
                    None => {}
                }
                
                self.last_signal = current_signal;
                return Ok(actions);
            }
        }
        
        Ok(vec![])
    }
    
    fn on_order_event(&mut self, _event: &OrderEvent, _context: &StrategyContext) -> Result<Vec<StrategyAction>, String> {
        Ok(vec![])
    }
    
    fn on_day_end(&mut self, _context: &StrategyContext) -> Result<Vec<StrategyAction>, String> {
        Ok(vec![])
    }
    
    fn on_stop(&mut self, _context: &StrategyContext) -> Result<Vec<StrategyAction>, String> {
        Ok(vec![])
    }
    
    fn get_config(&self) -> &StrategyConfig {
        &self.config
    }
    
    fn get_metrics(&self) -> StrategyMetrics {
        StrategyMetrics::new(self.config.strategy_id.clone())
    }
}

Built-in strategies

GlowBack includes several built-in strategies for reference:

Buy and hold

Simple strategy that buys on the first bar and holds until the end

Moving average crossover

Trades based on short-term and long-term moving average crossovers

Momentum

Buys when price momentum exceeds a threshold, sells on negative momentum

Mean reversion

Trades when price deviates significantly from its mean (z-score based)

RSI

Trades based on Relative Strength Index overbought/oversold levels

Strategy actions

Strategies return StrategyAction to communicate with the engine:
pub enum StrategyAction {
    PlaceOrder(Order),
    CancelOrder { order_id: OrderId },
    Log { level: LogLevel, message: String },
    SetParameter { key: String, value: serde_json::Value },
}

Placing orders

let order = Order::market_order(
    symbol.clone(),
    Side::Buy,
    quantity,
    strategy_id.clone()
);
actions.push(StrategyAction::PlaceOrder(order));

Market data buffer

Access historical market data using the MarketDataBuffer:
if let Some(buffer) = context.get_market_data(&symbol) {
    // Get the latest bar
    let latest_bar = buffer.get_latest_bar();
    
    // Get the last N bars
    let bars = buffer.get_bars(20);
    
    // Get current price
    let price = buffer.get_current_price();
}

Best practices

GlowBack uses rust_decimal::Decimal for financial calculations to avoid floating-point precision errors. Always use Decimal::from() or Decimal::from_f64_retain() when converting from f64.
Always check self.initialized at the start of event handlers to ensure the strategy is properly configured before processing events.
Market data may not be available for all symbols at all times. Always check for None when accessing market data and handle missing data appropriately.
Only use data that would have been available at the time of the decision. The StrategyContext provides data up to current_time.
Test your strategy across different symbols, time periods, and market conditions to ensure robustness.

Next steps

Data sources

Learn how to configure data providers

Backtesting

Run backtests to evaluate your strategy

Performance analytics

Analyze strategy performance metrics

Python usage

Use GlowBack from Python

Build docs developers (and LLMs) love