Orderflow & depth
Orderflow & depth
The contracts a footprint / volume-profile / DOM plugin consumes: the per-bar orderflow model
in TradeStrike.Pipeline.OrderFlow, and the live Level-2 / Level-3 market-depth surface in
TradeStrike.Pipeline.MarketDepth.
On this page
Orderflow — TradeStrike.Pipeline.OrderFlow
The per-bar orderflow model footprint-class indicators read, plus the volume-profile-window seam a drawing
tool requests. Add using TradeStrike.Pipeline.OrderFlow; to reach these types.
OrderFlowBar
A readonly record struct: the OHLCV Bar plus parallel per-price-level bid/ask
volume and trade-print counts. Prices are discretized to integer ticks; level i sits at
(LowTicks + i) × TickSize + PriceOffset. Designed for allocation-free reads in tight render loops.
| Member | Description |
|---|---|
Bar Bar |
The OHLCV bar this snapshot corresponds to. |
long LowTicks |
Integer tick offset of level index 0. |
double TickSize |
Price increment used for level indexing. |
double PriceOffset |
Constant added to every level price (0 for ordinary bars; non-zero only on derived / tick-grouped bars). |
int LevelCount |
Number of populated price levels (0 for an empty bar). |
long TotalBidVolume / TotalAskVolume
|
Cross-level side totals. O(1) (precomputed in ctor). |
long TotalTradeCount |
Total trade prints across all levels. O(1). |
long Delta |
TotalAskVolume - TotalBidVolume (positive = net buying). |
long LocalDeltaHigh / LocalDeltaLow
|
Intra-bar running-delta extremes with a 0-open floor (high ≥ 0, low ≤ 0). Cum-delta candle high/low = entering_cum + these. |
OrderFlowBarStats? Stats |
Optional tick-derived statistics; null when the bar wasn't built from a tick stream. |
bool HasStats |
True when Stats is populated. |
bool HasTradeCountSplit |
True when per-side trade-print counts are available. |
OrderFlowLevel GetLevel(int index) |
Read one level by index (0 = lowest price). Throws on out-of-range. |
long GetBidTradeCount(int index) / GetAskTradeCount(int index)
|
Per-side trade-print count at a level; 0 when the split wasn't tracked. |
bool TryGetLevelAtPrice(double price, out OrderFlowLevel level) |
Look up the level at a price; true only when it lands on a tick within range. |
bool TryGetPoc(out double pocPrice, out long pocVolume) |
Point of Control = highest-total-volume level (lower price wins on a tie). |
Constructors. Three overloads, additive:
OrderFlowBar.cs// Full-fidelity: bid/ask/trade-count arrays + running-delta extremes (validated).
public OrderFlowBar(Bar bar, long lowTicks, double tickSize,
long[] bidVolumes, long[] askVolumes, long[] tradeCounts,
long localDeltaHigh, long localDeltaLow,
long[]? bidTradeCounts = null, long[]? askTradeCounts = null,
OrderFlowBarStats? stats = null, double priceOffset = 0);
// Defaults the running-delta extremes to 0 (empty / no-tick bars).
public OrderFlowBar(Bar bar, long lowTicks, double tickSize,
long[] bidVolumes, long[] askVolumes, long[] tradeCounts);
// Volume-only: every level's TradeCount = 0 (seeding, fixtures).
public OrderFlowBar(Bar bar, long lowTicks, double tickSize,
long[] bidVolumes, long[] askVolumes);
OrderFlowLevel — one price row, a readonly record struct returned by
GetLevel / TryGetLevelAtPrice:
| Member | Description |
|---|---|
double Price |
Price at this level. |
long BidVolume / AskVolume
|
Volume credited to bids (sell-aggressor) / asks (buy-aggressor). |
long TradeCount |
Number of distinct trade prints at this price (Sierra-style). |
long TotalVolume |
BidVolume + AskVolume. |
long Delta |
AskVolume - BidVolume. |
OrderFlowBarStats
A readonly record struct of per-bar quantities that can only be computed during tick
aggregation (they depend on tick order). Carried by OrderFlowBar.Stats; null when no
tick aggregator ran.
OrderFlowBarStats.cspublic readonly record struct OrderFlowBarStats(
long RunningDeltaMax,
long RunningDeltaMin,
long AskVolumeSinceHigh,
long BidVolumeSinceHigh,
long AskVolumeSinceLow,
long BidVolumeSinceLow,
double VolumePerSecond,
bool? LastTickIsBid = null,
long FirstTickVolume = 0,
System.TimeSpan? TradeSpan = null);
| Member | Description |
|---|---|
long RunningDeltaMax / RunningDeltaMin
|
Highest / lowest the intra-bar running delta reached (no 0 floor). |
long AskVolumeSinceHigh / BidVolumeSinceHigh
|
Volume traded since the bar last made its high. |
long AskVolumeSinceLow / BidVolumeSinceLow
|
Volume traded since the bar last made its low. |
double VolumePerSecond |
Short-window volume rate. |
bool? LastTickIsBid |
Whether the bar's last classified tick hit the bid (sell-aggressor). Null when untracked. |
long FirstTickVolume |
Size of the bar's first classified trade. |
System.TimeSpan? TradeSpan |
Elapsed time from the bar's first to last print. |
long CotHigh |
AskVolumeSinceHigh - BidVolumeSinceHigh (commitment-of-traders at the high). |
long CotLow |
AskVolumeSinceLow - BidVolumeSinceLow. |
static OrderFlowBarStats Empty |
All-zero stats. |
OrderFlowResolution
Backfill granularity an orderflow consumer requests. Coarser = far less data = faster backfill, at the cost of aggressor fidelity. The value round-trips by name, so the numeric assignments are free to change.
| Value | Meaning |
|---|---|
Auto = 0 |
Default. Concrete resolution derived centrally from the chart's primary bar period. Not a point on the fidelity scale. |
Tick = 1 |
Full tick data: exact per-trade aggressor, exact volume-at-price and delta. Heaviest backfill. |
Second = 2 |
Native 1-second OHLCV; aggressor estimated per second. Bid/ask approximate. |
Minute = 3 |
Native 1-minute OHLCV; aggressor estimated per minute. Coarsest approximation. |
OrderFlowWindowRequest
An immutable readonly record struct describing the volume-at-price profile a tool wants for a
time window. Value equality makes it double as the host's cache key — same window + parameters resolve to
the same cached profile; dragging an anchor produces a distinct key and a fresh build.
OrderFlowWindowRequest.cspublic readonly record struct OrderFlowWindowRequest(
DateTime FromUtc,
DateTime ToUtcExclusive,
OrderFlowResolution Resolution,
int TickAggregation,
double ValueAreaPercentage,
bool EnableSmoothing,
int SmoothingPeriod,
int HighVolumeNodeCount,
int LowVolumeNodeCount,
int HighVolumeRegionSize,
int LowVolumeRegionSize);
bool HasWindow — true only when ToUtcExclusive > FromUtc.
VolumeProfileWindow
A finished volume-at-price profile for one window, a plain sealed record free of engine types so
a contracts-only drawing tool can render it. Produced host-side and handed back via
IOrderFlowWindowSource.
VolumeProfileWindow.cspublic sealed record VolumeProfileWindow(
IReadOnlyList<VolumeProfileBin> Bins,
double BucketStep,
double MaxVolume,
double MaxAbsDelta,
double TotalVolume,
double Poc,
double ValueAreaHigh,
double ValueAreaLow,
double HighPrice,
double LowPrice,
IReadOnlyList<double> HighVolumeNodePrices,
IReadOnlyList<double> LowVolumeNodePrices,
DateTime FromUtc,
DateTime ToUtcExclusive);
bool IsEmpty — true when the window enclosed no traded volume.
Each row is a VolumeProfileBin (readonly record struct):
| Member | Description |
|---|---|
double Price |
Bucketed price of this row. |
double TotalVolume |
Total traded volume at the bucket. |
double Delta |
Signed delta (ask − bid volume). |
bool InValueArea |
Whether the bucket falls inside the value area. |
IOrderFlowWindowSource
Host-provided seam that resolves an OrderFlowWindowRequest to a VolumeProfileWindow.
Surfaced to drawing tools through IDrawingToolRenderContext.OrderFlowWindows.
IOrderFlowWindowSource.cspublic interface IOrderFlowWindowSource
{
// Returns the cached profile when ready, or null while a build is in flight.
// On a cache miss, kicks an async build and repaints when done. Never blocks.
VolumeProfileWindow? GetOrRequest(in OrderFlowWindowRequest request);
}
Call it from the synchronous render loop: it returns immediately, so a tool paints a "calculating"
placeholder on null and the real profile on the next frame after the async build completes.
Market depth — TradeStrike.Pipeline.MarketDepth
The live order-book surface. A provider may offer aggregated Level-2 (IMarketDepthFeed), per-order
Level-3 / MBO (IMarketByOrderFeed), or both; the OrderBook unifies whichever arrives.
Add using TradeStrike.Pipeline.MarketDepth;.
IMarketDepthFeed
Live aggregated Level-2 depth feed. Emits DepthUpdate per price-level change. Events may arrive on
any thread; the feed reference-counts per instrument so subscribers share one upstream connection.
IMarketDepthFeed.cspublic interface IMarketDepthFeed : IDisposable
{
IDisposable Subscribe(string instrumentId, Action<DepthUpdate> onUpdate);
event Action? SubscriptionReset; // drop state; default no-op
}
SubscriptionReset fires when consumers must drop all depth state (typically a connection loss).
Disposing the token from Subscribe unsubscribes; the upstream sub is torn down when the last
subscriber drops.
IMarketByOrderFeed
Live Market-By-Order (Level-3) feed. Emits MboEvent per resting-order lifecycle event
(Add / Modify / Cancel). MboEvent.OrderId must be the venue's exchange-supplied id, stable across
Modify (including a price move).
IMarketByOrderFeed.cspublic interface IMarketByOrderFeed : IDisposable
{
IDisposable Subscribe(string instrumentId, Action<MboEvent> onEvent);
event Action? SubscriptionReset; // clear book on connection loss; default no-op
}
SeedingMarketByOrderFeed is a public, venue-agnostic decorator
(: IMarketByOrderFeed) that wraps an inner feed and seeds late subscribers with the current
book — it multiplexes every consumer of an instrument onto one inner subscription and replays the live book
as a burst of MboAction.Add events when a second view subscribes. Construct it with
new SeedingMarketByOrderFeed(innerFeed); it does not own the inner feed's lifecycle.IOrderBook
Read-only view onto a live OrderBook — the surface exposed via
IIndicatorContext.OrderBook. The mutation API stays on the concrete OrderBook; indicator
code only reads. Callers that need a frozen copy use OrderBookSnapshot.Capture.
| Member | Description |
|---|---|
string InstrumentId |
The instrument this book tracks. |
double BestBidPrice / BestAskPrice
|
Top of book, or double.NaN when the side is empty. |
double Spread |
Ask − bid, or NaN when either side is empty. |
int OrderCount |
Total per-order count across both sides. Zero in L2-only mode. |
int BidLevelCount / AskLevelCount
|
Distinct price levels per side. |
IEnumerable<PriceLevel> EnumerateBids() |
Bid levels top of book first (highest price). Live view. |
IEnumerable<PriceLevel> EnumerateAsks() |
Ask levels top of book first (lowest price). Live view. |
PriceLevel? GetLevel(Side side, double price) |
Level at an exact price; null when absent. |
long MboAddCount / MboModifyCount / MboCancelCount |
Monotonic MBO diagnostics counters. |
long MboDuplicateAddCount / MboModifyMissCount / MboCancelMissCount |
Miss / duplicate diagnostics (high misses = Modify/Cancel whose Add never landed). |
int PeakOrderCount / PeakLevelCount |
Peak observed counts since construction. |
OrderBook
The concrete sealed class OrderBook : IOrderBook for one instrument. Accepts both aggregated L2 and
per-order MBO events into a unified per-level surface. Not internally synchronised — feed code holds the lock.
| Member | Description |
|---|---|
OrderBook(string instrumentId) |
Construct for an instrument (throws on blank id). |
void ApplyDepth(in DepthUpdate update) |
Apply one aggregated L2 event. Set creates/replaces a level's size; Delete (or size 0) removes it. |
void ApplyMbo(in MboEvent evt) |
Apply one MBO event (Add / Modify / Cancel). Unknown id on Modify/Cancel is a silent no-op (a price-move Modify resurrects the order). |
void Clear() |
Drop every level + per-order entry (used on reconnect). |
Plus the full IOrderBook read surface above. | |
OrderBookSnapshot
Immutable, render-safe copy of a book at one moment — flat Bids / Asks arrays
(index 0 = top of book) so iteration is allocation-free and rows match visual ladder order. Per-order detail is
dropped; each row is aggregated only.
| Member | Description |
|---|---|
string InstrumentId |
Instrument the source book tracks. |
DateTime CapturedAtUtc |
Wall-clock time the snapshot was taken (UTC). |
IReadOnlyList<Row> Bids / Asks
|
Rows, top of book first. |
double BestBidPrice / BestAskPrice
|
Bids[0].Price / Asks[0].Price, or NaN when empty. |
record struct Row(double Price, long Size, int OrderCount) |
One snapshot row (OrderCount informational, 0 in L2 mode). |
static OrderBookSnapshot Capture(IOrderBook book, DateTime capturedAtUtc, int maxLevelsPerSide = int.MaxValue) |
Capture a flat copy under the feed lock. maxLevelsPerSide caps depth per side. |
static OrderBookSnapshot Empty(string instrumentId, DateTime capturedAtUtc) |
A "no data yet" default for renderers. |
PriceLevel
One price level inside an OrderBook (a sealed class). Carries the aggregated
Size and, when MBO-fed, an ordered per-order queue. A level constructed only from L2 reports
OrderCount == 0.
| Member | Description |
|---|---|
double Price |
Price of this level (immutable while in the book). |
Side Side |
Which side this level is on. |
long Size |
Aggregated total size (sum of the per-order queue when MBO-fed). |
int OrderCount |
Per-order entries at this level. 0 in L2-only mode. |
IEnumerable<OrderBookEntry> EnumerateOrders() |
Per-order queue front to back. Empty in L2-only mode. Live view. |
OrderBookEntry
One resting order in an MBO-mode book (a sealed class; identity is OrderId).
| Member | Description |
|---|---|
string OrderId |
Venue-supplied id, stable for the order's lifetime. |
Side Side |
Side the order rests on. |
double Price |
Current price (changes on a price-move Modify). |
long Size |
Current size (changes on Modify). |
ulong QueuePriority |
Vendor queue position (smaller = front; 0 = preserve arrival order). |
DepthUpdate
One aggregated Level-2 depth event, a readonly record struct passed by value. Feed sources pass
SequenceNumber = 0; the framework's sequencer stamps it during fan-out.
DepthUpdate.cspublic readonly record struct DepthUpdate(
long SequenceNumber,
DateTime ExchangeTimestampUtc,
string InstrumentId,
Side Side,
DepthAction Action,
double Price,
long Size);
DepthAction
Kind of update a DepthUpdate carries (an enum : byte).
| Value | Meaning |
|---|---|
Set = 0 |
Replace the size at this price level; creates the level if absent. |
Delete = 1 |
Remove the price level entirely (carried Size ignored). |
MboEvent
One Market-By-Order event, a readonly record struct passed by value. Carries the individual
resting order's identity, price, size and queue priority. On a price-move Modify, PrevPrice holds the
old price (double.NaN when unchanged).
MboEvent.cspublic readonly record struct MboEvent(
long SequenceNumber,
DateTime ExchangeTimestampUtc,
string InstrumentId,
string OrderId,
Side Side,
MboAction Action,
double Price,
long Size,
ulong QueuePriority,
double PrevPrice);
MboAction
Lifecycle of an MBO event (an enum : byte). Mirrors Rithmic DepthByOrder NEW / CHANGE / DELETE.
| Value | Meaning |
|---|---|
Add = 0 |
A new resting order appeared. The book must not already contain the id. |
Modify = 1 |
An order changed size and/or moved price. Size 0 is NOT a delete here. |
Cancel = 2 |
Order is gone; the book removes it by OrderId. |
Side
Side of the book a depth or order event applies to (an enum : byte). Two-valued — every
depth event is sided at the source (aggressor classification on a trade is separate, via TickFlags).
| Value | Meaning |
|---|---|
Bid = 0 |
Resting buy order. Stacks under best bid in descending price order. |
Ask = 1 |
Resting sell order. Stacks above best ask in ascending price order. |
Example — reading the book from an indicator
DepthImbalance.csusing TradeStrike.Pipeline.Indicators;
using TradeStrike.Pipeline.MarketDepth;
// Inside an indicator: read top-of-book imbalance each calculation.
protected override void OnCalculate()
{
IOrderBook book = Context.OrderBook;
if (book is null) return;
double bid = book.BestBidPrice, ask = book.BestAskPrice;
if (double.IsNaN(bid) || double.IsNaN(ask)) return; // one side empty
PriceLevel? bestBid = book.GetLevel(Side.Bid, bid);
PriceLevel? bestAsk = book.GetLevel(Side.Ask, ask);
long bidSize = bestBid?.Size ?? 0, askSize = bestAsk?.Size ?? 0;
// Imbalance in [-1, 1]: positive = bid-heavy.
long total = bidSize + askSize;
Imbalance[0] = total == 0 ? 0 : (double)(bidSize - askSize) / total;
}
// To hand the book to a render thread, snapshot it under the feed callback:
var snap = OrderBookSnapshot.Capture(Context.OrderBook, DateTime.UtcNow, maxLevelsPerSide: 20);