Skip to content

CI codecov Crates.io Version Docs.rs PyPI - Version npm Documentation Status License Discord

Wingfoil

Wingfoil is a blazingly fast stream processing engine for latency-critical systems such as electronic trading and real-time AI.

Wire a graph of calculations once and Wingfoil runs it — interpreted, compiled into a single monomorphized function, or as compiled islands inside an interpreted graph. Backtest it over history, then run it live without changing the wiring.

It ships with sixteen production-ready adapters covering tick stores, message buses, market protocols and observability backends, so graphs plug into real data sources and sinks in a line.

Features

Quick Start

cargo add wingfoil            # Rust
pip install wingfoil          # Python
npm install @wingfoil/client  # TypeScript client for the web adapter

A simple linear pipeline, with all nodes ticking in lock-step:

use std::time::Duration;
use wingfoil::{RunFor, RunMode};
use wingfoil::prelude::*;

fn main() {
    GraphBuilder::new()
        .ticker(Duration::from_secs(1))
        .count()
        .map(|i| format!("hello, world {i}"))
        .print()
        .build()
        .run(RunMode::RealTime, RunFor::Cycles(3))
        .unwrap();
}

This output is produced:

hello, world 1
hello, world 2
hello, world 3

Order Book Example

Wingfoil lets you wire up complex business logic, splitting and recombining streams and modulating the frequency of data. Adapters make it easy to plug in real data sources and sinks. Here we load a CSV of AAPL limit orders, maintain an order book with the lobster crate, derive trades and two-way prices, and export both back to CSV:

let book = RefCell::new(lobster::OrderBook::default());
let get_time = |msg: &Message| NanoTime::new((msg.seconds * 1e9) as u64);

let g = GraphBuilder::new();
let (fills, prices) = csv_read(&g, &source_path, get_time, true, None)?
    .map(move |chunk: &Burst<Message>| process_orders(chunk, &book))
    .split();

let _prices_sink = prices.filter_none().distinct().csv_write(&prices_path)?;
let _fills_sink = fills.csv_write(&fills_path)?;

g.build().run(RunMode::HistoricalFrom(NanoTime::ZERO), RunFor::Forever)?;

The frequencies of the inputs and outputs are all different to each other — messages arrive in same-timestamp bursts, the top of book changes less often, and trades are sparser still. This output is produced:

AAPL best bid/ask with fills overlaid

An hour of market data — 91,998 messages — replays in about a tenth of a second. Full example.

Execution tiers

One wiring function, wrapped in nitro! { fn my_graph(g: &GraphBuilder) -> ... }, expands to a module offering all three tiers:

Tier Entry point What it is
Interpreted fluent chaining directly, or my_graph::interpreted() One dyn boundary per op; open world — threaded/busy-poll sources, feedback, bursts.
Compiled my_graph::compiled(run_mode, run_for) The whole graph monomorphized into one function, state in locals — fastest, static DAGs.
Nested (island) my_graph::nested(&g, inputs...) A compiled sub-graph mounted as one node of an interpreted graph — hot core compiled, edges stay open.

Semantics live once, in each op's cycle function — the tiers differ only in how the engine reaches it, so there is no duplicated execution logic behind those three doors. core/dual_mode has the rules governing what a nitro! wiring accepts.

Performance

Read the ratios, not the absolute times: these were captured on shared 4-core cloud VMs, each comparison measured back to back in the same run. Full method, caveats and per-workload tables: benches/README.md.

Measurement
Engine overhead per node cycle ~27 ns (10×10 graph, 100 nodes, every node ticking every cycle)
Compiled vs interpreted 4.4×–37× faster across eight workloads
Nested island vs interpreted 2.2×–10.2× faster
Interpreted vs the legacy engine 0.56×–0.84× — the port is faster on all eight
vs rxrust / tokio async streams ~79× / ~134× faster at depth 10, and the gap grows with depth

Wingfoil visits every node once per tick, in topological order. Libraries that propagate along one path at a time re-visit shared nodes once per path — so on a branch-and-recombine graph their cost doubles with every level while Wingfoil's stays flat. core/topological_sort explains the mechanism in 40 lines.

Branch/recombine cost by depth: wingfoil flat, rxrust and tokio doubling per level

Where the engine sits against FPGA, kernel-bypass and GC'd stacks — and what is deliberately not claimed — is in where wingfoil sits.

Examples

46 runnable examples, each in its own directory with a README covering what it teaches, the wiring, and its expected output. Full index: examples/README.md.

If you are new, run these three in order — they cover the whole model between them:

cargo run --manifest-path crates/wingfoil/Cargo.toml --example hello_graph   # wire → build → run
cargo run --manifest-path crates/wingfoil/Cargo.toml --example ema_crossover # fold/join/map/filter at backtest scale
cargo run --manifest-path crates/wingfoil/Cargo.toml --features csv --example order_book

Then pick a direction: adapters/ to plug in real data, core/dual_mode for the execution tiers, core/run_mode to backtest, or showcase/ for end-to-end latency tracing across processes.

Links

Get Involved!

We want to hear from you! Especially if you:

  • are interested in contributing
  • know of a project that Wingfoil would be well-suited for
  • would like to request a feature or report a bug
  • have any feedback

Please do get in touch: