Skip to content

53 - Event Bus

Scope

utils/event_bus.py is a lightweight, per-process topic bus for asynchronous order side effects. It is not the cross-process market-data bus; ZeroMQ owns that job.

Each Flask worker that imports the global singleton has its own subscriber registry and 10-worker ThreadPoolExecutor. A service publishes in the process handling the request, and that process dispatches its local subscribers.

Dispatch is bounded. EventBus.DEFAULT_MAX_PENDING is 1000 callbacks queued-or-running; beyond that, further callbacks are dropped rather than queued, because the executor's own work queue is unbounded and a publisher that outruns its subscribers would otherwise grow it until the process is killed. Drops are counted and logged sparsely, and bus.stats() reports pending/max/dropped for health reporting.

Flow

text
order/sandbox service
  -> typed event with topic
  -> EventBus.publish()
  -> shared per-process thread pool
     -> log subscriber
     -> Socket.IO subscriber
     -> Telegram subscriber
     -> WhatsApp subscriber
     -> ZeroMQ proxy-relay subscriber
     -> strategy book subscriber

Callbacks are copied under a lock and submitted without blocking the publisher. _safe_call catches/logs subscriber exceptions so notification or logging failure cannot change the order response.

Topics

TopicEvent
order.placedSuccessful single order
order.failedFailed order
order.no_actionSmart order already at target
order.modified, order.modify_failedModify result
order.cancelled, order.cancel_failedCancel result
orders.all_cancelledCancel-all summary
order.updateAsync broker postback / order-WS fill or rejection, and sandbox engine transitions
gtt.placed, gtt.failedGTT placement result
gtt.modified, gtt.modify_failedGTT modify result
gtt.cancelled, gtt.cancel_failedGTT cancel result
gtt.triggered, gtt.expiredGTT lifecycle transitions
position.closedClose-position summary
basket.completedBasket summary
split.completedSplit summary
options.completedOptions-order summary
multiorder.completedMulti-leg summary
analyzer.errorAnalyzer validation/runtime error
sandbox.order_filledEngine-driven fill refresh
sandbox.auto_squareoffEngine-driven square-off refresh
sandbox.t1_settlementEngine-driven settlement refresh

Batch services suppress child events and publish one summary, preventing duplicate logs/chat alerts.

Subscribers

subscribers/register_all() wires log, Socket.IO, Telegram, and WhatsApp callbacks for the order and GTT topics. Sandbox engine-internal topics go only to Socket.IO because they are UI refresh signals rather than user API calls; they are not duplicated into analyzer logs or chat alerts. order.update is engine-driven and broker-driven for the same reason: it goes to Socket.IO and to wsproxy_subscriber, which republishes it on the ZeroMQ bus so the WebSocket proxy can relay it to clients that sent subscribe_orders.

subscribers/strategy_book_subscriber.register(bus) is wired separately, from the database-initialization thread in app.py rather than from register_all(), because it must be listening before the first order is accepted. It subscribes to order.placed, order.update, and the four batch-completion topics.

Telegram/WhatsApp subscribers skip failure and analyzer-error chat notifications. The log and Socket.IO consumers still record/emit the applicable state.

Event Data

Order events carry mode, API type, strategy, request/response data, and fields needed for notification formatting. API keys may be passed in memory for username resolution but must be stripped from persisted request logs. Do not add broker tokens or other decrypted credentials to an event.

Why Per-Process

The bus decouples side effects on a single request path with minimal dependencies. It makes no durability or cross-worker delivery guarantee. Features that require cross-process delivery, replay, or durable queues need a different transport; do not infer those properties from this bus.

Adding An Event Or Subscriber

  1. Add a typed event under events/ with a stable topic.
  2. Publish once at the service boundary, after the outcome is known.
  3. Add callbacks and registrations in subscribers/__init__.py.
  4. Keep callbacks idempotent where duplicate upstream requests are possible.
  5. Test publisher response independence and subscriber failure isolation.

Key Files

FilePurpose
utils/event_bus.pyBus and executor singleton
events/Typed payloads
subscribers/__init__.pyRegistration
subscribers/log_subscriber.pyDatabase logging
subscribers/socketio_subscriber.pyBrowser refresh/events
subscribers/telegram_subscriber.pyTelegram alerts
subscribers/whatsapp_subscriber.pyWhatsApp alerts
subscribers/wsproxy_subscriber.pyZeroMQ relay of order updates to the proxy
subscribers/strategy_book_subscriber.pyPer-strategy position book