<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexandru Stefan</title>
    <description>The latest articles on DEV Community by Alexandru Stefan (@alexandrustefan_90).</description>
    <link>https://dev.to/alexandrustefan_90</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2159125%2F77f50743-35bf-4b23-9c9a-dafea5dc1355.jpg</url>
      <title>DEV Community: Alexandru Stefan</title>
      <link>https://dev.to/alexandrustefan_90</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexandrustefan_90"/>
    <language>en</language>
    <item>
      <title>Building a NinjaTrader 8 Indicator: What Actually Went Wrong</title>
      <dc:creator>Alexandru Stefan</dc:creator>
      <pubDate>Mon, 23 Feb 2026 20:04:56 +0000</pubDate>
      <link>https://dev.to/alexandrustefan_90/building-a-ninjatrader-8-indicator-what-actually-went-wrong-1lh1</link>
      <guid>https://dev.to/alexandrustefan_90/building-a-ninjatrader-8-indicator-what-actually-went-wrong-1lh1</guid>
      <description>&lt;p&gt;I'm an IT Engineer with a background in banking systems who trades futures on the side for several years now. At some point the two worlds collided: I was spending too much time drawing levels by hand before each session. Previous day high/low, weekly open, initial balance, Globex session ranges; by the time I had everything laid out on a fresh chart, I'd already missed the first move. The software instinct kicked in: automate it!&lt;/p&gt;

&lt;p&gt;So I built my own NinjaTrader 8 indicator. It turned out to be a lot harder than expected. NT8's rendering model, threading constraints, and some very specific C# compatibility quirks bit me repeatedly. Here's what actually went wrong and how I fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rendering problem
&lt;/h2&gt;

&lt;p&gt;NT8's standard &lt;code&gt;Draw.HorizontalLine&lt;/code&gt; creates persistent chart objects. With 40–50 levels across all providers (previous day/week/month OHLC, opening range, initial balance, VWAP bands, Globex Asia/London), panning the chart starts to feel sluggish. The objects accumulate, and NT has to manage them all.&lt;/p&gt;

&lt;p&gt;The fix was to skip the draw object system entirely for labels and hook directly into the native render cycle via &lt;code&gt;SharpDX.Direct2D1&lt;/code&gt;. Labels are redrawn every frame at their current pixel position. No chart objects, no stale cleanup logic, and no pan lag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChartControl&lt;/span&gt; &lt;span class="n"&gt;chartControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChartScale&lt;/span&gt; &lt;span class="n"&gt;chartScale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chartControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chartScale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;VisualPinLevelLabelsToRightEdge&lt;/span&gt;
        &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;chartControl&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;chartScale&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ChartPanel&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;RenderTarget&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;_pinnedLineLabels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;EnsurePinnedLineLabelResources&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_pinnedLineLabelTextFormat&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;_pinnedLineLabelBrush&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculate dynamic layout bounds so labels don't clip off-screen&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChartPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ChartPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;6f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChartPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;420f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VisualLevelLabelFontSize&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;6f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;PinnedLineLabel&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pinnedLineLabels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PinnedLineLabel&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Translate price value to Y pixel coordinates in real-time&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;chartScale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetYByValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;DxRectangleF&lt;/span&gt; &lt;span class="n"&gt;layoutRect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DxRectangleF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Feed into SharpDX RenderTarget&lt;/span&gt;
        &lt;span class="n"&gt;_pinnedLineLabelBrush&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ToDxColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Brush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GhostWhite&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;RenderTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_pinnedLineLabelTextFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;layoutRect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_pinnedLineLabelBrush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DxDrawTextOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoSnap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tradeoff: you now own the resource lifecycle. &lt;code&gt;EnsurePinnedLineLabelResources()&lt;/code&gt; creates the &lt;code&gt;TextFormat&lt;/code&gt; and &lt;code&gt;SolidColorBrush&lt;/code&gt; on first use and re-creates them on window resize events. You have to dispose them manually or you'll leak GPU memory. Not hard, just something NT's normal drawing API would have handled for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting weekly and monthly levels without loading a year of bars
&lt;/h2&gt;

&lt;p&gt;Previous week and month OHLC is where most indicators take a shortcut that hurts performance. The naive approach is to load hundreds of bars on the primary chart series and scan backward. On a tick chart or 1-minute chart with months of history, that's a lot of data.&lt;/p&gt;

