Skip to content

Event-driven strategy triggers + news/event data providers (push & polled) #534

@MDUYN

Description

@MDUYN

Summary

Today every TradingStrategy is woken on a fixed bar/time schedule (time_unit + interval). This makes a whole family of event-reactive strategies — most importantly news / NLP / on-chain event reactions — awkward or impossible to express, because the relevant event is the arrival of a message, not the close of a bar.

This issue proposes two related additions that together unlock that strategy family:

  1. A trigger abstraction so a strategy can declare "run me when X happens" in addition to (or instead of) "run me every N minutes".
  2. A news / event data provider family with two concrete implementations:
    • Polled (every N seconds, hit a REST endpoint / RSS / API).
    • Pushed (subscribe to a websocket / SSE feed; deliver messages as they arrive).

The goal is second-level reactivity (typical news-API latency is 100ms–few seconds), not true sub-millisecond HFT. That makes it a tractable Python-level addition.

Motivation

See examples/strategies_showcase/25_news_nlp_subsecond/README.md. The README currently says "not possible" because the framework lacks an event-driven trigger model. With a small additive change this becomes a 🟢 workable strategy type covering:

  • Macro release reactions (CPI, FOMC, etc.).
  • Listing / delisting announcements.
  • Hack / exploit alerts on on-chain feeds.
  • Sentiment-spike trades on social-media firehoses.
  • Any "if event of type X arrives, evaluate strategy" workflow.

Proposed design (sketch)

1. Trigger abstraction

from investing_algorithm_framework import (
    TradingStrategy, EventTrigger, IntervalTrigger, TimeUnit,
)

class NewsReactionStrategy(TradingStrategy):
    triggers = [
        IntervalTrigger(time_unit=TimeUnit.MINUTE, interval=5),  # heartbeat
        EventTrigger(source="news", filter=lambda evt: evt.symbol == "BTC"),
    ]
    data_sources = [...]

    def run_strategy(self, context, data, event=None):
        if event is not None:
            # Fired by EventTrigger — react immediately.
            ...
        else:
            # Fired by the heartbeat — periodic housekeeping.
            ...

time_unit + interval remain for backward compatibility (sugar for a single IntervalTrigger).

2. Event data provider family

class EventDataProvider(DataProvider):
    """Base class for providers that emit discrete events."""
    async def stream(self) -> AsyncIterator[Event]: ...

class PolledNewsDataProvider(EventDataProvider):
    """Polls an HTTP endpoint every N seconds, emits new items."""

class WebSocketEventDataProvider(EventDataProvider):
    """Subscribes to a websocket; emits each message as an Event."""

A new EventBus inside the runtime owns the event loop, fans out events to all registered EventTriggers, and invokes the matching strategies' run_strategy(..., event=evt).

3. Backtest semantics

For backtests, an EventDataProvider can expose a historical event tape (CSV/JSON of timestamped events). The backtest engine merges that tape with bar timestamps and replays them in chronological order, calling run_strategy once per replayed event — this gives faithful in-sample replay of event-driven strategies.

Out of scope

  • True sub-millisecond HFT (still belongs in a separate iaf_realtime runtime).
  • L2 order-book streaming (separate issue).
  • Per-event kernel-bypass / colocation concerns.

Acceptance criteria

  • Trigger abstract base + IntervalTrigger + EventTrigger implementations.
  • triggers class attribute on TradingStrategy; falls back to a single IntervalTrigger from the existing time_unit/interval if not set (backward compatible).
  • EventDataProvider base + at least one concrete provider (PolledNewsDataProvider).
  • EventBus in the live runtime; events are dispatched to matching triggers.
  • Backtest replays a historical event tape merged with bar timestamps.
  • Showcase: convert examples/strategies_showcase/25_news_nlp_subsecond from 🔴 to 🟢 with a runnable example (e.g. polled crypto-news API → simple sentiment threshold → BTC trade).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions