Error C0234 Custom Indicator

I’m trying to build a custom indicator and keep getting a lot of the same errors CS0234 type or namespace etc.


I have done the repair, deleted the file I added. Repaired again. Looked for 3rd party Dll. That isn’t in the list. Not sure what to do next. And this was a new install.

You’re missing a reference in the top of the indicator.

2 Likes

@several is correct, you’re missing a reference.

Display attribute is in the System.ComponentModel.DataAnnotations namespace so you need to add the following at the top of your code. I believe this should do it.

using System.ComponentModel.DataAnnotations;

Here is the full code if anyone is willing to take a look and find my errors.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.DrawingTools;
using SharpDX;
using SharpDX.Direct2D1;

namespace NinjaTrader.NinjaScript.Indicators
{
public class TapeReaderReversals : Indicator
{
private Dictionary<string, DXMediaMap> dxmBrushes;
private SharpDX.RectangleF reuseRectangle;
private SharpDX.Vector2 reuseVector1, reuseVector2;
private Series vap;
private Series ma;
private Series spike;
private Series convic;
private Series exptypprice;
private Series allSpikes;
private Series convicInput;
private Series typPriceSeries;
private SMA sma;
private Series signalBrushKey;

    protected override void OnStateChange()
    {
        if (State == State.SetDefaults)
        {
            Description = @"TapeReader with Reversals for trend and volume analysis. Paints candles: Blue (Reversal/Buy), Green (Bullish), Gray (Status Quo), Orange (Flux), Dark Red (Warning/Sell), Red (No Trend), Plum (Volume Divergence).";
            Name = "TapeReaderReversals";
            Calculate = Calculate.OnBarClose;
            IsOverlay = true;
            DisplayInDataBox = true;
            PaintPriceMarkers = true;
            IsSuspendedWhileInactive = false;
            BarsRequiredToPlot = 60;

            WantAllBuySpikes = true;
            DarkMode = true;
            ShowArrows = true;
            ShowLabels = true;
            Length = 12;
            SellAlert = -1.75;
            ReversalAlert = 1.25;
            ConfirmLength = 18;
            Lookback = 10;
            DeviationLength = 60;
            Deviate = 2.0;
            ShadowWidth = 1;

            DarkModeBuy = Brushes.White;
            LiteModeBuy = Brushes.Blue;
            Sell = CreateColor(210, 59, 104); // Dark Red
            EndOfTrend = Brushes.Red;
            OhMy = Brushes.Plum;
            Flux = Brushes.Orange;
            Stay = Brushes.Green;
            Quo = Brushes.Gray;

            if (DarkModeBuy != null && DarkModeBuy.CanFreeze) DarkModeBuy.Freeze();
            if (LiteModeBuy != null && LiteModeBuy.CanFreeze) LiteModeBuy.Freeze();
            if (Sell != null && Sell.CanFreeze) Sell.Freeze();
            if (EndOfTrend != null && EndOfTrend.CanFreeze) EndOfTrend.Freeze();
            if (OhMy != null && OhMy.CanFreeze) OhMy.Freeze();
            if (Flux != null && Flux.CanFreeze) Flux.Freeze();
            if (Stay != null && Stay.CanFreeze) Stay.Freeze();
            if (Quo != null && Quo.CanFreeze) Quo.Freeze();

            dxmBrushes = new Dictionary<string, DXMediaMap>
            {
                { "darkModeBuy", new DXMediaMap { MediaBrush = DarkModeBuy } },
                { "liteModeBuy", new DXMediaMap { MediaBrush = LiteModeBuy } },
                { "sell", new DXMediaMap { MediaBrush = Sell } },
                { "endOfTrend", new DXMediaMap { MediaBrush = EndOfTrend } },
                { "ohMy", new DXMediaMap { MediaBrush = OhMy } },
                { "flux", new DXMediaMap { MediaBrush = Flux } },
                { "stay", new DXMediaMap { MediaBrush = Stay } },
                { "quo", new DXMediaMap { MediaBrush = Quo } }
            };

            Print($"TapeReaderReversals Version: 2025-08-24; Property Initialized: WantAllBuySpikes={WantAllBuySpikes}");
        }
        else if (State == State.Configure)
        {
            if (Length < 1) Length = 1;
            if (ConfirmLength < 1) ConfirmLength = 1;
            if (Lookback < 1) Lookback = 1;
            if (DeviationLength < 1) DeviationLength = 1;
            if (Deviate < 0) Deviate = 0;
            if (ShadowWidth < 1) ShadowWidth = 1;

            vap = new Series<double>(this);
            ma = new Series<double>(this);
            spike = new Series<double>(this);
            convic = new Series<double>(this);
            exptypprice = new Series<double>(this);
            allSpikes = new Series<double>(this);
            convicInput = new Series<double>(this);
            typPriceSeries = new Series<double>(this);
            signalBrushKey = new Series<string>(this);
            sma = SMA(vap, 12);

            Print("TapeReaderReversals compiled successfully on " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }

    protected override void OnBarUpdate()
    {
        if (CurrentBar < 2) return;

        BarBrushes[0] = Brushes.Transparent;
        CandleOutlineBrushes[0] = Brushes.Transparent;

        // Volume Accumulation Percentage
        double totalBP = 0;
        double totalVol = 0;
        for (int i = 0; i < Length && CurrentBar - i >= 0; i++)
        {
            double bp = (High[i] != Low[i]) ? (2 * Close[i] - High[i] - Low[i]) / (High[i] - Low[i]) : 0;
            totalBP += Volume[i] * (double.IsNaN(bp) ? 0 : bp);
            totalVol += Volume[i];
        }
        vap[0] = totalVol != 0 ? 100 * totalBP / totalVol : 0;
        ma[0] = sma[0];

        // Spike Detection
        double sDev = StDevLogReturns(Lookback) * Math.Sqrt(Lookback / (Lookback - 1.0));
        double m = sDev * Close[1];
        spike[0] = (Close[0] - Close[1]) / (m + double.Epsilon);
        spike[0] = Math.Max(-5, Math.Min(5, spike[0]));

        // Volume Divergence
        double volStDev = RelativeVolumeStDev(DeviationLength);
        bool aboveDev = volStDev >= Deviate;
        bool increase = Volume[0] > Volume[1];
        bool decrease = Volume[0] < Volume[1];
        bool devIncrease = increase && aboveDev;
        bool devDecrease = decrease && aboveDev;
        bool volumeDivergence = devIncrease || devDecrease;

        // Conviction Logic
        double sumCon = 0;
        for (int i = 0; i < ConfirmLength && CurrentBar - i >= 0; i++)
        {
            double convictSpread = High[i] - Low[i];
            double bodyTop = Close[i] > Open[i] ? Close[i] : Open[i];
            double bodyBottom = Close[i] < Open[i] ? Close[i] : Open[i];
            double body = bodyTop - bodyBottom;
            double perBody = convictSpread != 0 ? body / convictSpread : 0;
            double rangePer = Low[i] != 0 ? convictSpread / Low[i] : 0;
            double bodyPer = rangePer * perBody;

            double bc = Close[i] > Open[i] && Volume[i] > Volume[i + 1] ? 2 + 2 * bodyPer :
                        Close[i] > Open[i] && Volume[i] <= Volume[i + 1] ? 1 + 1 * bodyPer : 0;
            double brc = Close[i] < Open[i] && Volume[i] > Volume[i + 1] ? -2 - 2 * bodyPer :
                         Close[i] < Open[i] && Volume[i] <= Volume[i + 1] ? -1 - 1 * bodyPer : 0;
            sumCon += bc + brc;
        }
        convicInput[0] = sumCon;
        convic[0] = EMA(EMA(EMA(convicInput, 2), 2), 2)[0];

        // Typical Price EMA
        typPriceSeries[0] = (High[0] + Low[0] + Close[0]) / 3;
        exptypprice[0] = EMA(EMA(EMA(typPriceSeries, 3), 3), 3)[0];

        // Conviction and Flux Logic
        bool flux = exptypprice[0] < exptypprice[1] && convic[0] < convic[1];
        bool chop = (exptypprice[0] < exptypprice[1] && convic[0] > convic[1]) ||
                    (exptypprice[0] > exptypprice[1] && convic[0] < convic[1]);
        bool stay = !flux && !chop;

        // AllSpikes Logic
        allSpikes[0] = ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                        (volumeDivergence && spike[0] > 1)) ? 1 :
                       vap[0] < ma[1] ? -1 : allSpikes[1];

        // Signal Color Logic
        bool buyCondition = (!WantAllBuySpikes && CrossesAbove(vap, ma, 3) &&
                            ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                             (volumeDivergence && spike[0] > 1))) ||
                            (WantAllBuySpikes && allSpikes[1] == -1 &&
                            ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                             (volumeDivergence && spike[0] > 1)));

