Strategies
Strategies in market_model operate on price paths only. They produce a
Signal at each step given the price history so far. They have zero
knowledge of order books, portfolio state, or exchange mechanics.
PriceStrategy trait
#![allow(unused)] fn main() { pub trait PriceStrategy: Send + Sync { fn signal(&self, history: &[f64]) -> Signal; } }
Strategies are stateless: given the same history slice, they always
produce the same Signal. Any state (warm-up windows, rolling averages)
is derived from the history on each call.
Available strategies
MaCrossover
Long when the fast moving average is above the slow MA, short when below.
#![allow(unused)] fn main() { let s = MaCrossover::new(5, 20); }
RsiStrategy
Long when RSI drops below the oversold threshold, short when above overbought.
#![allow(unused)] fn main() { let s = RsiStrategy::new(14, 30.0, 70.0); }
BuyAndHold / AlwaysFlat
Baseline strategies: always long, and never trade.
Using strategies with the runner
PriceStrategy is separate from Simulatable. To evaluate a strategy on
simulated paths, iterate the runner output and call signal at each step:
#![allow(unused)] fn main() { let runner = SimulationRunner::new(gbm, config); let result = runner.run(); for path_idx in 0..result.n_paths { let mut history = Vec::new(); for entry in 0..=result.n_steps { let price = result.get(path_idx, entry).unwrap(); history.push(*price); let sig = strategy.signal(&history); // accumulate PnL, record signals, etc. } } }
Adapter for engine
Strategies defined here can be used inside the full exchange engine via an
adapter. The adapter converts Signal::Long to a buy market order,
Signal::Short to a sell, and Signal::Flat to no action. The adapter
lives in the engine crate, not here.