//
// ORB_930_Indicator.cs
// NinjaTrader 8 — Opening Range Breakout  v1.4
// Gabo Trading 2026
//
// Cambios v1.4:
//   - StartHour / StartMinute: hora de inicio del rango configurable
//   - RangeLengthMinutes: duración del rango (cantidad de velas en 1M)
//   - TradeWindowMinutes: cuántos minutos dura la ventana operativa post-rango
//   - BodyMin: cuerpo mínimo de ruptura (puntos)
//

#region Using declarations
using System;
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;
#endregion

namespace NinjaTrader.NinjaScript.Indicators
{
    public class ORB_930 : Indicator
    {
        // ── Parámetros con defaults ──────────────────────────────────
        private int    startHour          = 9;
        private int    startMinute        = 30;
        private int    rangeLengthMinutes = 5;
        private int    tradeWindowMinutes = 55;
        private double bodyMin            = 3.0;

        // ── Estado interno ───────────────────────────────────────────
        private double orbHigh    = double.NaN;
        private double orbLow     = double.NaN;
        private double orbHighSet = double.NaN;
        private double orbLowSet  = double.NaN;

        private bool   built      = false;
        private bool   rangeSet   = false;
        private bool   longFired  = false;
        private bool   shortFired = false;
        private int    lastDay    = -1;

        private TimeZoneInfo nyTZ;

        // ── Calculados en OnStateChange para no recalcular cada barra ─
        private int rangeStartMins = 0;   // inicio del rango en minutos
        private int rangeEndMins   = 0;   // fin del rango
        private int tradeEndMins   = 0;   // fin de la ventana operativa

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Name               = "ORB_930";
                Description        = "Opening Range Breakout — configurable";
                Calculate          = Calculate.OnBarClose;
                IsOverlay          = true;
                DisplayInDataBox   = false;
                DrawOnPricePanel   = true;
                PaintPriceMarkers  = false;

                AddPlot(Brushes.Transparent, "ORB_Dummy");
            }
            else if (State == State.Configure)
            {
                try   { nyTZ = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); }
                catch { nyTZ = TimeZoneInfo.FindSystemTimeZoneById("America/New_York"); }

