Architecture Overview¶
Wu uses a modular design where components communicate through defined interfaces. This document explains the structural decisions and their rationale.
Component Model¶
The library organizes around five core abstractions:
┌─────────────┐
│ Runner │ ← Orchestrates execution
└──────┬──────┘
│
┌───┴───────────────────┐
▼ ▼
┌─────────┐ ┌──────────┐
│ Reader │──────────│ Strategy │ ← Generates signals
└─────────┘ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │Portfolio │ ← Executes trades
└─────────────────┤ │
└──────────┘
Data flow moves in one direction. Readers fetch data from sources (CSV files, databases, APIs). Strategies consume that data, update indicators, and produce signals. Portfolios execute signals as trades, managing cash and positions. The runner coordinates these operations in sequence.
Polymorphism uses C's struct-and-function-pointer pattern. A base struct defines the interface (function pointers). Concrete implementations embed the base struct as their first member, then add specific state. This gives inheritance-like behavior without vtables or dynamic dispatch overhead.
State management is explicit. Every component carries its state in its struct. No hidden globals, no singletons. You can serialize a portfolio's state by copying its struct. You can inspect indicator internal buffers directly in a debugger.
Interface Contracts¶
Each abstraction defines a minimal contract:
WU_Reader provides one method: next(). Call it repeatedly to fetch
data points. It returns NULL when exhausted. The reader owns the returned
data and may reuse buffers between calls.
WU_Strategy has one method: update(). Pass it an array of data
points (one per asset). It returns an array of signals (also one per
asset). The strategy owns the signal buffer.
WU_Portfolio has three methods: update() to process signals,
value() to get current portfolio value, and pnl() to calculate
profit/loss. The portfolio maintains positions and cash internally.
WU_Indicator provides an update() method that accepts a value and
returns the computed indicator value. Indicators maintain their own
internal buffers and state.
These minimal contracts keep implementations simple. A CSV reader doesn't need to know about strategies. A strategy doesn't care where data comes from. A portfolio doesn't care how signals were generated.
Composition Over Configuration¶
The library provides building blocks rather than a framework. You wire components together explicitly:
WU_Runner runner = wu_runner_new(
portfolio,
strategy,
wu_reader_list(reader1, reader2)
);
This explicit wiring makes data flow visible. You can see what connects to what. No dependency injection, no service locators, no magic auto-wiring.
If the runner's behavior doesn't fit your needs, write your own loop. The components work independently. The runner is convenience, not requirement.
Type System¶
Wu defines three data types: Candles (OHLCV bars), Trades (tick data), and Singles (scalar values with timestamps). Strategies declare what types they accept. Readers declare what types they produce. The runner validates compatibility at construction.
This type system is intentionally minimal. It covers common use cases without trying to model every possible market data structure. If you need something different, wrap it in one of these three types or extend the type enum.
Extension Points¶
Custom Portfolios: Implement the WU_Portfolio interface to create
portfolios with different execution logic, risk models, or position
management approaches.
Custom Strategies: Implement the WU_Strategy interface. Your
strategy can use any indicator, maintain any state, and generate signals
based on any logic.
Custom Readers: Implement the WU_Reader interface to pull data from
any source. PostgreSQL, REST APIs, ZeroMQ feeds, synthetic generators—if
you can read it, you can backtest against it.
Custom Indicators: Indicators are just structs with an update function. Implement new indicators following the existing patterns.
Limitations by Design¶
No order book: Wu executes trades immediately at given prices. There's no order matching, no queue, no price-time priority. This simplification works for backtesting but doesn't model market microstructure.
No event system: Components communicate through synchronous function calls. No event bus, no message queue, no async callbacks. This keeps reasoning about execution order straightforward.
No persistence layer: Wu doesn't save or load state. Backtests run in memory from start to finish. If you need persistence, implement it around the library.
Why C?¶
C provides control over memory layout and allocation patterns. When performance matters, you can see exactly what the processor executes. No garbage collection pauses. No hidden allocations.
C also forces explicit design decisions. Without automatic memory management or inheritance hierarchies, you think carefully about ownership, lifetimes, and interfaces.
The downside is verbosity and manual memory management. The Python bindings provide a higher-level interface when C's explicitness isn't needed.