Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

Building Custom Tables in NinjaTrader 8: A Friendly Guide

6 min read

If you’ve ever wished you could see all your favorite indicators—like price, RSI, ATR, and moving averages—organized neatly on your chart, you’re in luck. With NinjaTrader 8’s SharpDX graphics library, you can create your own custom table right on the chart. Let’s walk through how this works, in a laid-back, easy-to-understand way.

What’s This All About?

This indicator shows you how to build a custom table using NinjaScript. It calculates key values (think SMA, EMA, RSI, ATR, and even Rate of Change) in the background and then draws them as a table on your chart. The magic happens in a custom function called DrawTableCell(), which takes care of drawing each cell with a nice background, border, and centered text.


Key Features

  • Customizable Layout:
    You can adjust the size of each cell with CellHeight and CellWidth and choose exactly where the table appears using StartX and StartY.
  • Color Your World:
    Change the look of your table by setting different colors for the cell borders and backgrounds. This way, your dashboard fits perfectly with your chart’s style.
  • Easy Expansion:
    Want to add another indicator? No problem! Just call DrawTableCell() with the new data, specify the correct row and column, and you’re done.

How Does It Work?

  1. Initialization:
    In the OnStateChange() method, we set up our default parameters. Think of it as laying out the canvas: here, you decide the cell size, table position, and color scheme.
  2. Calculations:
    In OnBarUpdate(), the indicator computes various values like moving averages and the RSI. These values are stored in internal variables that we’ll later display in our table.
  3. Rendering the Table:
    Finally, OnRender() takes over. This method is called every time your chart updates. Here, we use SharpDX to draw each cell by calling our helper function DrawTableCell(). It’s that simple—each cell gets drawn with its own background, border, and centered text.

Why Bother with a Custom Table?

Imagine having all your important stats right on your chart—no more squinting at multiple panels or switching back and forth between windows. With your custom table, you get a quick, at-a-glance view of everything that matters to you. It’s like having your personal trading dashboard built exactly how you want it!

// Rajandran R - Creator of OpenAlgo
// website - openalgo.in / marketcalls.in
// NinjaScript Table v1.0
// Date - 17/02/2025
//
// This NinjaScript indicator demonstrates how to create and position a custom table
// on a chart using the SharpDX graphics library. The table is drawn via a custom
// method called DrawTableCell which handles the drawing of individual cells.
//
// How to Use DrawTableCell:
//
//    DrawTableCell( renderTarget, textFormat, borderBrush, text, col, row, cellWidth, cellHeight, startX, startY, bgBrush, textBrush )
//
//    text      : The string or numeric value to display in the cell.
//    col       : The column number where the cell is placed (starting at 1).
//    row       : The row number where the cell is placed (starting at 1).
//    bgBrush   : The background brush (color) for the cell.
//    textBrush : The brush (color) for the text inside the cell.
//
// The overall table layout and positioning are controlled by the following parameters:
//    CellHeight, CellWidth : Define the size of each cell.
//    StartX, StartY        : Specify the starting (top-left) position of the table on the chart.
//
// Usage Example:
//    // Draws a cell with the text "Hello World" in the first column and first row,
//    // with a blue background and white text.
//    DrawTableCell( renderTarget, textFormat, borderBrush, "Hello World", 1, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush );
//
// To add new rows or columns, increment the row or col parameters accordingly.
// You can customize the rendering (borders, fonts, colors, etc.) by modifying DrawTableCell.
  


#region Using declarations
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;

// SharpDX references
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DirectWrite;
using Brush = SharpDX.Direct2D1.Brush;
#endregion


namespace NinjaTrader.NinjaScript.Indicators
{
    public class TableCell : Indicator
    {
        // --------------------------------------------------
        // User-defined properties (Parameters)
        // --------------------------------------------------
        [NinjaScriptProperty]
        [Range(10, 200)]
        [Display(Name = "Cell Height", Order = 1, GroupName = "Table Settings")]
        public int CellHeight { get; set; }

        [NinjaScriptProperty]
        [Range(50, 400)]
        [Display(Name = "Cell Width", Order = 2, GroupName = "Table Settings")]
        public int CellWidth { get; set; }

        [NinjaScriptProperty]
        [Range(0, 2000)]
        [Display(Name = "Start X", Order = 3, GroupName = "Table Settings")]
        public int StartX { get; set; }

        [NinjaScriptProperty]
        [Range(0, 2000)]
        [Display(Name = "Start Y", Order = 4, GroupName = "Table Settings")]
        public int StartY { get; set; }

        [XmlIgnore]
        [Display(Name = "Table Border Color", Order = 5, GroupName = "Table Colors")]
        public System.Windows.Media.Brush TableBorderColor { get; set; }

        [Browsable(false)]
        public string TableBorderColorSerializable
        {
            get { return Serialize.BrushToString(TableBorderColor); }
            set { TableBorderColor = Serialize.StringToBrush(value); }
        }