        string selectedBrush;
        if (buyCondition)
            selectedBrush = DarkMode ? "darkModeBuy" : "liteModeBuy"; // Blue (LiteMode) or White (DarkMode)
        else if (vap[0] < ma[1])
            selectedBrush = "endOfTrend"; // Red (No Trend)
        else if (devIncrease || devDecrease)
            selectedBrush = "ohMy"; // Plum (Volume Divergence)
        else if (vap[0] < ma[0] || spike[0] <= SellAlert)
            selectedBrush = "sell"; // Dark Red (Warning/Sell)
        else if (flux)
            selectedBrush = "flux"; // Orange (Flux/Downward)
        else if (spike[0] > 0 || stay)
            selectedBrush = "stay"; // Green (Bullish)
        else
            selectedBrush = "quo"; // Gray (Status Quo)

        signalBrushKey[0] = selectedBrush;

        // Arrows
        if (ShowArrows && buyCondition)
        {
            Draw.ArrowUp(this, "BuyArrow" + CurrentBar, true, 0, Low[0] - TickSize * 2, DarkMode ? DarkModeBuy : LiteModeBuy);
        }
        if (ShowArrows && vap[0] < ma[1])
        {
            Draw.ArrowDown(this, "SellArrow" + CurrentBar, true, 0, High[0] + TickSize * 2, EndOfTrend);
        }

        // Labels
        if (ShowLabels && CurrentBar >= BarsRequiredToPlot)
        {
            string labelText;
            System.Windows.Media.Brush labelBrush;

            if (buyCondition)
            {
                labelText = WantAllBuySpikes ? "REVERSING. Buy Signal" : "Beginning of Trend. Buy Signal";
                labelBrush = DarkMode ? DarkModeBuy : LiteModeBuy;
            }
            else if (vap[0] < ma[1])
            {
                labelText = "End of Trend, Sell";
                labelBrush = EndOfTrend;
            }
            else if (devIncrease || devDecrease)
            {
                labelText = "Something's Happening!";
                labelBrush = OhMy;
            }
            else if (vap[0] < ma[0] || spike[0] <= SellAlert)
            {
                labelText = "Warning / Sell";
                labelBrush = Sell;
            }
            else if (flux)
            {
                labelText = "Flux / Chop";
                labelBrush = Flux;
            }
            else if (spike[0] > 0 || stay)
            {
                labelText = "Bullish";
                labelBrush = Stay;
            }
            else
            {
                labelText = "Status Quo";
                labelBrush = Quo;
            }

            Draw.Text(this, "Label" + CurrentBar, $"{labelText} ({selectedBrush})", 0, Low[0] - TickSize * 10, labelBrush);
            Print($"Bar {CurrentBar}: selectedBrush={selectedBrush}, buyCondition={buyCondition}, vap[0]={vap[0]:F2}, ma[0]={ma[0]:F2}, spike[0]={spike[0]:F2}, flux={flux}, stay={stay}, devIncrease={devIncrease}, devDecrease={devDecrease}, volume[0]={Volume[0]:F0}, volume[1]={Volume[1]:F0}, volStDev={volStDev:F2}");
        }
    }

