//+------------------------------------------------------------------+ //| ADR_Reversion_Grid_v136.mq5 | //| Copyright 2025, Algorithm Factory | //| Feature: CSV Signals + MQL5 Economic Calendar News Filter | //+------------------------------------------------------------------+ #property copyright "Algorithm Factory" #property link "" #property version "1.38" #property strict #include //--- ENUMS --- enum ENUM_ENTRY_MODE { MODE_ADR = 0, // ADR Entry MODE_RSI = 1 // RSI Entry }; enum ENUM_DIR_FILTER { FILTER_BOTH = 0, // Long & Short FILTER_LONG = 1, // Only Long FILTER_SHORT = 2, // Only Short FILTER_NEUTRAL = 3 // Neutral }; enum ENUM_NEWS_IMPORTANCE { NEWS_HIGH_ONLY = 0, // Nur High Impact NEWS_MEDIUM_HIGH = 1 // Medium + High Impact }; //--- INPUT PARAMETERS --- input group "=== CSV Signal Integration ===" input bool InpUseCSVSignals = true; // CSV-Signale nutzen? input int InpCSVCheckInterval = 15; // CSV Check Interval (Minuten) input string InpCSVFilename = "last_known_signals.csv"; // CSV Dateiname input bool InpDebugMode = false; // Debug-Modus (ausführliche Logs) input group "=== News Filter (MQL5 Calendar) ===" input bool InpUseNewsFilter = true; // News-Filter aktivieren? input ENUM_NEWS_IMPORTANCE InpNewsImportance = NEWS_HIGH_ONLY; // Welche News filtern? input int InpMinutesBeforeNews = 30; // Minuten VOR News input int InpMinutesAfterNews = 30; // Minuten NACH News input bool InpNewsFilterBothCurrencies = true; // Beide Währungen prüfen? input group "--- Direction Filter ---" input ENUM_DIR_FILTER InpStartDirection = FILTER_BOTH; input group "--- Positions Management ---" input double InpFirstLot = 0.03; // Start Lotgröße input double InpGridLot = 0.03; // Grid Lotgröße input int InpMaxPositions = 20; // Max Anzahl Positionen input double InpIndividualSL_ADR = 3.0; // SL pro Trade in ADR input group "--- Money Management ---" input bool InpUseMM = false; // MM Aktivieren? input double InpRefBalance = 10000.0; // Startkapital (Referenz) input double InpBalanceStep_Pct = 10.0; // Balance Step % input double InpLotIncrease_Pct = 10.0; // Lot Increase % input group "--- Entry Logic Selection ---" input ENUM_ENTRY_MODE InpEntryMode = MODE_ADR; // Entry Mode input group "--- ADR Settings ---" input int InpADRPeriod = 14; // ADR Periode input double InpEntryADR_Pct = 60.0; // Entry: % von ADR input double InpGridStep_ADR_Pct = 10.0; // Grid Step input group "--- RSI Settings (D1) ---" input int InpRSI_Period = 14; // RSI Periode input int InpRSI_Upper = 70; // RSI Upper input int InpRSI_Lower = 30; // RSI Lower input group "--- Dynamic Exit Targets ---" input double InpStartTarget_ADR = 10.0; // Ziel bei Start input double InpTargetDecay_ADR = 0.0; // Abzug pro Grid-Level input group "--- Stochastic Settings (H1) ---" input int InpStochK = 5; input int InpStochD = 3; input int InpStochSlowing = 3; input int InpStochUpper = 85; input int InpStochLower = 15; input group "--- Time Filter ---" input bool InpUseEODClose = false; input int InpEODHour = 23; input int InpEODMinute = 50; input int InpMagicNumber = 101; // WICHTIG: Unique ID pro Chart! //--- GLOBALS --- CTrade trade; int handleStoch; int handleRSI = INVALID_HANDLE; double adrValue = 0.0; double dayRange = 0.0; datetime lastProcessedH1Bar = 0; // Stats & Monitoring double stat_HighWaterMark = 0.0; double stat_MaxDD_Equity_Money = 0.0; double stat_MaxDD_Equity_Percent = 0.0; string stat_EA_Action = "Initializing..."; // Runtime State for Filter ENUM_DIR_FILTER g_currentFilter; // CSV Signal Integration bool g_CSVMode = false; ENUM_DIR_FILTER g_CSVDirection = FILTER_NEUTRAL; datetime g_LastCSVCheck = 0; string g_CSVStatus = "Not checked yet"; string g_CSVSignalText = "N/A"; // News Filter bool g_NewsBlockActive = false; string g_NextNewsInfo = "Keine News"; datetime g_NextNewsTime = 0; string g_NextNewsName = ""; // Performance Optimization - NEU! datetime g_LastNewsCheckTime = 0; int g_NewsCheckInterval = 300; // News nur alle 5 Minuten prüfen datetime g_LastADRCalcDay = 0; // GUI Object Names string btnBothName = "Btn_Dir_Both"; string btnLongName = "Btn_Dir_Long"; string btnShortName = "Btn_Dir_Short"; string btnNeutralName = "Btn_Dir_Neutral"; string lblMaxDDName = "Lbl_Info_MaxDD"; string lblPosName = "Lbl_Info_Pos"; string lblStatusName = "Lbl_Info_Status"; string lblSetupName = "Lbl_Info_Setup"; string lblCSVInfoName = "Lbl_CSV_Info"; string lblCSVTimeName = "Lbl_CSV_Time"; string lblNewsInfoName = "Lbl_News_Info"; string lblCSVDirectionName = "Lbl_CSV_Direction"; //+------------------------------------------------------------------+ //| Init | //+------------------------------------------------------------------+ int OnInit() { // Prüfe ob wir im Strategy Tester sind bool isInTester = MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION); // Im Tester: FILTER_NEUTRAL macht keinen Sinn (würde alle Trades blockieren) // Setze auf FILTER_BOTH wenn NEUTRAL gewählt wurde if(isInTester && InpStartDirection == FILTER_NEUTRAL) { g_currentFilter = FILTER_BOTH; Print("WARNUNG: FILTER_NEUTRAL im Tester nicht sinnvoll - nutze FILTER_BOTH stattdessen!"); } else { g_currentFilter = InpStartDirection; } // Init HWM stat_HighWaterMark = AccountInfoDouble(ACCOUNT_BALANCE); // --- CHART STYLING --- ChartSetInteger(0, CHART_SHOW_GRID, false); ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrBlack); ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrWhite); ChartSetInteger(0, CHART_COLOR_CHART_UP, clrWhite); ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrWhite); ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite); ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack); ChartSetInteger(0, CHART_MODE, CHART_CANDLES); Comment(""); ChartRedraw(); // Indikatoren handleStoch = iStochastic(_Symbol, PERIOD_H1, InpStochK, InpStochD, InpStochSlowing, MODE_SMA, STO_LOWHIGH); if(handleStoch == INVALID_HANDLE) return(INIT_FAILED); if (InpEntryMode == MODE_RSI) { handleRSI = iRSI(_Symbol, PERIOD_D1, InpRSI_Period, PRICE_CLOSE); if(handleRSI == INVALID_HANDLE) return(INIT_FAILED); } trade.SetExpertMagicNumber(InpMagicNumber); trade.SetTypeFilling(ORDER_FILLING_IOC); // GUI erstellen CreateButtons(); CreateLabels(); UpdateButtonsState(); // CSV-Mode initialisieren // WICHTIG: Im Strategy Tester macht CSV-Steuerung keinen Sinn! if(InpUseCSVSignals && !isInTester) { if(InpDebugMode) Print("CSV-Signal Mode aktiviert. Prüfe Datei: ", InpCSVFilename); CheckCSVFile(); // Timer für periodische CSV-Prüfung starten (in Sekunden) int timerSeconds = InpCSVCheckInterval * 60; if(!EventSetTimer(timerSeconds)) { Print("FEHLER: Timer konnte nicht gestartet werden!"); return(INIT_FAILED); } if(InpDebugMode) Print("Timer gestartet: Prüfe CSV alle ", InpCSVCheckInterval, " Minuten"); } else { if(isInTester) { if(InpDebugMode) Print("CSV-Signal Mode DEAKTIVIERT im Strategy Tester (keine Datei-Zugriffe im Tester)"); g_CSVMode = false; } else { if(InpDebugMode) Print("CSV-Signal Mode deaktiviert. Nutze manuelle Button-Steuerung."); } } // News-Filter initialisieren if(InpUseNewsFilter) { if(InpDebugMode) { Print("News-Filter aktiviert. Wichtigkeit: ", (InpNewsImportance == NEWS_HIGH_ONLY) ? "Nur HIGH" : "MEDIUM + HIGH", " | Zeitfenster: ", InpMinutesBeforeNews, " Min vorher / ", InpMinutesAfterNews, " Min nachher"); } CheckUpcomingNews(); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(handleStoch != INVALID_HANDLE) IndicatorRelease(handleStoch); if(handleRSI != INVALID_HANDLE) IndicatorRelease(handleRSI); EventKillTimer(); ObjectDelete(0, btnBothName); ObjectDelete(0, btnLongName); ObjectDelete(0, btnShortName); ObjectDelete(0, btnNeutralName); ObjectDelete(0, lblMaxDDName); ObjectDelete(0, lblPosName); ObjectDelete(0, lblStatusName); ObjectDelete(0, lblSetupName); ObjectDelete(0, lblCSVInfoName); ObjectDelete(0, lblCSVTimeName); ObjectDelete(0, lblNewsInfoName); ObjectDelete(0, lblCSVDirectionName); Comment(""); } //+------------------------------------------------------------------+ //| Timer Event | //+------------------------------------------------------------------+ void OnTimer() { if(InpUseCSVSignals) { CheckCSVFile(); } } //+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { // --- 1. MONITORING --- double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); if(currentBalance > stat_HighWaterMark) stat_HighWaterMark = currentBalance; double equityDD_Money = stat_HighWaterMark - currentEquity; if(equityDD_Money > stat_MaxDD_Equity_Money) stat_MaxDD_Equity_Money = equityDD_Money; double equityDD_Pct = 0.0; if(stat_HighWaterMark > 0) equityDD_Pct = (equityDD_Money / stat_HighWaterMark) * 100.0; if(equityDD_Pct > stat_MaxDD_Equity_Percent) stat_MaxDD_Equity_Percent = equityDD_Pct; // --- 2. PREPARATIONS --- ValidatePositionsAgainstFilter(); CalculateADR_and_Range(); CheckUpcomingNews(); // Jetzt optimiert mit Caching! int openPositions = 0; ENUM_POSITION_TYPE openType = POSITION_TYPE_BUY; for(int i=0; i 0) { double lastEntry = 0.0; for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { lastEntry = PositionGetDouble(POSITION_PRICE_OPEN); break; } } } stat_EA_Action = StringFormat("Managing Basket (%s). Next Grid: %.5f", (openType == POSITION_TYPE_BUY) ? "Long" : "Short", lastEntry); } else { if(InpEntryMode == MODE_ADR) { double neededRange = adrValue * (InpEntryADR_Pct / 100.0); if(dayRange < neededRange) { stat_EA_Action = StringFormat("Wait Range (%.5f < %.5f)", dayRange, neededRange); } else { stat_EA_Action = "ADR met. Scanning for Stoch Entry..."; } } else if(InpEntryMode == MODE_RSI) { stat_EA_Action = "Scanning for RSI + Stoch Entry..."; } } // --- 4. LOGIC EXECUTION --- if(openPositions > 0) { CheckBasketExit(openType, openPositions); datetime currentH1Bar = iTime(_Symbol, PERIOD_H1, 0); if(currentH1Bar != lastProcessedH1Bar && openPositions < InpMaxPositions) { lastProcessedH1Bar = currentH1Bar; // News-Filter: Grid-Nachkauf blockieren wenn News anstehen if(g_NewsBlockActive) { stat_EA_Action = StringFormat("Grid blockiert: %s", g_NextNewsInfo); } else { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double requiredDist = adrValue * (InpGridStep_ADR_Pct / 100.0); double slDistancePoints = adrValue * InpIndividualSL_ADR; double lastEntryPrice = 0.0; for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { lastEntryPrice = PositionGetDouble(POSITION_PRICE_OPEN); break; } } } if(lastEntryPrice > 0) { bool gridOk = false; double dynGridLot = GetDynamicLot(InpGridLot); if(openType == POSITION_TYPE_SELL && ask >= (lastEntryPrice + requiredDist)) { if(g_currentFilter == FILTER_SHORT || g_currentFilter == FILTER_BOTH) { gridOk = true; string comment = BuildTradeComment(false, openPositions + 1, "Short"); trade.Sell(dynGridLot, _Symbol, ask, ask + slDistancePoints, 0, comment); } } if(openType == POSITION_TYPE_BUY && bid <= (lastEntryPrice - requiredDist)) { if(g_currentFilter == FILTER_LONG || g_currentFilter == FILTER_BOTH) { gridOk = true; string comment = BuildTradeComment(false, openPositions + 1, "Long"); trade.Buy(dynGridLot, _Symbol, bid, bid - slDistancePoints, 0, comment); } } } } } } // EOD Close if(InpUseEODClose) { MqlDateTime tm; TimeToStruct(TimeCurrent(), tm); if(tm.hour == InpEODHour && tm.min >= InpEODMinute) { if(openPositions > 0) { CloseAllPositions(); stat_EA_Action = "EOD Time. Closed all positions."; } else { stat_EA_Action = "EOD Time. Waiting for next day."; } } } // ENTRY LOGIC if(openPositions == 0) { datetime currentH1Bar = iTime(_Symbol, PERIOD_H1, 0); if(currentH1Bar != lastProcessedH1Bar) { lastProcessedH1Bar = currentH1Bar; // News-Filter: Entry blockieren wenn News anstehen if(g_NewsBlockActive) { stat_EA_Action = StringFormat("Entry blockiert: %s", g_NextNewsInfo); } else { bool entryConditionMet = false; if(InpEntryMode == MODE_ADR) { double neededRange = adrValue * (InpEntryADR_Pct / 100.0); if(dayRange > neededRange) entryConditionMet = true; } else if(InpEntryMode == MODE_RSI) { double rsiValues[]; if(CopyBuffer(handleRSI, 0, 0, 1, rsiValues) == 1) { if(rsiValues[0] >= InpRSI_Upper || rsiValues[0] <= InpRSI_Lower) { entryConditionMet = true; } } } if(entryConditionMet) { double kBuffer[], dBuffer[]; ArraySetAsSeries(kBuffer, true); ArraySetAsSeries(dBuffer, true); if(CopyBuffer(handleStoch, 0, 0, 3, kBuffer) < 3) return; if(CopyBuffer(handleStoch, 1, 0, 3, dBuffer) < 3) return; bool shortSignal = (kBuffer[2] >= InpStochUpper && kBuffer[1] < InpStochUpper); bool longSignal = (kBuffer[2] <= InpStochLower && kBuffer[1] > InpStochLower); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double slDistancePoints = adrValue * InpIndividualSL_ADR; double dynFirstLot = GetDynamicLot(InpFirstLot); if(shortSignal && (g_currentFilter == FILTER_SHORT || g_currentFilter == FILTER_BOTH)) { string comment = BuildTradeComment(true, 1, "Short"); trade.Sell(dynFirstLot, _Symbol, ask, ask + slDistancePoints, 0, comment); stat_EA_Action = "Entered Short"; } if(longSignal && (g_currentFilter == FILTER_LONG || g_currentFilter == FILTER_BOTH)) { string comment = BuildTradeComment(true, 1, "Long"); trade.Buy(dynFirstLot, _Symbol, bid, bid - slDistancePoints, 0, comment); stat_EA_Action = "Entered Long"; } } } } } // --- 5. VISUALS UPDATE --- UpdateVisuals(openPositions); } //+------------------------------------------------------------------+ //| Chart Event Handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { if(g_CSVMode) { Print("WARNUNG: Manuelle Button-Steuerung deaktiviert. CSV-Datei steuert die Richtung!"); ObjectSetInteger(0, sparam, OBJPROP_STATE, false); ChartRedraw(); return; } if(sparam == btnBothName) { g_currentFilter = FILTER_BOTH; UpdateButtonsState(); ValidatePositionsAgainstFilter(); } else if(sparam == btnLongName) { g_currentFilter = FILTER_LONG; UpdateButtonsState(); ValidatePositionsAgainstFilter(); } else if(sparam == btnShortName) { g_currentFilter = FILTER_SHORT; UpdateButtonsState(); ValidatePositionsAgainstFilter(); } else if(sparam == btnNeutralName) { g_currentFilter = FILTER_NEUTRAL; UpdateButtonsState(); ValidatePositionsAgainstFilter(); } ChartRedraw(); } } //+------------------------------------------------------------------+ //| CSV Integration Functions | //+------------------------------------------------------------------+ void CheckCSVFile() { g_LastCSVCheck = TimeCurrent(); int fileHandle = FileOpen(InpCSVFilename, FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ); if(fileHandle == INVALID_HANDLE) { g_CSVMode = false; g_CSVStatus = "Datei nicht gefunden"; g_CSVSignalText = "FILE NOT FOUND"; Print("FEHLER: CSV-Datei nicht gefunden: ", InpCSVFilename); Print("Nutze manuelle Button-Steuerung als Fallback."); UpdateButtonsState(); return; } bool foundSymbol = false; string newSignal = "NEUTRAL"; string matchedCsvSymbol = ""; int lineNumber = 0; // DEBUG: Nur bei aktiviertem Debug-Modus if(InpDebugMode) { Print("========================================"); Print("CSV-Check: Suche Symbol '", _Symbol, "'"); Print("========================================"); } while(!FileIsEnding(fileHandle)) { string line = FileReadString(fileHandle); lineNumber++; // Entferne Carriage Return und Whitespace StringReplace(line, "\r", ""); StringReplace(line, "\n", ""); StringTrimLeft(line); StringTrimRight(line); // Überspringe leere Zeilen if(line == "") { if(InpDebugMode) Print("Zeile ", lineNumber, ": [LEER]"); continue; } // Überspringe Header-Zeile if(lineNumber == 1 || StringFind(line, "Waehrungspaar") >= 0 || StringFind(line, "Letztes_Signal") >= 0) { if(InpDebugMode) Print("Zeile ", lineNumber, ": [HEADER] ", line); continue; } // Debug: Zeige die gelesene Zeile (nur im Debug-Modus) if(InpDebugMode) Print("Zeile ", lineNumber, ": '", line, "'"); string parts[]; int count = StringSplit(line, ';', parts); if(count >= 2) { string csvSymbol = parts[0]; StringTrimLeft(csvSymbol); StringTrimRight(csvSymbol); string csvSignal = parts[1]; StringTrimLeft(csvSignal); StringTrimRight(csvSignal); StringToUpper(csvSignal); // Debug: Zeige geparste Werte (nur im Debug-Modus) if(InpDebugMode) Print(" -> Parsed: Symbol='", csvSymbol, "' Signal='", csvSignal, "'"); // VERBESSERTE VERGLEICHSLOGIK: bool isMatch = false; if(csvSymbol == _Symbol) { // Exakter Match isMatch = true; if(InpDebugMode) Print(" -> EXAKTER MATCH!"); } else if(StringFind(_Symbol, csvSymbol) == 0) { // Symbol beginnt mit CSV-Symbol (z.B. NZDUSDm vs NZDUSD) isMatch = true; if(InpDebugMode) Print(" -> PREFIX MATCH!"); } else if(StringFind(_Symbol, csvSymbol + ".") == 0 || StringFind(_Symbol, csvSymbol + "#") == 0 || StringFind(_Symbol, csvSymbol + "m") == 0) { // Symbol hat bekanntes Suffix isMatch = true; if(InpDebugMode) Print(" -> SUFFIX MATCH!"); } else { if(InpDebugMode) Print(" -> Kein Match (suche '", _Symbol, "' vs gefunden '", csvSymbol, "')"); } if(isMatch) { foundSymbol = true; newSignal = csvSignal; matchedCsvSymbol = csvSymbol; if(InpDebugMode) { Print("========================================"); Print("ERFOLG! MT5-Symbol: '", _Symbol, "' = CSV-Symbol: '", csvSymbol, "' Signal: ", csvSignal); Print("========================================"); } break; } } else { if(InpDebugMode) Print(" -> FEHLER: Konnte Zeile nicht in 2 Teile splitten (count=", count, ")"); } } FileClose(fileHandle); if(!foundSymbol) { g_CSVMode = true; g_CSVStatus = "Symbol nicht in CSV gefunden"; g_CSVSignalText = "NOT FOUND"; g_CSVDirection = FILTER_NEUTRAL; // Fehler IMMER ausgeben (nicht nur im Debug-Modus) Print("WARNUNG: Symbol '", _Symbol, "' nicht in CSV gefunden! (", lineNumber, " Zeilen gelesen)"); if(InpDebugMode) { Print("Prüfe das Expert-Log oben für Details."); } g_currentFilter = FILTER_NEUTRAL; UpdateButtonsState(); return; } // Signal parsen ENUM_DIR_FILTER oldFilter = g_currentFilter; ENUM_DIR_FILTER newFilter = FILTER_NEUTRAL; if(newSignal == "BUY") { newFilter = FILTER_LONG; g_CSVSignalText = "BUY"; } else if(newSignal == "SELL") { newFilter = FILTER_SHORT; g_CSVSignalText = "SELL"; } else { newFilter = FILTER_NEUTRAL; g_CSVSignalText = "NEUTRAL"; } g_CSVMode = true; g_CSVDirection = newFilter; g_CSVStatus = "CSV erfolgreich gelesen"; // Erfolg IMMER ausgeben (kompakt, nicht nur im Debug-Modus) Print("CSV OK: '", _Symbol, "' → Signal: ", g_CSVSignalText); // Ausführliche Details nur im Debug-Modus if(InpDebugMode) { Print("========================================"); Print("CSV ERFOLGREICH GELESEN!"); Print("MT5-Symbol: '", _Symbol, "'"); Print("CSV-Symbol: '", matchedCsvSymbol, "'"); Print("Signal: ", g_CSVSignalText); Print("Filter: ", (newFilter == FILTER_LONG ? "LONG" : (newFilter == FILTER_SHORT ? "SHORT" : "NEUTRAL"))); Print("========================================"); } // Wenn Signal geändert hat: Gegenpositionen schließen if(oldFilter != newFilter) { CloseOppositePositions(oldFilter, newFilter); } g_currentFilter = newFilter; UpdateButtonsState(); } //+------------------------------------------------------------------+ //| CloseOppositePositions | //+------------------------------------------------------------------+ void CloseOppositePositions(ENUM_DIR_FILTER oldDir, ENUM_DIR_FILTER newDir) { // NEUTRAL als Ziel: Nichts schließen (einfach warten) if(newDir == FILTER_NEUTRAL) { Print("Neues Signal: NEUTRAL - halte bestehende Positionen, keine neuen Trades"); return; } // Von NEUTRAL kommend: Nichts zu schließen (keine Positionen) if(oldDir == FILTER_NEUTRAL) { Print("Signal wechselt von NEUTRAL zu ", (newDir == FILTER_LONG) ? "BUY" : "SELL"); return; } // BOTH sollte bei CSV-Steuerung nicht vorkommen if(oldDir == FILTER_BOTH || newDir == FILTER_BOTH) { Print("WARNUNG: FILTER_BOTH sollte bei CSV-Steuerung nicht vorkommen!"); return; } // Gleiche Richtung: Nichts zu tun if(oldDir == newDir) { return; } // BUY -> SELL oder SELL -> BUY: Gegenpositionen schließen for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); bool closeIt = false; if(newDir == FILTER_LONG && type == POSITION_TYPE_SELL) { closeIt = true; Print("Schließe Short-Position #", ticket, " wegen neuem BUY-Signal"); } else if(newDir == FILTER_SHORT && type == POSITION_TYPE_BUY) { closeIt = true; Print("Schließe Long-Position #", ticket, " wegen neuem SELL-Signal"); } if(closeIt) { trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| News Filter Functions - OPTIMIERT! | //+------------------------------------------------------------------+ void CheckUpcomingNews() { if(!InpUseNewsFilter) { g_NewsBlockActive = false; g_NextNewsInfo = "Deaktiviert"; return; } // OPTIMIERUNG: Nur alle X Sekunden prüfen statt bei jedem Tick! datetime currentTime = TimeCurrent(); if(currentTime - g_LastNewsCheckTime < g_NewsCheckInterval && g_LastNewsCheckTime > 0) { return; // Nutze gecachte Werte } g_LastNewsCheckTime = currentTime; datetime serverTime = TimeTradeServer(); datetime dateFrom = serverTime - (InpMinutesAfterNews * 60); datetime dateTo = serverTime + (InpMinutesBeforeNews * 60); string currencyBase = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); string currencyQuote = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); ENUM_CALENDAR_EVENT_IMPORTANCE minImportance = (InpNewsImportance == NEWS_HIGH_ONLY) ? CALENDAR_IMPORTANCE_HIGH : CALENDAR_IMPORTANCE_MODERATE; MqlCalendarValue values[]; datetime nearestNewsTime = 0; string nearestNewsName = ""; ENUM_CALENDAR_EVENT_IMPORTANCE nearestImportance = CALENDAR_IMPORTANCE_NONE; // Base-Währung prüfen if(CalendarValueHistory(values, dateFrom, dateTo, NULL, currencyBase)) { for(int i = 0; i < ArraySize(values); i++) { MqlCalendarEvent event; if(CalendarEventById(values[i].event_id, event)) { if(event.time_mode != CALENDAR_TIMEMODE_DATETIME && event.time_mode != CALENDAR_TIMEMODE_DATE) continue; if(event.importance < minImportance) continue; if(nearestNewsTime == 0 || MathAbs((int)(values[i].time - serverTime)) < MathAbs((int)(nearestNewsTime - serverTime))) { nearestNewsTime = values[i].time; nearestNewsName = event.name; nearestImportance = event.importance; } } } } // Quote-Währung prüfen (wenn aktiviert) if(InpNewsFilterBothCurrencies && currencyBase != currencyQuote) { if(CalendarValueHistory(values, dateFrom, dateTo, NULL, currencyQuote)) { for(int i = 0; i < ArraySize(values); i++) { MqlCalendarEvent event; if(CalendarEventById(values[i].event_id, event)) { if(event.time_mode != CALENDAR_TIMEMODE_DATETIME && event.time_mode != CALENDAR_TIMEMODE_DATE) continue; if(event.importance < minImportance) continue; if(nearestNewsTime == 0 || MathAbs((int)(values[i].time - serverTime)) < MathAbs((int)(nearestNewsTime - serverTime))) { nearestNewsTime = values[i].time; nearestNewsName = event.name; nearestImportance = event.importance; } } } } } // Auswertung if(nearestNewsTime > 0) { int minutesDiff = (int)((nearestNewsTime - serverTime) / 60); string impText = ""; switch(nearestImportance) { case CALENDAR_IMPORTANCE_HIGH: impText = "HIGH"; break; case CALENDAR_IMPORTANCE_MODERATE: impText = "MEDIUM"; break; case CALENDAR_IMPORTANCE_LOW: impText = "LOW"; break; default: impText = "NONE"; break; } if(minutesDiff > 0) { g_NewsBlockActive = true; g_NextNewsInfo = StringFormat("%s [%s] in %d Min", nearestNewsName, impText, minutesDiff); } else { g_NewsBlockActive = true; g_NextNewsInfo = StringFormat("%s [%s] vor %d Min", nearestNewsName, impText, -minutesDiff); } g_NextNewsTime = nearestNewsTime; g_NextNewsName = nearestNewsName; } else { g_NewsBlockActive = false; g_NextNewsInfo = "Klar - Keine News"; g_NextNewsTime = 0; g_NextNewsName = ""; } } //+------------------------------------------------------------------+ //| Display Logic - IM TESTER DEAKTIVIERT! | //+------------------------------------------------------------------+ void UpdateVisuals(int positions) { // OPTIMIERUNG: Im Strategy Tester keine GUI-Updates (bringt nichts und kostet Zeit) static bool isInTester = (MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION)); if(isInTester) return; // Zeile 1: MaxDD string txtDD = StringFormat("MaxDD zu HWM-Balance in %%: %.2f%%", stat_MaxDD_Equity_Percent); ObjectSetString(0, lblMaxDDName, OBJPROP_TEXT, txtDD); // Zeile 2: Positionen string txtPos = StringFormat("Anzahl an Positionen: %d", positions); ObjectSetString(0, lblPosName, OBJPROP_TEXT, txtPos); // Zeile 3: Status string txtStatus = StringFormat("Status: %s", stat_EA_Action); ObjectSetString(0, lblStatusName, OBJPROP_TEXT, txtStatus); // Zeile 4: Config / Setup Info (Mit Magic Number!) string txtSetup = StringFormat("Config: Cap %.0f | Grid %.0f%% | Target %.0f%% | Lot %.2f | Magic %d", InpEntryADR_Pct, InpGridStep_ADR_Pct, InpStartTarget_ADR, InpFirstLot, InpMagicNumber); ObjectSetString(0, lblSetupName, OBJPROP_TEXT, txtSetup); // Zeile 5: CSV Info if(InpUseCSVSignals) { string modeText = g_CSVMode ? "AKTIV" : "INAKTIV"; color modeColor = g_CSVMode ? clrLimeGreen : clrOrange; string txtCSVInfo = StringFormat("CSV-Mode: %s | Signal: %s | Status: %s", modeText, g_CSVSignalText, g_CSVStatus); ObjectSetString(0, lblCSVInfoName, OBJPROP_TEXT, txtCSVInfo); ObjectSetInteger(0, lblCSVInfoName, OBJPROP_COLOR, modeColor); // Zeile 6: Letzte CSV-Prüfung string timeText = "Noch nie"; if(g_LastCSVCheck > 0) { timeText = TimeToString(g_LastCSVCheck, TIME_DATE|TIME_MINUTES); } string txtCSVTime = StringFormat("Letzte CSV-Prüfung: %s (Interval: %d Min)", timeText, InpCSVCheckInterval); ObjectSetString(0, lblCSVTimeName, OBJPROP_TEXT, txtCSVTime); } else { ObjectSetString(0, lblCSVInfoName, OBJPROP_TEXT, "CSV-Mode: DEAKTIVIERT (Nutze manuelle Buttons)"); ObjectSetInteger(0, lblCSVInfoName, OBJPROP_COLOR, clrGray); ObjectSetString(0, lblCSVTimeName, OBJPROP_TEXT, ""); } // Zeile 7: News-Filter Info if(InpUseNewsFilter) { string newsFilterText = ""; color newsColor = clrLimeGreen; if(g_NewsBlockActive) { newsFilterText = StringFormat("News-Filter: BLOCKIERT | %s", g_NextNewsInfo); newsColor = clrRed; } else { newsFilterText = StringFormat("News-Filter: %s", g_NextNewsInfo); newsColor = clrLimeGreen; } ObjectSetString(0, lblNewsInfoName, OBJPROP_TEXT, newsFilterText); ObjectSetInteger(0, lblNewsInfoName, OBJPROP_COLOR, newsColor); } else { ObjectSetString(0, lblNewsInfoName, OBJPROP_TEXT, "News-Filter: DEAKTIVIERT"); ObjectSetInteger(0, lblNewsInfoName, OBJPROP_COLOR, clrGray); } // Zeile 8: CSV Traderichtung if(InpUseCSVSignals && g_CSVMode) { string directionText = ""; color directionColor = clrYellow; if(g_CSVSignalText == "NOT FOUND") { directionText = StringFormat("%s: NICHT GEFUNDEN IN CSV → KEINE TRADES", _Symbol); directionColor = clrOrange; } else { switch(g_currentFilter) { case FILTER_LONG: directionText = StringFormat("%s: BUY → NUR LONG TRADES ERLAUBT", _Symbol); directionColor = clrLimeGreen; break; case FILTER_SHORT: directionText = StringFormat("%s: SELL → NUR SHORT TRADES ERLAUBT", _Symbol); directionColor = clrRed; break; case FILTER_NEUTRAL: directionText = StringFormat("%s: NEUTRAL → KEINE NEUEN TRADES", _Symbol); directionColor = clrYellow; break; } } ObjectSetString(0, lblCSVDirectionName, OBJPROP_TEXT, directionText); ObjectSetInteger(0, lblCSVDirectionName, OBJPROP_COLOR, directionColor); } else if(InpUseCSVSignals && !g_CSVMode) { ObjectSetString(0, lblCSVDirectionName, OBJPROP_TEXT, StringFormat("%s: CSV-DATEI NICHT GEFUNDEN → WARNUNG", _Symbol)); ObjectSetInteger(0, lblCSVDirectionName, OBJPROP_COLOR, clrOrange); } else { ObjectSetString(0, lblCSVDirectionName, OBJPROP_TEXT, "CSV-Steuerung: DEAKTIVIERT (Manuelle Buttons aktiv)"); ObjectSetInteger(0, lblCSVDirectionName, OBJPROP_COLOR, clrGray); } } //+------------------------------------------------------------------+ //| GUI Helper | //+------------------------------------------------------------------+ void CreateLabels() { int x = 20; int fontSize = 11; string font = "Arial Bold"; color c = clrWhite; // Label 1: MaxDD (Y=50, da Buttons bei Y=5 ca 30px hoch sind) CreateSingleLabel(lblMaxDDName, x, 50, "MaxDD: 0.0%", fontSize, font, c); // Label 2: Positionen CreateSingleLabel(lblPosName, x, 75, "Anzahl: 0", fontSize, font, c); // Label 3: Status CreateSingleLabel(lblStatusName, x, 100, "Status: Init", fontSize, font, c); // Label 4: Setup Info CreateSingleLabel(lblSetupName, x, 125, "Setup: ...", fontSize, font, c); // Label 5: CSV Info CreateSingleLabel(lblCSVInfoName, x, 150, "CSV: Init...", fontSize, font, clrYellow); // Label 6: CSV Time CreateSingleLabel(lblCSVTimeName, x, 175, "CSV Check: ...", fontSize, font, clrWhite); // Label 7: News Info CreateSingleLabel(lblNewsInfoName, x, 200, "News: Init...", fontSize, font, clrYellow); // Label 8: CSV Traderichtung CreateSingleLabel(lblCSVDirectionName, x, 225, "CSV-Richtung: N/A", fontSize+2, font, clrYellow); } //+------------------------------------------------------------------+ //| CreateSingleLabel | //+------------------------------------------------------------------+ void CreateSingleLabel(string name, int x, int y, string text, int fSize, string font, color c) { if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetString(0, name, OBJPROP_FONT, font); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fSize); ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); } } //+------------------------------------------------------------------+ //| CreateButtons | //+------------------------------------------------------------------+ void CreateButtons() { int xBase = 20; int yBase = 5; // Ganz oben int width = 90; int height = 30; int gap = 5; CreateSingleButton(btnBothName, xBase, yBase, width, height, "BOTH"); CreateSingleButton(btnLongName, xBase + width + gap, yBase, width, height, "LONG"); CreateSingleButton(btnShortName, xBase + (width + gap)*2, yBase, width, height, "SHORT"); CreateSingleButton(btnNeutralName, xBase + (width + gap)*3, yBase, width, height, "NEUTRAL"); } //+------------------------------------------------------------------+ //| CreateSingleButton | //+------------------------------------------------------------------+ void CreateSingleButton(string name, int x, int y, int w, int h, string text) { if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrSilver); ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrGray); ObjectSetInteger(0, name, OBJPROP_STATE, false); ObjectSetInteger(0, name, OBJPROP_ZORDER, 10); } } //+------------------------------------------------------------------+ //| UpdateButtonsState | //+------------------------------------------------------------------+ void UpdateButtonsState() { color activeColor = clrSilver; color inactiveColor = clrWhiteSmoke; color disabledColor = clrDarkGray; // Wenn CSV-Mode aktiv: Alle Buttons ausgrauen if(g_CSVMode) { ObjectSetInteger(0, btnBothName, OBJPROP_BGCOLOR, disabledColor); ObjectSetInteger(0, btnLongName, OBJPROP_BGCOLOR, disabledColor); ObjectSetInteger(0, btnShortName, OBJPROP_BGCOLOR, disabledColor); ObjectSetInteger(0, btnNeutralName, OBJPROP_BGCOLOR, disabledColor); ObjectSetString(0, btnBothName, OBJPROP_TEXT, "BOTH (CSV)"); ObjectSetString(0, btnLongName, OBJPROP_TEXT, "LONG (CSV)"); ObjectSetString(0, btnShortName, OBJPROP_TEXT, "SHORT (CSV)"); ObjectSetString(0, btnNeutralName, OBJPROP_TEXT, "NEUTRAL (CSV)"); switch(g_currentFilter) { case FILTER_BOTH: ObjectSetInteger(0, btnBothName, OBJPROP_BGCOLOR, clrGray); break; case FILTER_LONG: ObjectSetInteger(0, btnLongName, OBJPROP_BGCOLOR, clrGray); break; case FILTER_SHORT: ObjectSetInteger(0, btnShortName, OBJPROP_BGCOLOR, clrGray); break; case FILTER_NEUTRAL: ObjectSetInteger(0, btnNeutralName, OBJPROP_BGCOLOR, clrGray); break; } } else { ObjectSetString(0, btnBothName, OBJPROP_TEXT, "BOTH"); ObjectSetString(0, btnLongName, OBJPROP_TEXT, "LONG"); ObjectSetString(0, btnShortName, OBJPROP_TEXT, "SHORT"); ObjectSetString(0, btnNeutralName, OBJPROP_TEXT, "NEUTRAL"); color cBoth = (g_currentFilter == FILTER_BOTH) ? clrSkyBlue : inactiveColor; color cLong = (g_currentFilter == FILTER_LONG) ? clrLimeGreen : inactiveColor; color cShort = (g_currentFilter == FILTER_SHORT) ? clrTomato : inactiveColor; color cNeutral = (g_currentFilter == FILTER_NEUTRAL) ? clrKhaki : inactiveColor; ObjectSetInteger(0, btnBothName, OBJPROP_BGCOLOR, cBoth); ObjectSetInteger(0, btnLongName, OBJPROP_BGCOLOR, cLong); ObjectSetInteger(0, btnShortName, OBJPROP_BGCOLOR, cShort); ObjectSetInteger(0, btnNeutralName, OBJPROP_BGCOLOR, cNeutral); } ObjectSetInteger(0, btnBothName, OBJPROP_STATE, false); ObjectSetInteger(0, btnLongName, OBJPROP_STATE, false); ObjectSetInteger(0, btnShortName, OBJPROP_STATE, false); ObjectSetInteger(0, btnNeutralName, OBJPROP_STATE, false); ChartRedraw(); } //+------------------------------------------------------------------+ //| Helpers | //+------------------------------------------------------------------+ string BuildTradeComment(bool isFirstEntry, int positionNumber, string direction) { // CSV-Status string csvPart = g_CSVMode ? StringFormat("CSV:%s", g_CSVSignalText) : "Manual"; // Entry-Typ string entryType = ""; if(isFirstEntry) { entryType = (InpEntryMode == MODE_ADR) ? "ADR" : "RSI"; } else { entryType = "Grid"; } // Vollständiger Comment string comment = StringFormat("%s | %s-%s #%d", csvPart, entryType, direction, positionNumber); return comment; } //+------------------------------------------------------------------+ //| ADR Berechnung - OPTIMIERT! NUR 1x PRO TAG | //+------------------------------------------------------------------+ void CalculateADR_and_Range() { datetime currentDay = iTime(_Symbol, PERIOD_D1, 0); // OPTIMIERUNG: ADR nur einmal pro Tag berechnen! if(currentDay != g_LastADRCalcDay || adrValue == 0) { g_LastADRCalcDay = currentDay; double sum = 0; for(int i=1; i<=InpADRPeriod; i++) sum += (iHigh(_Symbol, PERIOD_D1, i) - iLow(_Symbol, PERIOD_D1, i)); adrValue = sum / InpADRPeriod; if(adrValue == 0) adrValue = 100 * _Point; } // DayRange muss aktuell bleiben (aber viel schneller als ADR-Calc) dayRange = iHigh(_Symbol, PERIOD_D1, 0) - iLow(_Symbol, PERIOD_D1, 0); } //+------------------------------------------------------------------+ //| ValidatePositionsAgainstFilter | //+------------------------------------------------------------------+ void ValidatePositionsAgainstFilter() { if(g_currentFilter == FILTER_BOTH || g_currentFilter == FILTER_NEUTRAL) return; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); bool closeIt = false; if(g_currentFilter == FILTER_LONG && type == POSITION_TYPE_SELL) closeIt = true; if(g_currentFilter == FILTER_SHORT && type == POSITION_TYPE_BUY) closeIt = true; if(closeIt) trade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //| GetDynamicLot | //+------------------------------------------------------------------+ double GetDynamicLot(double baseLot) { if(!InpUseMM || InpRefBalance <= 0 || InpBalanceStep_Pct <= 0) return(baseLot); double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(currentBalance < InpRefBalance) return(baseLot); double profitAbs = currentBalance - InpRefBalance; double profitPct = (profitAbs / InpRefBalance) * 100.0; int steps = (int)(profitPct / InpBalanceStep_Pct); double multiplier = 1.0 + (steps * (InpLotIncrease_Pct / 100.0)); double calculatedLot = baseLot * multiplier; double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); calculatedLot = MathFloor(calculatedLot / step) * step; if(calculatedLot < min) calculatedLot = min; if(calculatedLot > max) calculatedLot = max; return(calculatedLot); } //+------------------------------------------------------------------+ //| CheckBasketExit | //+------------------------------------------------------------------+ void CheckBasketExit(ENUM_POSITION_TYPE type, int count) { double totalLots = 0.0; double weightedPrice = 0.0; for(int i=PositionsTotal()-1; i>=0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { double vol = PositionGetDouble(POSITION_VOLUME); double price = PositionGetDouble(POSITION_PRICE_OPEN); weightedPrice += (price * vol); totalLots += vol; } } } if(totalLots <= 0) return; double avgPrice = weightedPrice / totalLots; double targetPct = InpStartTarget_ADR; if (count > 1) { targetPct = targetPct - ((count - 1) * InpTargetDecay_ADR); } double targetDistPoints = adrValue * (targetPct / 100.0); bool triggerClose = false; double currentPrice = 0.0; if(type == POSITION_TYPE_BUY) { currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(currentPrice >= (avgPrice + targetDistPoints)) triggerClose = true; } else if(type == POSITION_TYPE_SELL) { currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(currentPrice <= (avgPrice - targetDistPoints)) triggerClose = true; } if(triggerClose) CloseAllPositions(); } //+------------------------------------------------------------------+ //| CloseAllPositions | //+------------------------------------------------------------------+ void CloseAllPositions() { for(int i=PositionsTotal()-1; i>=0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { trade.PositionClose(ticket); } } } }