&lt;p&gt;Instead, I use &lt;code&gt;AddDataSeries&lt;/code&gt; in &lt;code&gt;State.Configure&lt;/code&gt; to add dedicated Weekly and Monthly bar series. NT sources those independently from the primary chart, so the indicator gets completed prior-period bars without any extra chart history. When the HTF series doesn't have enough completed bars yet (fresh chart load, limited data subscription), a &lt;code&gt;BarsRequest&lt;/code&gt; fallback fires asynchronously and populates the same override slot once the data arrives.&lt;/p&gt;

&lt;p&gt;The tricky part is that &lt;code&gt;BarsRequest&lt;/code&gt; completes on a background thread. You can't touch NinjaScript objects from there; everything that touches the indicator state has to be marshalled back to the UI thread. Getting that callback chain right without deadlocking or writing stale data took a few iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that inflated Volume Profile by ~3x
&lt;/h2&gt;

&lt;p&gt;Volume Profile requires tick data: every trade's price and size. NT8 delivers ticks via &lt;code&gt;OnMarketData&lt;/code&gt;, and I was collecting &lt;code&gt;MarketDataType.Last&lt;/code&gt; ticks inside that handler.&lt;/p&gt;

&lt;p&gt;The problem: when you add secondary data series (&lt;code&gt;AddDataSeries&lt;/code&gt;), &lt;code&gt;OnMarketData&lt;/code&gt; fires for &lt;strong&gt;all series&lt;/strong&gt;, not just the primary chart. The Week and Month series are completed historical bars with no real-time subscription, but the 1-minute series does receive live ticks, which meant each tick was arriving multiple times across series. The volume profile was showing roughly 3× actual volume, and the Point of Control was drifting to wherever the noise concentrated.&lt;/p&gt;

&lt;p&gt;The fix is a single guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnMarketData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MarketDataEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BarsInProgress&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// primary series only&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarketDataType&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;MarketDataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... collect tick&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. Took longer than it should have to find because the profile &lt;em&gt;looked&lt;/em&gt; reasonable, just slightly off. The tell was comparing POC against a reference chart with known values.&lt;/p&gt;

&lt;h2&gt;
  
  
  The startup freeze on dense charts
&lt;/h2&gt;

&lt;p&gt;Load a week of 1-second bars on NQ and you're looking at hundreds of thousands of bars. For each bar, I was rendering active confluence zones, and each zone needed to know how many bars ago its session started, so I could anchor the zone's left edge at the session open.&lt;/p&gt;

&lt;p&gt;My first implementation scanned &lt;code&gt;Time[]&lt;/code&gt; backwards on every bar for every zone. On a fresh load that's O(bars × zones), and with that many bars and 20+ zones it was spending several seconds just on that loop during the historical pass. The chart appeared frozen.&lt;/p&gt;

&lt;p&gt;The fix was a dictionary keyed by session ID, populated once per session boundary and looked up in O(1) for all subsequent bars. The scan still happens once per session, and after that it's a simple dictionary lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  NT8 will reject modern C# syntax at export time
&lt;/h2&gt;

&lt;p&gt;NinjaTrader's compiled-assembly export doesn't support all C# language features, even when your local &lt;code&gt;dotnet build&lt;/code&gt; passes clean. Specific things that broke at NT export time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;record&lt;/code&gt; types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;with&lt;/code&gt; expressions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;init&lt;/code&gt;-only setters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DateOnly&lt;/code&gt; / &lt;code&gt;TimeOnly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Target-typed &lt;code&gt;new()&lt;/code&gt; in some contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The indication is that the indicator imports fine as source but fails when you try to export as a compiled assembly. You won't see a meaningful error in NT; it just fails to export or silently misbehaves. The fix was to go through the codebase and replace every instance with older C# equivalents. Not glamorous, but necessary if you want to ship a compiled binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;After all of that, the indicator loads a dense chart cleanly, shows levels from all sources without hand-drawing anything, and clusters proximate levels into visual zones so it's obvious at a glance where multiple timeframes agree.&lt;/p&gt;

&lt;p&gt;If you trade futures on NinjaTrader 8 and want to stop manually maintaining your reference levels, check out &lt;a href="https://wicklabs.xyz/indicators/key-levels-pro-nt8" rel="noopener noreferrer"&gt;Key Levels PRO&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>programming</category>
      <category>trading</category>
      <category>ninjatrader</category>
    </item>
  </channel>
</rss>