        [XmlIgnore]
        [Display(Name = "Table Fill Color", Order = 6, GroupName = "Table Colors")]
        public System.Windows.Media.Brush TableFillColor { get; set; }

        [Browsable(false)]
        public string TableFillColorSerializable
        {
            get { return Serialize.BrushToString(TableFillColor); }
            set { TableFillColor = Serialize.StringToBrush(value); }
        }

        // --------------------------------------------------
        // Internal fields to store indicator values
        // --------------------------------------------------
        private double sma50Val, sma100Val, sma200Val;
        private double ema50Val, ema100Val, ema200Val;
        private double rsiVal, atrVal, rocVal;

        // SharpDX text format resource
        private TextFormat textFormat;

        // --------------------------------------------------
        // OnStateChange: Initialization & Cleanup
        // --------------------------------------------------
        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description = "Example table-drawing indicator in NinjaTrader 8 using SharpDX.";
                Name = "TableCell";
                Calculate = Calculate.OnBarClose;
                IsOverlay = true;   // Draw on top of the price panel
                DisplayInDataBox = false;
                DrawOnPricePanel = true;
                PaintPriceMarkers = false;
                ScaleJustification = ScaleJustification.Right;

                // Default parameter values
                CellHeight = 40;
                CellWidth = 100;
                StartX = 50;
                StartY = 50;
                TableBorderColor = System.Windows.Media.Brushes.Blue;
                TableFillColor = System.Windows.Media.Brushes.Black;
            }
            else if (State == State.Configure)
            {
                // Additional configuration if needed
            }
            else if (State == State.DataLoaded)
            {
                // Nothing additional needed here
            }
            else if (State == State.Terminated)
            {
                // Dispose of unmanaged resources
                if (textFormat != null)
                    textFormat.Dispose();
            }
        }

        // --------------------------------------------------
        // OnBarUpdate: Calculate indicator values
        // --------------------------------------------------
        protected override void OnBarUpdate()
        {
            if (CurrentBar < 200)
                return;

            sma50Val = SMA(Close, 50)[0];
            sma100Val = SMA(Close, 100)[0];
            sma200Val = SMA(Close, 200)[0];

            ema50Val = EMA(Close, 50)[0];
            ema100Val = EMA(Close, 100)[0];
            ema200Val = EMA(Close, 200)[0];

            rsiVal = RSI(Close, 14, 3)[0];
            atrVal = ATR(14)[0];

            if (Close[1] != 0)
                rocVal = ((Close[0] - Close[1]) / Close[1]) * 100.0;
        }

        // --------------------------------------------------
        // Private method to draw a single table cell
        // --------------------------------------------------
        private void DrawTableCell(RenderTarget renderTarget, TextFormat textFormat, Brush borderBrush, string text, int col, int row, int cellWidth, int cellHeight, int startX, int startY, Brush bgBrush, Brush txtBrush)
        {
            float x1 = startX + (col - 1) * cellWidth;
            float y1 = startY + (row - 1) * cellHeight;
            RectangleF rect = new RectangleF(x1, y1, cellWidth, cellHeight);

            renderTarget.FillRectangle(rect, bgBrush);
            renderTarget.DrawRectangle(rect, borderBrush, 1);

            using (var layout = new TextLayout(Core.Globals.DirectWriteFactory, text, textFormat, cellWidth, cellHeight))
            {
                layout.TextAlignment = SharpDX.DirectWrite.TextAlignment.Center;
                layout.ParagraphAlignment = SharpDX.DirectWrite.ParagraphAlignment.Center;
                renderTarget.DrawTextLayout(new Vector2(x1, y1), layout, txtBrush);
            }
        }

        // --------------------------------------------------
        // OnRender: Draw the table using SharpDX
        // --------------------------------------------------
        protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
        {
            base.OnRender(chartControl, chartScale);

            var renderTarget = RenderTarget;
            if (renderTarget == null)
                return;

            if (textFormat == null)
            {
                textFormat = new TextFormat(Core.Globals.DirectWriteFactory,
                    "Arial", FontWeight.Normal, FontStyle.Normal, 14.0f)
                {
                    TextAlignment = SharpDX.DirectWrite.TextAlignment.Center,
                    ParagraphAlignment = SharpDX.DirectWrite.ParagraphAlignment.Center
                };
            }

            // Convert WPF brushes to SharpDX brushes
            using (Brush borderBrush = TableBorderColor.ToDxBrush(renderTarget))
            using (Brush fillBrush = TableFillColor.ToDxBrush(renderTarget))
            using (Brush whiteBrush = System.Windows.Media.Brushes.White.ToDxBrush(renderTarget))
            using (Brush blueBrush = System.Windows.Media.Brushes.Blue.ToDxBrush(renderTarget))
            using (Brush greyBrush = System.Windows.Media.Brushes.DimGray.ToDxBrush(renderTarget))
            {
                // Row 1 (Headers)
                DrawTableCell(renderTarget, textFormat, borderBrush, "Open", 1, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "Close", 2, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "Change %", 3, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "RSI", 4, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "ATR", 5, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "Volume", 6, 1, CellWidth, CellHeight, StartX, StartY, blueBrush, whiteBrush);

                // Row 2 (Data)
                DrawTableCell(renderTarget, textFormat, borderBrush, Open[0].ToString("F2"), 1, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, Close[0].ToString("F2"), 2, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, rocVal.ToString("F2") + "%", 3, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, rsiVal.ToString("F2"), 4, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, atrVal.ToString("F2"), 5, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, Volume[0].ToString("F0"), 6, 2, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);

                // Row 3 (Headers for MAs)
                DrawTableCell(renderTarget, textFormat, borderBrush, "SMA 50", 1, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "SMA 100", 2, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "SMA 200", 3, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "EMA 50", 4, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "EMA 100", 5, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, "EMA 200", 6, 3, CellWidth, CellHeight, StartX, StartY, greyBrush, whiteBrush);

                // Row 4 (Data for MAs)
                DrawTableCell(renderTarget, textFormat, borderBrush, sma50Val.ToString("F2"), 1, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, sma100Val.ToString("F2"), 2, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, sma200Val.ToString("F2"), 3, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, ema50Val.ToString("F2"), 4, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, ema100Val.ToString("F2"), 5, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
                DrawTableCell(renderTarget, textFormat, borderBrush, ema200Val.ToString("F2"), 6, 4, CellWidth, CellHeight, StartX, StartY, fillBrush, whiteBrush);
            }
        }
    }
}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
	public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
	{
		private TableCell[] cacheTableCell;
		public TableCell TableCell(int cellHeight, int cellWidth, int startX, int startY)
		{
			return TableCell(Input, cellHeight, cellWidth, startX, startY);
		}

		public TableCell TableCell(ISeries<double> input, int cellHeight, int cellWidth, int startX, int startY)
		{
			if (cacheTableCell != null)
				for (int idx = 0; idx < cacheTableCell.Length; idx++)
					if (cacheTableCell[idx] != null && cacheTableCell[idx].CellHeight == cellHeight && cacheTableCell[idx].CellWidth == cellWidth && cacheTableCell[idx].StartX == startX && cacheTableCell[idx].StartY == startY && cacheTableCell[idx].EqualsInput(input))
						return cacheTableCell[idx];
			return CacheIndicator<TableCell>(new TableCell(){ CellHeight = cellHeight, CellWidth = cellWidth, StartX = startX, StartY = startY }, input, ref cacheTableCell);
		}
	}
}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
	public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
	{
		public Indicators.TableCell TableCell(int cellHeight, int cellWidth, int startX, int startY)
		{
			return indicator.TableCell(Input, cellHeight, cellWidth, startX, startY);
		}

		public Indicators.TableCell TableCell(ISeries<double> input , int cellHeight, int cellWidth, int startX, int startY)
		{
			return indicator.TableCell(input, cellHeight, cellWidth, startX, startY);
		}
	}
}

