commit 633d4ce71a5465d19d1145b1c3199b4832c30b8d Author: anggol23 Date: Wed Apr 15 23:53:01 2026 +0700 feat: Hedge Fund Ultimate Pro v5.1 - Dual Engine EA (Swing + Scalp) with adaptive SL/TP and 7-layer anti-MC protection diff --git a/README.md b/README.md new file mode 100644 index 0000000..3518755 --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# Hedge Fund Ultimate Pro v5.1 — Dual Engine EA (MT5) + +> Expert Advisor MetaTrader 5 dengan dual engine: **Swing Trend-Follow** + **Scalp Mean-Reversion**, dilengkapi 7 lapis proteksi anti-Margin Call dan SL/TP fully adaptive berbasis ATR. + +--- + +## Daftar Isi + +- [Fitur Utama](#fitur-utama) +- [Persyaratan](#persyaratan) +- [Cara Install](#cara-install) +- [Cara Menggunakan](#cara-menggunakan) +- [Penjelasan Parameter Input](#penjelasan-parameter-input) +- [Arsitektur & Penjelasan Kode](#arsitektur--penjelasan-kode) +- [Sistem Proteksi Anti-MC](#sistem-proteksi-anti-mc) +- [Cara Backtest](#cara-backtest) +- [Disclaimer](#disclaimer) + +--- + +## Fitur Utama + +| Fitur | Keterangan | +|-------|------------| +| **Dual Engine** | Swing (trend-following) + Scalp (mean-reversion) berjalan bersamaan | +| **Full Adaptive SL/TP** | Semua SL, TP, Breakeven, dan Trailing berbasis ATR — otomatis menyesuaikan volatilitas | +| **Dynamic TP Extension** | TP swing otomatis diperpanjang +1 ATR saat harga mendekati target; TP scalp bergeser ke BB mid | +| **7 Lapis Anti-MC** | Daily loss limit, equity circuit breaker, margin check, total position cap, dynamic risk reduction, cooldown, weekend close | +| **Progressive Trailing** | Trailing stop makin ketat seiring profit bertambah | +| **Partial Close** | Tutup 50% posisi saat R:R 1:1, sisanya biarkan jalan | +| **Auto-Detect Filling** | Otomatis mendeteksi mode filling broker (FOK/IOC/RETURN) | +| **News Filter** | Menghindari entry 30 menit sebelum dan 15 menit sesudah jam news | +| **Multi-Instrument** | Bisa dipakai di pair/instrument apapun tanpa ubah parameter (karena semua ATR-based) | + +--- + +## Persyaratan + +- **Platform**: MetaTrader 5 (MT5) +- **Bahasa**: MQL5 +- **Library**: `Trade/Trade.mqh` (sudah bawaan MT5) +- **Timeframe Disarankan**: M15 atau M30 (EA akan otomatis ambil HTF H1 untuk trend filter) +- **Akun**: Hedge atau Netting (disarankan akun Hedge) +- **VPS**: Disarankan agar EA berjalan 24/5 tanpa putus + +--- + +## Cara Install + +1. **Copy file** `hedge.mq5` ke folder: + ``` + [MT5 Data Folder]\MQL5\Experts\ + ``` + > Untuk menemukan Data Folder: di MT5, klik **File → Open Data Folder** + +2. **Compile**: Buka MetaEditor, buka file `hedge.mq5`, tekan **F7** (Compile). Pastikan **0 errors**. + +3. **Attach ke chart**: + - Buka chart pair yang diinginkan (misal EURUSD, XAUUSD) + - Pilih timeframe M15 atau M30 + - Drag EA dari Navigator → Expert Advisors ke chart + - Centang **Allow Algo Trading** di tab Common + - Atur parameter sesuai kebutuhan di tab Inputs + - Klik **OK** + +4. **Pastikan AutoTrading aktif** (tombol AutoTrading di toolbar MT5 harus hijau/aktif) + +--- + +## Cara Menggunakan + +### Penggunaan Standar (Default) +Parameter default sudah dioptimasi untuk pair forex major. Tinggal attach ke chart dan biarkan berjalan. + +### Untuk Gold (XAUUSD) +Karena semua SL/TP berbasis ATR, EA otomatis menyesuaikan. Anda bisa menurunkan `MaxSpread` ke 50 dan `Scalp_MaxSpread` ke 30 untuk Gold. + +### Scalp Only +Set `MaxSwingPos = 0` untuk menonaktifkan swing, hanya scalp yang berjalan. + +### Swing Only +Set `EnableScalp = false` untuk menonaktifkan scalp. + +--- + +## Penjelasan Parameter Input + +### Risk Management + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `RiskPercent` | 0.75% | Risiko per trade swing terhadap balance | +| `ScalpRiskPercent` | 0.3% | Risiko per trade scalp (lebih kecil karena frekuensi lebih tinggi) | +| `MaxDailyLossP` | 3.0% | Jika total loss hari ini melebihi 3% balance, EA berhenti trading & tutup semua posisi | +| `MaxDrawdownP` | 8.0% | Circuit breaker: jika equity turun 8% dari peak, tutup semua posisi | +| `MaxSwingPos` | 2 | Maksimal posisi swing terbuka bersamaan | +| `MaxScalpPos` | 3 | Maksimal posisi scalp terbuka bersamaan | +| `MagicSwing` | 778899 | Magic number untuk identifikasi posisi swing | +| `MagicScalp` | 778900 | Magic number untuk identifikasi posisi scalp | +| `CooldownBars` | 3 | Jumlah bar menunggu setelah loss sebelum entry lagi | +| `MaxConsecLoss` | 3 | Setelah 3 loss berturut-turut, risk dipotong 50% | +| `MaxTotalPos` | 4 | Maksimal TOTAL posisi (swing + scalp gabungan) | +| `DailyProfitTarget` | 2.0% | Berhenti trading jika profit hari ini sudah >= 2% balance (0 = nonaktif) | +| `MinMarginLevel` | 200% | Tidak entry baru jika margin level di bawah 200% | + +### Swing: Trend Filter + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `EMA_Fast` | 50 | EMA cepat untuk deteksi trend | +| `EMA_Slow` | 200 | EMA lambat untuk deteksi trend | +| `HTF` | H1 | Timeframe untuk analisis trend (Higher Time Frame) | +| `ADX_Period` | 14 | Period ADX untuk kekuatan trend | +| `ADX_Min` | 20.0 | Minimum ADX untuk konfirmasi trend kuat | + +### Swing: Entry + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `RSI_Period` | 14 | Period RSI untuk filter overbought/oversold | +| `RSI_OB` | 70.0 | Level RSI overbought (tidak buy di atas ini) | +| `RSI_OS` | 30.0 | Level RSI oversold (tidak sell di bawah ini) | +| `ATR_SL_Multi` | 1.5 | SL ditempatkan di low candle - (ATR × 1.5) | +| `Swing_RR` | 2.5 | Risk:Reward ratio swing (TP = SL distance × 2.5) | +| `ATR_Period` | 14 | Period ATR | + +### Swing: Management + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `UsePartialClose` | true | Aktifkan partial close (tutup sebagian posisi) | +| `PartialCloseRR` | 1.0 | Partial close saat profit = 1× SL distance | +| `PartialPercent` | 50% | Tutup 50% lot saat partial close | +| `BE_ATR_Multi` | 1.0 | Pindahkan SL ke breakeven saat profit = 1× ATR | +| `Trail_ATR_Multi` | 1.5 | Mulai trailing saat profit = 1.5× ATR | +| `Trail_Step_ATR` | 0.8 | Jarak trailing = 0.8× ATR (makin ketat seiring profit naik) | +| `CloseOnOpposite` | true | Tutup posisi berlawanan saat sinyal baru muncul | + +### Scalp: Settings + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `EnableScalp` | true | Aktifkan engine scalp | +| `BB_Period` | 20 | Period Bollinger Band | +| `BB_Dev` | 2.0 | Deviasi Bollinger Band | +| `Scalp_RSI_Period` | 7 | Period RSI cepat untuk scalp | +| `Scalp_RSI_OB` | 75.0 | RSI overbought trigger scalp | +| `Scalp_RSI_OS` | 25.0 | RSI oversold trigger scalp | +| `Scalp_RR` | 1.2 | Risk:Reward ratio scalp | +| `Scalp_SL_ATR` | 0.8 | SL scalp = ATR × 0.8 | +| `Scalp_MaxSpread` | 15 | Max spread untuk scalp (lebih ketat dari swing) | +| `Scalp_BE_ATR` | 0.3 | Breakeven trigger scalp = ATR × 0.3 (adaptive) | +| `Scalp_Trail_ATR` | 0.15 | Trail distance scalp = ATR × 0.15 (adaptive) | + +### Filters + +| Parameter | Default | Penjelasan | +|-----------|---------|------------| +| `MaxSpread` | 30 | Spread maksimal untuk swing entry (dalam points) | +| `UseSessionFilter` | true | Aktifkan filter jam trading | +| `SessionStartHour` | 2 | Jam mulai trading swing (London open) | +| `SessionEndHour` | 20 | Jam akhir trading swing (NY close) | +| `ScalpStartHour` | 7 | Jam mulai scalp (jam sibuk) | +| `ScalpEndHour` | 17 | Jam akhir scalp | +| `CloseBeforeWeekend` | true | Tutup semua posisi hari Jumat jam 20:00 | +| `AvoidHighImpactHour` | true | Hindari entry dekat jam news | +| `NewsHours` | "8,13,15" | Jam server rawan news (pisahkan dengan koma) | + +--- + +## Arsitektur & Penjelasan Kode + +### Alur Utama (OnTick) + +``` +Setiap Tick: + ├── ManageSwing() → Kelola posisi swing (partial close, BE, trailing, TP extension) + ├── ManageScalp() → Kelola posisi scalp (adaptive BE, trailing, TP adjust) + ├── EquitySafe() → Cek equity drawdown circuit breaker + ├── CheckWeekendClose() → Tutup semua jika Jumat >= 20:00 + │ + └── [Hanya saat Bar Baru]: + ├── DailySafe() → Cek daily loss (closed + floating) + ├── CooldownOK() → Cek cooldown setelah loss + ├── DailyTargetReached() → Cek apakah sudah cukup profit + ├── MarginOK() → Cek margin level + ├── LoadIndicators() → Load semua data indikator + │ + ├── ENGINE 1: SWING + │ ├── SwingBuySignal() → EMA trend + ADX + DI + RSI + Price Action + │ └── SwingSellSignal() → Kebalikan dari buy + │ + └── ENGINE 2: SCALP + ├── ScalpBuySignal() → BB lower bounce + RSI(7) oversold + └── ScalpSellSignal() → BB upper bounce + RSI(7) overbought +``` + +### Penjelasan Fungsi-Fungsi Utama + +#### `DetectFilling()` +Mendeteksi mode filling yang didukung broker secara otomatis. Mencegah error order karena salah filling mode. + +#### `OnInit()` +Inisialisasi semua handle indikator (EMA, ADX, RSI, ATR, Bollinger Band), restore peak equity dari GlobalVariable (persistent antar restart), hitung consecutive losses dari history. + +#### `SwingBuySignal()` / `SwingSellSignal()` +Sinyal entry swing dengan 6 filter: +1. **Trend**: EMA 50 > EMA 200 (buy) atau < (sell) di timeframe H1 +2. **Kekuatan Trend**: ADX >= 20 +3. **Directional**: +DI > -DI (buy) atau sebaliknya +4. **RSI Zone**: RSI 40-70 (buy), 30-60 (sell) — hindari overbought/oversold +5. **Momentum Candle**: Body > 40% ATR, wick ratio > 50% +6. **Pattern**: Breakout high sebelumnya ATAU engulfing pattern + +#### `ScalpBuySignal()` / `ScalpSellSignal()` +Sinyal entry scalp dengan 6 filter: +1. Candle menyentuh BB lower/upper +2. Close kembali di dalam BB (bounce, bukan tembus) +3. RSI(7) di area oversold (<25) / overbought (>75) +4. Candle bullish/bearish konfirmasi +5. Masih ada ruang ke BB middle +6. Tidak melawan trend kuat (ADX > 30) + +#### `LotCalc()` +Menghitung lot berdasarkan risiko: +``` +Lot = (Balance × Risk%) / (SL_points × tick_value / tick_size × Point) +``` +Dibulatkan ke `SYMBOL_VOLUME_STEP` broker, diklem antara `VOLUME_MIN` dan `VOLUME_MAX`. + +#### `ManageSwing()` +Manajemen posisi swing yang berjalan setiap tick: +- **Partial Close**: Tutup 50% lot saat profit = 1× SL distance (tracking via GlobalVariable agar persistent) +- **Breakeven**: Geser SL ke entry + 20% ATR saat profit >= 1× ATR +- **Progressive Trailing**: Trail semakin ketat (dari 100% ke 40% base distance) seiring profit bertambah +- **Dynamic TP Extension**: Saat harga mencapai 80% jarak ke TP, TP diperpanjang +1 ATR + +#### `ManageScalp()` +Manajemen posisi scalp (semua ATR-adaptive): +- **Adaptive Breakeven**: Trigger di ATR × 0.3, lock profit 5% ATR +- **Adaptive Trailing**: Trail distance ATR × 0.15, mulai setelah 1.5× BE trigger +- **Dynamic TP**: Geser TP ke BB mid jika lebih menguntungkan + +#### `OnTrade()` +Event handler yang melacak win/loss secara real-time: +- Menghitung consecutive losses untuk dynamic risk reduction +- Reset counter otomatis saat ganti hari +- Membersihkan GlobalVariable partial close saat posisi tertutup + +--- + +## Sistem Proteksi Anti-MC + +EA ini memiliki **7 lapis proteksi** yang bekerja berlapis-lapis: + +| Lapis | Fungsi | Cara Kerja | +|-------|--------|------------| +| 1 | `DailySafe()` | Stop trading + close all jika loss hari ini > 3% balance (closed + floating) | +| 2 | `EquitySafe()` | Circuit breaker: close all jika equity turun 8% dari peak (persistent antar restart) | +| 3 | `MarginOK()` | Tidak entry baru jika margin level < 200% | +| 4 | `MaxTotalPos` | Batasi total posisi swing + scalp gabungan (max 4) | +| 5 | `GetEffectiveRisk()` | Potong risk 50% setelah 3 loss berturut-turut | +| 6 | `CooldownOK()` | Tidak entry selama 3 bar setelah loss terakhir | +| 7 | `CheckWeekendClose()` | Close all posisi Jumat sore, hindari gap weekend | + +Ditambah proteksi entry: +- **Spread filter** — tidak entry saat spread terlalu lebar +- **Session filter** — hanya trading di jam aktif +- **News filter** — skip entry 30 menit sebelum & 15 menit sesudah jam news +- **ValidateSLTP()** — validasi SL/TP terhadap `STOPS_LEVEL` dan `FREEZE_LEVEL` broker +- **DailyProfitTarget** — berhenti trading saat sudah profit 2% hari ini + +--- + +## Cara Backtest + +1. Buka **Strategy Tester** di MT5 (Ctrl+R) +2. Pilih Expert: `hedge` +3. Pilih Symbol & Timeframe (M15/M30) +4. Period: minimal **2 tahun** data +5. Modelling: **Every tick based on real ticks** (paling akurat) +6. Deposit: sesuaikan dengan modal rencana +7. Klik **Start** + +### Tips Backtest +- Gunakan **Every tick based on real ticks** untuk hasil paling realistis +- Test di beberapa pair berbeda untuk melihat universalitas +- Perhatikan **Max Drawdown %** dan **Profit Factor** +- Target: Profit Factor > 1.5, Max DD < 15% + +--- + +## Disclaimer + +> **PERINGATAN**: Trading forex/CFD memiliki risiko tinggi. EA ini **TIDAK menjamin profit**. Past performance (backtest) TIDAK menjamin hasil di masa depan. Selalu test di **akun demo** terlebih dahulu. Gunakan modal yang siap Anda risikokan. Pembuat tidak bertanggung jawab atas kerugian yang terjadi. + +--- + +## Lisensi + +Kode ini dibagikan untuk tujuan edukasi. Gunakan dengan risiko sendiri. diff --git a/hedge.mq5 b/hedge.mq5 new file mode 100644 index 0000000..1ae30fd --- /dev/null +++ b/hedge.mq5 @@ -0,0 +1,1183 @@ +//+------------------------------------------------------------------+ +//| 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 + +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(); + + 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; + datetime dayStart = iTime(_Symbol, PERIOD_D1, 0); + HistorySelect(dayStart, 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, 3, atr_val) < 3) 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; +} + +//============================================================ +// 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((price - sl) < minDist || (tp - price) < minDist) return false; + } + else + { + if((sl - price) < minDist || (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 && (ask - sl) < freezeDist) return false; + if(direction == -1 && (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; } + +//============================================================ +// 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; + + if(rsi_val[0] >= RSI_OB || rsi_val[0] < 40.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); + return (breakout || engulfing); +} + +//============================================================ +// 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; + + if(rsi_val[0] <= RSI_OS || rsi_val[0] > 60.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); + return (breakout || engulfing); +} + +//============================================================ +// 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); + if(nSL > sl && 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); + if((nSL < sl || 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 jika lebih jauh dari TP awal + double newTP = tp; + if(type==POSITION_TYPE_BUY && bbMid[0] > tp && bbMid[0] > price) + newTP = NormalizePrice(bbMid[0]); + else if(type==POSITION_TYPE_SELL && bbMid[0] < tp && bbMid[0] < price) + newTP = NormalizePrice(bbMid[0]); + + if(type==POSITION_TYPE_BUY) + { + double nSL = NormalizePrice(price - trailDist); + if(nSL > sl && nSL > openPrice) + { + trade.SetExpertMagicNumber(MagicScalp); + trade.PositionModify(ticket, nSL, newTP); + } + else if(newTP != tp && newTP > tp) + { + trade.SetExpertMagicNumber(MagicScalp); + trade.PositionModify(ticket, sl, newTP); + } + } + else + { + double nSL = NormalizePrice(price + trailDist); + if((nSL < sl || sl == 0) && nSL < openPrice) + { + trade.SetExpertMagicNumber(MagicScalp); + trade.PositionModify(ticket, nSL, newTP); + } + else if(newTP != tp && newTP < tp) + { + trade.SetExpertMagicNumber(MagicScalp); + trade.PositionModify(ticket, sl, newTP); + } + } + } + } +} + +//============================================================ +// ON TRADE EVENT +//============================================================ +void OnTrade() +{ + static int lastDealsTotal = 0; + 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 * 30) return; // ATR terlalu kecil + + 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)); + } + } + } + } +} \ No newline at end of file