#region Using declarations using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Xml.Serialization; using NinjaTrader.Cbi; using NinjaTrader.Gui; using NinjaTrader.Gui.Chart; using NinjaTrader.Gui.SuperDom; using NinjaTrader.Gui.Tools; using NinjaTrader.Data; using NinjaTrader.NinjaScript; using NinjaTrader.Core.FloatingPoint; using NinjaTrader.NinjaScript.Indicators; using NinjaTrader.NinjaScript.DrawingTools; #endregion namespace NinjaTrader.NinjaScript.Strategies { [Gui.CategoryOrder("Scanning Logic", 10)] [Gui.CategoryOrder("Trigger Conditions", 20)] [Gui.CategoryOrder("Options", 30)] [Gui.CategoryOrder("Settings", 40)] public class StructureFlowEntrySystem : Strategy { // v1 = initial version (29-JUN-2025) #region Variables private string codeUpdated = "Last updated: 29-JUN-2025"; private string codeVers = "v1"; private string codeAuthor = "Author: XX"; private string codeFor = "For: Reece"; private string codeOrder = "Order Ref: # 10278"; private string codeProductName = "Product Name: StructureFlowEntrySystem"; private Order long1, short1, stop1, profit1; private int lcb; private bool beDone, trailStart; private double TickValue, TicksPerPoint, stopPrice, profitPrice, entryPrice; private bool isError = false; private SimpleFont myFont = new SimpleFont("Calibri", 12); private LuxAlgoMarketStructureFractal bos_indi_15m; private FairValueGapFVGIndicator fvg_indi_15m; private FractalsIndicator fractals_indi_15m; private OrderBlockIndicator orderBlock_indi_15m; // FVG private int lastDirection15m; private double activeFvgTop; private double activeFvgBottom; private DateTime activeFvgTimestamp; private bool isLongFvgTrigger; private bool isShortFvgTrigger; // Fractals private double activeFractalHighToSweep; private double activeFractalLowToSweep; private bool isLongFractalSweepTrigger; private bool isShortFractalSweepTrigger; // Order Blocks private double activeObTop; private double activeObBottom; private bool isLongOrderBlockTrigger; private bool isShortOrderBlockTrigger; private Swing swing_5m; private FairValueGapFVGIndicator fvg_indi_5m; // Entry Logic private double active5mGapTop; private double active5mGapBottom; private double active5mSwingLowPrice; private double active5mSwingHighPrice; private bool active5mFvgFoundInSwing; private bool isLongEntrySignal; private bool isShortEntrySignal; #endregion #region Config public override string DisplayName { get { return Name; } } protected override void OnOrderTrace(DateTime timestamp, string message) { if (traceOrders) PM(string.Format("{0} {1}", timestamp, message)); } protected override void OnStateChange() { if (State == State.SetDefaults) { Name = Description = "Structure Flow Entry System"; Calculate = Calculate.OnPriceChange; TraceOrders = true; StopTargetHandling = StopTargetHandling.PerEntryExecution; IsExitOnSessionCloseStrategy = false; EntryHandling = EntryHandling.UniqueEntries; EntriesPerDirection = 1; versionInfo = codeVers + " " + codeUpdated; backTest = true; qty1 = 2; stopPoints = 0.25; scanningTimeframe = 15; triggerConditionsFractal = triggerConditionsFVG = triggerConditionsOrderBlock = true; useBreakeven = useTrail = true; closePctofPos1 = 50; trailPoints = 30; profit1RR = 2; indiChochBosLength = 5; indiFractalPeriods = 1; indiOrderBlockMaxBoxes = 10; indiOrderBlockBarsForEntry = indiFractalLookback = indiFractalBarsForEntry = 5; entrySwingStrength = 5; entryStartTime = 930; // Default to 9:30 AM entryEndTime = 1530; // Default to 3:30 PM } else if (State == State.Configure) { RealtimeErrorHandling = RealtimeErrorHandling.IgnoreAllErrors; AddDataSeries(BarsPeriodType.Minute, scanningTimeframe); } else if (State == State.DataLoaded) { // === Configuration Validation === // The primary chart must be a minute-based chart type. isError = BarsPeriod.BarsPeriodType != BarsPeriodType.Minute; if (isError) { Draw.TextFixed(this, "ErrorMessage", "Error: This strategy must be applied to a Minute-based chart.", TextPosition.BottomRight, Brushes.White, myFont, Brushes.Red, Brushes.Red, 100, DashStyleHelper.Solid, 4, false, ""); return; } // The 'scanningTimeframe' must be a higher value than the primary chart's timeframe. isError = scanningTimeframe <= BarsPeriod.Value; if (isError) { string msg = string.Format("Error: The 'scanningTimeframe' ({0}) must be greater than the chart's timeframe ({1})", scanningTimeframe, BarsPeriod.Value); Draw.TextFixed(this, "ErrorMessage", msg, TextPosition.BottomRight, Brushes.White, myFont, Brushes.Red, Brushes.Red, 100, DashStyleHelper.Solid, 4, false, ""); return; } // === End Validation === // 15 min indicators bos_indi_15m = LuxAlgoMarketStructureFractal(BarsArray[1], showOutput, "", indiChochBosLength, true, Brushes.Blue, true, Brushes.Red, true, Brushes.LightBlue, true, Brushes.DarkRed, false, "Top Right", "Small"); fvg_indi_15m = FairValueGapFVGIndicator(BarsArray[1], showOutput, "", true, true, Brushes.Green, Brushes.Red, Brushes.Blue, 50, false, Brushes.Pink); fractals_indi_15m = FractalsIndicator(BarsArray[1], indiFractalPeriods, showOutput, ""); orderBlock_indi_15m = OrderBlockIndicator(BarsArray[1], showOutput, "", true, Brushes.Blue, Brushes.Red, 50, false); beDone = trailStart = false; TickValue = Instrument.MasterInstrument.PointValue * TickSize; TicksPerPoint = Instrument.MasterInstrument.PointValue / TickValue; // FVG lastDirection15m = 0; activeFvgTop = 0; activeFvgBottom = 0; isLongFvgTrigger = false; isShortFvgTrigger = false; // Fractals activeFractalHighToSweep = 0; activeFractalLowToSweep = 0; isLongFractalSweepTrigger = false; isShortFractalSweepTrigger = false; // Order Blocks activeObTop = 0; activeObBottom = 0; isLongOrderBlockTrigger = false; isShortOrderBlockTrigger = false; swing_5m = Swing(entrySwingStrength); fvg_indi_5m = FairValueGapFVGIndicator(showOutput, "", true, true, Brushes.Green, Brushes.Red, Brushes.Blue, 50, false, Brushes.Pink); active5mSwingLowPrice = 0; active5mSwingHighPrice = 0; active5mFvgFoundInSwing = false; isLongEntrySignal = false; isShortEntrySignal = false; active5mGapTop = 0; active5mGapBottom = 0; PrintSettings(); } } #endregion #region On Bar Update protected override void OnBarUpdate() { if (CurrentBar < BarsRequiredToTrade || CurrentBars[1] < BarsRequiredToTrade || State == State.Historical && !backTest) return; if (bos_indi_15m == null || fvg_indi_15m == null || fractals_indi_15m == null || orderBlock_indi_15m == null) return; lcb = (!Bars.IsTickReplay && (State == State.Historical || Calculate == Calculate.OnBarClose)) ? 0 : 1; double bid = GetCurrentBid(); double ask = GetCurrentAsk(); if (BarsInProgress == 0) { // Get the current directional bias from the 15m indicator int newDirection15m = (int)bos_indi_15m.Direction[lcb]; if (newDirection15m != lastDirection15m) { PM("New Directional bias found: " + newDirection15m); } #region Scanning Logic // Reset FVG trigger flags at the start of each bar isLongFvgTrigger = false; isShortFvgTrigger = false; // --- ADDED: Only run FVG logic if the condition is enabled --- if (triggerConditionsFVG) { // 1. Check for a change in directional bias. This resets the active FVG. if (newDirection15m != 0 && newDirection15m != lastDirection15m) { activeFvgTop = 0; activeFvgBottom = 0; } // 2. Check if the active FVG has been invalidated. if (activeFvgTop > 0) { if (lastDirection15m == 1 && Closes[1][lcb] < activeFvgBottom) { activeFvgTop = 0; activeFvgBottom = 0; PM("Invalidated FVG"); } else if (lastDirection15m == -1 && Closes[1][lcb] > activeFvgTop) { activeFvgTop = 0; activeFvgBottom = 0; PM("Invalidated FVG"); } } // 3. If we don't have an active FVG, check if a NEW one has formed. if (activeFvgTop == 0 && newDirection15m != 0) { if (newDirection15m == 1) { if (fvg_indi_15m.BullFvgTop[lcb] > 0) { activeFvgTop = fvg_indi_15m.BullFvgTop[lcb]; activeFvgBottom = fvg_indi_15m.BullFvgBottom[lcb]; PM("New Bullish FVG found on 15m"); } } else if (newDirection15m == -1) { if (fvg_indi_15m.BearFvgTop[lcb] > 0) { activeFvgTop = fvg_indi_15m.BearFvgTop[lcb]; activeFvgBottom = fvg_indi_15m.BearFvgBottom[lcb]; PM("New Bearish FVG found on 15m"); } } } // 4. Finally, check for the trigger condition. if (activeFvgTop > 0) { if (lastDirection15m == 1 && Close[0] >= activeFvgBottom && Close[0] <= activeFvgTop) { isLongFvgTrigger = true; PM("FVG Triggered"); } else if (lastDirection15m == -1 && Close[0] >= activeFvgBottom && Close[0] <= activeFvgTop) { isShortFvgTrigger = true; PM("FVG Triggered"); } } } #endregion #region Fractal Sweep Logic // Reset fractal sweep trigger flags at the start of each bar isLongFractalSweepTrigger = false; isShortFractalSweepTrigger = false; // Only run Fractal logic if the condition is enabled --- if (triggerConditionsFractal) { if (newDirection15m != lastDirection15m) { activeFractalHighToSweep = 0; activeFractalLowToSweep = 0; } if (lastDirection15m == 1) { if (activeFractalLowToSweep == 0) { for (int i = lcb; i < lcb + 50; i++) { double fractalLowPrice = fractals_indi_15m.FractalLow[i]; if (fractalLowPrice > 0) { activeFractalLowToSweep = fractalLowPrice; break; } } } if (activeFractalLowToSweep > 0 && Close[0] < activeFractalLowToSweep) { isLongFractalSweepTrigger = true; activeFractalLowToSweep = 0; } } else if (lastDirection15m == -1) { if (activeFractalHighToSweep == 0) { for (int i = lcb; i < lcb + 50; i++) { double fractalHighPrice = fractals_indi_15m.FractalHigh[i]; if (fractalHighPrice > 0) { activeFractalHighToSweep = fractalHighPrice; break; } } } if (activeFractalHighToSweep > 0 && Close[0] > activeFractalHighToSweep) { isShortFractalSweepTrigger = true; activeFractalHighToSweep = 0; } } } #endregion #region Order Block Logic // Reset order block trigger flags at the start of each bar isLongOrderBlockTrigger = false; isShortOrderBlockTrigger = false; // Only run Order Block logic if the condition is enabled if (triggerConditionsOrderBlock) { if (newDirection15m != lastDirection15m) { activeObTop = 0; activeObBottom = 0; } if (lastDirection15m == 1) { if (activeObTop > 0 && Closes[1][lcb] > activeObTop) { isLongOrderBlockTrigger = true; activeObTop = 0; activeObBottom = 0; } if (activeObTop == 0) { for (int i = lcb; i < lcb + 50; i++) { double obTop = orderBlock_indi_15m.BullishObTop[i]; if (obTop > 0) { activeObTop = obTop; activeObBottom = orderBlock_indi_15m.BullishObBottom[i]; break; } } } } else if (lastDirection15m == -1) { if (activeObTop > 0 && Closes[1][lcb] < activeObBottom) { isShortOrderBlockTrigger = true; activeObTop = 0; activeObBottom = 0; } if (activeObTop == 0) { for (int i = lcb; i < lcb + 50; i++) { double obTop = orderBlock_indi_15m.BearishObTop[i]; if (obTop > 0) { activeObTop = obTop; activeObBottom = orderBlock_indi_15m.BearishObBottom[i]; break; } } } } } #endregion // Update the last known direction at the end of the logic block if (newDirection15m != 0) { lastDirection15m = newDirection15m; } #region Entry Logic // Reset final entry signals at the start of each bar isLongEntrySignal = false; isShortEntrySignal = false; // Check if any of the higher timeframe triggers are active bool is15mLongTrigger = (triggerConditionsFVG && isLongFvgTrigger) || (triggerConditionsFractal && isLongFractalSweepTrigger) || (triggerConditionsOrderBlock && isLongOrderBlockTrigger); bool is15mShortTrigger = (triggerConditionsFVG && isShortFvgTrigger) || (triggerConditionsFractal && isShortFractalSweepTrigger) || (triggerConditionsOrderBlock && isShortOrderBlockTrigger); // --- ENTRY PATTERN STATE MACHINE (5-MINUTE CHART) --- // For Bearish Setups (Swing Low -> Swing High, looking for a Bullish Gap to break) if (is15mShortTrigger) { // 1. A new Swing Low marks the START of a potential entry structure. Invalidate everything from the previous structure. if (swing_5m.SwingLow[0] > 0) { active5mSwingLowPrice = swing_5m.SwingLow[0]; active5mSwingHighPrice = 0; active5mGapTop = 0; active5mGapBottom = 0; PM(string.Format("Entry Logic (Short): New 5m Swing Low detected at {0}. Waiting for Swing High.", active5mSwingLowPrice)); } // 2. A new Swing High, AFTER a Swing Low, COMPLETES the structure. We must now validate it for a SINGULAR bullish gap. if (swing_5m.SwingHigh[0] > 0 && active5mSwingLowPrice > 0 && active5mSwingHighPrice == 0) { active5mSwingHighPrice = swing_5m.SwingHigh[0]; PM(string.Format("Entry Logic (Short): 5m Swing High at {0}. Validating swing leg for a singular Bullish FVG.", active5mSwingHighPrice)); int swingLowBarsAgo = swing_5m.SwingLowBar(0, 1, 100); if (swingLowBarsAgo > -1) { List foundGaps = new List(); for (int i = 0; i <= swingLowBarsAgo; i++) { // For a bearish setup, we look for a BULLISH gap to break down through if (fvg_indi_5m.BullFvgTop[i] > 0) { foundGaps.Add(new FVGData { TopPrice = fvg_indi_5m.BullFvgTop[i], BottomPrice = fvg_indi_5m.BullFvgBottom[i] }); } } // Validate: There must be exactly ONE gap. if (foundGaps.Count == 1) { active5mGapTop = foundGaps[0].TopPrice; active5mGapBottom = foundGaps[0].BottomPrice; PM(string.Format("Entry Logic (Short): Valid singular Bullish FVG found. Armed. Range: {0} - {1}", active5mGapBottom, active5mGapTop)); } else { //PM(string.Format("Entry Logic (Short): Found {0} gaps. Required 1. Disarming setup.", foundGaps.Count)); active5mSwingLowPrice = 0; // Invalidate the whole structure } } } // 3. If armed, check for invalidation or trigger conditions. if (active5mGapTop > 0) { // Invalidation: Price (wick) breaks below the swing low. if (Low[0] < active5mSwingLowPrice) { PM(string.Format("Entry Logic (Short): Invalidated. Price wick at {0} broke swing low at {1}.", Low[0], active5mSwingLowPrice)); active5mSwingLowPrice = 0; active5mGapTop = 0; } // Trigger: Price CLOSES below the armed bullish gap. else if (Close[0] < active5mGapBottom) { // Check time filter before setting the final signal int currentTime = ToTime(Time[0]); if (currentTime >= entryStartTime && currentTime <= entryEndTime) { isShortEntrySignal = true; PrintTo = PrintTo.OutputTab2; PM("--- SHORT ENTRY SIGNAL MET ---"); PrintTo = PrintTo.OutputTab1; // The actual EnterShort() call would go here, on the next bar's open. } else { PM(string.Format("Entry Logic (Short): Trigger met, but outside of trade session ({0}).", currentTime)); } active5mSwingLowPrice = 0; // Invalidate after trigger to prevent re-entry active5mGapTop = 0; } } } // For Bullish Setups (Swing High -> Swing Low, looking for a Bearish Gap to break) if (is15mLongTrigger) { // 1. A new Swing High marks the START. if (swing_5m.SwingHigh[0] > 0) { active5mSwingHighPrice = swing_5m.SwingHigh[0]; active5mSwingLowPrice = 0; active5mGapTop = 0; active5mGapBottom = 0; PM(string.Format("Entry Logic (Long): New 5m Swing High detected at {0}. Waiting for Swing Low.", active5mSwingHighPrice)); } // 2. A new Swing Low COMPLETES the structure. Validate for a SINGULAR bearish gap. if (swing_5m.SwingLow[0] > 0 && active5mSwingHighPrice > 0 && active5mSwingLowPrice == 0) { active5mSwingLowPrice = swing_5m.SwingLow[0]; PM(string.Format("Entry Logic (Long): 5m Swing Low at {0}. Validating swing leg for a singular Bearish FVG.", active5mSwingLowPrice)); int swingHighBarsAgo = swing_5m.SwingHighBar(0, 1, 100); if (swingHighBarsAgo > -1) { List foundGaps = new List(); for (int i = 0; i <= swingHighBarsAgo; i++) { // For a bullish setup, we look for a BEARISH gap to break up through if (fvg_indi_5m.BearFvgTop[i] > 0) { foundGaps.Add(new FVGData { TopPrice = fvg_indi_5m.BearFvgTop[i], BottomPrice = fvg_indi_5m.BearFvgBottom[i] }); } } if (foundGaps.Count == 1) { active5mGapTop = foundGaps[0].TopPrice; active5mGapBottom = foundGaps[0].BottomPrice; PM(string.Format("Entry Logic (Long): Valid singular Bearish FVG found. Armed. Range: {0} - {1}", active5mGapBottom, active5mGapTop)); } else { PM(string.Format("Entry Logic (Long): Found {0} gaps. Required 1. Disarming setup.", foundGaps.Count)); active5mSwingHighPrice = 0; // Invalidate the whole structure } } } // 3. If armed, check for invalidation or trigger. if (active5mGapTop > 0) { // Invalidation: Price (wick) breaks above the swing high. if (High[0] > active5mSwingHighPrice) { PM(string.Format("Entry Logic (Long): Invalidated. Price wick at {0} broke swing high at {1}.", High[0], active5mSwingHighPrice)); active5mSwingHighPrice = 0; active5mGapTop = 0; } // Trigger: Price CLOSES above the armed bearish gap. else if (Close[0] > active5mGapTop) { int currentTime = ToTime(Time[0]); if (currentTime >= entryStartTime && currentTime <= entryEndTime) { isLongEntrySignal = true; PrintTo = PrintTo.OutputTab2; PM("--- LONG ENTRY SIGNAL MET ---"); PrintTo = PrintTo.OutputTab1; // The actual EnterLong() call would go here, on the next bar's open. } else { PM(string.Format("Entry Logic (Long): Trigger met, but outside of trade session ({0}).", currentTime)); } active5mSwingHighPrice = 0; // Invalidate after trigger active5mGapTop = 0; } } } #endregion #region Trail if (Position.MarketPosition != MarketPosition.Flat && !trailStart && useTrail) { if (Position.MarketPosition == MarketPosition.Long) { double trailTrig = Position.AveragePrice + (0 * TickSize); // update if (bid >= trailTrig) { trailStart = true; PM("Trail started @" + bid); } } else if (Position.MarketPosition == MarketPosition.Short) { double trailTrig = Position.AveragePrice - (0 * TickSize); // update if (ask <= trailTrig) { trailStart = true; PM("Trail started @ " + ask); } } } if (trailStart) { if (Position.MarketPosition == MarketPosition.Long) { double newTrail = bid - (0 * TickSize); // update if (stop1 != null && stop1.StopPrice < newTrail && bid > newTrail) { ChangeOrder(stop1, stop1.Quantity, 0, newTrail); } } else if (Position.MarketPosition == MarketPosition.Short) { double newTrail = ask + (0 * TickSize); // update if (stop1 != null && stop1.StopPrice > newTrail && ask < newTrail) { ChangeOrder(stop1, stop1.Quantity, 0, newTrail); } } } #endregion #region Breakeven if (!beDone && useBreakeven) { if (Position.MarketPosition == MarketPosition.Long) { double trigger = Position.AveragePrice + (0 * TickSize); // update double bePrice = Position.AveragePrice + (0 * TickSize); // update if (qty1 >= 0 && stop1 != null && bid >= trigger) { if (stop1.StopPrice < bePrice && bid > bePrice) { ChangeOrder(stop1, stop1.Quantity, 0, bePrice); beDone = true; PM("Position 1 set to breakeven: " + bePrice); } } } else if (Position.MarketPosition == MarketPosition.Short) { double trigger = Position.AveragePrice - (0 * TickSize); // update double bePrice = Position.AveragePrice - (0 * TickSize); // update if (qty1 >= 0 && stop1 != null && ask <= trigger) { if (stop1.StopPrice > bePrice && ask < bePrice) { ChangeOrder(stop1, stop1.Quantity, 0, bePrice); beDone = true; PM("Position 1 set to breakeven: " + bePrice); } } } } #endregion } } #endregion #region Order Update 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 (traceOrders) PM("Order: " + order); // One time only, as we transition from historical // Convert any old historical order object references to the live order submitted to the real-time account if (long1 != null && long1.IsBacktestOrder && State == State.Realtime) long1 = GetRealtimeOrder(long1); if (short1 != null && short1.IsBacktestOrder && State == State.Realtime) short1 = GetRealtimeOrder(short1); if (stop1 != null && stop1.IsBacktestOrder && State == State.Realtime) stop1 = GetRealtimeOrder(stop1); if (profit1 != null && profit1.IsBacktestOrder && State == State.Realtime) profit1 = GetRealtimeOrder(profit1); if (order.Name == "LONG") long1 = order; if (order.Name == "SHORT") short1 = order; if (order.Name == "STOP") stop1 = order; if (order.Name == "PROFIT") profit1 = order; if (long1 != null && long1 == order && long1.OrderState == OrderState.Cancelled) long1 = null; if (short1 != null && short1 == order && short1.OrderState == OrderState.Cancelled) short1 = null; if (stop1 != null && stop1 == order && stop1.OrderState == OrderState.Cancelled) stop1 = null; if (profit1 != null && profit1 == order && profit1.OrderState == OrderState.Cancelled) profit1 = null; // Rejection handling if (order.OrderState == OrderState.Rejected) { PM("********************** Order rejected *******************************"); PM("Order Name: " + order.Name); PM("Error Code: " + error.ToString()); PM("Native Error: " + nativeError); PM("Order Type: " + order.OrderType); PM("Limit Price: " + limitPrice); PM("Stop Price: " + stopPrice); PM("Quantity: " + quantity); PM("Current Bid: " + GetCurrentBid()); PM("Current Ask: " + GetCurrentAsk()); PM("*************Any open trades will be cancelled **********************"); if (Position.MarketPosition == MarketPosition.Long) ExitLong(); if (Position.MarketPosition == MarketPosition.Short) ExitShort(); if (long1 != null) CancelOrder(long1); if (short1 != null) CancelOrder(short1); if (stop1 != null) CancelOrder(stop1); if (profit1 != null) CancelOrder(profit1); ResetEverything(); } } #endregion #region Position Update protected override void OnPositionUpdate(Cbi.Position position, double averagePrice, int quantity, Cbi.MarketPosition marketPosition) { PM("Market position: " + marketPosition + " | Qty: " + quantity + " | Avg Price: " + averagePrice); } #endregion #region Execution Update protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time) { if (Position.MarketPosition == MarketPosition.Flat) { ResetEverything(); } PM("Execution: " + execution.Name + " Direction: " + execution.MarketPosition + " Price: " + execution.Price + " Qty: " + execution.Quantity); if (execution.Order.OrderState != OrderState.Filled) return; if (execution.Order.Name == "LONG") { stopPrice = Position.AveragePrice - (stopPoints * TickSize); profitPrice = Position.AveragePrice + (0 * TickSize); // update ExitLongStopMarket(0, true, qty1, stopPrice, "STOP", execution.Name); ExitLongLimit(0, true, qty1, profitPrice, "PROFIT", execution.Name); PM("Stop: " + stopPrice + " and Profit: " + profitPrice + " orders entered."); } else if (execution.Order.Name == "SHORT") { stopPrice = Position.AveragePrice + (stopPoints * TickSize); profitPrice = Position.AveragePrice - (0 * TickSize); // update ExitShortStopMarket(0, true, qty1, stopPrice, "STOP", execution.Name); ExitShortLimit(0, true, qty1, profitPrice, "PROFIT", execution.Name); PM("Stop: " + stopPrice + " and Profit: " + profitPrice + " orders entered."); } } #endregion #region Reset private void ResetEverything() { long1 = short1 = profit1 = stop1 = null; beDone = trailStart = false; // reset anything else here for when the strategy is flat } #endregion #region Properties [NinjaScriptProperty] [Display(Name = "Backtest", Description = "", Order = 10, GroupName = "Settings")] public bool backTest { get; set; } [NinjaScriptProperty] [Display(Name = "NinjaScript Output", Description = "", Order = 20, GroupName = "Settings")] public bool showOutput { get; set; } [NinjaScriptProperty] [Display(Name = "Trace Orders", Description = "", Order = 40, GroupName = "Settings")] public bool traceOrders { get; set; } [NinjaScriptProperty] [ReadOnly(true)] [Display(Name = "Version:", Description = "", Order = 50, GroupName = "Settings")] public string versionInfo { get; set; } [NinjaScriptProperty] [Display(Name = "Quantity", Description = "", Order = 40, GroupName = "Options")] public int qty1 { get; set; } [NinjaScriptProperty] [Display(Name = "Stop (points from FVG)", Description = "", Order = 50, GroupName = "Options")] public double stopPoints { get; set; } [NinjaScriptProperty] [Display(Name = "Profit #1 (risk-to-reward ratio)", Description = "", Order = 60, GroupName = "Options")] public double profit1RR { get; set; } [NinjaScriptProperty] [Display(Name = "Close % when Profit 1 Reached", Description = "", Order = 65, GroupName = "Options")] public double closePctofPos1 { get; set; } [NinjaScriptProperty] [Display(Name = "Use Breakeven", Description = "", Order = 70, GroupName = "Options")] public bool useBreakeven { get; set; } [NinjaScriptProperty] [Display(Name = "Use Trail", Description = "", Order = 95, GroupName = "Options")] public bool useTrail { get; set; } [NinjaScriptProperty] [Display(Name = "Trail Points", Description = "", Order = 110, GroupName = "Options")] public double trailPoints { get; set; } [NinjaScriptProperty] [Display(Name = "Timeframe (mins)", Description = "", Order = 10, GroupName = "Scanning Logic")] public int scanningTimeframe { get; set; } [NinjaScriptProperty] [Display(Name = "CHoCH/BOS Length", Description = "", Order = 20, GroupName = "Scanning Logic")] public int indiChochBosLength { get; set; } [NinjaScriptProperty] [Display(Name = "FVG", Description = "", Order = 10, GroupName = "Trigger Conditions")] public bool triggerConditionsFVG { get; set; } [NinjaScriptProperty] [Display(Name = "Fractal High/Low Sweep", Description = "", Order = 20, GroupName = "Trigger Conditions")] public bool triggerConditionsFractal { get; set; } [NinjaScriptProperty] [Display(Name = "Fractal Periods", Description = "", Order = 30, GroupName = "Trigger Conditions")] public int indiFractalPeriods { get; set; } [NinjaScriptProperty] [Display(Name = "Fractal Look Back", Description = "", Order = 40, GroupName = "Trigger Conditions")] public int indiFractalLookback { get; set; } [NinjaScriptProperty] [Display(Name = "Fractal # of Bars to Look for Entry", Description = "", Order = 50, GroupName = "Trigger Conditions")] public int indiFractalBarsForEntry { get; set; } [NinjaScriptProperty] [Display(Name = "Order Block", Description = "", Order = 60, GroupName = "Trigger Conditions")] public bool triggerConditionsOrderBlock { get; set; } [NinjaScriptProperty] [Display(Name = "Maximum OB Boxes", Description = "", Order = 70, GroupName = "Trigger Conditions")] public int indiOrderBlockMaxBoxes { get; set; } [NinjaScriptProperty] [Display(Name = "Order Block # of Bars to Look for Entry", Description = "", Order = 80, GroupName = "Trigger Conditions")] public int indiOrderBlockBarsForEntry { get; set; } [NinjaScriptProperty] [Display(Name = "Entry Swing Strength", Description = "The strength of the swing point for entry logic.", Order = 10, GroupName = "Entry Logic")] public int entrySwingStrength { get; set; } [NinjaScriptProperty] [Display(Name = "Entry Start Time (HHmm)", Description = "Start time for entries (e.g., 930 for 9:30 AM)", Order = 20, GroupName = "Entry Logic")] public int entryStartTime { get; set; } [NinjaScriptProperty] [Display(Name = "Entry End Time (HHmm)", Description = "End time for entries (e.g., 1500 for 3:00 PM)", Order = 30, GroupName = "Entry Logic")] public int entryEndTime { get; set; } #endregion #region Print private void PrintSettings() { PM("#########################################################"); PM(codeAuthor); PM(codeFor); PM(codeOrder); PM(codeProductName); PM(codeUpdated); PM(codeVers); PM("#########################################################"); PM("Strategy enabled: " + DateTime.Now); PM("Strategy name: " + this.Name); PM("Strategy description: " + this.Description); PM("Strategy id: " + this.Id); PM("Strategy Settings..."); PM("Chart: " + this.ChartBars); PM("Chart: " + Bars.ToChartString()); PM("Instrument: " + Instrument.FullName); PM("Default Trading Hours: " + Instrument.MasterInstrument.TradingHours); PM("Chart Trading Hours: " + TradingHours.Name); PM("Timeframe: " + this.BarsPeriod); PM("Bars Period Type Name: " + BarsPeriod.BarsPeriodTypeName + " Type #" + BarsPeriod.BarsPeriodTypeSerialize + " Value: " + BarsPeriod.Value + " Value 2: " + BarsPeriod.Value2); PM("Tick Replay: " + IsTickReplays[0]); PM("Bars on chart: " + Count); PM("Bars - Start Date: " + Bars.FromDate.ToString("yyyy-MM-dd")); PM("Bars - End Date: " + Bars.ToDate.ToString("yyyy-MM-dd")); PM("Timezone: " + Core.Globals.GeneralOptions.TimeZoneInfo); PM("Install folder: " + NinjaTrader.Core.Globals.InstallDir); PM("User directory: " + NinjaTrader.Core.Globals.UserDataDir); PM("Machine ID: " + Cbi.License.MachineId); PM("---------------------------------------------------------"); PM("Setup..."); PM("Account: " + Account); PM("Calculate: " + Calculate); PM("Maximum bars look back: " + MaximumBarsLookBack); PM("Bars required to trade: " + BarsRequiredToTrade); PM("Start behhavior: " + StartBehavior); PM("---------------------------------------------------------"); PM("Historical fill processing..."); PM("Order fill resolution: " + OrderFillResolution); if (OrderFillResolution == OrderFillResolution.High) PM("Type: " + OrderFillResolutionType); if (OrderFillResolution == OrderFillResolution.High) PM("Value: " + OrderFillResolutionValue); PM("Fill limit orders on touch: " + IsFillLimitOnTouch); PM("Slippage: " + Slippage); PM("---------------------------------------------------------"); PM("Order handling..."); PM("Entries per direction: " + EntriesPerDirection); PM("Entry handling: " + EntryHandling); PM("Exit on session close: " + IsExitOnSessionCloseStrategy); if (IsExitOnSessionCloseStrategy) PM("Exit on session close seconds: " + ExitOnSessionCloseSeconds); PM("Stop & target submission: " + StopTargetHandling); PM("---------------------------------------------------------"); PM("Order properties..."); PM("Set order quantity: " + SetOrderQuantity); if (SetOrderQuantity == SetOrderQuantity.DefaultQuantity) PM("Default quantity: " + DefaultQuantity); PM("Time in force: " + TimeInForce); PM("---------------------------------------------------------"); PM("---------------------------------------------------------"); } private void PM(string msg, [System.Runtime.CompilerServices.CallerMemberName] string sourceMethod = "") { if (!showOutput) return; DateTime timestamp = DateTime.Now; try { if (Bars != null && CurrentBar >= 0) { timestamp = Time[0]; } } catch { /* Fallback to DateTime.Now */ } Print(string.Format("[{0} ({1}) - {2} {3}] {4:yyyy-MM-dd HH:mm:ss} - {5}", Name, this.Id, Bars != null ? Bars.ToChartString() : "No Bars", sourceMethod, timestamp, msg)); } #endregion } }