Algostream_strategy.StrategyThe strategy contract.
The layers below produce every ingredient of a statistical-arbitrage decision — cointegration, spread z-scores, hedge ratios, GARCH, VaR — but nothing that decided anything. Pairs.Snapshot.signal was computed, published, and read by exactly one caller: Snapshot.to_string, which printed it. This module type is where the decision goes.
Deliberately dependency-light. algostream.strategy does not depend on algostream.backtest, and it does not depend on algostream.infrastructure.event_bus. A live runner can therefore implement against S without linking the fill simulator, and the backtest can drive it without an event bus. That is the whole reason this is its own library.
Rules an implementation must honour. These are contract, not style:
ctx.ts_ns. lib/strategy is on the wall-clock lint list, so Clock.now_*, Unix.gettimeofday and Timestamp.now all fail CI here.on_event is called inside the engine's inner loop.on_event may be called with the same logical signal repeatedly — a z-score stays past its entry band for many ticks. Track what you have already acted on in state and return [] the second time, or the engine will submit an order per tick.state is expected; mutation of anything reachable through ctx is not.Every tunable is a float and must be reachable through params_of_assoc / params_to_assoc, because that pair is how algostream.optimization moves through the parameter space without knowing the concrete params type. An integer dimension is a float with an integrality check inside params_of_assoc. A parameter not reachable that way is a parameter the optimizer cannot tune.
type subscription = | Symbol of string| Bars of {}| Pair of {pair : Algostream_pairs.Pair_id.t;y_symbol : string;x_symbol : string;}The engine drives a Pairs.Per_pair.t inline for this pair and emits Event.Pair_snapshot.
| Timer_every of {}What market data the engine must feed this strategy.
module type S = sig ... endtype packed = (module S)First-class packed strategy, for code that holds a heterogeneous collection.
Look up a required parameter, returning a uniform error message when absent.
val require_in :
(string * float) list ->
string ->
lo:float ->
hi:float ->
(float, string) Stdlib.resultAs require, plus an inclusive range check.
As require, plus a check that the value is integral.