Strategy trait
Main strategy trait that all strategies must implement.
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;
}
Lifecycle:
- initialize() - Called once before backtest starts
- on_market_event() - Called for each market data event
- on_order_event() - Called when orders are filled/cancelled
- on_day_end() - Called at end of each trading day
- on_stop() - Called once when backtest completes
Example implementation:
use gb_types::*;
struct MyStrategy {
config: StrategyConfig,
position_opened: bool,
}
impl Strategy for MyStrategy {
fn initialize(&mut self, config: &StrategyConfig) -> Result<(), String> {
self.config = config.clone();
Ok(())
}
fn on_market_event(
&mut self,
event: &MarketEvent,
context: &StrategyContext,
) -> Result<Vec<StrategyAction>, String> {
let mut actions = Vec::new();
// Example: Buy on first bar
if !self.position_opened {
if let MarketEvent::Bar(bar) = event {
let order = Order::market_order(
bar.symbol.clone(),
Side::Buy,
Decimal::from(10),
self.config.strategy_id.clone()
);
actions.push(StrategyAction::PlaceOrder(order));
self.position_opened = true;
}
}
Ok(actions)
}
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())
}
}
StrategyContext
Strategy context provides access to market data, portfolio, and order management.
pub struct StrategyContext {
pub current_time: DateTime<Utc>,
pub portfolio: Portfolio,
pub market_data: HashMap<Symbol, MarketDataBuffer>,
pub pending_orders: Vec<Order>,
pub strategy_id: String,
}
market_data
HashMap<Symbol, MarketDataBuffer>
Market data buffers for each symbol
Methods:
impl StrategyContext {
pub fn new(strategy_id: String, initial_capital: Decimal) -> Self
pub fn get_position(&self, symbol: &Symbol) -> Option<&Position>
pub fn get_current_price(&self, symbol: &Symbol) -> Option<Decimal>
pub fn get_market_data(&self, symbol: &Symbol) -> Option<&MarketDataBuffer>
pub fn get_available_cash(&self) -> Decimal
pub fn get_portfolio_value(&self) -> Decimal
}
Usage in strategy:
fn on_market_event(
&mut self,
event: &MarketEvent,
context: &StrategyContext,
) -> Result<Vec<StrategyAction>, String> {
let symbol = event.symbol();
// Get current position
let position = context.get_position(symbol);
// Get current price
let price = context.get_current_price(symbol).unwrap();
// Get market data buffer
let buffer = context.get_market_data(symbol).unwrap();
let bars = buffer.get_bars(20); // Get last 20 bars
// Check available cash
let cash = context.get_available_cash();
Ok(vec![])
}
MarketDataBuffer
Buffer for market data with rolling window.
pub struct MarketDataBuffer {
pub symbol: Symbol,
pub data: Vec<MarketEvent>,
pub max_size: usize,
}
Methods:
impl MarketDataBuffer {
pub fn new(symbol: Symbol, max_size: usize) -> Self
pub fn add_event(&mut self, event: MarketEvent)
pub fn get_current_price(&self) -> Option<Decimal>
pub fn get_latest_bar(&self) -> Option<&Bar>
pub fn get_bars(&self, count: usize) -> Vec<&Bar>
}
StrategyAction
Action that a strategy can take.
pub enum StrategyAction {
PlaceOrder(Order),
CancelOrder { order_id: OrderId },
Log { level: LogLevel, message: String },
SetParameter { key: String, value: serde_json::Value },
}
Example:
let actions = vec![
StrategyAction::PlaceOrder(order),
StrategyAction::Log {
level: LogLevel::Info,
message: "Placed buy order".to_string(),
},
];
StrategyConfig
Strategy configuration parameters.
pub struct StrategyConfig {
pub strategy_id: String,
pub name: String,
pub description: String,
pub parameters: HashMap<String, serde_json::Value>,
pub symbols: Vec<Symbol>,
pub initial_capital: Decimal,
pub risk_limits: RiskLimits,
pub enabled: bool,
}
Methods:
impl StrategyConfig {
pub fn new(strategy_id: String, name: String) -> Self
pub fn add_symbol(&mut self, symbol: Symbol) -> &mut Self
pub fn set_parameter<T: Serialize>(&mut self, key: &str, value: T) -> &mut Self
pub fn get_parameter<T>(&self, key: &str) -> Option<T>
where
T: for<'de> Deserialize<'de>,
}
Example:
let mut config = StrategyConfig::new(
"my_strategy".to_string(),
"My Strategy".to_string()
);
config
.add_symbol(Symbol::equity("AAPL"))
.set_parameter("lookback_period", 20)
.set_parameter("threshold", 0.05);
// Later, in strategy:
let lookback: usize = config.get_parameter("lookback_period").unwrap_or(10);
Built-in strategies
BuyAndHoldStrategy
Simple buy and hold strategy for testing.
pub struct BuyAndHoldStrategy {
config: StrategyConfig,
initialized: bool,
position_opened: bool,
}
Methods:
impl BuyAndHoldStrategy {
pub fn new() -> Self
}
Example:
let strategy = Box::new(BuyAndHoldStrategy::new());
let result = engine.run_with_strategy(strategy).await?;
MovingAverageCrossoverStrategy
Moving average crossover strategy.
pub struct MovingAverageCrossoverStrategy {
config: StrategyConfig,
initialized: bool,
short_period: usize,
long_period: usize,
position_size: Decimal,
last_signal: Option<Signal>,
}
Methods:
impl MovingAverageCrossoverStrategy {
pub fn new(short_period: usize, long_period: usize) -> Self
}
Example:
// 10-day / 20-day MA crossover
let strategy = Box::new(MovingAverageCrossoverStrategy::new(10, 20));
let result = engine.run_with_strategy(strategy).await?;
Logic:
- Buys when short MA crosses above long MA
- Sells when short MA crosses below long MA
- Uses 95% of available capital
MomentumStrategy
Momentum strategy based on price momentum.
pub struct MomentumStrategy {
config: StrategyConfig,
initialized: bool,
lookback_period: usize,
momentum_threshold: Decimal,
position_size: Decimal,
rebalance_frequency: usize,
days_since_rebalance: usize,
}
Methods:
impl MomentumStrategy {
pub fn new(lookback_period: usize, momentum_threshold: f64) -> Self
}
Example:
// 5-day lookback, 5% threshold
let strategy = Box::new(MomentumStrategy::new(5, 0.05));
let result = engine.run_with_strategy(strategy).await?;
Logic:
- Buys when momentum > threshold (strong positive momentum)
- Sells when momentum < -threshold (strong negative momentum)
- Rebalances every N days (default: 5)
MeanReversionStrategy
Mean reversion strategy using z-scores.
pub struct MeanReversionStrategy {
config: StrategyConfig,
initialized: bool,
lookback_period: usize,
entry_threshold: Decimal,
exit_threshold: Decimal,
position_size: Decimal,
max_position_size: Decimal,
}
Methods:
impl MeanReversionStrategy {
pub fn new(lookback_period: usize, entry_threshold: f64, exit_threshold: f64) -> Self
}
Example:
// 20-day lookback, 2.0 entry, 1.0 exit
let strategy = Box::new(MeanReversionStrategy::new(20, 2.0, 1.0));
let result = engine.run_with_strategy(strategy).await?;
Logic:
- Buys when price is 2+ standard deviations below mean
- Sells when price is 2+ standard deviations above mean
- Exits positions when price returns toward mean
- Scales into positions (25% increments)
RsiStrategy
RSI (Relative Strength Index) strategy.
pub struct RsiStrategy {
config: StrategyConfig,
initialized: bool,
lookback_period: usize,
oversold_threshold: Decimal,
overbought_threshold: Decimal,
position_size: Decimal,
}
Methods:
impl RsiStrategy {
pub fn new(lookback_period: usize, oversold_threshold: f64, overbought_threshold: f64) -> Self
}
Example:
// 14-day RSI, 30/70 thresholds
let strategy = Box::new(RsiStrategy::new(14, 30.0, 70.0));
let result = engine.run_with_strategy(strategy).await?;
Logic:
- Buys when RSI < oversold threshold (default: 30)
- Sells when RSI > overbought threshold (default: 70)
- Uses 95% of capital
StrategyMetrics
Strategy performance metrics.
pub struct StrategyMetrics {
pub strategy_id: String,
pub start_time: DateTime<Utc>,
pub end_time: Option<DateTime<Utc>>,
pub total_return: Decimal,
pub annualized_return: Decimal,
pub volatility: Decimal,
pub sharpe_ratio: Option<Decimal>,
pub max_drawdown: Decimal,
pub total_trades: u64,
pub winning_trades: u64,
pub losing_trades: u64,
pub win_rate: Decimal,
pub average_win: Decimal,
pub average_loss: Decimal,
pub profit_factor: Decimal,
pub total_commissions: Decimal,
}
Methods:
impl StrategyMetrics {
pub fn new(strategy_id: String) -> Self
}