    private double StDevLogReturns(int length)
    {
        double sum = 0, sumSquares = 0, count = 0;
        for (int i = 1; i <= length && CurrentBar - i >= 1; i++)
        {
            double logReturn = Close[i + 1] != 0 ? Math.Log(Close[i] / Close[i + 1]) : 0;
            sum += logReturn;
            sumSquares += logReturn * logReturn;
            count++;
        }
        double mean = count > 0 ? sum / count : 0;
        return count > 1 ? Math.Sqrt(sumSquares / count - mean * mean) : 0;
    }

    private double RelativeVolumeStDev(int length)
    {
        double sum = 0, sumSquares = 0, count = 0;
        for (int i = 0; i < length && CurrentBar - i >= 0; i++)
        {
            sum += Volume[i];
            sumSquares += Volume[i] * Volume[i];
            count++;
        }
        double mean = count > 0 ? sum / count : 0;
        return count > 1 ? Math.Sqrt(sumSquares / count - mean * mean) / (mean + double.Epsilon) : 0;
    }

    private bool CrossesAbove(Series<double> series1, Series<double> series2, int withinBars)
    {
        for (int i = 0; i <= withinBars && CurrentBar - i >= 0; i++)
        {
            if (i + 1 < series1.Count && series1[i] > series2[i] && series1[i + 1] <= series2[i + 1])
                return true;
        }
        return false;
    }