                RecalcWindows();
            }
        }

        // Recalcula los límites de ventana cada vez que cambia un parámetro
        private void RecalcWindows()
        {
            rangeStartMins = startHour * 60 + startMinute;
            rangeEndMins   = rangeStartMins + rangeLengthMinutes;
            tradeEndMins   = rangeEndMins   + tradeWindowMinutes;
        }

        protected override void OnBarUpdate()
        {
            DateTime barNY  = TimeZoneInfo.ConvertTime(Time[0], TimeZoneInfo.Local, nyTZ);
            int      nyMins = barNY.Hour * 60 + barNY.Minute;
            int      nyDay  = barNY.Day;

            // ── Nuevo día ──────────────────────────────────────────────
            if (nyDay != lastDay)
            {
                lastDay    = nyDay;
                orbHigh    = double.NaN;
                orbLow     = double.NaN;
                orbHighSet = double.NaN;
                orbLowSet  = double.NaN;
                built      = false;
                rangeSet   = false;
                longFired  = false;
                shortFired = false;
            }

            // ── Ventanas ───────────────────────────────────────────────
            bool inOpeningRange = (nyMins >= rangeStartMins && nyMins < rangeEndMins);
            bool inTradeWindow  = (nyMins >= rangeEndMins   && nyMins < tradeEndMins);

            // ── Acumular rango ─────────────────────────────────────────
            if (inOpeningRange)
            {
                built = true;
                if (double.IsNaN(orbHigh) || High[0] > orbHigh) orbHigh = High[0];
                if (double.IsNaN(orbLow)  || Low[0]  < orbLow)  orbLow  = Low[0];
                BackBrushes[0] = new SolidColorBrush(Color.FromArgb(50, 255, 220, 0));
            }

            // ── Fijar rango y dibujar líneas al entrar en ventana ──────
            if (!rangeSet && inTradeWindow && built
                && !double.IsNaN(orbHigh) && !double.IsNaN(orbLow))
            {
                orbHighSet = orbHigh;
                orbLowSet  = orbLow;
                rangeSet   = true;

                string   dateTag   = barNY.ToString("yyyyMMdd");
                DateTime lineStart = Time[0];
                DateTime lineEnd   = Time[0].AddMinutes(tradeWindowMinutes);

                Draw.Line(this, "ORBH_" + dateTag,
                    false,
                    lineStart, orbHighSet,
                    lineEnd,   orbHighSet,
                    Brushes.Lime, DashStyleHelper.Solid, 2);

                Draw.Line(this, "ORBL_" + dateTag,
                    false,
                    lineStart, orbLowSet,
                    lineEnd,   orbLowSet,
                    Brushes.Red, DashStyleHelper.Solid, 2);
            }

            // ── Señales ────────────────────────────────────────────────
            if (rangeSet && inTradeWindow && CurrentBar > 0)
            {
                bool isBull = Close[0] > Open[0];
                bool isBear = Close[0] < Open[0];

                // LONG
                if (!longFired && isBull
                    && (Close[0] - orbHighSet) >= bodyMin
                    && Close[1] <= orbHighSet)
                {
                    longFired = true;

                    Draw.ArrowUp(this,
                        "ORBL_" + CurrentBar.ToString(),
                        false, 0,
                        Low[0] - TickSize * 4,
                        Brushes.Lime);

                    Alert("ORB_LONG", Priority.High,
                        "ORB LONG — ejecutar",
                        NinjaTrader.Core.Globals.InstallDir + @"\sounds\Alert1.wav",
                        10, Brushes.Black, Brushes.Lime);
                }

                // SHORT
                if (!shortFired && isBear
                    && (orbLowSet - Close[0]) >= bodyMin
                    && Close[1] >= orbLowSet)
                {
                    shortFired = true;

                    Draw.ArrowDown(this,
                        "ORBS_" + CurrentBar.ToString(),
                        false, 0,
                        High[0] + TickSize * 4,
                        Brushes.Red);

                    Alert("ORB_SHORT", Priority.High,
                        "ORB SHORT — ejecutar",
                        NinjaTrader.Core.Globals.InstallDir + @"\sounds\Alert1.wav",
                        10, Brushes.Black, Brushes.Red);
                }
            }
        }

        // ── Propiedades expuestas en el panel de NinjaTrader ──────────

        [NinjaScriptProperty]
        [Range(0, 23)]
        [Display(Name = "Hora inicio (NY)", GroupName = "ORB — Configuración", Order = 1)]
        public int StartHour
        {
            get { return startHour; }
            set { startHour = value; RecalcWindows(); }
        }

        [NinjaScriptProperty]
        [Range(0, 59)]
        [Display(Name = "Minuto inicio (NY)", GroupName = "ORB — Configuración", Order = 2)]
        public int StartMinute
        {
            get { return startMinute; }
            set { startMinute = value; RecalcWindows(); }
        }

        [NinjaScriptProperty]
        [Range(1, 240)]
        [Display(Name = "Duración del rango (minutos)", GroupName = "ORB — Configuración", Order = 3)]
        public int RangeLengthMinutes
        {
            get { return rangeLengthMinutes; }
            set { rangeLengthMinutes = value; RecalcWindows(); }
        }

        [NinjaScriptProperty]
        [Range(1, 480)]
        [Display(Name = "Ventana operativa (minutos)", GroupName = "ORB — Configuración", Order = 4)]
        public int TradeWindowMinutes
        {
            get { return tradeWindowMinutes; }
            set { tradeWindowMinutes = value; RecalcWindows(); }
        }

        [NinjaScriptProperty]
        [Range(0.5, 200.0)]
        [Display(Name = "Cuerpo mínimo (puntos)", GroupName = "ORB — Configuración", Order = 5)]
        public double BodyMin
        {
            get { return bodyMin; }
            set { bodyMin = Math.Max(0.5, value); }
        }

        [Browsable(false)]
        [XmlIgnore]
        public Series<double> OrbHigh { get { return Values[0]; } }
    }
}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
    public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
    {
        private ORB_930[] cacheORB_930;
        public ORB_930 ORB_930(int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            return ORB_930(Input, startHour, startMinute, rangeLengthMinutes, tradeWindowMinutes, bodyMin);
        }

        public ORB_930 ORB_930(ISeries<double> input, int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            if (cacheORB_930 != null)
                for (int idx = 0; idx < cacheORB_930.Length; idx++)
                    if (cacheORB_930[idx] != null
                        && cacheORB_930[idx].StartHour          == startHour
                        && cacheORB_930[idx].StartMinute        == startMinute
                        && cacheORB_930[idx].RangeLengthMinutes == rangeLengthMinutes
                        && cacheORB_930[idx].TradeWindowMinutes == tradeWindowMinutes
                        && cacheORB_930[idx].BodyMin            == bodyMin
                        && cacheORB_930[idx].EqualsInput(input))
                        return cacheORB_930[idx];
            return CacheIndicator<ORB_930>(new ORB_930()
            {
                StartHour          = startHour,
                StartMinute        = startMinute,
                RangeLengthMinutes = rangeLengthMinutes,
                TradeWindowMinutes = tradeWindowMinutes,
                BodyMin            = bodyMin
            }, input, ref cacheORB_930);
        }
    }
}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
    public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
    {
        public Indicators.ORB_930 ORB_930(int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            return indicator.ORB_930(Input, startHour, startMinute, rangeLengthMinutes, tradeWindowMinutes, bodyMin);
        }

        public Indicators.ORB_930 ORB_930(ISeries<double> input, int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            return indicator.ORB_930(input, startHour, startMinute, rangeLengthMinutes, tradeWindowMinutes, bodyMin);
        }
    }
}

namespace NinjaTrader.NinjaScript.Strategies
{
    public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
    {
        public Indicators.ORB_930 ORB_930(int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            return indicator.ORB_930(Input, startHour, startMinute, rangeLengthMinutes, tradeWindowMinutes, bodyMin);
        }

        public Indicators.ORB_930 ORB_930(ISeries<double> input, int startHour, int startMinute, int rangeLengthMinutes, int tradeWindowMinutes, double bodyMin)
        {
            return indicator.ORB_930(input, startHour, startMinute, rangeLengthMinutes, tradeWindowMinutes, bodyMin);
        }
    }
}

#endregion
