Engineering note · Historical chart labels
NinjaTrader Price Labels: Keep the Value That Belongs to Each Line
An old horizontal line should display its own recorded price. If every historical label reads the latest level, moving the text around will not fix the underlying problem. First separate the stored value from where the chart draws it.
- 01Identify each segment
- 02Retain its price
- 03Choose label placement
- 04Check scale and reload
Clarify what historical means in the request
A public NinjaTrader question began with adding a price beside a line. The important clarification was that every historical level should retain its historical price. That turns a text-placement request into a state-retention requirement.
Ask when a segment starts and when it finishes. Is it a session level, a confirmed pivot or a value that changes on every bar? A level that legitimately evolves during its active interval needs a different rule from one that becomes fixed at creation.
Also define which history should be shown. All retained levels, only those intersecting the visible time range, and only the latest level are different display contracts. The right choice comes from the task, not from the number of objects that happen to fit on one screenshot.
Store a record for each completed segment
One possible design stores a segment identity, start time or bar reference, end reference and price. When the level resets, finish the old record and create a new one. The active segment can still change under its defined rule; completed records should not borrow the active segment’s latest value.
Use the same record to derive the line and its label. If the line comes from historical data but the label reads a current scalar variable, the two can disagree even though each rendering call works correctly.
Keep a stable identity across the line and label. If the implementation uses drawing objects, review how it creates or updates their tags. If it uses custom rendering, review how it selects records for each frame. Do not choose a rewrite before inspecting the existing approach.
- Segment identity: which historical level is this?
- Time boundary: where does the segment begin and end?
- Stored price: which value belongs to this segment?
- Display state: visible, clipped or intentionally omitted under the agreed rule.
Choose between segment labels and an edge column
A label beside a segment answers which price that segment represents. A column at the right edge can show several retained prices, but it needs an additional way to identify which segment each belongs to. Similar prices may produce overlapping labels.
Write down the collision rule before coding. Options include a separate list, a limited visible selection or offset labels with an explicit connector. If text is moved away from its true price height, make the relationship clear. Otherwise the readability fix can introduce a misleading chart cue.
An inset from the panel edge is a display distance. A price offset is a market-value distance. Keep those settings separate. The owner should not need to change a price value merely to move a label away from the chart border.
Recalculate screen placement when the chart renders
NinjaTrader exposes ChartScale.GetYByValue to convert a value into the chart’s vertical pixel coordinate. A custom-rendered line and its label should use the applicable panel and scale consistently. A pixel captured before a scale change is not a permanent location for that price.
For an OnRender implementation, keep the stored segment price separate from the current drawing coordinates. Redraw against the current viewport. Review the platform’s rendering and SharpDX guidance for the actual implementation, including resource lifetime; this article does not supply a compiled indicator.
Formatting is another independent concern. Use the instrument’s appropriate price representation and check that the displayed text corresponds to the stored value. Do not use label text as the authoritative data record.
Make reloads part of the acceptance example
A historical label that survives a zoom can still disappear or change after reloading the indicator. Decide how records are reconstructed from loaded history or retained state. Keep session settings and data coverage in the reproduction steps so the test compares the same input.
Use a small reviewed sequence with two completed segments and one active segment. Record the intended values before changing the viewport. Then resize, scroll, rescale and reload. Separate a missing historical input from a drawing defect instead of treating both as the same failure.
For a repair request, the useful first source section is the code that creates or resets the level and the code that draws it. Provide only authorized source, with private details removed. The smallest change depends on that evidence; a screenshot alone cannot establish whether the indicator needs a series fix, a drawing-tag fix or a different render loop.
Three acceptance checks
Proposed tests, not executed customer results. Record actual outcomes separately.
A new level starts
Input: Finish one segment and start another with a different recorded price.
Expected: Each retained segment displays its own value. Updating the active segment does not rewrite the completed label.
The viewport changes
Input: Resize, scroll and change the scale while the same segments remain available.
Expected: Line and label remain associated. Any clipping or collision follows the agreed display rule.
The indicator reloads
Input: Reload with the same source version, data range and session settings.
Expected: The defined historical records and label values are reconstructed consistently, or missing data is reported rather than invented.