    #region Properties
    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Want All Buy Spikes", Order = 1, GroupName = "Parameters")]
    public bool WantAllBuySpikes { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Dark Mode", Order = 2, GroupName = "Parameters")]
    public bool DarkMode { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Show Arrows", Order = 3, GroupName = "Parameters")]
    public bool ShowArrows { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Show Labels", Order = 4, GroupName = "Parameters")]
    public bool ShowLabels { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Length", Order = 5, GroupName = "Parameters")]
    public int Length { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Sell Alert", Order = 6, GroupName = "Parameters")]
    public double SellAlert { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Reversal Alert", Order = 7, GroupName = "Parameters")]
    public double ReversalAlert { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Confirm Length", Order = 8, GroupName = "Parameters")]
    public int ConfirmLength { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Lookback", Order = 9, GroupName = "Parameters")]
    public int Lookback { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Deviation Length", Order = 10, GroupName = "Parameters")]
    public int DeviationLength { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Deviate", Order = 11, GroupName = "Parameters")]
    public double Deviate { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Shadow Width", Order = 12, GroupName = "Parameters")]
    public int ShadowWidth { get; set; }

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Dark Mode Buy", Description = "Color for buy signals in DarkMode", Order = 13, GroupName = "Colors")]
    public System.Windows.Media.Brush DarkModeBuy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Lite Mode Buy", Description = "Color for buy signals in LiteMode", Order = 14, GroupName = "Colors")]
    public System.Windows.Media.Brush LiteModeBuy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Sell", Description = "Color for warning/sell conditions", Order = 15, GroupName = "Colors")]
    public System.Windows.Media.Brush Sell { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "End of Trend", Description = "Color for no trend signals", Order = 16, GroupName = "Colors")]
    public System.Windows.Media.Brush EndOfTrend { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Oh My", Description = "Color for volume divergence signals", Order = 17, GroupName = "Colors")]
    public System.Windows.Media.Brush OhMy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Flux", Description = "Color for flux/downward conditions", Order = 18, GroupName = "Colors")]
    public System.Windows.Media.Brush Flux { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Stay", Description = "Color for bullish conditions", Order = 19, GroupName = "Colors")]
    public System.Windows.Media.Brush Stay { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Quo", Description = "Color for status quo conditions", Order = 20, GroupName = "Colors")]
    public System.Windows.Media.Brush Quo { get; set; }

    [Browsable(false)]
    public string QuoSerializable
    {
        get { return Serialize.BrushToString(Quo); }
        set { Quo = Serialize.StringToBrush(value); }
    }
    #endregion

    #region Miscellaneous
    public class DXMediaMap
    {
        public System.Windows.Media.Brush MediaBrush;
        public SharpDX.Direct2D1.Brush DxBrush;
    }

    private System.Windows.Media.Brush CreateColor(int r, int g, int b)
    {
        System.Windows.Media.SolidColorBrush brush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb((byte)r, (byte)g, (byte)b));
        if (brush.CanFreeze)
            brush.Freeze();
        return brush;
    }

    private void InitDxBrushes()
    {
        foreach (var item in dxmBrushes)
        {
            if (item.Value.DxBrush != null)
            {
                item.Value.DxBrush.Dispose();
                item.Value.DxBrush = null;
            }
        }

        if (RenderTarget == null || RenderTarget.IsDisposed)
            return;

        try
        {
            foreach (var item in dxmBrushes)
            {
                if (item.Value.MediaBrush != null)
                {
                    item.Value.DxBrush = item.Value.MediaBrush.ToDxBrush(RenderTarget);
                }
                else
                {
                    Log($"InitDxBrushes: MediaBrush for {item.Key} is null", LogLevel.Warning);
                }
            }
        }
        catch (Exception ex)
        {
            Log($"InitDxBrushes Error: {ex}", LogLevel.Error);
        }
    }

    protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
    {
        if (Bars == null || ChartControl == null || !IsVisible)
            return;

        InitDxBrushes();
        int barPaintWidth = chartControl.GetBarPaintWidth(ChartBars) - 1;

        for (int idx = ChartBars.FromIndex; idx <= ChartBars.ToIndex; idx++)
        {
            if (idx - Displacement < 0 || idx - Displacement >= ChartBars.Count || idx - Displacement < BarsRequiredToPlot)
                continue;

            double open = Open.GetValueAt(idx);
            double high = High.GetValueAt(idx);
            double low = Low.GetValueAt(idx);
            double close = Close.GetValueAt(idx);

            int barX = chartControl.GetXByBarIndex(ChartBars, idx);
            int barLeftX = barX - barPaintWidth / 2;
            float yOpen = chartScale.GetYByValue(open);
            float yHigh = chartScale.GetYByValue(high);
            float yLow = chartScale.GetYByValue(low);
            float yClose = chartScale.GetYByValue(close);

            string brushKey = idx < signalBrushKey.Count ? signalBrushKey.GetValueAt(idx) : "quo";

            if (brushKey == null || !dxmBrushes.ContainsKey(brushKey) || dxmBrushes[brushKey].DxBrush == null)
            {
                Print($"Error: brushKey {brushKey} not found or DxBrush is null at bar {idx}");
                brushKey = "quo";
            }

            UpdateVectors(ref reuseVector1, ref reuseVector2, barX, yHigh, barX, yLow);
            RenderTarget.DrawLine(reuseVector1, reuseVector2, dxmBrushes[brushKey].DxBrush, ShadowWidth);

            if (close == open)
            {
                UpdateVectors(ref reuseVector1, ref reuseVector2, barLeftX - 1, yOpen, barX + barPaintWidth / 2 - 1, yOpen);
                RenderTarget.DrawLine(reuseVector1, reuseVector2, dxmBrushes[brushKey].DxBrush, ShadowWidth);
            }
            else
            {
                float yTop = close > open ? yClose : yOpen;
                float yBottom = close > open ? yOpen : yClose;
                UpdateRect(ref reuseRectangle, barLeftX, yTop, barPaintWidth - 1, Math.Abs(yClose - yOpen));
                RenderTarget.FillRectangle(reuseRectangle, dxmBrushes[brushKey].DxBrush);
                UpdateRect(ref reuseRectangle, barLeftX - (ShadowWidth / 2), yBottom, barPaintWidth - (ShadowWidth / 2), Math.Abs(yClose - yOpen));
                RenderTarget.DrawRectangle(reuseRectangle, dxmBrushes[brushKey].DxBrush, ShadowWidth);
            }

            Print($"Render Bar {idx}: brushKey={brushKey}, close={close:F2}, open={open:F2}");
        }
    }

    public override void OnRenderTargetChanged()
    {
        InitDxBrushes();
    }

    private void UpdateRect(ref SharpDX.RectangleF rect, float x, float y, float width, float height)
    {
        rect.X = x;
        rect.Y = y;
        rect.Width = width;
        rect.Height = height;
    }

    private void UpdateVectors(ref SharpDX.Vector2 v1, ref SharpDX.Vector2 v2, float x1, float y1, float x2, float y2)
    {
        v1.X = x1;
        v1.Y = y1;
        v2.X = x2;
        v2.Y = y2;
    }
    #endregion
}

}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private TapeReaderReversals cacheTapeReaderReversals;
public TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth)
{
return TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth);
}

    public TapeReaderReversals TapeReaderReversals(ISeries<double> input, bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth)
    {
        if (cacheTapeReaderReversals != null)
            for (int idx = 0; idx < cacheTapeReaderReversals.Length; idx++)
                if (cacheTapeReaderReversals[idx] != null && cacheTapeReaderReversals[idx].WantAllBuySpikes == wantAllBuySpikes && cacheTapeReaderReversals[idx].DarkMode == darkMode && cacheTapeReaderReversals[idx].ShowArrows == showArrows && cacheTapeReaderReversals[idx].ShowLabels == showLabels && cacheTapeReaderReversals[idx].Length == length && cacheTapeReaderReversals[idx].SellAlert == sellAlert && cacheTapeReaderReversals[idx].ReversalAlert == reversalAlert && cacheTapeReaderReversals[idx].ConfirmLength == confirmLength && cacheTapeReaderReversals[idx].Lookback == lookback && cacheTapeReaderReversals[idx].DeviationLength == deviationLength && cacheTapeReaderReversals[idx].Deviate == deviate && cacheTapeReaderReversals[idx].ShadowWidth == shadowWidth && cacheTapeReaderReversals[idx].EqualsInput(input))
                    return cacheTapeReaderReversals[idx];
        return CacheIndicator<TapeReaderReversals>(new TapeReaderReversals { WantAllBuySpikes = wantAllBuySpikes, DarkMode = darkMode, ShowArrows = showArrows, ShowLabels = showLabels, Length = length, SellAlert = sellAlert, ReversalAlert = reversalAlert, ConfirmLength = confirmLength, Lookback = lookback, DeviationLength = deviationLength, Deviate = deviate, ShadowWidth = shadowWidth }, input, ref cacheTapeReaderReversals);
    }
}

}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth)
{
return indicator.TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth);
}
}
}

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth)
{
return indicator.TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth);
}
}
}

#endregion

This one would also use level 2 and tick data but I get errors.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.DrawingTools;
using SharpDX;
using SharpDX.Direct2D1;

namespace NinjaTrader.NinjaScript.Indicators
{
public class TapeReaderReversals : Indicator
{
private Dictionary<string, DXMediaMap> dxmBrushes;
private SharpDX.RectangleF reuseRectangle;
private SharpDX.Vector2 reuseVector1, reuseVector2;
private Series vap;
private Series ma;
private Series spike;
private Series convic;
private Series exptypprice;
private Series allSpikes;
private Series convicInput;
private Series typPriceSeries;
private SMA sma;
private Series signalBrushKey;
// Tick data storage
private double buyVolume, sellVolume;
private Series buyVolumeSeries;
private Series sellVolumeSeries;

