Skip to content

Overview

An indicator is an immutable, self-folding value: it consumes samples one at a time and exposes its current value, without needing its state threaded as an explicit external parameter — this already is the state. In Virtufin.Core.Behaviour:

public interface IIndicator<TSelf, TSample, TValue> : ISignal<TValue>
    where TSelf : IIndicator<TSelf, TSample, TValue>
{
    TSelf Add(TSample sample);
}

Virtufin.Base.Behaviour.SimpleMovingAverage<TTime,TValue> (virtufin-dotnet) is the first concrete implementation: a time-windowed simple moving average, generic over both the time axis and the value type, backed by an ImmutableQueue of in-window samples.

Two consumption modes, one indicator class

The identical indicator instance works two ways:

  1. Inline — a strategy embeds it directly in its own IStrategyState, calls .Add(sample) per observation, reads .Value. No worker, no State store involved — see strategies/MovingAverage for the worked example (a 10-day SMA of BTCUSDT, the concrete case that drove this whole design).
  2. Worker-backed (this devkit) — IndicatorWorkerBase deploys the indicator as its own worker. State-only wouldn't be enough here: a different strategy reading it via an ad hoc GetState call inside its own Decide/Step would violate the purity every Virtufin.Core.Behaviour interface maintains. Instead the consuming strategy subscribes to the .changed topic like any other market observation, plus one bootstrap GetState read at worker startup to seed its Initial state before event-folding takes over.

Choose worker-backed when an indicator needs to be computed once and shared across multiple strategies/scenarios, or when it needs to run independently of any one strategy's lifecycle. Choose inline when it's private to a single strategy — no reason to pay for a separate deployment and State round-trip.

Where this devkit fits

IndicatorWorkerBase<TIndicator, TSample, TValue> is generic over the indicator, its sample type, and its value type — unlike ExecutorWorkerBase<TState> (virtufin-execution-devkit), which is deliberately fixed to RichTradeAction/TradeEvent because every executor in the org drives the same trade ADTs. Indicators have no such single shared sample shape (a moving average over candles, an RSI over ticks, a cross-asset spread indicator all look different), so genericity here is a real requirement, not premature abstraction.

See Bridge for the dispatch/persistence/routing details.