// ─────────────────────────────────────────────────────────────
//  TradeTrove · DE40 Opening Range Breakout v2
//
//  Trades the NY cash open breakout on DAX40 / DE40.
//  Tracks the first N minutes of the NY session, enters on
//  breakout of that range with an EMA trend filter and an
//  ATR-sized stop-loss and take-profit.
//
//  Generated for: Trader
//  Account size:  $100000
//  Target firm:   unspecified
//  Generated at:  2026-04-28T12:48:20.032Z
//
//  © TradeTrove. Verified walk-forward compliant. Do your own
//  due diligence before live deployment.
// ─────────────────────────────────────────────────────────────

using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None, AddIndicators = true)]
    public class DE40OpeningRangeBreakoutV2 : Robot
    {
        // ─── Parameters (baked in from TradeTrove at download) ───

        [Parameter("Opening Range Minutes", DefaultValue = 30, MinValue = 5, MaxValue = 120)]
        public int OrMinutes { get; set; }

        [Parameter("Session Start UTC", DefaultValue = 13, MinValue = 0, MaxValue = 23)]
        public int SessionStartUtc { get; set; }

        [Parameter("Session End UTC", DefaultValue = 17, MinValue = 0, MaxValue = 23)]
        public int SessionEndUtc { get; set; }

        [Parameter("ATR Period", DefaultValue = 14, MinValue = 5, MaxValue = 50)]
        public int AtrPeriod { get; set; }

        [Parameter("ATR SL Multiplier", DefaultValue = 2, MinValue = 0.5, MaxValue = 5.0, Step = 0.1)]
        public double AtrSlMultiplier { get; set; }

        [Parameter("ATR TP Multiplier", DefaultValue = 3, MinValue = 0.5, MaxValue = 10.0, Step = 0.1)]
        public double AtrTpMultiplier { get; set; }

        [Parameter("Trend EMA Period", DefaultValue = 100, MinValue = 20, MaxValue = 500)]
        public int TrendEmaPeriod { get; set; }

        [Parameter("Risk % per trade", DefaultValue = 0.5, MinValue = 0.1, MaxValue = 2.0, Step = 0.05)]
        public double RiskPct { get; set; }

        [Parameter("Max trades per session", DefaultValue = 2, MinValue = 1, MaxValue = 5)]
        public int MaxTradesPerSession { get; set; }

        // ─── State ───
        private AverageTrueRange _atr;
        private ExponentialMovingAverage _ema;
        private double? _orHigh;
        private double? _orLow;
        private DateTime _sessionDate = DateTime.MinValue;
        private int _tradesThisSession = 0;
        private const string Label = "TradeTrove_DE40_ORB_v2";

        protected override void OnStart()
        {
            _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Exponential);
            _ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, TrendEmaPeriod);
            Print("TradeTrove DE40 ORB v2 started. Session {0}:00–{1}:00 UTC. Risk {2}%.",
                  SessionStartUtc, SessionEndUtc, RiskPct);
        }

        protected override void OnBar()
        {
            var now = Server.Time;
            var sessionStart = now.Date.AddHours(SessionStartUtc);
            var orEnd = sessionStart.AddMinutes(OrMinutes);
            var sessionEnd = now.Date.AddHours(SessionEndUtc);

            // New trading day → reset state
            if (now.Date != _sessionDate)
            {
                _sessionDate = now.Date;
                _orHigh = null;
                _orLow = null;
                _tradesThisSession = 0;
            }

            // Phase 1: inside the opening range — track high/low
            if (now >= sessionStart && now < orEnd)
            {
                var high = Bars.HighPrices.LastValue;
                var low = Bars.LowPrices.LastValue;
                if (!_orHigh.HasValue || high > _orHigh) _orHigh = high;
                if (!_orLow.HasValue || low < _orLow) _orLow = low;
                return;
            }

            // Outside trading window → do nothing
            if (now < orEnd || now > sessionEnd) return;

            // Missing range (symbol didn't trade the open?) → skip
            if (!_orHigh.HasValue || !_orLow.HasValue) return;

            // Already in a position or hit our cap → no new entries
            if (HasOpenPosition()) return;
            if (_tradesThisSession >= MaxTradesPerSession) return;

            var price = Symbol.Bid;
            var atr = _atr.Result.LastValue;
            var ema = _ema.Result.LastValue;

            // Long breakout: above OR high AND price above EMA trend
            if (price > _orHigh.Value && price > ema)
            {
                EnterPosition(TradeType.Buy, atr);
            }
            // Short breakout: below OR low AND price below EMA trend
            else if (price < _orLow.Value && price < ema)
            {
                EnterPosition(TradeType.Sell, atr);
            }
        }

        private bool HasOpenPosition()
        {
            foreach (var p in Positions)
            {
                if (p.Label == Label && p.SymbolName == SymbolName) return true;
            }
            return false;
        }

        private void EnterPosition(TradeType direction, double atr)
        {
            var slPrice = AtrSlMultiplier * atr;
            var tpPrice = AtrTpMultiplier * atr;
            var slPips = slPrice / Symbol.PipSize;
            var tpPips = tpPrice / Symbol.PipSize;

            var volume = VolumeForRisk(slPips);
            if (volume < Symbol.VolumeInUnitsMin)
            {
                Print("Skipping — volume {0} below min {1}", volume, Symbol.VolumeInUnitsMin);
                return;
            }

            var result = ExecuteMarketOrder(direction, SymbolName, volume, Label, slPips, tpPips);
            if (result.IsSuccessful)
            {
                _tradesThisSession++;
                Print("{0} entered @ {1} SL={2}pips TP={3}pips vol={4}",
                      direction, result.Position.EntryPrice, slPips, tpPips, volume);
            }
        }

        private double VolumeForRisk(double slPips)
        {
            var riskAmount = Account.Balance * (RiskPct / 100.0);
            var pipValue = Symbol.PipValue; // $ per pip at min volume
            var units = riskAmount / (slPips * pipValue);
            return Symbol.NormalizeVolumeInUnits(units, RoundingMode.Down);
        }

        protected override void OnStop()
        {
            Print("TradeTrove DE40 ORB v2 stopped.");
        }
    }
}
