Skip to content

behaviour Specification

Purpose

The behaviour layer models every stateful part of the trading loop (strategies, indicators, executors) as an explicit-state process, and defines how such a process becomes a worker. It is language-agnostic; the .NET reference implementation is Virtufin.Core.Behavior in the lib repo, and the process-to-worker translation is Virtufin.Behavior.DevKit in the strategy-devkit repo.

Related specs: - Scenarios -- the strategy triplet leg names which strategy code runs; this spec defines what shape that code has. - Worker Management -- the worker contract (IWorker, IMultiOutputWorker) that the Worker Translation requirement targets. - Pub/Sub Topics -- the common envelope and derived-event time rule every worker output carries.

Requirements

Requirement: Process Coalgebra

Every stateful component of the trading loop SHALL be modelled as an explicit-state process (coalgebra) with the shape (State, Input) → (State, Output) and a designated initial state.

Scenario: Explicit initial state

  • WHEN a process is defined
  • THEN it SHALL expose its initial state as a designated value
  • AND hidden state (indicator values, model weights, cooldown timers) SHALL be exposed as an explicit parameter and return value, not captured in closures

Scenario: Step threading

  • WHEN a process steps on input i from state s producing (s', o)
  • THEN the next step SHALL accept s' as its state argument
  • AND s' SHALL be the only way state evolves — there SHALL be no separate mutation channel

Requirement: Decide Morphism

The strategy's decide function SHALL be a process specialised to the trading domain: strategy state paired with a market observation and a portfolio state, producing the next strategy state and zero or more trade actions.

Scenario: Domain universes

  • WHEN a decide process is defined
  • THEN its state SHALL belong to the strategy-state universe, its market input to the market-observation universe (covering both raw feed events and derived signals), its portfolio input to the portfolio-state universe, and its actions to the trade-action universe

Scenario: Zero or more actions

  • WHEN decide steps on an observation that warrants no trade
  • THEN it SHALL emit an empty collection of actions
  • WHEN decide steps on an observation that warrants multiple simultaneous intents
  • THEN it SHALL emit all of them as one collection

Scenario: State is not part of the input pair

  • WHEN a decide process is driven over a stream of observations
  • THEN the strategy state SHALL be threaded by the driver as a separate accumulator
  • AND the stream element SHALL consist of the environment inputs (market observation, portfolio state) only — callers SHALL NOT supply strategy state per element

Requirement: Execute Pairing

decide SHALL pair with an executor of the same coalgebra shape — (ExecutionState, Action) → (ExecutionState, Event) — to form the trading loop; fills produced by the executor feed back into the portfolio state observed by the next decide step.

Scenario: Loop composition

  • WHEN decide emits a trade action and the executor produces the corresponding outcome event
  • THEN the outcome SHALL fold into the portfolio state that decide observes on its next step
  • AND the composed system SHALL be corecursive: the portfolio input of decide is an output of the loop

Requirement: Derived Portfolio State

The portfolio state observed by decide SHALL be a value derived by folding position events with an algebra (State, Event) → State; strategies observe the folded state, not the raw event stream.

Scenario: Folding position events

  • WHEN position events (opens, updates, closes, flips) occur
  • THEN the portfolio state SHALL be the fold of those events from an empty initial state
  • AND the same event sequence SHALL always yield the same portfolio state

Requirement: Scan Driver

A driver SHALL exist that folds any process over an ordered stream of inputs, emitting the pair of evolved state and output for every input.

Scenario: Per-element emission

  • WHEN a process is run over a stream of n inputs
  • THEN the driver SHALL emit exactly n (state, output) pairs, in input order

Scenario: Fresh state per subscription

  • WHEN the driver is subscribed to the same cold input stream twice
  • THEN each subscription SHALL fold from the process's initial state independently

Scenario: Checkpoint restart

  • WHEN the driver is started with an explicit checkpoint state instead of the initial state
  • THEN folding SHALL resume from the checkpoint as though prior inputs had already been applied

Scenario: Opt-in reactivity

  • WHEN a process is consumed synchronously (tests, batch backtests)
  • THEN the coalgebra SHALL remain callable directly, step by step, without the stream driver

Requirement: Worker Translation

A process SHALL be deployable as a worker through one generic translation layer, so every process kind (strategies, indicators, executors) uses the same input, validation, output and envelope rules rather than a per-kind bridge.

Scenario: Decoding or skipping input

  • WHEN a worker hosting a process receives a CloudEvent
  • THEN the translation layer SHALL decode it to the process's input, or skip it without stepping when the event is not one the process consumes
  • AND a skipped event SHALL change no state and produce no output

Scenario: Configuration validated before stepping

  • WHEN a CloudEvent lacks configuration the process requires (for example scenarioid)
  • THEN the worker SHALL reply with an error naming the missing key
  • AND it SHALL do so before stepping: the process state SHALL be unchanged and no side effect (such as placing an order) SHALL occur

Scenario: Zero or more outputs per step

  • WHEN one step yields n outputs (for example decide emitting several trade actions)
  • THEN the worker SHALL return n CloudEvents, in output order, each routed by its own topic
  • AND n = 0 SHALL publish nothing

Scenario: Common envelope on every output

  • WHEN the worker builds an output CloudEvent
  • THEN it SHALL carry the input's common envelope extensions and the derived-event time rule of the pubsub-topics spec's Common Envelope Extensions requirement
  • AND every output SHALL get its own CloudEvent id

Requirement: Glossary

The platform SHALL use the terms Event, Signal, Indicator, Process state and Market data with one meaning each, so specs, code and docs classify every value the same way. The event catalog lists everything implemented today under these terms.

Scenario: Event

  • WHEN something is an immutable fact with a time
  • THEN it SHALL be an event, and events SHALL be the only thing that travels on topics

Scenario: Signal

  • WHEN a value is folded from events ((State, Event) → State), such as an order book or a portfolio state
  • THEN it SHALL be a signal: state that never travels itself
  • AND a worker that owns a signal MAY publish an event announcing that it changed

Scenario: Indicator

  • WHEN a signal folds samples through the fixed indicator interface (Add(sample), Value), such as a moving average
  • THEN it SHALL be an indicator, and its published changed events SHALL be events about that signal

Scenario: Process state

  • WHEN a process threads state between steps (Process Coalgebra requirement)
  • THEN that state SHALL be process state, and it SHALL NOT travel

Scenario: Market data

  • WHEN a value is a fact about one market stream, raw or derived with no tuning choice (trades, book updates, the order book, candles at a venue's standard intervals, mid, spread)
  • THEN it SHALL be market data: world-scoped, identical for every consumer of that market

Scenario: Classifying a derived value

  • WHEN a value is derived from market data
  • THEN it SHALL be market data if every consumer would derive the identical value (no tuning parameter beyond the venue's own standard intervals)
  • AND it SHALL be an indicator, scenario-scoped, if it depends on a parameter a strategy or scenario chooses (a window length, a lag, a decay, a model): SMA(n), log return over a chosen lag, entropy, volatility estimates

Scenario: Topic follows the classification

  • WHEN market data is published
  • THEN it SHALL use the world-scoped market-data topics (act|hyp.<name>.exchange.<venue>.*) of the pubsub-topics spec
  • WHEN an indicator value is published
  • THEN it SHALL use the scenario-scoped indicator topics (sc.<scenarioId>.indicator.*) of the pubsub-topics spec's Indicator Events requirement

Scenario: A candle is market data

  • WHEN trades are aggregated into a candle over a venue's standard interval (1m, 1h, 1d, ...)
  • THEN the candle SHALL be market data, whatever the interval
  • AND a moving average over those candles SHALL be an indicator, because its window is a strategy's choice

Scenario: The order book is a signal published as market data

  • WHEN a worker folds order-book snapshots and deltas into a book
  • THEN the book SHALL be a signal kept in State, not an event
  • AND the worker's orderbook.changed notifications SHALL be market-data events about that signal