    protected override void OnStateChange()
    {
        if (State == State.SetDefaults)
        {
            Description = @"TapeReader with Reversals for trend and volume analysis with buy/sell tick data. Paints candles: Blue (Reversal/Buy), Green (Bullish), Gray (Status Quo), Orange (Flux), Dark Red (Warning/Sell), Red (No Trend), Plum (Volume Divergence).";
            Name = "TapeReaderReversals";
            Calculate = Calculate.OnBarClose;
            IsOverlay = true;
            DisplayInDataBox = true;
            PaintPriceMarkers = true;
            IsSuspendedWhileInactive = false;
            BarsRequiredToPlot = 60;

            WantAllBuySpikes = true;
            DarkMode = true;
            ShowArrows = true;
            ShowLabels = true;
            Length = 12;
            SellAlert = -1.75;
            ReversalAlert = 1.25;
            ConfirmLength = 18;
            Lookback = 10;
            DeviationLength = 60;
            Deviate = 2.0;
            ShadowWidth = 1;
            BuySellRatioThreshold = 2.0; // Buy volume must be 2x sell volume for confirmation

            DarkModeBuy = Brushes.White;
            LiteModeBuy = Brushes.Blue;
            Sell = CreateColor(210, 59, 104); // Dark Red
            EndOfTrend = Brushes.Red;
            OhMy = Brushes.Plum;
            Flux = Brushes.Orange;
            Stay = Brushes.Green;
            Quo = Brushes.Gray;

            if (DarkModeBuy != null && DarkModeBuy.CanFreeze) DarkModeBuy.Freeze();
            if (LiteModeBuy != null && LiteModeBuy.CanFreeze) LiteModeBuy.Freeze();
            if (Sell != null && Sell.CanFreeze) Sell.Freeze();
            if (EndOfTrend != null && EndOfTrend.CanFreeze) EndOfTrend.Freeze();
            if (OhMy != null && OhMy.CanFreeze) OhMy.Freeze();
            if (Flux != null && Flux.CanFreeze) Flux.Freeze();
            if (Stay != null && Stay.CanFreeze) Stay.Freeze();
            if (Quo != null && Quo.CanFreeze) Quo.Freeze();

            dxmBrushes = new Dictionary<string, DXMediaMap>
            {
                { "darkModeBuy", new DXMediaMap { MediaBrush = DarkModeBuy } },
                { "liteModeBuy", new DXMediaMap { MediaBrush = LiteModeBuy } },
                { "sell", new DXMediaMap { MediaBrush = Sell } },
                { "endOfTrend", new DXMediaMap { MediaBrush = EndOfTrend } },
                { "ohMy", new DXMediaMap { MediaBrush = OhMy } },
                { "flux", new DXMediaMap { MediaBrush = Flux } },
                { "stay", new DXMediaMap { MediaBrush = Stay } },
                { "quo", new DXMediaMap { MediaBrush = Quo } }
            };

            Print($"TapeReaderReversals Version: 2025-08-24; Property Initialized: WantAllBuySpikes={WantAllBuySpikes}");
        }
        else if (State == State.Configure)
        {
            if (Length < 1) Length = 1;
            if (ConfirmLength < 1) ConfirmLength = 1;
            if (Lookback < 1) Lookback = 1;
            if (DeviationLength < 1) DeviationLength = 1;
            if (Deviate < 0) Deviate = 0;
            if (ShadowWidth < 1) ShadowWidth = 1;
            if (BuySellRatioThreshold < 1) BuySellRatioThreshold = 1;

            vap = new Series<double>(this);
            ma = new Series<double>(this);
            spike = new Series<double>(this);
            convic = new Series<double>(this);
            exptypprice = new Series<double>(this);
            allSpikes = new Series<double>(this);
            convicInput = new Series<double>(this);
            typPriceSeries = new Series<double>(this);
            signalBrushKey = new Series<string>(this);
            buyVolumeSeries = new Series<double>(this);
            sellVolumeSeries = new Series<double>(this);
            sma = SMA(vap, 12);

            buyVolume = 0;
            sellVolume = 0;

            Print("TapeReaderReversals compiled successfully on " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        }
        else if (State == State.DataLoaded)
        {
            // Ensure market data subscription for tick data
            AddDataSeries(BarsPeriodType.Tick, 1);
        }
    }

    protected override void OnMarketData(MarketDataEventArgs e)
    {
        if (CurrentBar < BarsRequiredToPlot) return;

        if (e.MarketDataType == MarketDataType.Last)
        {
            double lastPrice = e.Price;
            double bidPrice = e.Bid.Price;
            double askPrice = e.Ask.Price;

            if (lastPrice >= askPrice)
                buyVolume += e.Volume;
            else if (lastPrice <= bidPrice)
                sellVolume += e.Volume;

            Print($"Tick: LastPrice={lastPrice:F2}, Bid={bidPrice:F2}, Ask={askPrice:F2}, BuyVol={buyVolume:F0}, SellVol={sellVolume:F0}");
        }
    }

    protected override void OnBarUpdate()
    {
        if (CurrentBar < 2) return;

        // Store buy/sell volumes for the current bar
        buyVolumeSeries[0] = buyVolume;
        sellVolumeSeries[0] = sellVolume;

        // Reset for next bar
        buyVolume = 0;
        sellVolume = 0;

        BarBrushes[0] = Brushes.Transparent;
        CandleOutlineBrushes[0] = Brushes.Transparent;

        // Volume Accumulation Percentage
        double totalBP = 0;
        double totalVol = 0;
        for (int i = 0; i < Length && CurrentBar - i >= 0; i++)
        {
            double bp = (High[i] != Low[i]) ? (2 * Close[i] - High[i] - Low[i]) / (High[i] - Low[i]) : 0;
            totalBP += Volume[i] * (double.IsNaN(bp) ? 0 : bp);
            totalVol += Volume[i];
        }
        vap[0] = totalVol != 0 ? 100 * totalBP / totalVol : 0;
        ma[0] = sma[0];

        // Spike Detection
        double sDev = StDevLogReturns(Lookback) * Math.Sqrt(Lookback / (Lookback - 1.0));
        double m = sDev * Close[1];
        spike[0] = (Close[0] - Close[1]) / (m + double.Epsilon);
        spike[0] = Math.Max(-5, Math.Min(5, spike[0]));

        // Volume Divergence
        double volStDev = RelativeVolumeStDev(DeviationLength);
        bool aboveDev = volStDev >= Deviate;
        bool increase = Volume[0] > Volume[1];
        bool decrease = Volume[0] < Volume[1];
        bool devIncrease = increase && aboveDev;
        bool devDecrease = decrease && aboveDev;
        bool volumeDivergence = devIncrease || devDecrease;

        // Conviction Logic
        double sumCon = 0;
        for (int i = 0; i < ConfirmLength && CurrentBar - i >= 0; i++)
        {
            double convictSpread = High[i] - Low[i];
            double bodyTop = Close[i] > Open[i] ? Close[i] : Open[i];
            double bodyBottom = Close[i] < Open[i] ? Close[i] : Open[i];
            double body = bodyTop - bodyBottom;
            double perBody = convictSpread != 0 ? body / convictSpread : 0;
            double rangePer = Low[i] != 0 ? convictSpread / Low[i] : 0;
            double bodyPer = rangePer * perBody;

            double bc = Close[i] > Open[i] && Volume[i] > Volume[i + 1] ? 2 + 2 * bodyPer :
                        Close[i] > Open[i] && Volume[i] <= Volume[i + 1] ? 1 + 1 * bodyPer : 0;
            double brc = Close[i] < Open[i] && Volume[i] > Volume[i + 1] ? -2 - 2 * bodyPer :
                         Close[i] < Open[i] && Volume[i] <= Volume[i + 1] ? -1 - 1 * bodyPer : 0;
            sumCon += bc + brc;
        }
        convicInput[0] = sumCon;
        convic[0] = EMA(EMA(EMA(convicInput, 2), 2), 2)[0];

        // Typical Price EMA
        typPriceSeries[0] = (High[0] + Low[0] + Close[0]) / 3;
        exptypprice[0] = EMA(EMA(EMA(typPriceSeries, 3), 3), 3)[0];

        // Conviction and Flux Logic
        bool flux = exptypprice[0] < exptypprice[1] && convic[0] < convic[1];
        bool chop = (exptypprice[0] < exptypprice[1] && convic[0] > convic[1]) ||
                    (exptypprice[0] > exptypprice[1] && convic[0] < convic[1]);
        bool stay = !flux && !chop;

        // AllSpikes Logic
        allSpikes[0] = ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                        (volumeDivergence && spike[0] > 1)) ? 1 :
                       vap[0] < ma[1] ? -1 : allSpikes[1];

        // Tick Data Enhanced Buy Condition
        bool tickBuyConfirmation = sellVolumeSeries[0] > 0 ? buyVolumeSeries[0] / sellVolumeSeries[0] >= BuySellRatioThreshold : buyVolumeSeries[0] > 0;
        bool buyCondition = (!WantAllBuySpikes && CrossesAbove(vap, ma, 3) &&
                            ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                             (volumeDivergence && spike[0] > 1)) && tickBuyConfirmation) ||
                            (WantAllBuySpikes && allSpikes[1] == -1 &&
                            ((spike[0] > ReversalAlert && Close[0] > Open[0]) || spike[0] > 2.95 ||
                             (volumeDivergence && spike[0] > 1)) && tickBuyConfirmation);

