Logging CSV tick by tick data while in trades based on historical data in playback, can it be done?

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Windows.Media;
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.Strategies;
#endregion

// Do not change namespace
namespace NinjaTrader.NinjaScript.Strategies
{
public class ChatGPT_LogTick_ATR : Strategy
{
private ATR atr;
private EMA ema;

    // shading
    private Brush timeShadeBrush;
    private readonly Brush timeShadeClear = null;

    // ===== per-entry container (one TradeID per entry fill) =====
    private class ManagedTrade
    {
        public string TradeId; // UNIQUE: entry OrderId
        public string EntryOrderId;
        public Order StopLossOrder;
        public Order ProfitTargetOrder;
        public double EntryPrice;
        public int EntryQty;
        public string Side; // "LONG"/"SHORT"
        public double AtrAtEntryPts;
        public double StopAtrMultAtEntry;
        public double TargetAtrMultAtEntry;
        public bool IsOpen;
        public ManagedTrade(string tradeId, string entryOrderId, double entryPrice, int qty, string side,
                            double atrPts, double stopK, double tgtK)
        {
            TradeId = tradeId;
            EntryOrderId = entryOrderId;
            EntryPrice = entryPrice;
            EntryQty = qty;
            Side = side;
            AtrAtEntryPts = atrPts;
            StopAtrMultAtEntry = stopK;
            TargetAtrMultAtEntry = tgtK;
            StopLossOrder = null;
            ProfitTargetOrder = null;
            IsOpen = true;
        }
    }

    public enum EntryMode
    {
        BarExtremaLimit,
        OneTickBreak,
        FibonacciRetracement
    }

    private readonly List<ManagedTrade> openTrades = new List<ManagedTrade>();
    private readonly List<Order> pendingEntryOrders = new List<Order>();
    private int entrySeq = 0;

    // one-bar-valid working entry (entry mechanics only)
    private Order workingEntryOrder = null;
    private int   workingOrderBar   = -1;
    private int   lastProcessedBar  = -1;

    // CSV: buffered logging
    private StreamWriter logWriter = null;
    private string sessionCsvPath = null;
    private readonly List<string> logBuffer = new List<string>();

    // flatten-at-time tracker
    private DateTime lastFlattenSessionDate = DateTime.MinValue;