namespace NinjaTrader.NinjaScript.Strategies
{
	public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
	{
		public Indicators.TableCell TableCell(int cellHeight, int cellWidth, int startX, int startY)
		{
			return indicator.TableCell(Input, cellHeight, cellWidth, startX, startY);
		}

		public Indicators.TableCell TableCell(ISeries<double> input , int cellHeight, int cellWidth, int startX, int startY)
		{
			return indicator.TableCell(input, cellHeight, cellWidth, startX, startY);
		}
	}
}

#endregion

Final Thoughts

Creating your own table in NinjaTrader 8 is not only fun but also super practical. You can customize it as much as you need, and it can evolve as your trading style changes. Whether you’re a newbie or a seasoned trader, this little dashboard can help you keep your focus where it belongs—on making smart trading decisions.

I hope you enjoyed this friendly tour through building custom tables in NT8. Feel free to tweak the code, add your own indicators, and make it truly yours.

Happy trading!

Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

How to Install Amibroker and Ninjatrader on MacBook Air

This is a routine question we get from Mac users and in this tutorial, we will be learning how to install Windows 11 inside...
Rajandran R
48 sec read

How to get US Futures market live data in…

In this tutorial you will be learning how to get live US Futures Data (Es-Mini, Nymex Crude Oil etc) in Ninjatrader 8 platform for...
Aniket Kale
1 min read

Which is Better Trading Analysis Tool: NinjaTrader 8 v/s…

Both NinaTrader 8 and Amibroker are capable of advanced charting and analysis, as well as auto-trading, algo-trading support. It is very difficult to choose...
Aniket Kale
2 min read

Leave a Reply

Get Notifications, Alerts on Market Updates, Trading Tools, Automation & More