using NinjaTrader.Cbi; using NinjaTrader.Data; using NinjaTrader.Gui; using NinjaTrader.NinjaScript.Indicators; using NinjaTrader.NinjaScript.Indicators.NinjaCodingIndicators; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Windows.Media; using System.Linq; using NinjaTrader.NinjaScript.DrawingTools; using System.Windows; using System.Xml.Serialization; using NinjaTrader.Gui.Tools; using System.Security.Cryptography; using System.Text; using System.Runtime.InteropServices; using System.Net.Http; namespace NinjaTrader.NinjaScript.Strategies { [CategoryOrder(Setup, 0)] [CategoryOrder(GlobalSettings, 1)] [CategoryOrder(TimeWindowSettings, 2)] [CategoryOrder(PositionSettings, 3)] [CategoryOrder(EntrySettings, 4)] [CategoryOrder(ScaleInSettings, 5)] [CategoryOrder(FilterSettings, 6)] [CategoryOrder(ExitSettings, 7)] [CategoryOrder(MartingaleSettings, 8)] [CategoryOrder("License", 9)] public class MagicalMACDBot : Strategy { #region License Validation private bool isValidated = false; private static Dictionary licenseCache = new Dictionary(); private const int CACHE_DURATION_HOURS = 24; private string cachedHardwareId = null; [NinjaScriptProperty] [Display(Name = "License Key", Description = "Enter your Whop license key", Order = 1, GroupName = "License")] public string LicenseKey { get; set; } #endregion private string GetHardwareId() { if (!string.IsNullOrEmpty(cachedHardwareId)) return cachedHardwareId; try { var hardwareInfo = new StringBuilder(); GetSystemInfo(out SYSTEM_INFO sysInfo); // Collect hardware information hardwareInfo.Append(sysInfo.dwProcessorType) .Append(sysInfo.wProcessorArchitecture) .Append(sysInfo.wProcessorLevel) .Append(sysInfo.wProcessorRevision) .Append(sysInfo.dwNumberOfProcessors); // Add environment info hardwareInfo.Append(Environment.ProcessorCount) .Append(Environment.MachineName) .Append(Environment.OSVersion.Version.ToString()) .Append(Environment.UserName) .Append(Environment.SystemDirectory); // Hash the hardware info using (var sha256 = SHA256.Create()) { var bytes = Encoding.UTF8.GetBytes(hardwareInfo.ToString()); var hash = sha256.ComputeHash(bytes); cachedHardwareId = BitConverter.ToString(hash).Replace("-", ""); } return cachedHardwareId; } catch (Exception ex) { Log($"Error generating hardware ID: {ex.Message}", LogLevel.Error); return null; } } private bool ValidateLicense() { if (string.IsNullOrEmpty(LicenseKey)) { Log("License key is empty.", LogLevel.Error); return false; } var hwid = GetHardwareId(); if (string.IsNullOrEmpty(hwid)) { Log("Failed to generate hardware ID.", LogLevel.Error); return false; } // Check cache only for non-backtest scenarios or if already validated if (!Backtest && licenseCache.ContainsKey(LicenseKey)) { var cacheTime = licenseCache[LicenseKey]; if (DateTime.Now.Subtract(cacheTime).TotalHours < CACHE_DURATION_HOURS) { Log("Using cached license validation.", LogLevel.Information); return true; } licenseCache.Remove(LicenseKey); } try { using (var client = new System.Net.Http.HttpClient()) { const string apiKey = "A6FHGMmhg3PXodjW1XFmYx7Tg5cQgQlEETvTckfIQ1o"; client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); Log($"Making API request to validate license key: {LicenseKey}", LogLevel.Information); Log($"Hardware ID for validation: {hwid}", LogLevel.Information); // Send HWID in metadata var hwidMetadata = $"{{\"metadata\": {{\"HWID\": \"{hwid}\"}}}}"; var content = new StringContent(hwidMetadata, System.Text.Encoding.UTF8, "application/json"); // Validate license with Whop API var requestUrl = $"https://api.whop.com/api/v2/memberships/{LicenseKey}/validate_license"; var response = client.PostAsync(requestUrl, content).Result.Content.ReadAsStringAsync().Result; Log($"Raw API Response: {response}", LogLevel.Information); // Check if license is valid bool isValid = (response.Contains("\"status\":\"active\"") || response.Contains("\"status\":\"trialing\"") || response.Contains("\"status\":\"completed\"")) && response.Contains("\"valid\":true"); if (isValid) { Log("License validated successfully.", LogLevel.Information); if (!Backtest) // Only cache for non-backtest scenarios { licenseCache[LicenseKey] = DateTime.Now; } isValidated = true; return true; } Log("License validation failed - Invalid response from Whop API", LogLevel.Error); return false; } } catch (Exception ex) { Log($"Error validating license: {ex.Message}", LogLevel.Error); // Offline grace period check - not applicable during backtest if (!Backtest && licenseCache.ContainsKey(LicenseKey)) { var cacheTime = licenseCache[LicenseKey]; if (DateTime.Now.Subtract(cacheTime).TotalHours < (CACHE_DURATION_HOURS * 2)) { Log("Using offline grace period.", LogLevel.Warning); return true; } } Log("License validation failed - No valid cache during offline period", LogLevel.Error); return false; } } private void StopStrategy() { try { // Only stop if we're not in settings dialog and actually running if (State != State.SetDefaults && State != State.Configure && State != State.Terminated && IsEnabled && !_isTerminating && !_isInSettingsDialog && // Don't stop during settings dialog (ChartControl != null || Account != null) && // Only stop if actually on chart or in analyzer State != State.Historical) // Don't stop during historical/backtest { _isTerminating = true; Log("Strategy stopping due to invalid license.", LogLevel.Error); IsEnabled = false; _isTerminating = false; } } catch (Exception ex) { Log($"Error in StopStrategy: {ex.Message}", LogLevel.Error); } } [DllImport("kernel32.dll")] private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo); [StructLayout(LayoutKind.Sequential)] private struct SYSTEM_INFO { public ushort wProcessorArchitecture; public ushort wReserved; public uint dwPageSize; public IntPtr lpMinimumApplicationAddress; public IntPtr lpMaximumApplicationAddress; public IntPtr dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public ushort wProcessorLevel; public ushort wProcessorRevision; } #region Constants private const string SystemVersion = " V1.9"; private const string StrategyName = "Magical MACD Bot - Vincere Trading"; public override string DisplayName { get { return StrategyName + SystemVersion; } } private const string Setup = "Setup"; private const string GlobalSettings = "Global Settings"; private const string PositionSettings = "Position Settings"; private const string EntrySettings = "Entry Settings"; private const string ScaleInSettings = "Scale In Settings"; private const string FilterSettings = "Filter Settings"; private const string ExitSettings = "Exit Settings"; private const string TimeWindowSettings = "Time Settings"; private const string MartingaleSettings = "Martingale Settings"; #endregion #region Parameters [NinjaScriptProperty] [Display(Name = "Max Daily Loss On/Off", GroupName = GlobalSettings, Order = 20)] public bool MaxLossIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Max Daily Loss", GroupName = GlobalSettings, Order = 21)] public double MaxSessionLoss { get; set; } [NinjaScriptProperty] [Display(Name = "Max Daily Win On/Off", GroupName = GlobalSettings, Order = 22)] public bool MaxWinIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Max Daily Profit", GroupName = GlobalSettings, Order = 23)] public double MaxSessionProfit { get; set; } [NinjaScriptProperty] [Display(Name = "Backtest", GroupName = GlobalSettings, Order = 25)] public bool Backtest { get; set; } [NinjaScriptProperty] [Display(Name = "Pos Size 1", GroupName = PositionSettings, Order = 5)] public int PosSize1 { get; set; } [NinjaScriptProperty] [Display(Name = "Pos Size 2", GroupName = PositionSettings, Order = 6)] public int PosSize2 { get; set; } [NinjaScriptProperty] [Display(Name = "Pos Size 3", GroupName = PositionSettings, Order = 7)] public int PosSize3 { get; set; } [NinjaScriptProperty] [Display(Name = "Trade Window", GroupName = TimeWindowSettings, Order = 13)] public bool TradeWindow1IsOn { get; set; } [PropertyEditor("NinjaTrader.Gui.Tools.AutoCloseTimeEditorKey")] [NinjaScriptProperty] [Display(Name = "Trade Start 1", GroupName = TimeWindowSettings, Order = 14)] public DateTime TradeStart1 { get; set; } [PropertyEditor("NinjaTrader.Gui.Tools.AutoCloseTimeEditorKey")] [NinjaScriptProperty] [Display(Name = "Trade End 1", GroupName = TimeWindowSettings, Order = 15)] public DateTime TradeEnd1 { get; set; } [NinjaScriptProperty] [Display(Name = "Three Light Candles", Order = 3, GroupName = EntrySettings)] public bool ThreeLightCandlesIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Light To Dark", Order = 4, GroupName = EntrySettings)] public bool LightToDarkIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Light To Opposite", Order = 5, GroupName = EntrySettings)] public bool LightToOppositeIsOn { get; set; } [Display(Name = "Price MA Type", Order = 6, GroupName = EntrySettings)] public MyStrategyMAType MyMaType { get; set; } [Display(Name = "Price MA Period", Order = 7, GroupName = EntrySettings)] public int MaPeriod { get; set; } public enum MyStrategyMAType { EMA, SMA, HMA, TMA, TEMA, VWMA, WMA } [Range(1, int.MaxValue), NinjaScriptProperty] [Display(Name = "Fast Length", Order = 10, GroupName = EntrySettings)] public int FastLength { get; set; } [Range(1, int.MaxValue), NinjaScriptProperty] [Display(Name = "Slow Length", Order = 12, GroupName = EntrySettings)] public int SlowLength { get; set; } [Range(1, int.MaxValue), NinjaScriptProperty] [Display(Name = "Signal Length", Order = 13, GroupName = EntrySettings)] public int SignalLength { get; set; } [NinjaScriptProperty] [Display(Name = "Oscillator MA Type", Order = 14, GroupName = EntrySettings)] public NCMACD.MAType SmaSource { get; set; } [NinjaScriptProperty] [Display(Name = "Signal Line MA Type", Order = 15, GroupName = EntrySettings)] public NCMACD.MAType SmaSignal { get; set; } public enum Trade { Both, Long, Short } [NinjaScriptProperty] [Display(Name = "Trade Direction", GroupName = EntrySettings, Order = 17)] public Trade MyTradeDirection { get; set; } public enum EntryOrderType { Market, Limit } [NinjaScriptProperty] [Display(Name = "Entry Order Type", GroupName = EntrySettings, Order = 18)] public EntryOrderType MyEntryOrderType { get; set; } [NinjaScriptProperty] [Display(Name = "Limit Order Tick Offset", GroupName = EntrySettings, Order = 18)] public int LimitOrderOffset { get; set; } [NinjaScriptProperty] [Display(Name = "Bars Fill Wait", GroupName = EntrySettings, Order = 19)] public int BarsFillWait { get; set; } [NinjaScriptProperty] [Display(Name = "Re-Entry", GroupName = EntrySettings, Order = 20)] public bool ReEntryIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Re Wait Bars", GroupName = EntrySettings, Order = 21)] public int ReEntryWaitBars { get; set; } [NinjaScriptProperty] [Display(Name = "Reverse Trade Direction", GroupName = EntrySettings, Order = 22)] public bool ReverseTradeDirection { get; set; } [NinjaScriptProperty] [Display(Name = "Scale In", GroupName = ScaleInSettings, Order = 23)] public bool ScaleInIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Scale Size", GroupName = ScaleInSettings, Order = 24)] public int ScaleInSize { get; set; } [NinjaScriptProperty] [Display(Name = "Max Scale Ins", GroupName = ScaleInSettings, Order = 25)] public int MaxScaleIns { get; set; } [NinjaScriptProperty] [Display(Name = "Stop Loss Ticks", GroupName = ExitSettings, Order = 15)] public double StopLossTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Profit Target 1 Ticks", GroupName = ExitSettings, Order = 16)] public double ProfitTargetTicks1 { get; set; } [NinjaScriptProperty] [Display(Name = "Profit Target 2 Ticks", GroupName = ExitSettings, Order = 17)] public double ProfitTargetTicks2 { get; set; } [NinjaScriptProperty] [Display(Name = "Profit Target 3 Ticks", GroupName = ExitSettings, Order = 18)] public double ProfitTargetTicks3 { get; set; } [NinjaScriptProperty] [Display(Name = "BreakEven", GroupName = ExitSettings, Order = 19)] public bool BreakEvenIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "BreakEven Offset Ticks", GroupName = ExitSettings, Order = 21)] public double BreakEvenOffset { get; set; } [NinjaScriptProperty] [Display(Name = "BreakEven After Ticks", GroupName = ExitSettings, Order = 22)] public double BreakEvenAfterTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Trail", GroupName = ExitSettings, Order = 23)] public bool TrailIsOn { get; set; } [NinjaScriptProperty] [Display(Name = "Trail By Ticks", GroupName = ExitSettings, Order = 24)] public int TrailByTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Start Trail After Ticks", GroupName = ExitSettings, Order = 25)] public int StartTrailAfterTicks { get; set; } [NinjaScriptProperty] [Display(Name = "Trail Frequency", GroupName = ExitSettings, Order = 26)] public int TrailFrequency { get; set; } #endregion #region Values #endregion #region Init private SolidColorBrush _bgColor; private bool globalValidationState = false; // Added global state variable private readonly List _positions = new List(); //Corrected private double _currentProfit; //Added to track current profit private bool _isTerminating = false; // Add field for termination state tracking private bool _isInSettingsDialog = false; // Added flag for settings dialog state protected override void OnStateChange() { try { switch (State) { case State.SetDefaults: LicenseKey = string.Empty; SetDefaults(); SetParameters(); break; case State.Configure: // Set dialog flag at the start of Configure state _isInSettingsDialog = true; if ((ChartControl != null || Account != null) && !globalValidationState && IsEnabled && !_isTerminating) { if (!ValidateLicense()) { Log("License validation required.", LogLevel.Information); return; } globalValidationState = true; } // Always perform these operations regardless of validation ClearOutputWindow(); SuperImpose(); _bgColor = new SolidColorBrush(Colors.Green) { Opacity = 0.25 }; _bgColor.Freeze(); // Reset dialog flag at the end of Configure state _isInSettingsDialog = false; break; case State.DataLoaded: if ((ChartControl != null || Account != null) && !globalValidationState && IsEnabled && !_isTerminating && !_isInSettingsDialog) { if (!ValidateLicense()) { Log("License validation required for data processing.", LogLevel.Information); return; } } InitIndicators(); break; case State.Historical: if (!Backtest && State == State.Historical) return; if ((ChartControl != null || Account != null) && !globalValidationState && IsEnabled && !_isTerminating && !_isInSettingsDialog) { if (!ValidateLicense()) { Log("License validation required for historical processing.", LogLevel.Information); return; } } // Additional check for backtest mode if (Backtest && !isValidated && IsEnabled && !_isTerminating && !_isInSettingsDialog) { if (!ValidateLicense()) { Log("License validation required for backtesting.", LogLevel.Information); return; } } break; case State.Realtime: if ((ChartControl != null || Account != null) && !globalValidationState && IsEnabled && !_isTerminating && !_isInSettingsDialog) { if (!ValidateLicense()) { Log("License validation required for realtime trading.", LogLevel.Information); return; } } if (IsEnabled && !_isTerminating && !_isInSettingsDialog) Log("Strategy entering realtime state.", LogLevel.Information); break; case State.Terminated: // Only log actual terminations, not settings changes if (!IsEnabled && (ChartControl != null || Account != null) && !_isTerminating && !_isInSettingsDialog) { Log("Strategy disabled by user.", LogLevel.Information); } break; } } catch (Exception ex) { if (ChartControl != null || Account != null) { Log($"Error in OnStateChange: {ex.Message}", LogLevel.Error); if (!_isInSettingsDialog) // Don't stop during settings dialog StopStrategy(); } } } private void SuperImpose() { if (Calculate == Calculate.OnEachTick || Calculate == Calculate.OnPriceChange) Calculate = Calculate.OnBarClose; if (IsExitOnSessionCloseStrategy) IsExitOnSessionCloseStrategy = false; TraceOrders = false; } private void SetDefaults() { Name = StrategyName + SystemVersion; Calculate = Calculate.OnBarClose; IsExitOnSessionCloseStrategy = false; IsUnmanaged = true; BarsRequiredToTrade = 0; IsInstantiatedOnEachOptimizationIteration = true; RealtimeErrorHandling = RealtimeErrorHandling.StopCancelCloseIgnoreRejects; ShowTransparentPlotsInDataBox = true; } private void SetParameters() { PosSize1 = 1; PosSize2 = 2; PosSize3 = 3; StopLossTicks = 20; ProfitTargetTicks1 = 30; ProfitTargetTicks2 = 60; ProfitTargetTicks3 = 90; FastLength = 12; SlowLength = 26; SignalLength = 9; MaPeriod = 9; SmaSource = NinjaTrader.NinjaScript.Indicators.NinjaCodingIndicators.NCMACD.MAType.EMA; SmaSignal = NinjaTrader.NinjaScript.Indicators.NinjaCodingIndicators.NCMACD.MAType.EMA; TrailByTicks = 30; TrailIsOn = true; BreakEvenIsOn = true; TradeStart1 = new DateTime(2020, 01, 01, 9, 30, 0); TradeEnd1 = new DateTime(2020, 01, 01, 16, 30, 0); MaxSessionLoss = 500; MaxSessionProfit = 500; StartTrailAfterTicks = 60; BreakEvenAfterTicks = 30; LimitOrderOffset = 5; ReEntryWaitBars = 5; TrailFrequency = 10; MaxScaleIns = 1; ScaleInSize = 1; Backtest = false; // Default to false for backtest parameter } private Series _macdDiff; private Series _macdApi; private void InitIndicators() { try { InitMA(); if (_ma == null) { Log("Error: Moving Average indicator failed to initialize", LogLevel.Error); return; } var ind = NCMACD(FastLength, SlowLength, SignalLength, SmaSource, SmaSignal); if (ind == null) { Log("Error: MACD indicator failed to initialize", LogLevel.Error); return; } _macdDiff = ind.Diff; _macdApi = ind.Api; if (_macdDiff == null || _macdApi == null) { Log("Error: MACD series failed to initialize", LogLevel.Error); return; } AddChartIndicator(ind); } catch (Exception ex) { Log($"Error in InitIndicators: {ex.Message}", LogLevel.Error); } } private Indicator _ma; private void InitMA() { try { switch (MyMaType) { case MyStrategyMAType.SMA: _ma = SMA(MaPeriod); break; case MyStrategyMAType.EMA: _ma = EMA(MaPeriod); break; case MyStrategyMAType.HMA: _ma = HMA(MaPeriod); break; case MyStrategyMAType.TEMA: _ma = TEMA(MaPeriod); break; case MyStrategyMAType.TMA: _ma = TMA(MaPeriod); break; case MyStrategyMAType.VWMA: _ma = VWMA(MaPeriod); break; case MyStrategyMAType.WMA: _ma = WMA(MaPeriod); break; default: _ma = EMA(MaPeriod); // Default to EMA if type is not recognized break; } if (_ma != null) { _ma.Plots[0].Brush = Brushes.Blue; _ma.Plots[0].Width = 4; AddChartIndicator(_ma); } else { Log("Error: Failed to initialize MA indicator", LogLevel.Error); } } catch (Exception ex) { Log($"Error in InitMA: {ex.Message}", LogLevel.Error); } } #endregion #region Business protected override void OnBarUpdate() { try { // Check for initialization and backtest conditions if (CurrentBar < 1 || _ma == null || _macdDiff == null || _macdApi == null) return; // Validate license for backtesting if (State == State.Historical && Backtest) { if (!isValidated) { Log("Validating license for backtest operation...", LogLevel.Information); if (!ValidateLicense()) { Log("License validation failed for backtest.", LogLevel.Error); StopStrategy(); return; } Log("License validated successfully for backtest.", LogLevel.Information); } } // Skip processing during historical data if backtesting is disabled else if (State == State.Historical && !Backtest) return; ColorInsideTradeWindow(TradeStart1, TradeEnd1, TradeWindow1IsOn); Reset(); SetSignal(); ScaleInPositions(); OpenPositions(); ExitModule(); } catch (Exception ex) { Log($"Error in OnBarUpdate: {ex.Message}", LogLevel.Error); } } private bool _clearOnClose; private int _reEntryWaitBars; private void Reset() { if (Time[0].Date != Time[1].Date) { _stopTradingForSession = false; _currentProfit = 0; // Reset profit at the start of each day if (Position.MarketPosition == MarketPosition.Flat) _positions.Clear(); if (Position.MarketPosition != MarketPosition.Flat) _clearOnClose = true; } if (_reEntryOrderPlaced) { _reEntryWaitBars++; if (_reEntryWaitBars >= ReEntryWaitBars) { _reEntryWaitBars = 0; _reEntryOrderPlaced = false; if (_entryOrder != null) CancelOrder(_entryOrder); } } } private void ColorInsideTradeWindow(DateTime start, DateTime end, bool isOn) { if (!isOn) return; var timeNow = ToTime(Time[0]); if (start < end) { if (timeNow >= ToTime(start) && timeNow <= ToTime(end)) { BackBrush = _bgColor; } } else if (start > end) { if (timeNow >= ToTime(start) || timeNow <= ToTime(end)) { BackBrush = _bgColor; } } } private bool IsInsideTradeWindow() { if (!TradeWindow1IsOn) return true; var timeNow = ToTime(Time[0]); if (TradeStart1 < TradeEnd1) { if (timeNow >= ToTime(TradeStart1) && timeNow <= ToTime(TradeEnd1)) { return true; } } else if (TradeStart1 > TradeEnd1) { if (timeNow >= ToTime(TradeStart1) || timeNow <= ToTime(TradeEnd1)) { return true; } } return false; } private void ExitModule() { if (Position.MarketPosition == MarketPosition.Flat) return; if (Position.MarketPosition == MarketPosition.Long) BreakEven(High[0]); if (Position.MarketPosition == MarketPosition.Short) BreakEven(Low[0]); Trail(Close[0]); } protected override void OnMarketData(MarketDataEventArgs marketDataUpdate) { if (State != State.Realtime) return; if (marketDataUpdate.MarketDataType == MarketDataType.Last) { BreakEven(marketDataUpdate.Last); Trail(marketDataUpdate.Last); } } private bool _beApplied; private void BreakEven(double price) { if (!BreakEvenIsOn) return; if (Position.MarketPosition == MarketPosition.Flat) { _beApplied = false; return; } if (_beApplied) return; if (Position.MarketPosition == MarketPosition.Long) { if (price > _entryPrice + BreakEvenAfterTicks * TickSize) { var newStop = _entryPrice + BreakEvenOffset * TickSize; if (newStop > _stop) { if (_exitStopOrder != null) { _stop = newStop; ChangeStopExitOrder(); _beApplied = true; } } } } if (Position.MarketPosition == MarketPosition.Short) { if (price < _entryPrice - BreakEvenAfterTicks * TickSize) { var newStop = _entryPrice - BreakEvenOffset * TickSize; if (newStop < _stop) { if (_exitStopOrder != null) { _stop = newStop; ChangeStopExitOrder(); _beApplied = true; } } } } } private void Trail(double price) { if (!TrailIsOn) return; if (Position.MarketPosition == MarketPosition.Long && price >= _entryPrice + StartTrailAfterTicks * TickSize) { var newStop = price - TrailByTicks * TickSize; if (newStop > _stop + TrailFrequency * TickSize) { _stop = newStop; ChangeStopExitOrder(); } } if (Position.MarketPosition == MarketPosition.Short && price <= _entryPrice - StartTrailAfterTicks * TickSize) { var newStop = price + TrailByTicks * TickSize; if (newStop < _stop - TrailFrequency * TickSize) { _stop = newStop; ChangeStopExitOrder(); } } } private bool _enterLong; private bool _enterShort; private void SetSignal() { try { _enterLong = CheckThreeLightCandles(true) || CheckLightToDark(true) || CheckLightToOpposite(true); _enterShort = CheckThreeLightCandles(false) || CheckLightToDark(false) || CheckLightToOpposite(false); // Apply trade direction filter if (MyTradeDirection == Trade.Long) _enterShort = false; else if (MyTradeDirection == Trade.Short) _enterLong = false; if (ReverseTradeDirection) { var enterShort = _enterShort; _enterShort = _enterLong; _enterLong = enterShort; } // Check time window and backtest settings if (!IsInsideTradeWindow() || (State == State.Historical && !Backtest)) { _enterLong = false; _enterShort = false; } } catch (Exception ex) { Log($"Error in SetSignal: {ex.Message}", LogLevel.Error); _enterLong = false; _enterShort = false; } } public bool CheckThreeLightCandles(bool isLong) { if (!ThreeLightCandlesIsOn) return false; if (!isLong) return _macdApi[0] == -1 && _macdApi[1] == -1 && _macdApi[2] == -1 && Close[0] < _ma[0]; return _macdApi[0] == 1 && _macdApi[1] == 1 && _macdApi[2] == 1 && Close[0] > _ma[0]; } public bool CheckLightToDark(bool isLong) { if (!LightToDarkIsOn) return false; if (!isLong) return _macdApi[0] == -2 && _macdApi[1] == 1 && Close[0] < _ma[0]; return _macdApi[0] == 2 && _macdApi[1] == -1 && Close[0] > _ma[0]; } public bool CheckLightToOpposite(bool isLong) { if (!LightToOppositeIsOn) return false; if (!isLong) return _macdApi[0] == -2 && _macdApi[1] == -1 && Close[0] < _ma[0]; return _macdApi[0] == 2 && _macdApi[1] == 1 && Close[0] > _ma[0]; } public bool CheckAny(bool isLong) { return CheckThreeLightCandles(isLong) || CheckLightToDark(isLong) || CheckLightToOpposite(isLong); } private int _orderPlacedCounter; private void OpenPositions() { try { if (Position.MarketPosition != MarketPosition.Flat) return; // Add backtest check for historical data if (State == State.Historical && !Backtest) { if (_entryOrder != null) CancelOrder(_entryOrder); return; } if (_stopTradingForSession) { if (_entryOrder != null) CancelOrder(_entryOrder); return; } // Daily loss/profit checks if (MaxLossIsOn && _currentProfit <= -MaxSessionLoss) { _stopTradingForSession= true; if (_entryOrder != null) CancelOrder(_entryOrder); return; } if (MaxWinIsOn && _currentProfit >= MaxSessionProfit) { _stopTradingForSession = true; if (_entryOrder != null) CancelOrder(_entryOrder); return; } // Handle market orders if (MyEntryOrderType == EntryOrderType.Market) { if (_enterLong && (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Long)) { PlaceMarketEntryOrder(true); } if (_enterShort && (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Short)) { PlaceMarketEntryOrder(false); } } // Handle limit orders if (MyEntryOrderType == EntryOrderType.Limit) { if (_orderPlacedCounter >= BarsFillWait) { if (_entryOrder != null) CancelOrder(_entryOrder); _orderPlacedCounter = 0; } else { _orderPlacedCounter++; } if (_enterLong && (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Long)) { PlaceLimitEntryOrder(true, Close[0] - TickSize * LimitOrderOffset); _orderPlacedCounter = 0; } if (_enterShort && (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Short)) { PlaceLimitEntryOrder(false, Close[0] + TickSize * LimitOrderOffset); _orderPlacedCounter = 0; } } } catch (Exception ex) { Log($"Error in OpenPositions: {ex.Message}", LogLevel.Error); if (_entryOrder != null) CancelOrder(_entryOrder); } } private void ScaleInPositions() { if (Position.MarketPosition == MarketPosition.Flat) { _scaleInCount = 0; return; } if (!ScaleInIsOn) return; if (_scaleInCount >= MaxScaleIns) return; if (Position.MarketPosition == MarketPosition.Long && _enterLong) { _scaleInCount++; PlaceScaleInOrder(true); } if (Position.MarketPosition == MarketPosition.Short && _enterShort) { _scaleInCount++; PlaceScaleInOrder(false); } } private void ExitPositions() { if (Position.MarketPosition == MarketPosition.Long) { PlaceMarketExitOrder(true); } if (Position.MarketPosition == MarketPosition.Short) { PlaceMarketExitOrder(false); } } #endregion #region Orders private const string EnterLongName = "Enter Long"; private const string EnterShortName = "Enter Short"; private const string ScaleInLongName = "Scale In Long"; private const string ScaleInShortName = "Scale In Short"; private const string ExitLongStopName = "Stop Long"; private const string ExitShortStopName = "Stop Short"; private const string MarketCloseLong = "Exit Long Market"; private const string MarketCloseShort = "Exit Short Market"; private Order _exitMarketOrder; private Order _entryOrder; private Order _scaleInOrder; private Order _exitStopOrder; private Order _entryOrderShort; private Order _entryOrderLong; private double _stop; private int _scaleInCount; private const string ExitLongProfitName1 = "PT1-Long"; private const string ExitShortProfitName1 = "PT1-Short"; private const string ExitLongProfitName2 = "PT2-Long"; private const string ExitShortProfitName2 = "PT2-Short"; private const string ExitLongProfitName3 = "PT3-Long"; private const string ExitShortProfitName3 = "PT3-Short"; private Order _exitProfitOrder1; private Order _exitProfitOrder2; private Order _exitProfitOrder3; private double _profit1; private double _profit2; private double _profit3; private void PlaceMarketEntryOrder(bool isLong) { var posSize = PosSize1 + PosSize2 + PosSize3; if (_entryOrder != null) { _reEntryOrderPlaced = false; _reEntryWaitBars = 0; CancelOrder(_entryOrder); } if (MyTradeDirection == Trade.Both) { if (isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, posSize, 0, 0, "", EnterLongName); } if (!isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, posSize, 0, 0, "", EnterShortName); } } if (MyTradeDirection == Trade.Long) { if (isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, posSize, 0, 0, "", EnterLongName); } } if (MyTradeDirection == Trade.Short) { if (!isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, posSize, 0, 0, "", EnterShortName); } } } private void PlaceScaleInOrder(bool isLong) { var posSize = ScaleInSize; if (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Long) { if (isLong) { _scaleInOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, posSize, 0, 0, "", ScaleInLongName + "#" + _scaleInCount); } } if (MyTradeDirection == Trade.Both || MyTradeDirection == Trade.Short) { if (!isLong) { _scaleInOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, posSize, 0, 0, "", ScaleInShortName + "#" + _scaleInCount); } } } private void PlaceLimitEntryOrder(bool isLong, double price) { var posSize = PosSize1 + PosSize2 + PosSize3; if (_entryOrder != null) CancelOrder(_entryOrder); if (MyTradeDirection == Trade.Both) { if (isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, posSize, price, 0, "", EnterLongName); } if (!isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, posSize, price, 0, "", EnterShortName); } } if (MyTradeDirection == Trade.Long) { if (isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, posSize, price, 0, "", EnterLongName); } } if (MyTradeDirection == Trade.Short) { if (!isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, posSize, price, 0, "", EnterShortName); } } } private void PlaceReEntryStopOrder(bool isLong) { var posSize = PosSize1 + PosSize2 + PosSize3; if (isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.StopMarket, posSize, 0, _entryPrice, "", EnterLongName); } if (!isLong) { _entryOrder = SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.StopMarket, posSize, 0, _entryPrice, "", EnterShortName); } } private void PlaceMarketExitOrder(bool isLong) { if (isLong) { _exitMarketOrder = SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.Market, Position.Quantity, 0, 0, "", MarketCloseLong); } else if (!isLong) { _exitMarketOrder = SubmitOrderUnmanaged(0, OrderAction.BuyToCover, OrderType.Market, Position.Quantity, 0, 0, "", MarketCloseShort); } } private void ChangeStopExitOrder() { ChangeOrder(_exitStopOrder, Position.Quantity, 0, _stop); } private int _posSize1; private int _posSize2; private int _posSize3; private void AdjustOrders() { if (_exitStopOrder != null) ChangeOrder(_exitStopOrder, Position.Quantity, 0, _stop); if (_exitProfitOrder1.OrderState != OrderState.Filled && _exitProfitOrder2.OrderState != OrderState.Filled && _exitProfitOrder3.OrderState != OrderState.Filled) { if (_posSize1 == 0) _posSize1 = PosSize1 + ScaleInSize; else { _posSize1 += ScaleInSize; } ChangeOrder(_exitProfitOrder1, _posSize1, _exitProfitOrder1.LimitPrice, 0); } if (_exitProfitOrder1.OrderState == OrderState.Filled && _exitProfitOrder2.OrderState != OrderState.Filled && _exitProfitOrder3.OrderState != OrderState.Filled) { if (_posSize2 == 0) _posSize2 = PosSize2 + ScaleInSize; else { _posSize2 += ScaleInSize; } ChangeOrder(_exitProfitOrder2, _posSize2, _exitProfitOrder2.LimitPrice, 0); } if (_exitProfitOrder1.OrderState == OrderState.Filled && _exitProfitOrder2.OrderState == OrderState.Filled && _exitProfitOrder3.OrderState != OrderState.Filled) { if (_posSize3 == 0) _posSize3 = PosSize3 + ScaleInSize; else { _posSize3 += ScaleInSize; } ChangeOrder(_exitProfitOrder3, _posSize3, _exitProfitOrder3.LimitPrice, 0); } } private void PlaceStopExitOrders(bool isStopLong) { if (isStopLong) { _exitStopOrder = SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.StopMarket, Position.Quantity, 0, _stop, "", ExitLongStopName); } if (!isStopLong) { _exitStopOrder = SubmitOrderUnmanaged(0, OrderAction.BuyToCover, OrderType.StopMarket, Position.Quantity, 0, _stop, "", ExitShortStopName); } } private void PlaceLimitProfitExitOrder1(bool isProfitOrderLong) { if (isProfitOrderLong) { _exitProfitOrder1 = SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.Limit, PosSize1, _profit1, 0, "", ExitLongProfitName1); } if (!isProfitOrderLong) { _exitProfitOrder1 = SubmitOrderUnmanaged(0, OrderAction.BuyToCover, OrderType.Limit, PosSize1, _profit1, 0, "", ExitShortProfitName1); } } private void PlaceLimitProfitExitOrder2(bool isProfitOrderLong) { if (isProfitOrderLong) { _exitProfitOrder2 = SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.Limit, PosSize2, _profit2, 0, "", ExitLongProfitName2); } if (!isProfitOrderLong) { _exitProfitOrder2 = SubmitOrderUnmanaged(0, OrderAction.BuyToCover, OrderType.Limit, PosSize2, _profit2, 0, "", ExitShortProfitName2); } } private void PlaceLimitProfitExitOrder3(bool isProfitOrderLong) { if (isProfitOrderLong) { _exitProfitOrder3 = SubmitOrderUnmanaged(0, OrderAction.Sell, OrderType.Limit, PosSize3, _profit3, 0, "", ExitLongProfitName3); } if (!isProfitOrderLong) { _exitProfitOrder3 = SubmitOrderUnmanaged(0, OrderAction.BuyToCover, OrderType.Limit, PosSize3, _profit3, 0, "", ExitShortProfitName3); } } #endregion #region Execution protected override void OnOrderUpdate(Order order, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, OrderState orderState, DateTime time, ErrorCode error, string comment) { } private bool _orderIgnored; protected override void OnOrderTrace(DateTime timestamp, string message) { //base.OnOrderTrace(timestamp, message); if (message.Contains("Ignored")) { _orderIgnored = true; } } protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time) { if (!IsFilled(execution.Order)) return; ManageOrders(execution); } private bool IsFilled(Order order) { if (order.OrderState == OrderState.Filled) return true; if (order.OrderState == OrderState.PartFilled) { if (order.Filled == order.Quantity) { return true; } } return false; } private double _entryPrice; private bool _reEntryOrderPlaced; private void ManageOrders(Execution ex) { var orderName = ex.Order.Name; Print(orderName); if (orderName == EnterLongName || orderName == EnterShortName) { _entryPrice = ex.Order.AverageFillPrice; _beApplied = false; _orderPlacedCounter = 0; _reEntryOrderPlaced = false; _reEntryWaitBars = 0; _posSize1 = 0; _posSize2 = 0; _posSize3 = 0; var stopSize = StopLossTicks * TickSize; var profitSize1 = ProfitTargetTicks1 * TickSize; var profitSize2 = ProfitTargetTicks2 * TickSize; var profitSize3 = ProfitTargetTicks3 * TickSize; if (orderName == EnterLongName) { _stop = ex.Order.AverageFillPrice - stopSize; _profit1 = ex.Order.AverageFillPrice + profitSize1; _profit2 = ex.Order.AverageFillPrice + profitSize2; _profit3 = ex.Order.AverageFillPrice + profitSize3; PlaceStopExitOrders(true); PlaceLimitProfitExitOrder1(true); if (PosSize2 > 0) PlaceLimitProfitExitOrder2(true); if (PosSize3 > 0) PlaceLimitProfitExitOrder3(true); CreatePosition(true, ex.Order); } if (orderName == EnterShortName) { _stop = ex.Order.AverageFillPrice + stopSize; _profit1 = ex.Order.AverageFillPrice - profitSize1; _profit2 = ex.Order.AverageFillPrice - profitSize2; _profit3 = ex.Order.AverageFillPrice - profitSize3; PlaceStopExitOrders(false); PlaceLimitProfitExitOrder1(false); if (PosSize2 > 0) PlaceLimitProfitExitOrder2(false); if (PosSize3 > 0) PlaceLimitProfitExitOrder3(false); CreatePosition(false, ex.Order); } } if (orderName.Contains(ScaleInLongName) || orderName.Contains(ScaleInShortName)) { AdjustOrders(); AddToPosition(ex.Order); } if (orderName == ExitLongStopName || orderName == ExitShortStopName) { if (_exitProfitOrder1 != null) CancelOrder(_exitProfitOrder1); if (_exitProfitOrder2 != null) CancelOrder(_exitProfitOrder2); if (_exitProfitOrder3 != null) CancelOrder(_exitProfitOrder3); ScaleOrCloseOut(ex.Order); if (ex.Order.Quantity == PosSize1 + PosSize2 + PosSize3 && ReEntryIsOn) { if (orderName == ExitLongStopName) { PlaceReEntryStopOrder(true); } if (orderName == ExitShortStopName) { PlaceReEntryStopOrder(false); } _reEntryOrderPlaced = true; } } if (orderName == ExitLongProfitName1 || orderName == ExitShortProfitName1) { if (PosSize2 == 0 && PosSize3 == 0) { if (_exitStopOrder != null) CancelOrder(_exitStopOrder); } else { ChangeStopExitOrder(); } ScaleOrCloseOut(ex.Order); } if (orderName == ExitLongProfitName2 || orderName == ExitShortProfitName2) { if (PosSize3 == 0) { if (_exitStopOrder != null) CancelOrder(_exitStopOrder); } else { ChangeStopExitOrder(); } ScaleOrCloseOut(ex.Order); } if (orderName == ExitLongProfitName3 || orderName == ExitShortProfitName3) { if (_exitStopOrder != null) CancelOrder(_exitStopOrder); ScaleOrCloseOut(ex.Order); } if (orderName == MarketCloseLong || orderName == MarketCloseShort) { if (_exitStopOrder != null) CancelOrder(_exitStopOrder); if (_exitProfitOrder1 != null) CancelOrder(_exitProfitOrder1); if (_exitProfitOrder2 != null) CancelOrder(_exitProfitOrder2); if (_exitProfitOrder3 != null) CancelOrder(_exitProfitOrder3); ScaleOrCloseOut(ex.Order); } } #endregion #region Virtual Trades class MyPosition { public List Trades { get; set; } public bool IsOpen { get; set; } public bool IsLong { get; set; } public double Pnl { get; set; } } class MyTrade { public double EntryPrice { get; set; } public double ExitPrice { get; set; } public int Qnt { get; set; } public int ClosedQnt { get; set; } public double Pnl { get; set; } public bool IsClosed { get; set; } } private void CreatePosition(bool isLong, Order order) { var pos = new MyPosition() { Trades = new List(), IsOpen = true, IsLong = isLong }; pos.Trades.Add(new MyTrade() { EntryPrice = order.AverageFillPrice, Qnt = order.Quantity }); _positions.Add(pos); } private void AddToPosition(Order order) { _positions[_positions.Count - 1].Trades.Add(new MyTrade() { EntryPrice = order.AverageFillPrice, Qnt = order.Quantity }); } private void ScaleOrCloseOut(Order order) { var pos = _positions[_positions.Count - 1]; var needToClose = order.Quantity; var closed = 0; double tradePnl = 0; foreach (var trade in pos.Trades) { if (trade.IsClosed) continue; if (trade.Qnt == needToClose) { trade.IsClosed = true; trade.ClosedQnt = trade.Qnt; needToClose = 0; if (pos.IsLong) { trade.Pnl = (order.AverageFillPrice - trade.EntryPrice) * trade.Qnt * Instrument.MasterInstrument.PointValue; } if (!pos.IsLong) { trade.Pnl = (trade.EntryPrice - order.AverageFillPrice) * trade.Qnt * Instrument.MasterInstrument.PointValue; } } else if (trade.Qnt < needToClose) { trade.IsClosed = true; trade.ClosedQnt = trade.Qnt; needToClose -= trade.Qnt; if (pos.IsLong) { trade.Pnl = (order.AverageFillPrice - trade.EntryPrice) * trade.Qnt * Instrument.MasterInstrument.PointValue; } if (!pos.IsLong) { trade.Pnl = (trade.EntryPrice - order.AverageFillPrice) * trade.Qnt * Instrument.MasterInstrument.PointValue; } } else { var tradeNotClosedQnt = trade.Qnt - trade.ClosedQnt; var extraPnl = 0d; if (tradeNotClosedQnt == needToClose) { if (pos.IsLong) { extraPnl = (order.AverageFillPrice - trade.EntryPrice) * needToClose * Instrument.MasterInstrument.PointValue; } if (!pos.IsLong) { extraPnl = (trade.EntryPrice - order.AverageFillPrice) * needToClose * Instrument.MasterInstrument.PointValue; } trade.Pnl += extraPnl; trade.IsClosed = true; trade.ClosedQnt = trade.Qnt; } else if (tradeNotClosedQnt < needToClose) { if (pos.IsLong) { extraPnl = (order.AverageFillPrice - trade.EntryPrice) * tradeNotClosedQnt * Instrument.MasterInstrument.PointValue; } if (!pos.IsLong) { extraPnl = (trade.EntryPrice - order.AverageFillPrice) * tradeNotClosedQnt * Instrument.MasterInstrument.PointValue; } trade.Pnl += extraPnl; trade.IsClosed = true; trade.ClosedQnt = trade.Qnt; needToClose -= tradeNotClosedQnt; } else if (tradeNotClosedQnt > needToClose) { if (pos.IsLong) { extraPnl = (order.AverageFillPrice - trade.EntryPrice) * needToClose * Instrument.MasterInstrument.PointValue; } if (!pos.IsLong) { extraPnl = (trade.EntryPrice - order.AverageFillPrice) * needToClose * Instrument.MasterInstrument.PointValue; } trade.Pnl += extraPnl; trade.ClosedQnt = needToClose; needToClose = 0; } } } var positionPnl = pos.Trades.Sum(x => x.Pnl); _currentProfit += positionPnl; // Update current profit if (!pos.Trades.All(x => x.IsClosed)) return; pos.Pnl = positionPnl; pos.IsOpen = false; var sessionPnl = _positions.Sum(p => p.Pnl); sessionPnl = Math.Round(sessionPnl); if (MaxWinIsOn && sessionPnl > MaxSessionProfit) { var color = Brushes.DarkGreen; Draw.Text(this, CurrentBar + "SPNL", true, "Session PNL " + sessionPnl, 0, Low[0] - TickSize * 20, 0, Brushes.White, new SimpleFont("Arial", 10), TextAlignment.Center, Brushes.Black, color, 80); _stopTradingForSession = true; } if (MaxLossIsOn && sessionPnl < MaxSessionLoss * -1) { var color = Brushes.DarkRed; Draw.Text(this, CurrentBar + "SPNL", true, "Session PNL " + sessionPnl, 0, Low[0] - TickSize * 20, 0, Brushes.White, new SimpleFont("Arial", 10), TextAlignment.Center, Brushes.Black, color, 80); _stopTradingForSession = true; } ClearOnClose(); } private void ClearOnClose() { if (_clearOnClose) { _positions.Clear(); _clearOnClose = false; } } private bool _stopTradingForSession; #endregion } }