    // ===== Ninja States =====
    protected override void OnStateChange()
    {
        if (State == State.SetDefaults)
        {
            Description = "Unmanaged; stacked entries; logs per-tick Equity per ENTRY fill.";
            Name = "ChatGPT_LogTick_ATR";
            IsUnmanaged = true;
            Calculate = Calculate.OnEachTick;
            EntriesPerDirection = 100;

            // Manual control only
            IsExitOnSessionCloseStrategy = false;
            ExitOnSessionCloseSeconds = 30;
            IsFillLimitOnTouch = false;
            MaximumBarsLookBack = MaximumBarsLookBack.Infinite;
            OrderFillResolution = OrderFillResolution.Standard;
            Slippage = 1;
            StartBehavior = StartBehavior.WaitUntilFlat;
            TimeInForce = TimeInForce.Gtc;

            // Diagnostics ON
            TraceOrders = true;

            RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
            BarsRequiredToTrade = 20;
            IsInstantiatedOnEachOptimizationIteration = true;

            FourBarSetup = true;
            FiveBarSetup = false;
            SixBarSetup = false;

            AtrPeriod = 21;
            RiskPerTrade = 350;
            MaxContracts = 15;

            TargetMultiplier = 15.0; // xATR
            StopLossMultiplier = 3.0; // xATR

            EntryType = EntryMode.BarExtremaLimit;
            FibRetracePct = 0.24;

            UseEmaFilter = false;
            EmaPeriod = 17;

            TimeFilter = true;
            WindowStart = "07:00:00";
            WindowEnd   = "08:45:00";

            ShowTimeShade = true;
            ShadeOpacity = 32;

            FlattenAtTime = true;
            FlattenTime = "14:30:00";

            LogFolder = @"C:\NT_Exports";
            CsvFilePrefix = "Gemini_EquityLog_ATR";
        }
        else if (State == State.Configure)
        {
            AddDataSeries(BarsPeriodType.Tick, 1);
        }
        else if (State == State.DataLoaded)
        {
            atr = ATR(AtrPeriod);
            ema = EMA(EmaPeriod);
            AddChartIndicator(ema);

            byte a = (byte)Math.Max(0, Math.Min(255, ShadeOpacity));
            var b = new SolidColorBrush(Color.FromArgb(a, 128, 128, 128)); b.Freeze();
            timeShadeBrush = b;

            try
            {
                if (!Directory.Exists(LogFolder)) Directory.CreateDirectory(LogFolder);
                string inst = Instrument?.MasterInstrument?.Name ?? "Instrument";
                string ts = DateTime.Now.ToString("yyyyMMdd_HHmmss");
                sessionCsvPath = Path.Combine(LogFolder, $"{CsvFilePrefix}_{inst}_{ts}.csv");
                logWriter = new StreamWriter(sessionCsvPath, append:false);
                // header once
                logWriter.WriteLine("TradeID,Time,LastPrice,RealizedPnL,UnrealizedPnL,Equity,EntryPrice,ATR_at_entry_pts,Side,EntryQty,Stop_ATR_mult,Target_ATR_mult");
                logWriter.Flush();
                Print($"[DIAG] Opened CSV at {sessionCsvPath}");
            }
            catch (Exception ex)
            {
                Print("[ChatGPT_LogTick_ATR] Could not open CSV: " + ex.Message);
            }
        }
        else if (State == State.Terminated || State == State.Finalized)
        {
            try
            {
                FlushBufferAll(); // final write
            }
            catch { }
            finally
            {
                try { if (logWriter != null) { logWriter.Close(); logWriter.Dispose(); } } catch { }
            }
        }
    }

