Files

1245 lines
44 KiB
Plaintext
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| 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 <Trade/Trade.mqh>
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));
}
}
}
}
}