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):
        # The worker type names the worker: source urn:com.virtufin:worker:movingaveragepython,
        # errors com.virtufin.movingaveragepython.error on wrk.movingaveragepython.error.
        super().__init__(worker_type="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 emits limit orders at the triggering candle's close, so type is always "limit" and price is that close (since 0.2.2; before, market orders with price: null, which SimulatedExecutor rejects).

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.