        // Signal Color Logic
        string selectedBrush;
        if (buyCondition)
            selectedBrush = DarkMode ? "darkModeBuy" : "liteModeBuy"; // Blue (LiteMode) or White (DarkMode)
        else if (vap[0] < ma[1])
            selectedBrush = "endOfTrend"; // Red (No Trend)
        else if (devIncrease || devDecrease)
            selectedBrush = "ohMy"; // Plum (Volume Divergence)
        else if (vap[0] < ma[0] || spike[0] <= SellAlert)
            selectedBrush = "sell"; // Dark Red (Warning/Sell)
        else if (flux)
            selectedBrush = "flux"; // Orange (Flux/Downward)
        else if (spike[0] > 0 || stay)
            selectedBrush = "stay"; // Green (Bullish)
        else
            selectedBrush = "quo"; // Gray (Status Quo)

        signalBrushKey[0] = selectedBrush;

        // Arrows
        if (ShowArrows && buyCondition)
        {
            Draw.ArrowUp(this, "BuyArrow" + CurrentBar, true, 0, Low[0] - TickSize * 2, DarkMode ? DarkModeBuy : LiteModeBuy);
        }
        if (ShowArrows && vap[0] < ma[1])
        {
            Draw.ArrowDown(this, "SellArrow" + CurrentBar, true, 0, High[0] + TickSize * 2, EndOfTrend);
        }

        // Labels
        if (ShowLabels && CurrentBar >= BarsRequiredToPlot)
        {
            string labelText;
            System.Windows.Media.Brush labelBrush;

            if (buyCondition)
            {
                labelText = WantAllBuySpikes ? "REVERSING. Buy Signal" : "Beginning of Trend. Buy Signal";
                labelBrush = DarkMode ? DarkModeBuy : LiteModeBuy;
            }
            else if (vap[0] < ma[1])
            {
                labelText = "End of Trend, Sell";
                labelBrush = EndOfTrend;
            }
            else if (devIncrease || devDecrease)
            {
                labelText = "Something's Happening!";
                labelBrush = OhMy;
            }
            else if (vap[0] < ma[0] || spike[0] <= SellAlert)
            {
                labelText = "Warning / Sell";
                labelBrush = Sell;
            }
            else if (flux)
            {
                labelText = "Flux / Chop";
                labelBrush = Flux;
            }
            else if (spike[0] > 0 || stay)
            {
                labelText = "Bullish";
                labelBrush = Stay;
            }
            else
            {
                labelText = "Status Quo";
                labelBrush = Quo;
            }

            Draw.Text(this, "Label" + CurrentBar, $"{labelText} ({selectedBrush})", 0, Low[0] - TickSize * 10, labelBrush);
            Print($"Bar {CurrentBar}: selectedBrush={selectedBrush}, buyCondition={buyCondition}, vap[0]={vap[0]:F2}, ma[0]={ma[0]:F2}, spike[0]={spike[0]:F2}, flux={flux}, stay={stay}, devIncrease={devIncrease}, devDecrease={devDecrease}, volume[0]={Volume[0]:F0}, volume[1]={Volume[1]:F0}, volStDev={volStDev:F2}, buyVol={buyVolumeSeries[0]:F0}, sellVol={sellVolumeSeries[0]:F0}");
        }
    }

