The Momentum strategy measures the rate of price change over a lookback period and takes positions when momentum exceeds threshold levels. Itβs designed to ride trends while periodically rebalancing.
The strategy only trades when days_since_rebalance >= rebalance_frequency:
fn on_market_event(&mut self, event: &MarketEvent, context: &StrategyContext) -> Result<Vec<StrategyAction>, String> { // Only rebalance on specified frequency if self.days_since_rebalance < self.rebalance_frequency { return Ok(vec![]); } if let Some(momentum) = self.calculate_momentum(&prices) { if momentum > self.momentum_threshold { // Go long } else if momentum < -self.momentum_threshold { // Close positions or go short } self.days_since_rebalance = 0; } Ok(actions)}
#[test]fn test_momentum_strategy() { let mut strategy = MomentumStrategy::new(5, 0.05); // 5-day lookback, 5% threshold let mut config = StrategyConfig::new( "test_momentum".to_string(), "Test Momentum".to_string() ); config.add_symbol(create_test_symbol()); assert!(strategy.initialize(&config).is_ok()); // Create test data with strong upward momentum let mut bars = Vec::new(); let base_time = Utc::now(); for i in 0..10 { let price = dec!(100) * (dec!(1) + Decimal::from(i) / dec!(50)); // 2% increase per day let bar = create_test_bar(price, base_time + chrono::Duration::days(i)); bars.push(bar); } let context = create_test_context_with_data(bars); // Set to rebalance frequency strategy.days_since_rebalance = 5; let actions = strategy.on_market_event(&event, &context); assert!(actions.is_ok()); // Test day end increment let day_end_actions = strategy.on_day_end(&context); assert!(day_end_actions.is_ok()); assert_eq!(strategy.days_since_rebalance, 1); // Resets after rebalancing}
From strategy.rs:629-652:When momentum exceeds threshold:
if momentum > self.momentum_threshold { let target_quantity = if let Some(price) = context.get_current_price(symbol) { (context.get_portfolio_value() * self.position_size) / price } else { Decimal::ZERO }; let current_quantity = current_position.map(|p| p.quantity).unwrap_or(Decimal::ZERO); let quantity_diff = target_quantity - current_quantity; if quantity_diff.abs() > Decimal::new(1, 4) { // Minimum trade size let side = if quantity_diff > Decimal::ZERO { Side::Buy } else { Side::Sell }; // Place order for quantity_diff }}