Skip to content

Python

virtufin-execution-devkit (PyPI, the virtufin feed) is the Python mirror of Virtufin.Execution.DevKit: the same bridge from an executor to a WorkManager worker, for the Python engine (text/x-python, application/x-python) and the Python local WorkManager (virtufin.worker_devkit.local). It needs virtufin-worker-devkit 0.42.0 or later.

Module What
virtufin.execution_devkit.executor Executor (one order in, one trade event out, hidden state), ExecutorWorkerBase
virtufin.execution_devkit.orders the order.submitted payload: OrderSubmitted, decode, encode, limit_order, topic, subject, order_id_of (the order contract of Virtufin.Trading.Contracts)
virtufin.execution_devkit.trade_events OrderAccepted, OrderFilled, OrderCanceled, OrderExpired, fill_id, to_output (TradeEventJson)

An executor worker

from datetime import datetime, timezone
from virtufin.execution_devkit import Executor, ExecutorWorkerBase, OrderFilled

class FillAtLimit(Executor):
    def execute(self, state, order):
        if order.price is None:
            raise ValueError("needs a limit price")
        return state, OrderFilled(order.order_id, order.qty, order.price, None, datetime.now(timezone.utc))

class FillAtLimitWorker(ExecutorWorkerBase):
    def __init__(self):
        super().__init__("fillatlimit", FillAtLimit())

_worker = FillAtLimitWorker()
Start = _worker.Start          # the activation's worker id scopes the derived ids
Process = _worker.process

Dispatch

Per input, in this order, as in C# (see Bridge):

  1. anything but com.virtufin.trading.order.submitted is ignored;
  2. the payload is decoded (orders.decode: every field required except price and strategy_id, amounts at 8 decimals half away from zero, the C# error texts) and the side checked; a malformed order is an error response and does not take its order id;
  3. an order whose order_id this instance already took is skipped (the last 100 000);
  4. scenarioid is required;
  5. the executor runs; its exception is an error response (Internal error: <message> on wrk.<type>.error, or the order's replytopic) and the order id stays taken;
  6. the trade event is published on sc.<scenarioid>.trading.order.<event> with ce-subject order/<order_id>, the order's envelope (virtufin.worker_devkit.envelope.stamp) and eventtime; then the executor's state advances.

WorkerBase.process adds the redelivery rules: a repeated order event (same source and id) never reaches handle, and every output gets an id derived from the worker id, the order event and the output, so a fresh instance that executes a replayed order again republishes the same event (with the same fill_id, trade_events.fill_id(order_id)).

Conformance

The C# SimulatedExecutor and the Python SimulatedExecutorPython (execution-workers) run the same vectors, conformance/simulated-executor.v1.json there, so a Python executor rejects, fills, carries and deduplicates exactly as a C# one: SimulatedExecutor, conformance vectors.

Tests

cd src/python && uv run --extra dev pytest -q