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:
- Inline — a strategy embeds it directly in its own
IStrategyState, calls.Add(sample)per observation, reads.Value. No worker, no State store involved — seestrategies/MovingAveragefor the worked example (a 10-day SMA of BTCUSDT, the concrete case that drove this whole design). - Worker-backed (this devkit) —
IndicatorWorkerBasedeploys the indicator as its own worker. State-only wouldn't be enough here: a different strategy reading it via an ad hocGetStatecall inside its ownDecide/Stepwould violate the purity everyVirtufin.Core.Behaviourinterface maintains. Instead the consuming strategy subscribes to the.changedtopic like any other market observation, plus one bootstrapGetStateread at worker startup to seed itsInitialstate 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.