    private double StDevLogReturns(int length)
    {
        double sum = 0, sumSquares = 0, count = 0;
        for (int i = 1; i <= length && CurrentBar - i >= 1; i++)
        {
            double logReturn = Close[i + 1] != 0 ? Math.Log(Close[i] / Close[i + 1]) : 0;
            sum += logReturn;
            sumSquares += logReturn * logReturn;
            count++;
        }
        double mean = count > 0 ? sum / count : 0;
        return count > 1 ? Math.Sqrt(sumSquares / count - mean * mean) : 0;
    }

    private double RelativeVolumeStDev(int length)
    {
        double sum = 0, sumSquares = 0, count = 0;
        for (int i = 0; i < length && CurrentBar - i >= 0; i++)
        {
            sum += Volume[i];
            sumSquares += Volume[i] * Volume[i];
            count++;
        }
        double mean = count > 0 ? sum / count : 0;
        return count > 1 ? Math.Sqrt(sumSquares / count - mean * mean) / (mean + double.Epsilon) : 0;
    }

    private bool CrossesAbove(Series<double> series1, Series<double> series2, int withinBars)
    {
        for (int i = 0; i <= withinBars && CurrentBar - i >= 0; i++)
        {
            if (i + 1 < series1.Count && series1[i] > series2[i] && series1[i + 1] <= series2[i + 1])
                return true;
        }
        return false;
    }

