//+------------------------------------------------------------------+ //| HEDGE FUND ULTIMATE PRO v5.1 – DUAL ENGINE (MT5) | //| Swing Trend-Follow + Scalp Mean-Reversion | //| God-Tier: Adaptive Risk + Anti-MC + Profit Accumulator | //+------------------------------------------------------------------+ #property strict #property copyright "Hedge Ultimate Pro v5.1" #property version "5.10" #include CTrade trade; //============================================================ // RISK MANAGEMENT //============================================================ input group "=== Risk Management ===" input double RiskPercent = 0.75; // Risk per trade swing (% balance) input double ScalpRiskPercent = 0.3; // Risk per trade scalp (% balance) - kecil! input double MaxDailyLossP = 3.0; // Max daily loss (% balance) input double MaxDrawdownP = 8.0; // Max equity drawdown (%) - circuit breaker input int MaxSwingPos = 2; // Max open swing positions input int MaxScalpPos = 3; // Max open scalp positions input int MagicSwing = 778899; // Magic Number swing input int MagicScalp = 778900; // Magic Number scalp input int CooldownBars = 3; // Cooldown bars setelah loss input int MaxConsecLoss = 3; // N loss berturut → risk -50% input int MaxTotalPos = 4; // Max TOTAL posisi (swing+scalp combined) input double DailyProfitTarget = 2.0; // Stop trading jika profit hari ini >= % balance (0=off) input double MinMarginLevel = 200.0; // Min margin level % untuk entry baru //============================================================ // TREND FILTER (SWING) //============================================================ input group "=== Swing: Trend Filter ===" input int EMA_Fast = 50; input int EMA_Slow = 200; input ENUM_TIMEFRAMES HTF = PERIOD_H1; // HTF untuk trend direction input int ADX_Period = 14; input double ADX_Min = 20.0; //============================================================ // SWING ENTRY //============================================================ input group "=== Swing: Entry ===" input int RSI_Period = 14; input double RSI_OB = 70.0; input double RSI_OS = 30.0; input double ATR_SL_Multi = 1.5; // SL = ATR x multi input double Swing_RR = 2.5; // R:R ratio swing input int ATR_Period = 14; //============================================================ // SWING TRADE MANAGEMENT //============================================================ input group "=== Swing: Management ===" input bool UsePartialClose = true; input double PartialCloseRR = 1.0; input double PartialPercent = 50.0; input double BE_ATR_Multi = 1.0; input double Trail_ATR_Multi = 1.5; input double Trail_Step_ATR = 0.8; input bool CloseOnOpposite = true; //============================================================ // SCALP SETTINGS //============================================================ input group "=== Scalp: Settings ===" input bool EnableScalp = true; // Aktifkan scalp mode input int BB_Period = 20; // Bollinger Band period input double BB_Dev = 2.0; // BB deviasi input int Scalp_RSI_Period = 7; // RSI cepat untuk scalp input double Scalp_RSI_OB = 75.0; // RSI overbought scalp trigger input double Scalp_RSI_OS = 25.0; // RSI oversold scalp trigger input double Scalp_RR = 1.2; // R:R ratio scalp (cepat ambil profit) input double Scalp_SL_ATR = 0.8; // SL scalp = ATR x multi (ketat) input double Scalp_MaxSpread = 15; // Max spread untuk scalp (lebih ketat) input double Scalp_BE_ATR = 0.3; // Scalp BE trigger = ATR × multi (adaptive) input double Scalp_Trail_ATR = 0.15; // Scalp trail distance = ATR × multi (adaptive) //============================================================ // FILTERS //============================================================ input group "=== Filters ===" input double MaxSpread = 30; input bool UseSessionFilter = true; input int SessionStartHour = 2; // London open input int SessionEndHour = 20; // NY close input int ScalpStartHour = 7; // Scalp hanya jam sibuk input int ScalpEndHour = 17; // Scalp stop sebelum NY close input bool CloseBeforeWeekend = true; input bool AvoidHighImpactHour = true; // Skip entry 30m sebelum/sesudah news hour input string NewsHours = "8,13,15"; // Jam server rawan news (pisah koma) //============================================================ // GLOBAL VARIABLES //============================================================ int ema_fast_handle, ema_slow_handle; int adx_handle, rsi_handle, atr_handle; int bb_handle, scalp_rsi_handle; double ema_fast_val[], ema_slow_val[]; double adx_val[], plus_di[], minus_di[]; double rsi_val[], atr_val[]; double bb_upper[], bb_lower[], bb_mid[]; double scalp_rsi_val[]; datetime lastSwingBar = 0; datetime lastLossTime = 0; int consecLosses = 0; datetime lastTradeDay = 0; // Track hari terakhir untuk OnTrade reset int lastDealsTotal = 0; // Track total deal terakhir untuk OnTrade string gvPeakEquity; // News hours parsed int newsHourArr[10]; int newsHourCount = 0; //============================================================ // AUTO-DETECT FILLING MODE //============================================================ ENUM_ORDER_TYPE_FILLING DetectFilling() { long fillMode = 0; SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE, fillMode); if((fillMode & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK; if((fillMode & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC; return ORDER_FILLING_RETURN; } //============================================================ // PARSE NEWS HOURS STRING //============================================================ void ParseNewsHours() { newsHourCount = 0; string parts[]; int count = StringSplit(NewsHours, ',', parts); for(int i = 0; i < count && i < 10; i++) { StringTrimLeft(parts[i]); StringTrimRight(parts[i]); newsHourArr[newsHourCount] = (int)StringToInteger(parts[i]); newsHourCount++; } } bool IsNearNewsHour() { if(!AvoidHighImpactHour || newsHourCount == 0) return false; MqlDateTime dt; TimeCurrent(dt); int min = dt.hour * 60 + dt.min; for(int i = 0; i < newsHourCount; i++) { int newsMin = newsHourArr[i] * 60; // Hindari 30 menit sebelum dan 15 menit sesudah if(min >= newsMin - 30 && min <= newsMin + 15) return true; } return false; } //============================================================ // INIT //============================================================ int OnInit() { trade.SetExpertMagicNumber(MagicSwing); trade.SetDeviationInPoints(10); trade.SetTypeFilling(DetectFilling()); // Swing indicators ema_fast_handle = iMA(_Symbol, HTF, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE); ema_slow_handle = iMA(_Symbol, HTF, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE); adx_handle = iADX(_Symbol, _Period, ADX_Period); rsi_handle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE); atr_handle = iATR(_Symbol, _Period, ATR_Period); // Scalp indicators bb_handle = iBands(_Symbol, _Period, BB_Period, 0, BB_Dev, PRICE_CLOSE); scalp_rsi_handle = iRSI(_Symbol, _Period, Scalp_RSI_Period, PRICE_CLOSE); if(ema_fast_handle==INVALID_HANDLE || ema_slow_handle==INVALID_HANDLE || adx_handle==INVALID_HANDLE || rsi_handle==INVALID_HANDLE || atr_handle==INVALID_HANDLE || bb_handle==INVALID_HANDLE || scalp_rsi_handle==INVALID_HANDLE) { Print("ERROR: Gagal membuat indicator handle!"); return(INIT_FAILED); } ArraySetAsSeries(ema_fast_val, true); ArraySetAsSeries(ema_slow_val, true); ArraySetAsSeries(adx_val, true); ArraySetAsSeries(plus_di, true); ArraySetAsSeries(minus_di, true); ArraySetAsSeries(rsi_val, true); ArraySetAsSeries(atr_val, true); ArraySetAsSeries(bb_upper, true); ArraySetAsSeries(bb_lower, true); ArraySetAsSeries(bb_mid, true); ArraySetAsSeries(scalp_rsi_val, true); // Peak equity persistent gvPeakEquity = "HUP5_PeakEq_" + _Symbol + "_" + IntegerToString(MagicSwing); if(GlobalVariableCheck(gvPeakEquity)) { double stored = GlobalVariableGet(gvPeakEquity); double current = AccountInfoDouble(ACCOUNT_EQUITY); if(stored > current && stored < current * 2.0) current = stored; GlobalVariableSet(gvPeakEquity, current); } else GlobalVariableSet(gvPeakEquity, AccountInfoDouble(ACCOUNT_EQUITY)); CountRecentLosses(); ParseNewsHours(); // Inisialisasi tracking bar dan trade history untuk mencegah double-counting & entry tengah bar saat startup lastSwingBar = iTime(_Symbol, _Period, 0); lastTradeDay = iTime(_Symbol, PERIOD_D1, 0); HistorySelect(lastTradeDay, TimeCurrent()); lastDealsTotal = HistoryDealsTotal(); Print("HUP v5.0 DUAL ENGINE | Filling=", EnumToString(DetectFilling()), " | Scalp=", EnableScalp ? "ON" : "OFF", " | ConsecLoss=", consecLosses); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(ema_fast_handle!=INVALID_HANDLE) IndicatorRelease(ema_fast_handle); if(ema_slow_handle!=INVALID_HANDLE) IndicatorRelease(ema_slow_handle); if(adx_handle!=INVALID_HANDLE) IndicatorRelease(adx_handle); if(rsi_handle!=INVALID_HANDLE) IndicatorRelease(rsi_handle); if(atr_handle!=INVALID_HANDLE) IndicatorRelease(atr_handle); if(bb_handle!=INVALID_HANDLE) IndicatorRelease(bb_handle); if(scalp_rsi_handle!=INVALID_HANDLE) IndicatorRelease(scalp_rsi_handle); } //============================================================ // COUNT RECENT CONSECUTIVE LOSSES //============================================================ void CountRecentLosses() { consecLosses = 0; // Scan history 30 hari ke belakang untuk consec losses lintas hari HistorySelect(TimeCurrent() - 30 * 24 * 3600, TimeCurrent()); int total = HistoryDealsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = HistoryDealGetTicket(i); if(ticket == 0) continue; long magic = (long)HistoryDealGetInteger(ticket, DEAL_MAGIC); if(magic != MagicSwing && magic != MagicScalp) continue; if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue; if(HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue; double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION); if(profit < 0) { consecLosses++; if(consecLosses == 1) lastLossTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); } else break; } } //============================================================ // COUNT POSITIONS (by magic) //============================================================ int CountPositions(int magic) { int count = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != magic) continue; count++; } return count; } //============================================================ // CLOSE POSITIONS (by magic + optional direction filter) //============================================================ void ClosePositions(int magic, int dirFilter=0) { for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != magic) continue; if(dirFilter != 0) { int type = (int)PositionGetInteger(POSITION_TYPE); if(dirFilter == +1 && type != POSITION_TYPE_SELL) continue; // close sells only if(dirFilter == -1 && type != POSITION_TYPE_BUY) continue; // close buys only } trade.SetExpertMagicNumber(magic); trade.PositionClose(ticket); } } void CloseAllMyPositions() { ClosePositions(MagicSwing); ClosePositions(MagicScalp); } //============================================================ // DAILY LOSS CHECK (both engines combined) //============================================================ double GetTodayPnL() { double pnl = 0; datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); HistorySelect(dayStart, TimeCurrent()); for(int i = 0; i < HistoryDealsTotal(); i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket == 0) continue; long magic = (long)HistoryDealGetInteger(ticket, DEAL_MAGIC); if(magic != MagicSwing && magic != MagicScalp) continue; if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue; pnl += HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION); } return pnl; } bool DailySafe() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double maxLoss = balance * MaxDailyLossP / 100.0; double closedPnL = GetTodayPnL(); // Cek closed PnL if(closedPnL < -maxLoss) { CloseAllMyPositions(); Print("DAILY LOSS LIMIT: closed PnL=", DoubleToString(closedPnL,2)); return false; } // Cek closed + floating PnL combined double floatingPnL = GetFloatingPnL(); if((closedPnL + floatingPnL) < -maxLoss) { CloseAllMyPositions(); Print("DAILY LOSS LIMIT (floating): total=", DoubleToString(closedPnL+floatingPnL,2)); return false; } return true; } //============================================================ // FLOATING PnL (posisi terbuka milik EA ini) //============================================================ double GetFloatingPnL() { double pnl = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; long magic = (long)PositionGetInteger(POSITION_MAGIC); if(magic != MagicSwing && magic != MagicScalp) continue; pnl += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP); } return pnl; } //============================================================ // DAILY PROFIT TARGET (stop trading kalau sudah cukup) //============================================================ bool DailyTargetReached() { if(DailyProfitTarget <= 0) return false; double balance = AccountInfoDouble(ACCOUNT_BALANCE); double target = balance * DailyProfitTarget / 100.0; double todayPnL = GetTodayPnL(); return (todayPnL >= target); } //============================================================ // MARGIN CHECK //============================================================ bool MarginOK() { double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); // marginLevel=0 berarti tidak ada posisi terbuka (unlimited) if(marginLevel == 0) return true; return (marginLevel >= MinMarginLevel); } //============================================================ // TOTAL POSITION CHECK (combined) //============================================================ int CountAllMyPositions() { int count = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; long magic = (long)PositionGetInteger(POSITION_MAGIC); if(magic == MagicSwing || magic == MagicScalp) count++; } return count; } //============================================================ // EQUITY CIRCUIT BREAKER //============================================================ bool EquitySafe() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); double peak = GlobalVariableGet(gvPeakEquity); if(peak <= 0) peak = equity; if(equity > peak) { peak = equity; GlobalVariableSet(gvPeakEquity, peak); } double ddPercent = (peak - equity) / peak * 100.0; if(ddPercent >= MaxDrawdownP) { CloseAllMyPositions(); Print("CIRCUIT BREAKER: DD ", DoubleToString(ddPercent,1), "%"); return false; } return true; } //============================================================ // FILTERS //============================================================ bool SpreadOK(double maxSpr) { double s = (SymbolInfoDouble(_Symbol,SYMBOL_ASK) - SymbolInfoDouble(_Symbol,SYMBOL_BID)) / _Point; return s <= maxSpr; } bool SessionOK() { if(!UseSessionFilter) return true; MqlDateTime dt; TimeCurrent(dt); return (dt.hour >= SessionStartHour && dt.hour < SessionEndHour); } bool ScalpSessionOK() { MqlDateTime dt; TimeCurrent(dt); return (dt.hour >= ScalpStartHour && dt.hour < ScalpEndHour); } void CheckWeekendClose() { if(!CloseBeforeWeekend) return; MqlDateTime dt; TimeCurrent(dt); if(dt.day_of_week == 5 && dt.hour >= 20) CloseAllMyPositions(); } bool CooldownOK() { if(CooldownBars <= 0 || lastLossTime == 0) return true; int barsSinceLoss = iBarShift(_Symbol, _Period, lastLossTime, false); return (barsSinceLoss >= CooldownBars); } //============================================================ // DYNAMIC RISK //============================================================ double GetEffectiveRisk(double baseRisk) { double risk = baseRisk; if(consecLosses >= MaxConsecLoss) risk *= 0.5; return risk; } //============================================================ // LOAD ALL INDICATORS //============================================================ bool LoadIndicators() { if(CopyBuffer(ema_fast_handle, 0, 0, 3, ema_fast_val) < 3) return false; if(CopyBuffer(ema_slow_handle, 0, 0, 3, ema_slow_val) < 3) return false; if(CopyBuffer(adx_handle, 0, 0, 3, adx_val) < 3) return false; if(CopyBuffer(adx_handle, 1, 0, 3, plus_di) < 3) return false; if(CopyBuffer(adx_handle, 2, 0, 3, minus_di) < 3) return false; if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_val) < 3) return false; if(CopyBuffer(atr_handle, 0, 0, 50, atr_val) < 50) return false; if(CopyBuffer(bb_handle, 1, 0, 3, bb_upper) < 3) return false; // Upper if(CopyBuffer(bb_handle, 2, 0, 3, bb_lower) < 3) return false; // Lower if(CopyBuffer(bb_handle, 0, 0, 3, bb_mid) < 3) return false; // Middle if(CopyBuffer(scalp_rsi_handle, 0, 0, 3, scalp_rsi_val)< 3) return false; return true; } //============================================================ // VOLATILITY FILTER (DYNAMIC ATR) //============================================================ bool IsVolatilityOk() { int totalATR = ArraySize(atr_val); if(totalATR < 10) return true; // fallback jika data kurang double sum = 0; for(int i = 0; i < totalATR; i++) sum += atr_val[i]; double avgATR = sum / totalATR; // Volatilitas OK jika ATR saat ini >= 60% dari rata-rata 50 bar terakhir return (atr_val[1] >= avgATR * 0.6); } //============================================================ // LOT CALCULATION //============================================================ double LotCalc(double sl_points, double baseRisk) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk = balance * GetEffectiveRisk(baseRisk) / 100.0; double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(sl_points <= 0 || tickvalue <= 0 || ticksize <= 0) return 0.0; double lot = risk / (sl_points * (tickvalue / ticksize) * _Point); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); if(lotStep <= 0) lotStep = 0.01; lot = MathFloor(lot / lotStep) * lotStep; lot = MathMax(minLot, MathMin(maxLot, lot)); return NormalizeDouble(lot, 2); } //============================================================ // VALIDATE SL/TP vs BROKER LIMITS //============================================================ bool ValidateSLTP(double price, double sl, double tp, int direction) { long stopsLevel = 0; SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL, stopsLevel); double minDist = stopsLevel * _Point; if(minDist > 0) { if(direction == +1) { if((sl > 0 && (price - sl) < minDist) || (tp > 0 && (tp - price) < minDist)) return false; } else { if((sl > 0 && (sl - price) < minDist) || (tp > 0 && (price - tp) < minDist)) return false; } } long freezeLevel = 0; SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL, freezeLevel); double freezeDist = freezeLevel * _Point; if(freezeDist > 0) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(direction == +1 && sl > 0 && (ask - sl) < freezeDist) return false; if(direction == -1 && sl > 0 && (sl - bid) < freezeDist) return false; } return true; } double NormalizePrice(double price) { double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0) return NormalizeDouble(price, _Digits); return NormalizeDouble(MathRound(price / tickSize) * tickSize, _Digits); } //============================================================ // TREND ANALYSIS (SWING) //============================================================ int GetTrend() { if(ema_fast_val[0] > ema_slow_val[0]) return +1; if(ema_fast_val[0] < ema_slow_val[0]) return -1; return 0; } bool TrendStrong() { return adx_val[0] >= ADX_Min; } //============================================================ // DETECT PIN BAR PATTERN //============================================================ bool IsPinBar(int barIndex, int direction) { double open = iOpen(_Symbol, _Period, barIndex); double close = iClose(_Symbol, _Period, barIndex); double high = iHigh(_Symbol, _Period, barIndex); double low = iLow(_Symbol, _Period, barIndex); double range = high - low; if(range <= 0) return false; double body = MathAbs(close - open); if(direction == +1) // Bullish Pin Bar (Hammer) { double lowerShadow = MathMin(open, close) - low; double upperShadow = high - MathMax(open, close); return (lowerShadow >= range * 0.6 && body <= range * 0.3 && upperShadow <= range * 0.2); } else if(direction == -1) // Bearish Pin Bar (Shooting Star) { double lowerShadow = MathMin(open, close) - low; double upperShadow = high - MathMax(open, close); return (upperShadow >= range * 0.6 && body <= range * 0.3 && lowerShadow <= range * 0.2); } return false; } //============================================================ // SWING BUY SIGNAL //============================================================ bool SwingBuySignal() { if(GetTrend() != +1) return false; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid <= ema_fast_val[0]) return false; if(!TrendStrong()) return false; if(plus_di[0] <= minus_di[0]) return false; // Dioptimalkan menjadi 35.0 (sebelumnya 40.0) agar bisa menangkap pullback sehat yang lebih dalam if(rsi_val[0] >= RSI_OB || rsi_val[0] < 35.0) return false; double open1 = iOpen(_Symbol, _Period, 1); double close1 = iClose(_Symbol, _Period, 1); double open2 = iOpen(_Symbol, _Period, 2); double close2 = iClose(_Symbol, _Period, 2); double body1 = close1 - open1; double body2 = close2 - open2; double range1 = iHigh(_Symbol, _Period, 1) - iLow(_Symbol, _Period, 1); double atr = atr_val[1]; if(atr <= 0) return false; if(body1 <= 0 || body1 < atr * 0.4) return false; if(range1 > 0 && body1 / range1 < 0.5) return false; bool breakout = close1 > iHigh(_Symbol, _Period, 2); bool engulfing = (body2 < 0) && (close1 > open2) && (open1 < close2); bool pinbar = IsPinBar(1, +1); return (breakout || engulfing || pinbar); } //============================================================ // SWING SELL SIGNAL //============================================================ bool SwingSellSignal() { if(GetTrend() != -1) return false; double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(ask >= ema_fast_val[0]) return false; if(!TrendStrong()) return false; if(minus_di[0] <= plus_di[0]) return false; // Dioptimalkan menjadi 65.0 (sebelumnya 60.0) agar bisa menangkap pullback sehat yang lebih tinggi if(rsi_val[0] <= RSI_OS || rsi_val[0] > 65.0) return false; double open1 = iOpen(_Symbol, _Period, 1); double close1 = iClose(_Symbol, _Period, 1); double open2 = iOpen(_Symbol, _Period, 2); double close2 = iClose(_Symbol, _Period, 2); double body1 = open1 - close1; double body2 = open2 - close2; double range1 = iHigh(_Symbol, _Period, 1) - iLow(_Symbol, _Period, 1); double atr = atr_val[1]; if(atr <= 0) return false; if(body1 <= 0 || body1 < atr * 0.4) return false; if(range1 > 0 && body1 / range1 < 0.5) return false; bool breakout = close1 < iLow(_Symbol, _Period, 2); bool engulfing = (body2 < 0) && (close1 < open2) && (open1 > close2); bool pinbar = IsPinBar(1, -1); return (breakout || engulfing || pinbar); } //============================================================ // SCALP BUY SIGNAL (BB Lower bounce + RSI oversold) //============================================================ bool ScalpBuySignal() { if(!EnableScalp) return false; double close1 = iClose(_Symbol, _Period, 1); double low1 = iLow(_Symbol, _Period, 1); double close2 = iClose(_Symbol, _Period, 2); // Kondisi 1: Candle sebelumnya menyentuh/menembus BB lower if(low1 > bb_lower[1]) return false; // Kondisi 2: Close kembali di atas BB lower (bounce, bukan tembus terus) if(close1 < bb_lower[1]) return false; // Kondisi 3: RSI cepat di area oversold if(scalp_rsi_val[1] > Scalp_RSI_OS) return false; // Kondisi 4: Candle bounce bullish (close > open) if(close1 <= iOpen(_Symbol, _Period, 1)) return false; // Kondisi 5: Harga di bawah BB middle (masih ada ruang ke atas) if(close1 > bb_mid[0]) return false; // Kondisi 6: Tidak melawan trend kuat (ADX > 30 + bearish = skip) if(adx_val[0] > 30.0 && GetTrend() == -1) return false; return true; } //============================================================ // SCALP SELL SIGNAL (BB Upper bounce + RSI overbought) //============================================================ bool ScalpSellSignal() { if(!EnableScalp) return false; double close1 = iClose(_Symbol, _Period, 1); double high1 = iHigh(_Symbol, _Period, 1); double close2 = iClose(_Symbol, _Period, 2); // Kondisi 1: Candle sebelumnya menyentuh/menembus BB upper if(high1 < bb_upper[1]) return false; // Kondisi 2: Close kembali di bawah BB upper (bounce) if(close1 > bb_upper[1]) return false; // Kondisi 3: RSI cepat di area overbought if(scalp_rsi_val[1] < Scalp_RSI_OB) return false; // Kondisi 4: Candle bounce bearish if(close1 >= iOpen(_Symbol, _Period, 1)) return false; // Kondisi 5: Harga di atas BB middle if(close1 < bb_mid[0]) return false; // Kondisi 6: Tidak melawan trend kuat if(adx_val[0] > 30.0 && GetTrend() == +1) return false; return true; } //============================================================ // MANAGE SWING POSITIONS //============================================================ void ManageSwing() { double atrNow[]; ArraySetAsSeries(atrNow, true); if(CopyBuffer(atr_handle, 0, 0, 1, atrNow) < 1) return; double atr = atrNow[0]; if(atr <= 0) return; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicSwing) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double volume = PositionGetDouble(POSITION_VOLUME); int type = (int)PositionGetInteger(POSITION_TYPE); double price = (type==POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_BID) : SymbolInfoDouble(_Symbol,SYMBOL_ASK); double profitDist = (type==POSITION_TYPE_BUY) ? (price-openPrice) : (openPrice-price); double slDist = (type==POSITION_TYPE_BUY) ? (openPrice-sl) : (sl-openPrice); // Partial Close if(UsePartialClose && slDist > 0 && profitDist >= slDist * PartialCloseRR) { string gvP = "HUP5_SwP_" + IntegerToString(ticket); if(!GlobalVariableCheck(gvP)) { double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); if(lotStep <= 0) lotStep = 0.01; double closeLot = MathFloor((volume * PartialPercent / 100.0) / lotStep) * lotStep; closeLot = NormalizeDouble(closeLot, 2); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); if(closeLot >= minLot && (volume - closeLot) >= minLot) { trade.SetExpertMagicNumber(MagicSwing); if(trade.PositionClosePartial(ticket, closeLot)) GlobalVariableSet(gvP, 1.0); } } } // Breakeven if(profitDist >= atr * BE_ATR_Multi) { double lock = atr * 0.2; if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(openPrice + lock); if(sl < nSL) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, tp); } } else { double nSL = NormalizePrice(openPrice - lock); if(sl > nSL || sl == 0) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, tp); } } } // Progressive Trailing + Dynamic TP Extension if(profitDist >= atr * Trail_ATR_Multi) { double baseDist = atr * Trail_Step_ATR; double profATR = profitDist / atr; double tightF = MathMax(0.4, 1.0 - (profATR - Trail_ATR_Multi) * 0.15); double trailDist = baseDist * tightF; // Dynamic TP: jika harga sudah > 80% jarak ke TP, extend TP 1 ATR double tpDist = (type==POSITION_TYPE_BUY) ? (tp - openPrice) : (openPrice - tp); double newTP = tp; if(tp > 0 && tpDist > 0 && profitDist >= tpDist * 0.8) { if(type==POSITION_TYPE_BUY) newTP = NormalizePrice(tp + atr); else newTP = NormalizePrice(tp - atr); } if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(price - trailDist); // Trailing step minimal 10% dari ATR untuk mencegah spamming order modifikasi ke broker if((nSL - sl >= atr * 0.1 || sl == 0) && nSL > openPrice) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, newTP); } else if(newTP != tp && newTP > tp) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, sl, newTP); } } else { double nSL = NormalizePrice(price + trailDist); // Trailing step minimal 10% dari ATR if((sl - nSL >= atr * 0.1 || sl == 0) && nSL < openPrice) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, nSL, newTP); } else if(newTP != tp && newTP < tp) { trade.SetExpertMagicNumber(MagicSwing); trade.PositionModify(ticket, sl, newTP); } } } } } //============================================================ // MANAGE SCALP POSITIONS (fast BE + quick trail) //============================================================ void ManageScalp() { double atrNow[]; ArraySetAsSeries(atrNow, true); if(CopyBuffer(atr_handle, 0, 0, 1, atrNow) < 1) return; double atr = atrNow[0]; if(atr <= 0) return; // BB mid untuk dynamic TP double bbMid[]; ArraySetAsSeries(bbMid, true); if(CopyBuffer(bb_handle, 0, 0, 1, bbMid) < 1) return; double beTrigger = atr * Scalp_BE_ATR; // Adaptive BE trigger double lockDist = atr * 0.05; // Lock ~5% ATR profit double trailDist = atr * Scalp_Trail_ATR; // Adaptive trail distance double trailStart = beTrigger * 1.5; // Trail mulai setelah 1.5x BE trigger for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicScalp) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); int type = (int)PositionGetInteger(POSITION_TYPE); double price = (type==POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_BID) : SymbolInfoDouble(_Symbol,SYMBOL_ASK); double profitDist = (type==POSITION_TYPE_BUY) ? (price-openPrice) : (openPrice-price); // Adaptive Breakeven if(profitDist >= beTrigger) { if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(openPrice + lockDist); if(sl < nSL) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, tp); } } else { double nSL = NormalizePrice(openPrice - lockDist); if(sl > nSL || sl == 0) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, tp); } } } // Adaptive Trailing + Dynamic TP toward BB mid if(profitDist >= trailStart) { // Dynamic TP: geser ke BB mid secara dinamis mengikuti pergerakan band double newTP = tp; if(type==POSITION_TYPE_BUY && bbMid[0] > price) newTP = NormalizePrice(bbMid[0]); else if(type==POSITION_TYPE_SELL && bbMid[0] < price) newTP = NormalizePrice(bbMid[0]); // Hanya modifikasi jika perbedaan TP cukup signifikan untuk mencegah spamming order ke broker bool updateTP = (newTP != tp && MathAbs(tp - newTP) >= atr * 0.1); if(type==POSITION_TYPE_BUY) { double nSL = NormalizePrice(price - trailDist); // Trailing step minimal 5% dari ATR untuk scalp if((nSL - sl >= atr * 0.05 || sl == 0) && nSL > openPrice) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, newTP); } else if(updateTP) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, sl, newTP); } } else { double nSL = NormalizePrice(price + trailDist); // Trailing step minimal 5% dari ATR untuk scalp if((sl - nSL >= atr * 0.05 || sl == 0) && nSL < openPrice) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, nSL, newTP); } else if(updateTP) { trade.SetExpertMagicNumber(MagicScalp); trade.PositionModify(ticket, sl, newTP); } } } } } //============================================================ // ON TRADE EVENT //============================================================ void OnTrade() { datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); // Reset counter saat ganti hari (fix day-boundary bug) if(dayStart != lastTradeDay) { lastDealsTotal = 0; lastTradeDay = dayStart; } HistorySelect(dayStart, TimeCurrent()); int currentTotal = HistoryDealsTotal(); if(currentTotal > lastDealsTotal) { for(int i = lastDealsTotal; i < currentTotal; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket == 0) continue; long magic = (long)HistoryDealGetInteger(ticket, DEAL_MAGIC); if(magic != MagicSwing && magic != MagicScalp) continue; if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue; if(HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue; double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION); string mode = (magic == MagicScalp) ? "SCALP" : "SWING"; if(profit < 0) { consecLosses++; lastLossTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); Print(mode, " LOSS #", consecLosses, " net=", DoubleToString(profit,2)); } else if(profit > 0) { if(consecLosses > 0) Print(mode, " WIN +", DoubleToString(profit,2), " after ", consecLosses, " losses"); consecLosses = 0; } // Cleanup GlobalVariable partial close ulong posId = (ulong)HistoryDealGetInteger(ticket, DEAL_POSITION_ID); string gvP = "HUP5_SwP_" + IntegerToString(posId); if(GlobalVariableCheck(gvP)) GlobalVariableDel(gvP); } } lastDealsTotal = currentTotal; } //============================================================ // MAIN TICK //============================================================ void OnTick() { // ===== Management setiap tick ===== ManageSwing(); ManageScalp(); // ===== Circuit breakers ===== if(!EquitySafe()) return; CheckWeekendClose(); // ===== Bar baru check ===== datetime now = iTime(_Symbol, _Period, 0); bool newBar = (now != lastSwingBar); if(newBar) lastSwingBar = now; if(!newBar) return; // Semua entry hanya di bar baru // ===== Global filters ===== if(!DailySafe()) return; if(!CooldownOK()) return; if(DailyTargetReached()) return; // Sudah cukup profit hari ini if(!MarginOK()) return; // Margin terlalu rendah // ===== Load indicators ===== if(!LoadIndicators()) return; double atr = atr_val[1]; if(atr < _Point * 10) return; // Proteksi batas spread dasar (sangat kecil) if(!IsVolatilityOk()) return; // Filter volatilitas adaptif double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); int totalPos = CountAllMyPositions(); // ========================================================= // ENGINE 1: SWING ENTRIES // ========================================================= if(SpreadOK(MaxSpread) && SessionOK() && !IsNearNewsHour() && totalPos < MaxTotalPos) { int swingPos = CountPositions(MagicSwing); // --- SWING BUY --- if(SwingBuySignal() && swingPos < MaxSwingPos) { if(CloseOnOpposite) ClosePositions(MagicSwing, +1); // close sells double slP = NormalizePrice(iLow(_Symbol,_Period,1) - atr * ATR_SL_Multi); if(slP < ask) { double minD = atr * 0.5; if((ask - slP) < minD) slP = NormalizePrice(ask - minD); double tpP = NormalizePrice(ask + (ask - slP) * Swing_RR); if(ValidateSLTP(ask, slP, tpP, +1)) { double lot = LotCalc((ask - slP) / _Point, RiskPercent); if(lot > 0) { trade.SetExpertMagicNumber(MagicSwing); if(trade.Buy(lot, _Symbol, ask, slP, tpP, "SWING BUY")) Print("SWING BUY: Lot=", lot, " SL=", slP, " TP=", tpP); } } } } // --- SWING SELL --- if(SwingSellSignal() && swingPos < MaxSwingPos) { if(CloseOnOpposite) ClosePositions(MagicSwing, -1); // close buys double slP = NormalizePrice(iHigh(_Symbol,_Period,1) + atr * ATR_SL_Multi); if(slP > bid) { double minD = atr * 0.5; if((slP - bid) < minD) slP = NormalizePrice(bid + minD); double tpP = NormalizePrice(bid - (slP - bid) * Swing_RR); if(ValidateSLTP(bid, slP, tpP, -1)) { double lot = LotCalc((slP - bid) / _Point, RiskPercent); if(lot > 0) { trade.SetExpertMagicNumber(MagicSwing); if(trade.Sell(lot, _Symbol, bid, slP, tpP, "SWING SELL")) Print("SWING SELL: Lot=", lot, " SL=", slP, " TP=", tpP); } } } } } totalPos = CountAllMyPositions(); // Refresh setelah swing mungkin buka posisi // ========================================================= // ENGINE 2: SCALP ENTRIES // ========================================================= if(EnableScalp && SpreadOK(Scalp_MaxSpread) && ScalpSessionOK() && !IsNearNewsHour() && totalPos < MaxTotalPos) { int scalpPos = CountPositions(MagicScalp); // --- SCALP BUY (BB lower bounce) --- if(ScalpBuySignal() && scalpPos < MaxScalpPos) { double slDist = atr * Scalp_SL_ATR; double slP = NormalizePrice(ask - slDist); double tpP = NormalizePrice(ask + slDist * Scalp_RR); // TP tidak melewati BB mid (target realistis) if(tpP > bb_mid[0]) tpP = NormalizePrice(bb_mid[0]); // Pastikan masih ada ruang profit minimal 0.5x SL if((tpP - ask) < slDist * 0.5) { /* skip, target terlalu kecil */ } else if(ValidateSLTP(ask, slP, tpP, +1)) { double lot = LotCalc(slDist / _Point, ScalpRiskPercent); if(lot > 0) { trade.SetExpertMagicNumber(MagicScalp); if(trade.Buy(lot, _Symbol, ask, slP, tpP, "SCALP BUY")) Print("SCALP BUY: Lot=", lot, " SL=", slP, " TP=", tpP, " BB_low=", DoubleToString(bb_lower[1],_Digits)); } } } // --- SCALP SELL (BB upper bounce) --- if(ScalpSellSignal() && scalpPos < MaxScalpPos) { double slDist = atr * Scalp_SL_ATR; double slP = NormalizePrice(bid + slDist); double tpP = NormalizePrice(bid - slDist * Scalp_RR); // TP tidak melewati BB mid if(tpP < bb_mid[0]) tpP = NormalizePrice(bb_mid[0]); // Pastikan masih ada ruang profit minimal 0.5x SL if((bid - tpP) < slDist * 0.5) { /* skip */ } else if(ValidateSLTP(bid, slP, tpP, -1)) { double lot = LotCalc(slDist / _Point, ScalpRiskPercent); if(lot > 0) { trade.SetExpertMagicNumber(MagicScalp); if(trade.Sell(lot, _Symbol, bid, slP, tpP, "SCALP SELL")) Print("SCALP SELL: Lot=", lot, " SL=", slP, " TP=", tpP, " BB_up=", DoubleToString(bb_upper[1],_Digits)); } } } } }