Skip to content

Python Strategies

MovingAveragePython is the Python twin of the C# MovingAverage worker — same strategy rule, same canonical JSON contract, different engine. It shows the pattern is language-portable even though IDecide itself is a C# type.

The worker

from virtufin.worker_devkit import WorkerBase

class MovingAverageWorker(WorkerBase):
    def __init__(self):
        super().__init__(
            response_type="movingaveragepython.response",
            source="urn:virtufin:worker:movingaveragepython",
        )
        # ... SMA ring buffer, intent latch, portfolio fold ...

    def handle(self, event_data):
        # dispatch by event_data["type"]: position -> fold, candle -> step
        ...

_worker = MovingAverageWorker()

def Process(cloud_event):
    return _worker.process(cloud_event)

Python workers ship as source: a module under src/ with a module-level Process(cloud_event), stdlib only, with virtufin-workmanager (the devkit) resolved at load time.

Contract parity

Config keys are CloudEvent extension attributes (extension-only; payload fields ignored) — e.g. timewindowdays (default 20), symbol, scenarioid (required) — the same convention the C# bridge uses.

Response envelope: at parity with C#. An emitted order publishes the pubsub-topics spec's actual "Order submitted" event: topic sc.<scenarioid>.trading.order.submitted, stable ce-type = com.virtufin.trading.order.submitted (not overridable via replytopic), ce-subject = order/<order_id>, payload {"order_id":...,"symbol":...,"side":"buy"|"sell", "type":"market"|"limit","qty":...,"price":...,"venue":..., "submitted_at":...} (see MovingAveragePython/src/moving_average_python.py::_order_submitted_response). Routing goes through the publishtopic extension attribute -- the devkit's WorkerBase.response() only accepts id/message kwargs (built for the generic command/success envelope shape), so the worker builds this envelope directly, same as the old ad hoc one it replaces. This worker only ever emits market orders (no limit-price concept), so type is always "market" and price is always null.

Testing

pytest runs the suite; conftest.py falls back to a minimal worker_devkit stub when the private PyPI feed is unreachable locally — CI always installs the real package.