    #region Properties
    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Want All Buy Spikes", Order = 1, GroupName = "Parameters")]
    public bool WantAllBuySpikes { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Dark Mode", Order = 2, GroupName = "Parameters")]
    public bool DarkMode { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Show Arrows", Order = 3, GroupName = "Parameters")]
    public bool ShowArrows { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Show Labels", Order = 4, GroupName = "Parameters")]
    public bool ShowLabels { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Length", Order = 5, GroupName = "Parameters")]
    public int Length { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Sell Alert", Order = 6, GroupName = "Parameters")]
    public double SellAlert { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Reversal Alert", Order = 7, GroupName = "Parameters")]
    public double ReversalAlert { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Confirm Length", Order = 8, GroupName = "Parameters")]
    public int ConfirmLength { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Lookback", Order = 9, GroupName = "Parameters")]
    public int Lookback { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Deviation Length", Order = 10, GroupName = "Parameters")]
    public int DeviationLength { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Deviate", Order = 11, GroupName = "Parameters")]
    public double Deviate { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Shadow Width", Order = 12, GroupName = "Parameters")]
    public int ShadowWidth { get; set; }

    [NinjaScriptProperty]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Buy/Sell Ratio Threshold", Order = 13, GroupName = "Parameters")]
    public double BuySellRatioThreshold { get; set; }

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Dark Mode Buy", Description = "Color for buy signals in DarkMode", Order = 14, GroupName = "Colors")]
    public System.Windows.Media.Brush DarkModeBuy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Lite Mode Buy", Description = "Color for buy signals in LiteMode", Order = 15, GroupName = "Colors")]
    public System.Windows.Media.Brush LiteModeBuy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Sell", Description = "Color for warning/sell conditions", Order = 16, GroupName = "Colors")]
    public System.Windows.Media.Brush Sell { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "End of Trend", Description = "Color for no trend signals", Order = 17, GroupName = "Colors")]
    public System.Windows.Media.Brush EndOfTrend { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Oh My", Description = "Color for volume divergence signals", Order = 18, GroupName = "Colors")]
    public System.Windows.Media.Brush OhMy { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Flux", Description = "Color for flux/downward conditions", Order = 19, GroupName = "Colors")]
    public System.Windows.Media.Brush Flux { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Stay", Description = "Color for bullish conditions", Order = 20, GroupName = "Colors")]
    public System.Windows.Media.Brush Stay { get; set; }

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

    [NinjaScriptProperty]
    [XmlIgnore]
    [NinjaTrader.Gui.NinjaScript.Display(Name = "Quo", Description = "Color for status quo conditions", Order = 21, GroupName = "Colors")]
    public System.Windows.Media.Brush Quo { get; set; }

    [Browsable(false)]
    public string QuoSerializable
    {
        get { return Serialize.BrushToString(Quo); }
        set { Quo = Serialize.StringToBrush(value); }
    }
    #endregion

    #region Miscellaneous
    public class DXMediaMap
    {
        public System.Windows.Media.Brush MediaBrush;
        public SharpDX.Direct2D1.Brush DxBrush;
    }

    private System.Windows.Media.Brush CreateColor(int r, int g, int b)
    {
        System.Windows.Media.SolidColorBrush brush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb((byte)r, (byte)g, (byte)b));
        if (brush.CanFreeze)
            brush.Freeze();
        return brush;
    }

    private void InitDxBrushes()
    {
        foreach (var item in dxmBrushes)
        {
            if (item.Value.DxBrush != null)
            {
                item.Value.DxBrush.Dispose();
                item.Value.DxBrush = null;
            }
        }

        if (RenderTarget == null || RenderTarget.IsDisposed)
            return;

        try
        {
            foreach (var item in dxmBrushes)
            {
                if (item.Value.MediaBrush != null)
                {
                    item.Value.DxBrush = item.Value.MediaBrush.ToDxBrush(RenderTarget);
                }
                else
                {
                    Log($"InitDxBrushes: MediaBrush for {item.Key} is null", LogLevel.Warning);
                }
            }
        }
        catch (Exception ex)
        {
            Log($"InitDxBrushes Error: {ex}", LogLevel.Error);
        }
    }

    protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
    {
        if (Bars == null || ChartControl == null || !IsVisible)
            return;

        InitDxBrushes();
        int barPaintWidth = chartControl.GetBarPaintWidth(ChartBars) - 1;

        for (int idx = ChartBars.FromIndex; idx <= ChartBars.ToIndex; idx++)
        {
            if (idx - Displacement < 0 || idx - Displacement >= ChartBars.Count || idx - Displacement < BarsRequiredToPlot)
                continue;

            double open = Open.GetValueAt(idx);
            double high = High.GetValueAt(idx);
            double low = Low.GetValueAt(idx);
            double close = Close.GetValueAt(idx);

            int barX = chartControl.GetXByBarIndex(ChartBars, idx);
            int barLeftX = barX - barPaintWidth / 2;
            float yOpen = chartScale.GetYByValue(open);
            float yHigh = chartScale.GetYByValue(high);
            float yLow = chartScale.GetYByValue(low);
            float yClose = chartScale.GetYByValue(close);

            string brushKey = idx < signalBrushKey.Count ? signalBrushKey.GetValueAt(idx) : "quo";

            if (brushKey == null || !dxmBrushes.ContainsKey(brushKey) || dxmBrushes[brushKey].DxBrush == null)
            {
                Print($"Error: brushKey {brushKey} not found or DxBrush is null at bar {idx}");
                brushKey = "quo";
            }

            UpdateVectors(ref reuseVector1, ref reuseVector2, barX, yHigh, barX, yLow);
            RenderTarget.DrawLine(reuseVector1, reuseVector2, dxmBrushes[brushKey].DxBrush, ShadowWidth);

            if (close == open)
            {
                UpdateVectors(ref reuseVector1, ref reuseVector2, barLeftX - 1, yOpen, barX + barPaintWidth / 2 - 1, yOpen);
                RenderTarget.DrawLine(reuseVector1, reuseVector2, dxmBrushes[brushKey].DxBrush, ShadowWidth);
            }
            else
            {
                float yTop = close > open ? yClose : yOpen;
                float yBottom = close > open ? yOpen : yClose;
                UpdateRect(ref reuseRectangle, barLeftX, yTop, barPaintWidth - 1, Math.Abs(yClose - yOpen));
                RenderTarget.FillRectangle(reuseRectangle, dxmBrushes[brushKey].DxBrush);
                UpdateRect(ref reuseRectangle, barLeftX - (ShadowWidth / 2), yBottom, barPaintWidth - (ShadowWidth / 2), Math.Abs(yClose - yOpen));
                RenderTarget.DrawRectangle(reuseRectangle, dxmBrushes[brushKey].DxBrush, ShadowWidth);
            }

            Print($"Render Bar {idx}: brushKey={brushKey}, close={close:F2}, open={open:F2}");
        }
    }

    public override void OnRenderTargetChanged()
    {
        InitDxBrushes();
    }

    private void UpdateRect(ref SharpDX.RectangleF rect, float x, float y, float width, float height)
    {
        rect.X = x;
        rect.Y = y;
        rect.Width = width;
        rect.Height = height;
    }

    private void UpdateVectors(ref SharpDX.Vector2 v1, ref SharpDX.Vector2 v2, float x1, float y1, float x2, float y2)
    {
        v1.X = x1;
        v1.Y = y1;
        v2.X = x2;
        v2.Y = y2;
    }
    #endregion
}

}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private TapeReaderReversals cacheTapeReaderReversals;
public TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth, double buySellRatioThreshold)
{
return TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth, buySellRatioThreshold);
}

    public TapeReaderReversals TapeReaderReversals(ISeries<double> input, bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth, double buySellRatioThreshold)
    {
        if (cacheTapeReaderReversals != null)
            for (int idx = 0; idx < cacheTapeReaderReversals.Length; idx++)
                if (cacheTapeReaderReversals[idx] != null && cacheTapeReaderReversals[idx].WantAllBuySpikes == wantAllBuySpikes && cacheTapeReaderReversals[idx].DarkMode == darkMode && cacheTapeReaderReversals[idx].ShowArrows == showArrows && cacheTapeReaderReversals[idx].ShowLabels == showLabels && cacheTapeReaderReversals[idx].Length == length && cacheTapeReaderReversals[idx].SellAlert == sellAlert && cacheTapeReaderReversals[idx].ReversalAlert == reversalAlert && cacheTapeReaderReversals[idx].ConfirmLength == confirmLength && cacheTapeReaderReversals[idx].Lookback == lookback && cacheTapeReaderReversals[idx].DeviationLength == deviationLength && cacheTapeReaderReversals[idx].Deviate == deviate && cacheTapeReaderReversals[idx].ShadowWidth == shadowWidth && cacheTapeReaderReversals[idx].BuySellRatioThreshold == buySellRatioThreshold && cacheTapeReaderReversals[idx].EqualsInput(input))
                    return cacheTapeReaderReversals[idx];
        return CacheIndicator<TapeReaderReversals>(new TapeReaderReversals { WantAllBuySpikes = wantAllBuySpikes, DarkMode = darkMode, ShowArrows = showArrows, ShowLabels = showLabels, Length = length, SellAlert = sellAlert, ReversalAlert = reversalAlert, ConfirmLength = confirmLength, Lookback = lookback, DeviationLength = deviationLength, Deviate = deviate, ShadowWidth = shadowWidth, BuySellRatioThreshold = buySellRatioThreshold }, input, ref cacheTapeReaderReversals);
    }
}

}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth, double buySellRatioThreshold)
{
return indicator.TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth, buySellRatioThreshold);
}
}
}

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.TapeReaderReversals TapeReaderReversals(bool wantAllBuySpikes, bool darkMode, bool showArrows, bool showLabels, int length, double sellAlert, double reversalAlert, int confirmLength, int lookback, int deviationLength, double deviate, int shadowWidth, double buySellRatioThreshold)
{
return indicator.TapeReaderReversals(Input, wantAllBuySpikes, darkMode, showArrows, showLabels, length, sellAlert, reversalAlert, confirmLength, lookback, deviationLength, deviate, shadowWidth, buySellRatioThreshold);
}
}
}

#endregion