Orderflow
Orderflow ADVANCED
Per-price-level bid/ask volume per bar: the data behind footprint, delta and POC tools. Opt in once and the framework aggregates it for you.
On this page
Opting in
Footprint-class indicators need per-level, per-side traded volume. Building that for history means
loading tick (or second/minute) data, which is expensive, so the host only loads it when an indicator
on the chart asks. Tag the indicator with [RequiresOrderFlow] and read the per-bar data
through ctx.OrderFlow(seriesIndex), an ISeries<OrderFlowBar> aligned 1:1
with the bars.
Say how much you read. [RequiresOrderFlow] asks for the full footprint. An indicator that
reads only the per-bar totals (volume, buy volume, sell volume, trade count, delta) declares
[RequiresOrderFlow(OrderFlowDetail.Totals)]. The host then loads the venue's own bars
where they carry the buy/sell split, instead of the footprint. A chart loads the most any of its
indicators asks for, so a totals indicator next to a footprint simply reads the footprint's totals.
With totals, read TotalAskVolume, TotalBidVolume,
TotalTradeCount and Delta: historical bars can come without price levels.
ctx.OrderFlow still returns a series of
the right length, but on a chart that was not built with historical order flow every historical entry is
an empty snapshot (LevelCount == 0). When you add an indicator marked
[RequiresOrderFlow] to such a chart, the host reloads the chart with order-flow backfill
enabled.
The attribute covers the chart's own series, index 0. A series you add yourself is loaded
as bars unless you add it with ctx.AddDataSeries(spec, OrderFlowDetail.Totals) or
ctx.AddDataSeries(spec, OrderFlowDetail.PriceLevels); see
Adding a series.
The data arrives for history and live, as a pre-aggregated OrderFlowBar read in
OnDataLoaded and OnBarUpdate. If you also want the raw live trade stream, opt into ProcessesMarketData separately; the two
combine.
PocMarker.csusing SabrTrader.Pipeline.Indicators;
using SabrTrader.Pipeline.OrderFlow;
using SabrTrader.Pipeline.Plots;
using SabrTrader.Pipeline.Series;
[IndicatorDescription("Marks each bar's volume Point of Control: the price level with the most traded volume.")]
[IndicatorCategory(IndicatorCategory.OrderFlow)]
[IndicatorInput(IndicatorInputKind.None)]
[RequiresOrderFlow] // loads historical order flow for this chart
public sealed class PocMarker : IndicatorBase
{
private readonly ChunkedSeries<double> _poc = new();
public PocMarker(IIndicator? parent = null) : base(parent)
{
AddPlot(new Plot("POC", _poc, PlotStyle.Dots, new ChartColor(255, 152, 0), 4.0));
}
public override void OnDataLoaded(IIndicatorContext ctx)
{
var of = ctx.OrderFlow(0);
for (int i = of.Count - 1; i >= 0; i--)
_poc.Append(PocOf(of[i]));
}
public override void OnBarUpdate(IIndicatorContext ctx)
{
_poc.Append(PocOf(ctx.OrderFlow(0)[0]));
}
private static double PocOf(in OrderFlowBar bar)
=> bar.TryGetPoc(out double price, out _) ? price : double.NaN;
}
What an OrderFlowBar gives you
An OrderFlowBar is a value type holding the underlying Bar plus its
per-price-level volume, built for allocation-free reads in tight render loops. The members you use most:
| Member | Meaning |
|---|---|
Bar |
The OHLCV bar this order-flow snapshot belongs to. |
LevelCount / GetLevel(i)
|
Number of price levels and the level at index i (index 0 = lowest price). A bar with no trades has zero levels. |
TryGetLevelAtPrice(price, out level) |
Looks up the level at a price. True when the price is inside the bar's range and on the tick grid. |
TryGetPoc(out pocPrice, out pocVolume) |
Point of Control: the level with the highest total volume. On a tie the lower price wins. |
Delta / TotalBidVolume / TotalAskVolume / TotalTradeCount
|
Bar-level aggregates, precomputed, O(1). Delta is ask minus bid. |
TotalUpTickVolume / TotalDownTickVolume
|
Uptick and downtick volume for the bar by the tick rule (a trade at an unchanged price keeps the previous direction). O(1); 0 when Stats is null. |
LocalDeltaHigh / LocalDeltaLow
|
Highest and lowest running delta inside the bar, starting from 0 at the open (high >= 0, low <= 0). A cumulative-delta candle's wicks are the entering cumulative delta plus these. |
TickSize / LowTicks / PriceOffset
|
The price grid: level i sits at (LowTicks + i) × TickSize + PriceOffset. PriceOffset is 0 except on derived bars whose grid is anchored off the tick grid (tick grouping). |
Provenance / IsEstimated / HasTickRuleClassifiedVolume
|
How the bid/ask split was obtained: OrderFlowProvenance.Exact for a venue-stamped ladder, EstimatedFromBars when estimated from OHLCV bars, TickRuleClassified when some volume was credited by the tick rule because the venue sent no aggressor side (flags). |
Each OrderFlowLevel carries Price, BidVolume,
AskVolume, TradeCount, plus computed TotalVolume and
Delta (ask minus bid). When the producer tracked a per-side print split,
HasTradeCountSplit is true and GetBidTradeCount(i) /
GetAskTradeCount(i) return it; otherwise they return 0. The full member list, including
the helper types for building order flow yourself, is in the
order flow & depth reference.
Per-bar statistics
Some quantities depend on the order of the trades and can only be computed while ticks are aggregated.
When available, OrderFlowBar.HasStats is true and Stats (an
OrderFlowBarStats) exposes:
-
RunningDeltaMax/RunningDeltaMin: running-delta extremes within the bar. -
DeltaHighSetAfterLow: which extreme was set last (Sierra Chart's Finish AskVol BidVol Diff). -
AskVolumeSinceHigh,BidVolumeSinceHigh,AskVolumeSinceLow,BidVolumeSinceLow, and the commitment-of-traders helpersCotHigh/CotLow. -
FirstTradeUtc/LastTradeUtcandTradeSpan(last minus first). These are true UTC; to show them on the chart, convert withChartDisplayTime.FromUtc(utc, ctx.DisplayTimeZone)and keep the field itself unchanged. -
VolumePerSecond,FirstTickVolume,LastTickIsBid,LastTradePrice,LastTickRuleDirection. -
UpTickVolume/DownTickVolume: the tick-rule totals, also surfaced asOrderFlowBar.TotalUpTickVolume/TotalDownTickVolume.
Bars built without a tick aggregator (empty bars, multi-timeframe seeding, ladders without trade order)
carry Stats == null, so check HasStats first.
Stats == null.
Backfill resolution
OrderFlowResolution sets the granularity of the history loaded for order flow:
Tick (finest, exact aggressor and delta), Second, Minute
(coarsest, fastest, with estimated bid/ask), and Auto (the default, derived from the chart's
bar period: intraday charts get tick data, higher timeframes get second or minute data). When several
indicators on a chart ask for different resolutions, the chart loads the finest one requested.
IndicatorBase implements IOrderFlowResolutionAware, so every indicator has an
OrderFlowResolution property. The settings dialog shows it as a "Resolution" dropdown on
indicators marked [RequiresOrderFlow].
Open Interest
For the venue-reported open interest, tag the indicator with [RequiresOpenInterest] and
read ctx.OpenInterest(seriesIndex): a scalar series aligned 1:1 with the bars, with
double.NaN on bars that report no open interest. The attribute steers backfill routing; the
channel itself is always reachable. Hosts without an open-interest channel return an empty series, so
read defensively (TryGet, and treat a miss as NaN).
Order flow gives you the data; turning it into footprint cells, profile columns or POC lines is a rendering job. See Custom rendering.
Dynamic order-flow requirement
When the need for order flow depends on runtime configuration instead of being fixed for the class,
implement IDynamicOrderFlowRequirement, the runtime counterpart of
[RequiresOrderFlow]:
public interface IDynamicOrderFlowRequirement
{
bool RequiresOrderFlow { get; }
event Action? RequirementChanged;
}
The host reads RequiresOrderFlow whenever it plans a chart's backfill (chart build,
workspace restore, adding the indicator). Raise RequirementChanged when a configuration change
turns the need on; if the chart was built without historical order flow, the host reloads it with order
flow enabled.