    // ===== OnBarUpdate =====
    protected override void OnBarUpdate()
    {
        // === TICK stream: manage stops/BE every tick + buffer logs ===
        if (BarsInProgress == 1)
        {
            // DIAG: show that BIP=1 is alive even if we have no open trades (once per new bar)
            if (openTrades.Count == 0 && IsFirstTickOfBar)
                Print($"[DIAG] {Times[1][0]:O} BIP=1 tick path alive; openTrades=0 (no per-tick rows).");

            // Flatten-at-time (once per day, at/after threshold)
            if (FlattenAtTime)
            {
                TimeSpan ft = ParseTimeSpan(FlattenTime);
                if (ft != TimeSpan.MinValue)
                {
                    DateTime tNow = Times[1][0];
                    DateTime sessionDate = tNow.Date;
                    if (tNow.TimeOfDay >= ft && sessionDate != lastFlattenSessionDate)
                    {
                        Print($"[DIAG] FlattenAtTime triggered @ {tNow:O}");
                        for (int i = openTrades.Count - 1; i >= 0; i--)
                        {
                            var mt = openTrades[i];
                            if (!mt.IsOpen) continue;

                            try { if (mt.ProfitTargetOrder != null) CancelOrder(mt.ProfitTargetOrder); } catch {}
                            try { if (mt.StopLossOrder != null) CancelOrder(mt.StopLossOrder); } catch {}

                            string oco = "OCO-" + mt.EntryOrderId;
                            if (mt.Side == "LONG")
                                SubmitOrderUnmanaged(1, OrderAction.Sell, OrderType.Market, mt.EntryQty, 0, 0, oco, "FLAT");
                            else
                                SubmitOrderUnmanaged(1, OrderAction.BuyToCover, OrderType.Market, mt.EntryQty, 0, 0, oco, "FLAT");
                        }
                        lastFlattenSessionDate = sessionDate;
                    }
                }
            }

            // buffer logs per open trade per tick
            if (openTrades.Count > 0)
            {
                double lastPrice = Closes[1][0];
                string tIso = Times[1][0].ToString("o");

                foreach (var tr in openTrades)
                {
                    if (!tr.IsOpen) continue;

                    double trade_unrealized = (tr.Side == "LONG")
                        ? (lastPrice - tr.EntryPrice) * tr.EntryQty * Instrument.MasterInstrument.PointValue
                        : (tr.EntryPrice - lastPrice) * tr.EntryQty * Instrument.MasterInstrument.PointValue;

                    double trade_realized = 0;
                    double trade_equity = trade_realized + trade_unrealized;

                    logBuffer.Add(
                        $"{tr.TradeId},{tIso},{lastPrice:F2},{trade_realized:F2},{trade_unrealized:F2},{trade_equity:F2},{tr.EntryPrice:F2},{tr.AtrAtEntryPts:F4},{tr.Side},{tr.EntryQty},{tr.StopAtrMultAtEntry:F3},{tr.TargetAtrMultAtEntry:F3}"
                    );
                }

                // DIAG: periodic flush so you can see rows while running
                FlushBufferIfNeeded();
            }
            return;
        }

        // === PRIMARY series: evaluate entries once per new bar ===
        if (BarsInProgress != 0) return;
        if (CurrentBar < Math.Max(BarsRequiredToTrade, 6)) return;

        // cancel last bar's unfilled entry at new bar open (entry mechanics only)
        if (IsFirstTickOfBar && workingEntryOrder != null && CurrentBar > workingOrderBar)
        {
            Print($"[DIAG] Cancelling unfilled workingEntryOrder {workingEntryOrder?.Name} from prior bar.");
            try { CancelOrder(workingEntryOrder); } catch {}
            workingEntryOrder = null;
            workingOrderBar   = -1;
        }

        // true once-per-bar
        if (CurrentBar == lastProcessedBar) return;
        lastProcessedBar = CurrentBar;

        // time filter
        if (TimeFilter)
        {
            DateTime barTime = Time[0];
            TimeSpan startTs = ParseTimeSpan(WindowStart);
            TimeSpan endTs = ParseTimeSpan(WindowEnd);
            bool within = true;
            if (startTs != TimeSpan.MinValue && endTs != TimeSpan.MinValue)
            {
                DateTime start = barTime.Date + startTs;
                DateTime end   = barTime.Date + endTs;
                within = (barTime >= start && barTime <= end);
            }
            if (ShowTimeShade) BackBrushes[0] = within ? timeShadeBrush : timeShadeClear;
            if (!within)
            {
                if (IsFirstTickOfBar) Print($"[DIAG] Skipping bar {barTime:O} outside time window {WindowStart}-{WindowEnd}");
                return;
            }
        }
        else if (ShowTimeShade) BackBrushes[0] = timeShadeClear;

        // pattern logic (AS-IS — includes bar[0] just like your original)
        bool isUpBar0=Close[0]>Open[0]; bool isDownBar0=Close[0]<Open[0];
        bool isUpBar1=Close[1]>Open[1]; bool isDownBar1=Close[1]<Open[1];
        bool isUpBar2=Close[2]>Open[2]; bool isDownBar2=Close[2]<Open[2];
        bool isUpBar3=Close[3]>Open[3]; bool isDownBar3=Close[3]<Open[3];
        bool isUpBar4=Close[4]>Open[4]; bool isDownBar4=Close[4]<Open[4];
        bool isUpBar5=Close[5]>Open[5]; bool isDownBar5=Close[5]<Open[5];

        bool long6Bar  = isUpBar5 && isDownBar4 && isDownBar3 && isDownBar2 && isDownBar1 && isUpBar0;
        bool short6Bar = isDownBar5 && isUpBar4 && isUpBar3 && isUpBar2 && isUpBar1 && isDownBar0;
        bool long5Bar  = isUpBar4 && isDownBar3 && isDownBar2 && isDownBar1 && isUpBar0;
        bool short5Bar = isDownBar4 && isUpBar3 && isUpBar2 && isUpBar1 && isDownBar0;
        bool long4Bar  = isUpBar3 && isDownBar2 && isDownBar1 && isUpBar0;
        bool short4Bar = isDownBar3 && isUpBar2 && isUpBar1 && isDownBar0;

        bool anyEnabled = FourBarSetup || FiveBarSetup || SixBarSetup;
        bool use4 = FourBarSetup || !anyEnabled;
        bool use5 = FiveBarSetup;
        bool use6 = SixBarSetup;

        bool baseLong  = (use6 && long6Bar)  || (use5 && long5Bar)  || (use4 && long4Bar);
        bool baseShort = (use6 && short6Bar) || (use5 && short5Bar) || (use4 && short4Bar);

        // EMA filter (AS-IS)
        bool emaPassLong  = !UseEmaFilter || (isUpBar0 && Close[0] > ema[0]);
        bool emaPassShort = !UseEmaFilter || (isDownBar0 && Close[0] < ema[0]);

        bool takeLong  = baseLong  && emaPassLong;
        bool takeShort = baseShort && emaPassShort;

        string tagPrefix =
            (use6 && (long6Bar || short6Bar)) ? "4 BAR" :
            (use5 && (long5Bar || short5Bar)) ? "3 BAR" :
                                                "2 BAR";

        // sizing by risk (AS-IS; ATR[0])
        double stopPts = StopLossMultiplier * atr[0];
        double stopDollars = stopPts * Instrument.MasterInstrument.PointValue;
        if (stopDollars <= 0) return;
        int qty = (int)Math.Floor(RiskPerTrade / stopDollars);
        qty = Math.Min(qty, MaxContracts);

        if (qty > 0)
        {
            if (takeLong)
            {
                OrderType ordType; double limitPrice = 0.0; double stopPrice = 0.0;
                switch (EntryType)
                {
                    case EntryMode.BarExtremaLimit:
                        ordType    = OrderType.Limit;
                        limitPrice = Low[0];
                        break;
                    case EntryMode.OneTickBreak:
                        ordType   = OrderType.StopMarket;
                        stopPrice = High[0] + TickSize;
                        break;
                    case EntryMode.FibonacciRetracement:
                        ordType    = OrderType.Limit;
                        double rangeL = High[0] - Low[0];
                        limitPrice = High[0] - (FibRetracePct * rangeL);
                        break;
                    default:
                        ordType   = OrderType.StopMarket;
                        stopPrice = High[0] + TickSize;
                        break;
                }
                string sig = tagPrefix + " LE_" + (++entrySeq);
                Print($"[DIAG] SUBMIT {sig} qty={qty} type={ordType} lp={limitPrice} sp={stopPrice} at {Time[0]:O}");
                var entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, ordType, qty, limitPrice, stopPrice, string.Empty, sig);
                if (entryOrder != null)
                {
                    pendingEntryOrders.Add(entryOrder);
                    workingEntryOrder = entryOrder;
                    workingOrderBar   = CurrentBar;
                }
            }

            if (takeShort)
            {
                OrderType ordType; double limitPrice = 0.0; double stopPrice = 0.0;
                switch (EntryType)
                {
                    case EntryMode.BarExtremaLimit:
                        ordType    = OrderType.Limit;
                        limitPrice = High[0];
                        break;
                    case EntryMode.OneTickBreak:
                        ordType   = OrderType.StopMarket;
                        stopPrice = Low[0] - TickSize;
                        break;
                    case EntryMode.FibonacciRetracement:
                        ordType    = OrderType.Limit;
                        double rangeS = High[0] - Low[0];
                        limitPrice = Low[0] + (FibRetracePct * rangeS);
                        break;
                    default:
                        ordType   = OrderType.StopMarket;
                        stopPrice = Low[0] - TickSize;
                        break;
                }
                string sig = tagPrefix + " SE_" + (++entrySeq);
                Print($"[DIAG] SUBMIT {sig} qty={qty} type={ordType} lp={limitPrice} sp={stopPrice} at {Time[0]:O}");
                var entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, ordType, qty, limitPrice, stopPrice, string.Empty, sig);
                if (entryOrder != null)
                {
                    pendingEntryOrders.Add(entryOrder);
                    workingEntryOrder = entryOrder;
                    workingOrderBar   = CurrentBar;
                }
            }
        }
    }

    // ===== OnExecutionUpdate: open/close per-entry trades; submit brackets =====
    protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time)
    {
        var o = execution?.Order;

        // DIAG: always print executions seen
        Print($"[DIAG][OnExecutionUpdate] {time:O} name={o?.Name} state={o?.OrderState} act={o?.OrderAction} avg={o?.AverageFillPrice} filled={o?.Filled} mp={marketPosition}");

        if (o == null || o.OrderState != OrderState.Filled)
            return;

        // if entry filled, clear one-bar pointer so it won't be canceled next bar
        if (workingEntryOrder != null && o.OrderId == workingEntryOrder.OrderId)
        {
            workingEntryOrder = null;
            workingOrderBar = -1;
        }

        // ENTRY filled: open a new ManagedTrade and place its own SL/PT (on tick series)
        if (o.Name.Contains(" LE_") || o.Name.Contains(" SE_"))
        {
            string side = (o.OrderAction == OrderAction.Buy) ? "LONG" : "SHORT";
            string tradeId = o.OrderId;
            double atrPts = atr[0];
            var mt = new ManagedTrade(tradeId, o.OrderId, o.AverageFillPrice, o.Filled, side,
                                      atrPts, StopLossMultiplier, TargetMultiplier);

            string tagBase = (o.Name.StartsWith("4 ") ? "4 BAR" : (o.Name.StartsWith("3 ") ? "3 BAR" : "2 BAR"));
            string oco = "OCO-" + o.OrderId;

            double stopDist = StopLossMultiplier * atrPts;
            double tgtDist  = TargetMultiplier  * atrPts;

            if (side == "LONG")
            {
                double stopPrice   = mt.EntryPrice - stopDist;
                double targetPrice = mt.EntryPrice + tgtDist;
                mt.ProfitTargetOrder = SubmitOrderUnmanaged(1, OrderAction.Sell,       OrderType.Limit,     mt.EntryQty, targetPrice, 0, oco, tagBase + " Target");
                mt.StopLossOrder     = SubmitOrderUnmanaged(1, OrderAction.Sell,       OrderType.StopMarket, mt.EntryQty, 0,           stopPrice, oco, tagBase + " Stop Loss");
            }
            else
            {
                double stopPrice   = mt.EntryPrice + stopDist;
                double targetPrice = mt.EntryPrice - tgtDist;
                mt.ProfitTargetOrder = SubmitOrderUnmanaged(1, OrderAction.BuyToCover, OrderType.Limit,     mt.EntryQty, targetPrice, 0, oco, tagBase + " Target");
                mt.StopLossOrder     = SubmitOrderUnmanaged(1, OrderAction.BuyToCover, OrderType.StopMarket, mt.EntryQty, 0,           stopPrice, oco, tagBase + " Stop Loss");
            }
            openTrades.Add(mt);
            return;
        }

        // EXIT filled: close the specific ManagedTrade (PT/SL/FLAT)
        if (o.Name.EndsWith(" Stop Loss") || o.Name.EndsWith(" Target") || o.Name == "FLAT")
        {
            var mt = openTrades.FirstOrDefault(t => t.IsOpen && (t.StopLossOrder == o || t.ProfitTargetOrder == o));
            if (mt == null)
            {
                string oco = o.Oco;
                if (!string.IsNullOrEmpty(oco) && oco.StartsWith("OCO-"))
                {
                    string entryId = oco.Substring(4);
                    mt = openTrades.FirstOrDefault(t => t.IsOpen && t.EntryOrderId == entryId);
                }
            }

            if (mt != null)
            {
                double exitPrice = o.AverageFillPrice;
                double tradePnL = (mt.Side == "LONG")
                    ? (exitPrice - mt.EntryPrice) * mt.EntryQty * Instrument.MasterInstrument.PointValue
                    : (mt.EntryPrice - exitPrice) * mt.EntryQty * Instrument.MasterInstrument.PointValue;

                string finalTiso = Time[0].ToString("o");
                logBuffer.Add(
                    $"{mt.TradeId},{finalTiso},{exitPrice:F2},{tradePnL:F2},0.00,{tradePnL:F2},{mt.EntryPrice:F2},{mt.AtrAtEntryPts:F4},{mt.Side},{mt.EntryQty},{mt.StopAtrMultAtEntry:F3},{mt.TargetAtrMultAtEntry:F3}"
                );

                // DIAG: see exit row promptly
                FlushBufferIfNeeded();

                mt.IsOpen = false;
            }
            openTrades.RemoveAll(t => !t.IsOpen);
            return;
        }

        // safety-net: if flat, clear any lingering IsOpen flags
        if (marketPosition == MarketPosition.Flat && openTrades.Any(t => t.IsOpen))
        {
            foreach (var mt in openTrades) mt.IsOpen = false;
            openTrades.RemoveAll(t => !t.IsOpen);
        }
    }

    protected override void OnOrderUpdate(Order order, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, OrderState orderState, DateTime time, ErrorCode error, string nativeError)
    {
        if (order == null) return;

        // DIAG: visibility into order lifecycle (no BarsInProgress usage)
        if (order.OrderState == OrderState.Rejected || error != ErrorCode.NoError || !string.IsNullOrEmpty(nativeError))
        {
            Print($"[DIAG][OnOrderUpdate] {time:O} REJECTED/ERR name={order.Name} act={order.OrderAction} type={order.OrderType} " +
                  $"qty={order.Quantity} state={order.OrderState} err={error} native='{nativeError}' limit={limitPrice} stop={stopPrice}");
        }
        else if (order.OrderState == OrderState.Cancelled)
        {
            Print($"[DIAG][OnOrderUpdate] {time:O} CANCELLED name={order.Name} act={order.OrderAction} type={order.OrderType} " +
                  $"qty={order.Quantity} oco='{order.Oco}' limit={limitPrice} stop={stopPrice}");
        }
        else if (order.OrderState == OrderState.Working && IsFirstTickOfBar)
        {
            Print($"[DIAG][OnOrderUpdate] {time:O} WORKING name={order.Name} limit={limitPrice} stop={stopPrice}");
        }

        // original maintenance: drop pending list on cancel/reject
        if (order.Name.Contains(" LE_") || order.Name.Contains(" SE_"))
        {
            if (order.OrderState == OrderState.Cancelled || order.OrderState == OrderState.Rejected)
                pendingEntryOrders.RemoveAll(o => o.OrderId == order.OrderId);
        }
    }

    private TimeSpan ParseTimeSpan(string hhmmss)
    {
        if (string.IsNullOrWhiteSpace(hhmmss)) return TimeSpan.MinValue;
        TimeSpan ts; return TimeSpan.TryParse(hhmmss, out ts) ? ts : TimeSpan.MinValue;
    }

    // ===== CSV flush helpers =====
    private const int FlushEveryNLines = 500;

    private void FlushBufferIfNeeded()
    {
        if (logWriter == null) return;
        if (logBuffer.Count < FlushEveryNLines) return;

        try
        {
            foreach (var line in logBuffer) logWriter.WriteLine(line);
            logWriter.Flush();
            logBuffer.Clear();
        }
        catch (Exception ex)
        {
            Print("[DIAG] Flush error: " + ex.Message);
        }
    }

    private void FlushBufferAll()
    {
        if (logWriter == null) return;
        if (logBuffer.Count == 0) return;

        try
        {
            foreach (var line in logBuffer) logWriter.WriteLine(line);
            logWriter.Flush();
            logBuffer.Clear();
            Print("[DIAG] Final CSV flush complete.");
        }
        catch (Exception ex)
        {
            Print("[DIAG] Final flush error: " + ex.Message);
        }
    }

    #region Properties
    [NinjaScriptProperty, Display(Name="2 Bar", GroupName="Pattern Setups", Order=0)]
    public bool FourBarSetup { get; set; }

    [NinjaScriptProperty, Display(Name="3 Bar", GroupName="Pattern Setups", Order=1)]
    public bool FiveBarSetup { get; set; }

    [NinjaScriptProperty, Display(Name="4 Bar", GroupName="Pattern Setups", Order=2)]
    public bool SixBarSetup { get; set; }

    [NinjaScriptProperty, Range(1, int.MaxValue), Display(Name="ATR Period", GroupName="Risk Sizing", Order=0)]
    public int AtrPeriod { get; set; }

    [NinjaScriptProperty, Range(1, int.MaxValue), Display(Name="Risk Per Trade ($)", GroupName="Risk Sizing", Order=1)]
    public double RiskPerTrade { get; set; }

    [NinjaScriptProperty, Range(1, int.MaxValue), Display(Name="Max Contracts", GroupName="Risk Sizing", Order=2)]
    public int MaxContracts { get; set; }

    [NinjaScriptProperty, Range(0.1, double.MaxValue), Display(Name="Target (xATR)", GroupName="Exit Multipliers", Order=0)]
    public double TargetMultiplier { get; set; }

    [NinjaScriptProperty, Range(0.1, double.MaxValue), Display(Name="Stop (xATR)", GroupName="Exit Multipliers", Order=1)]
    public double StopLossMultiplier { get; set; }

    [NinjaScriptProperty, Display(Name="Entry Mode", GroupName="Entry Mode", Order=0)]
    public EntryMode EntryType { get; set; }

    [NinjaScriptProperty, Range(0.0, 1.0), Display(Name="Fib Retracement %", GroupName="Entry Mode", Order=1)]
    public double FibRetracePct { get; set; }

    [NinjaScriptProperty, Display(Name="Use EMA Filter", GroupName="EMA Filter", Order=0)]
    public bool UseEmaFilter { get; set; }

    [NinjaScriptProperty, Range(1, int.MaxValue), Display(Name="EMA Period", GroupName="EMA Filter", Order=1)]
    public int EmaPeriod { get; set; }

    [NinjaScriptProperty, Display(Name="Time Filter", GroupName="Time Filter", Order=0)]
    public bool TimeFilter { get; set; }

    [NinjaScriptProperty, Display(Name="Window Start (hh:mm:ss)", GroupName="Time Filter", Order=1)]
    public string WindowStart { get; set; }

    [NinjaScriptProperty, Display(Name="Window End (hh:mm:ss)", GroupName="Time Filter", Order=2)]
    public string WindowEnd { get; set; }

    [NinjaScriptProperty, Display(Name="Flatten At Time", GroupName="Flatten At Time", Order=0)]
    public bool FlattenAtTime { get; set; }

    [NinjaScriptProperty, Display(Name="Flatten Time (hh:mm:ss)", GroupName="Flatten At Time", Order=1)]
    public string FlattenTime { get; set; }

    [NinjaScriptProperty, Display(Name="Show Time Shade", GroupName="Visuals", Order=0)]
    public bool ShowTimeShade { get; set; }

    [NinjaScriptProperty, Range(0,255), Display(Name="Shade Opacity (0-255)", GroupName="Visuals", Order=1)]
    public int ShadeOpacity { get; set; }

    [NinjaScriptProperty, Display(Name="Log Folder", GroupName="Logging", Order=0)]
    public string LogFolder { get; set; }

    [NinjaScriptProperty, Display(Name="CSV File Prefix", GroupName="Logging", Order=1)]
    public string CsvFilePrefix { get; set; }
    #endregion
}

}