Learn NinjaScript: Drawing Heatmaps and Backgrounds
A line says “trend is up.” A background gradient that deepens in color as trend strengthens says “trend is up, and getting stronger for three bars now.” Backgrounds are an underused signal channel — cheap to implement, expressive when done well, and invisible until the reader hovers to notice them.
BackBrush paints the current bar’s panel background. BackBrushAll paints the whole panel from edge to edge. Combined with transparent brushes, these create heatmap visualizations where color intensity encodes a continuous value.
⚙️ BackBrush vs BackBrushAll
Two properties, two scopes:
BackBrushsets the background for the current bar’s column on the panel. Per-bar; each bar can have its own color.BackBrushAllsets the background for the entire panel. Global; the whole panel shares one color.
Per-bar is what you want for heatmaps. Global is for overlay-wide alerts — “the chart goes red when we’re past our daily loss limit” — where the entire panel should carry the warning.
🎨 Transparent Brushes
A solid background brush obliterates the bars. You can’t see the candles behind opaque red paint. Backgrounds need low opacity — typically 10-25% alpha — so the bars stay visible and the color is a subtle tint rather than a mask:
// Construct a transparent brush from a color and alpha (0-255).
private Brush MakeTransparentBrush(Color color, byte alpha)
{
Color c = Color.FromArgb(alpha, color.R, color.G, color.B);
SolidColorBrush brush = new SolidColorBrush(c);
brush.Freeze(); // WPF cross-thread safety
return brush;
}
// Typical usage: 40 (15% opacity) is strong enough to see but not buries bars.
Brush greenTint = MakeTransparentBrush(Colors.LimeGreen, 40);
Freeze() is critical. WPF brushes created on a non-UI thread must be frozen before being assigned to UI-bound properties, or you’ll get cross-thread exceptions when NinjaTrader tries to render. Freezing makes the brush immutable and thread-safe.
🌡️ Heatmap Patterns
A heatmap maps a continuous value to a color gradient. Higher volatility → deeper red. Lower volatility → fainter blue. The pattern: compute the value, normalize it to a 0-1 range, map that to a color, assign as the bar’s BackBrush:
double atr = ATR(14)[0];
double atrMax = MAX(ATR(14), 100)[0];
double atrMin = MIN(ATR(14), 100)[0];
// Normalize to 0-1 based on the recent ATR range.
double t = atrMax == atrMin ? 0 : (atr - atrMin) / (atrMax - atrMin);
t = Math.Max(0, Math.Min(1, t)); // clamp
// Map t to alpha: low ATR = faint tint, high ATR = deeper tint.
byte alpha = (byte)(t * 80); // 0-80 out of 255 (~30% max opacity)
BackBrush = MakeTransparentBrush(Colors.Red, alpha);
Normalization over a rolling window (MAX/MIN of the same indicator, 100 bars) keeps the heatmap responsive to the current volatility regime. An absolute threshold — “alpha = 80 when ATR > 2.0” — makes the colors change meaning when you switch instruments.
📏 Scaling the Gradient
Two practical decisions for heatmaps:
- Normalization window. Too short (20 bars): colors flicker as the range shifts. Too long (500 bars): the heatmap becomes static for most of the chart. 50-100 bars is usually the sweet spot for intraday.
- Max alpha. Pushing alpha too high buries the bars.
60-80 out of 255(roughly 25-30% opacity) is usually the visual maximum. Anything higher and the chart becomes the heatmap rather than bars-with-context.
🛠️ The Anchor Example — MySma With an ATR Volatility Heatmap
Extending MySma with a background heatmap that tints bars redder during high-volatility bars and keeps them clean during quiet bars:
protected override void OnBarUpdate()
{
if (CurrentBar < 100) return;
Values[0][0] = SMA(Period)[0];
double atr = ATR(14)[0];
double atrMax = MAX(ATR(14), 100)[0];
double atrMin = MIN(ATR(14), 100)[0];
double t = atrMax == atrMin ? 0 : (atr - atrMin) / (atrMax - atrMin);
t = Math.Max(0, Math.Min(1, t));
byte alpha = (byte)(t * 60);
BackBrush = MakeTransparentBrush(Colors.Red, alpha);
}
private Brush MakeTransparentBrush(Color color, byte alpha)
{
Color c = Color.FromArgb(alpha, color.R, color.G, color.B);
SolidColorBrush b = new SolidColorBrush(c);
b.Freeze();
return b;
}
Quiet bars stay clean. High-volatility bars show a red tint. The gradient normalizes over the last 100 bars, so “high” is relative to recent context rather than an absolute value.
📝 Pitfalls Checklist
- Solid (non-transparent) brushes obliterating bar visibility. Alpha matters. Start low (40 out of 255) and tune up only if the tint isn’t visible enough.
- Forgetting to freeze brushes. Cross-thread exception when WPF tries to render the bar. Always call
brush.Freeze()before returning from the helper. - Creating a new brush per bar unnecessarily. GC pressure on live charts with many bars. Cache brushes keyed by alpha value if the heatmap is discrete (e.g., 10 buckets of intensity).
- Confusing
BackBrushwithBarBrush.BarBrushcolors the candle itself;BackBrushcolors the panel background behind it. Different effects; easy to swap by accident. - Gradient math that clips out of range. Always clamp the normalized value to [0, 1] before mapping to alpha. Floating-point edge cases can send
tslightly negative or above 1. - Normalizing over too short a window. 20-bar normalization means every small ATR spike looks maximally red. 100 bars is a better default for stable color meaning.








