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.