Skip to content

Bridge

StrategyWorkerBase<TS, TM, TP, TA> is the bridge between the pure IDecide coalgebra and the WorkManager's worker contract (IWorker.ProcessAsync(CloudEvent)).

Topic binding

A strategy needs both market and position events. Bind with two explicit topics at CreateWorker (CreateWorkerRequest.topics; see virtufin-workmanager's multi-topic worker support) -- e.g. the scenario's market-data feed and its position feed. Do not bind with a wildcard: the deployed Dapr pubsub component is pubsub.redis (Redis Streams), which has no wildcard subscription support -- a worker bound to sc.<scenarioId>.> (a pattern this doc used to recommend) subscribes to a Redis Stream literally named "sc.<scenarioId>.>", which nothing is ever published to. Both topics arrive on the one worker and are separated by CloudEvent type.

Dispatch

flowchart TD
    E[CloudEvent] --> T{type?}
    T -->|virtufin.position.*| POS[PortfolioAlgebra.Apply<br/>fold holdings]
    POS --> N1[no response]
    T -->|virtufin.market.*| M{is TMarket?}
    M -->|no| N2[skipped]
    M -->|yes| STEP["Decide.Step(state, (market, portfolio))"]
    STEP --> ENC["ToOrderSubmission"]
    ENC --> RESP["Order submitted CloudEvent"]
    T -->|other| N3[ignored]
  • Position events are the feedback edge: fills fold into the portfolio, so the next decision sees updated holdings. No response is emitted.
  • Market events step the strategy; an emitted action becomes the pubsub-topics spec's "Order submitted" event (see ToOrderSubmission and BuildEnvelope). At most one action per step is supported today -- WorkerBase.IWorker.ProcessAsync returns exactly one response CloudEvent per trigger.
  • TMarket filtering: choosing RichMarketEvent as TMarket accepts derived signals (candles, VWAP, volatility) but skips raw MarketEvent ticks — a deliberate type-level filter.

Response routing

Unlike a typical WorkerBase worker (which publishes on output.Type, overridable per message via replytopic), the trading envelope keeps ce-type fixed at com.virtufin.trading.order.submitted regardless of replytopic -- it's a stable identifier per the pubsub-topics spec, independent of the per-scenario topic. Routing instead goes through WorkerBase.PublishTopicAttribute (publishtopic), set by BuildEnvelope to sc.<scenarioid>.trading.order.submitted; the WorkManager reads that extension to pick the publish topic and strips it before publish, so subscribers never see it (see virtufin-workmanager's WorkManager.PublishOutputAsync).

State

Strategy state and portfolio state live in worker instance fields — the engine keeps one worker object alive for the instance's lifetime, so state threads across deliveries. State is the driver's accumulator, never part of the input pair (see the behaviour spec's "State is not part of the input pair" requirement).

Config

Config is extension-only. ConfigResolution reads CloudEvent extension attributes (where the WorkManager stamps CreateWorkerRequest.config); payload fields are never consulted as config.