Bars & position state

Strategies

Bars & position state

The read-only state your OnBar logic works from: price history, the current bar, where you stand in the market, and the trading session.

Bars

Your decision logic needs the price series up to now. BarCount tells you how much history exists (use it to skip warm-up); CurrentBar is the most recently closed bar; and GetBar(barsAgo) walks back — GetBar(0) is the current bar, GetBar(1) the one before it, and so on (O(1)). Use the non-throwing TryGetBar when you are not sure the bar exists. A Bar is an OHLCV value struct with StartUtc, EndUtc, Open, High, Low, Close, Volume and TickCount.

C#int  n   = BarCount;            // total bars (history + bars closed since start)
Bar  cur = CurrentBar;          // most recent closed bar
Bar  ago = GetBar(3);           // 3 bars ago
if (TryGetBar(10, out Bar b)) { /* ... b safe to use ... */ }

bool closed = IsBarClosed;      // false during an intra-bar callback (see Calculation cadence)
double range = cur.High - cur.Low;

The history available before the run started is read directly via GetBar for warm-up, but it does not trigger OnBar — only bars that close after the run subscribes drive a callback. By the time your first OnBar fires, at least one bar is guaranteed, so reading CurrentBar there is always safe.

Position & account

The base class exposes your exposure three ways — IsFlat, IsLong, IsShort — which is what makes the edge-triggered entry/exit pattern read so cleanly. For finer detail, Position carries the signed Quantity (positive long, negative short), AverageEntryPrice, LastPrice, UnrealizedPnL and RealizedPnLSession; AccountSnapshot gives CashValue, Equity, RealizedPnL, BuyingPower and more. Both are nullable, so guard before dereferencing in paths that can run before any position or account exists.

C#if (IsFlat) { /* nothing on */ }

Position? pos = Position;            // null when flat / unreported
if (pos is { } p)
    Log($"qty={p.Quantity} entry={p.AverageEntryPrice} uPnL={p.UnrealizedPnL}");

Account? acc = AccountSnapshot;      // null when the account isn't reported yet
decimal? equity = acc?.Equity;

Session, identity & risk state

Beyond bars and position, the base class surfaces the rest of the run's context as read-only properties. Reach for these to gate entries by session, label logs, or respect account-level risk state.

Property What it is
Instrument The traded symbol (string).
Account The AccountId the run is bound to.
TickSize / PointValue Instrument tick size and per-point currency value, from the instrument catalog (double?, null when the host could not resolve one).
State / StrategyName The StrategyState (Created→…→Stopped/Faulted) and the run's display name.
WorkingOrders The run's live (non-terminal) orders, as IReadOnlyList<Order>.
Calculate The CalculationMode driving OnBar (see Calculation cadence).
IsFirstTickOfBar / IsFirstBarOfSession Edge flags for rolling per-bar / per-session accumulators.
InSession / Session Whether CurrentBar is inside trading hours, and the ISessionIterator for richer session queries.
TradingHours The resolved TradingHoursTemplate (sessions, holidays, exchange time zone), or null.
RiskStateRegistry An IAccountRiskStateRegistry? — the account's live running-tally risk state (e.g. consecutive losers), shared with manual orders and other strategies. Null when the host wired none (e.g. a backtest).

Session-anchored logic

For "trade RTH only" or "flatten at the close" logic, Session returns a stateful ISessionIterator (NinjaTrader's SessionIterator) created once on first access. IsFirstBarOfSession is the place to reset per-session state; InSession gates entries to inside trading hours.

C#protected override void OnBar()
{
    if (IsFirstBarOfSession) ResetDailyCounters();   // new trading day
    if (!InSession) return;                          // RTH-only gate

    // flatten everything when this bar opens a new session
    if (Session.IsNewSession(CurrentBar.EndUtc, includesEndTimestamp: true) && !IsFlat)
        Flatten();
}
Blocked entries. When you decline an entry for a risk/session reason, call NotifyEntryBlocked(reason) instead of silently returning. The default routes the reason through the logger with an "Entry blocked: " prefix; the live runtime also raises a structured event the Strategies tab can show, so a user understands why a run looks idle.