From 4cb86ecd5f6a07beef3dd002c7c985be42f2e2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Ph=C3=BA=20Nguy=E1=BB=85n=20H=C6=B0ng?= <87779412+hungpixi@users.noreply.github.com> Date: Tue, 17 Mar 2026 04:16:24 +0700 Subject: [PATCH] feat: MQL5 MultiSignal DCA CCBSN EA - 9 signal modes + adaptive DCA + CCBSN partial close - 9 indicator signal modes: Ichimoku, EMA Cross, RSI, BB Bounce, Stoch, CCI, MACD, Supertrend, Momentum - DCA multi-tier with adaptive distance and lot multiplier - CCBSN (partial close 50% -> breakeven -> trailing) - Sniper (trim oldest losing orders) - Anti-Detect for Prop Firm compliance - Python ML optimizer for DCA parameters - Wave strategy brute-force optimizer (2240+ combos) - 85+ optimizable inputs for MT5 Strategy Tester - Auto-detect filling type (Exness compatibility) - Retry logic for order closing Built by @hungpixi | Comarai.com --- .gitignore | 18 + IchiDCA_CCBSN_PropFirm_Fixed.mq5 | 1837 ++++++++++++++++++++++++++++++ README.md | 162 +++ dca_ml_optimizer.py | 877 ++++++++++++++ wave_strategy_optimizer.py | 799 +++++++++++++ 5 files changed, 3693 insertions(+) create mode 100644 .gitignore create mode 100644 IchiDCA_CCBSN_PropFirm_Fixed.mq5 create mode 100644 README.md create mode 100644 dca_ml_optimizer.py create mode 100644 wave_strategy_optimizer.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f81231c --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Sensitive files +context.md +*.set +*.csv +ml_params.mqh +.env* + +# Compiled +*.ex5 +*.log + +# IDE +.vscode/ +.idea/ + +# Python +__pycache__/ +*.pyc diff --git a/IchiDCA_CCBSN_PropFirm_Fixed.mq5 b/IchiDCA_CCBSN_PropFirm_Fixed.mq5 new file mode 100644 index 0000000..a08937b --- /dev/null +++ b/IchiDCA_CCBSN_PropFirm_Fixed.mq5 @@ -0,0 +1,1837 @@ +//+------------------------------------------------------------------+ +//| IchiDCA_CCBSN_PropFirm.mq5 | +//| DCA Ichimoku Cloud Break - CCBSN - Prop Firm | +//| Version 3.0 - Multi-Indicator + DCA + CCBSN - Partial Close + Trailing | +//+------------------------------------------------------------------+ +#property copyright "IchiDCA CCBSN MultiSignal v3.0" +#property link "" +#property version "2.00" +#property strict + +//+------------------------------------------------------------------+ +//| INCLUDES | +//+------------------------------------------------------------------+ +#include +#include + +//+------------------------------------------------------------------+ +//| ENUMS | +//+------------------------------------------------------------------+ +enum ENUM_TRADE_DIR +{ + DIR_BOTH = 0, // Buy & Sell + DIR_BUY = 1, // Chỉ Buy + DIR_SELL = 2 // Chỉ Sell +}; + +enum ENUM_INDI_MODE +{ + INDI_ICHIMOKU = 0, // Ichimoku Cloud Break + INDI_EMA_CROSS = 1, // EMA Crossover + INDI_RSI = 2, // RSI OB/OS + INDI_BB = 3, // Bollinger Band Bounce + INDI_STOCH = 4, // Stochastic Cross + INDI_CCI = 5, // CCI OB/OS + INDI_MACD_CROSS = 6, // MACD Histogram Cross + INDI_SUPERTREND = 7, // Supertrend + INDI_MOMENTUM = 8 // Momentum +}; + + +//+------------------------------------------------------------------+ +//| INPUT PARAMETERS | +//+------------------------------------------------------------------+ +// === Cơ bản === +input int InpMagicID = 9196; // Magic Number +input double InpLots = 0.01; // Lot Size +input ENUM_TRADE_DIR InpTradeDir = DIR_BOTH; // Hướng trade +input ENUM_INDI_MODE InpIndiMode = INDI_ICHIMOKU; // Kieu tin hieu +input ENUM_TIMEFRAMES InpTFSignal = PERIOD_M5; // TF cho signal + +// === RSI Signal === +input int InpRSIPeriod = 14; // RSI Period +input ENUM_APPLIED_PRICE InpRSIPrice = PRICE_CLOSE; // RSI Applied Price +input double InpRSIOB = 75.0; // RSI Overbought +input double InpRSIOS = 25.0; // RSI Oversold + +// === BB Signal === +input int InpBBPeriod = 20; // BB Period +input double InpBBDeviation = 2.0; // BB Deviation + +// === CCI Signal === +input int InpCCIPeriod = 14; // CCI Period +input ENUM_APPLIED_PRICE InpCCIPrice = PRICE_CLOSE; // CCI Applied Price +input double InpCCIOB = 100.0; // CCI Overbought +input double InpCCIOS = -100.0; // CCI Oversold + +// === Stochastic Signal === +input int InpStochK = 5; // Stoch %K Period +input int InpStochD = 3; // Stoch %D Period +input int InpStochSlowing = 3; // Stoch Slowing +input double InpStochOB = 80.0; // Stoch Overbought +input double InpStochOS = 20.0; // Stoch Oversold + +// === Momentum Signal === +input int InpMomentumPeriod = 14; // Momentum Period +input ENUM_APPLIED_PRICE InpMomentumPrice = PRICE_CLOSE; // Momentum Price +input double InpMomentumOB = 100.45; // Momentum Overbought +input double InpMomentumOS = 99.45; // Momentum Oversold + +// === Supertrend === +input int InpSTperiod = 21; // Supertrend Period +input double InpSTmultiplier = 3.0; // Supertrend Multiplier + +// === EMA Signal Cross === +input int InpSigEMAFast = 9; // Signal EMA Fast +input int InpSigEMASlow = 21; // Signal EMA Slow + +// === Ichimoku === +input int InpIchiTenkan = 9; // Ichimoku Tenkan-sen +input int InpIchiKijun = 26; // Ichimoku Kijun-sen +input int InpIchiSenkou = 52; // Ichimoku Senkou Span B + +// === EMA Trend Filter === +input bool InpUseEMAFilter = true; // Dùng EMA Filter +input ENUM_TIMEFRAMES InpTFEMAFilter = PERIOD_CURRENT; // Timeframe EMA Filter +input int InpEMAFast = 34; // EMA nhanh (xu hướng) +input int InpEMASlow = 89; // EMA chậm (xu hướng) + +// === MACD Trend Filter === +input bool InpUseMACDFilter = false; // Dùng MACD Filter +input ENUM_TIMEFRAMES InpTFMACDFilter = PERIOD_CURRENT; // Timeframe MACD Filter +input int InpFastEMAMACD = 30; // MACD Fast EMA +input int InpSlowEMAMACD = 50; // MACD Slow EMA +input int InpSMAMACD = 5; // MACD Signal SMA +input ENUM_APPLIED_PRICE InpAppliedPriceMACD = PRICE_WEIGHTED; // MACD Applied Price + +// === DCA === +input bool InpUseDCA = true; // Bật DCA +input double InpDCADistance = 10.0; // Khoảng cách DCA (pips) +input double InpDCADistMulti = 1.2; // Hệ số nhân khoảng cách DCA +input int InpMaxDCAOrders = 5; // Số lệnh DCA tối đa +input double InpDCALotMulti = 1.0; // Hệ số nhân lot DCA (1.0 = lot cố định) +input double InpDCATPPips = 50.0; // TP chuỗi DCA (pips) +input bool InpDCANeedSignal = true; // DCA phải cùng trend Ichimoku +input bool InpDCAOutTime = true; // Cho DCA ngoài giờ trade + +// === Sniper (Tỉa lệnh) === +input bool InpUseSniper = true; // Bật Sniper tỉa lệnh +input int InpOrders2StartSniper = 20; // Số lệnh kích hoạt Sniper +input int InpFirstOrdersSniper = 2; // Số lệnh đầu chuỗi để tỉa +input double InpPercentSniper = 10.0; // % profit tối thiểu để tỉa +input double InpTPSniper = 5.0; // TP pips sau khi tỉa + +// === Quản lý rủi ro (tất cả qua input, không hardcode) === +input double InpMaxSpread = 40.0; // Max Spread (pips) +input double InpMaxDrawdownMoney = 0.0; // Max DD tiền ($, 0=tắt) +input double InpMaxDrawdownPct = 0.0; // Max DD % (0=tắt) +input double InpDailyLossLimit = 0.0; // Giới hạn lỗ ngày ($, 0=tắt) +input double InpDailyProfitTarget = 0.0; // Mục tiêu lời ngày ($, 0=tắt) +input double InpTPPips = 10.0; // TP lệnh đơn (pips) +input double InpSLPips = 0.0; // SL lệnh đơn (pips, 0=không SL) + +// === Equity Trailing === +input bool InpUseEquityTrail = false; // Bật Equity Trailing +input double InpEquityTrailStart = 15.0; // Equity trail kích hoạt ($) +input double InpEquityTrailStep = 5.0; // Equity trail step ($) + +// === CCBSN: Chốt Cắt Bán Sớm Nửa + Gồng Trailing === +input bool InpUsePartialClose = true; // Bật chốt nửa (CCBSN) +input double InpPartialPercent = 50.0; // % lot chốt (50 = nửa) +input bool InpMoveSLToBE = true; // Move SL breakeven sau chốt +input double InpBEOffsetPips = 1.0; // Offset BE (pips, >0 = lock lời) + +// === Trailing cho phần còn lại === +input bool InpUseTrailing = true; // Bật Trailing phần còn lại +input double InpTrailStartPips = 10.0; // Pips profit kích hoạt trail +input double InpTrailStepPips = 5.0; // Trailing step (pips) +input bool InpUseATRTrail = false; // Dùng ATR thay pips cố định +input ENUM_TIMEFRAMES InpATRTimeframe = PERIOD_M15; // TF cho ATR +input int InpATRPeriod = 14; // ATR Period +input double InpATRMultiplier = 1.5; // ATR × multiplier = trail dist + +// === Anti-Detect (Prop Firm) === +input bool InpAntiDetect = true; // Bật Anti-Detect +input int InpMinDelaySeconds = 3; // Delay tối thiểu (giây) +input int InpMaxDelaySeconds = 15; // Delay tối đa (giây) +input int InpMaxOrdersPerDay = 10; // Max lệnh mở mới/ngày +input int InpDelayAfterClose = 60; // Delay sau khi đóng chuỗi (giây) + +// === Thời gian trade === +input bool InpUseTradingTime = false; // Giới hạn thời gian trade +input string InpStartTime = "08:00"; // Giờ bắt đầu +input string InpEndTime = "22:00"; // Giờ kết thúc +input bool InpCloseFriday = false; // Đóng hết vào thứ 6 +input int InpCloseFridayHour = 20; // Giờ đóng thứ 6 + +// === Rollover Filter === +input int InpFilterStartHour = 1; // Giờ bắt đầu trade (server) +input int InpFilterEndHour = 23; // Giờ kết thúc trade (server) + +//+------------------------------------------------------------------+ +//| GLOBAL VARIABLES | +//+------------------------------------------------------------------+ +CTrade g_trade; +CPositionInfo g_posInfo; + +// Indicator handles +int g_handleIchi = INVALID_HANDLE; +int g_handleRSI = INVALID_HANDLE; +int g_handleBB = INVALID_HANDLE; +int g_handleCCI = INVALID_HANDLE; +int g_handleStoch = INVALID_HANDLE; +int g_handleMom = INVALID_HANDLE; +int g_handleSigEF = INVALID_HANDLE; +int g_handleSigES = INVALID_HANDLE; +int g_handleSTatr = INVALID_HANDLE; +int g_handleEMAF = INVALID_HANDLE; +int g_handleEMAS = INVALID_HANDLE; +int g_handleMACD = INVALID_HANDLE; + +// State +datetime g_lastOrderTime = 0; +datetime g_lastCloseTime = 0; +int g_ordersOpenedToday = 0; +int g_lastDay = -1; +double g_dailyStartBalance = 0; +double g_maxEquity = 0; +datetime g_nextAllowedTime = 0; +datetime g_lastBarTime = 0; // New bar detection +int g_cachedSignal = 0; // Cache signal per bar + +// CCBSN State +int g_handleATR = INVALID_HANDLE; +bool g_buyPartialDone = false; // Đã chốt nửa Buy +bool g_sellPartialDone = false; // Đã chốt nửa Sell +double g_buyTrailSL = 0; // SL trailing Buy +double g_sellTrailSL = 0; // SL trailing Sell +double g_buyAvgAfterPartial = 0; // Giá avg sau partial close +double g_sellAvgAfterPartial = 0; // Giá avg sau partial close + +// Anti-detect: comment pool +string g_comments[]; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Auto-detect filling type for broker | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_FILLING GetFillingType() +{ + long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); + if((fm & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK; + if((fm & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC; + return ORDER_FILLING_RETURN; +} + +int OnInit() +{ + // Trade setup + g_trade.SetExpertMagicNumber(InpMagicID); + g_trade.SetDeviationInPoints(10); + g_trade.SetTypeFilling(GetFillingType()); + + // Ichimoku + g_handleIchi = iIchimoku(_Symbol, PERIOD_M5, InpIchiTenkan, InpIchiKijun, InpIchiSenkou); + if(g_handleIchi == INVALID_HANDLE) + { + Print("FATAL: Failed to create Ichimoku indicator!"); + return INIT_FAILED; + } + + // Multi-indicator signal handles + ENUM_TIMEFRAMES sigTF = (InpTFSignal == PERIOD_CURRENT) ? PERIOD_M5 : InpTFSignal; + + if(InpIndiMode == INDI_RSI) + { g_handleRSI = iRSI(_Symbol, sigTF, InpRSIPeriod, InpRSIPrice); } + + if(InpIndiMode == INDI_BB) + { g_handleBB = iBands(_Symbol, sigTF, InpBBPeriod, 0, InpBBDeviation, PRICE_CLOSE); } + + if(InpIndiMode == INDI_CCI) + { g_handleCCI = iCCI(_Symbol, sigTF, InpCCIPeriod, InpCCIPrice); } + + if(InpIndiMode == INDI_STOCH) + { g_handleStoch = iStochastic(_Symbol, sigTF, InpStochK, InpStochD, InpStochSlowing, MODE_SMA, STO_LOWHIGH); } + + if(InpIndiMode == INDI_MOMENTUM) + { g_handleMom = iMomentum(_Symbol, sigTF, InpMomentumPeriod, InpMomentumPrice); } + + if(InpIndiMode == INDI_EMA_CROSS) + { + g_handleSigEF = iMA(_Symbol, sigTF, InpSigEMAFast, 0, MODE_EMA, PRICE_CLOSE); + g_handleSigES = iMA(_Symbol, sigTF, InpSigEMASlow, 0, MODE_EMA, PRICE_CLOSE); + } + + if(InpIndiMode == INDI_SUPERTREND) + { g_handleSTatr = iATR(_Symbol, sigTF, InpSTperiod); } + + if(InpIndiMode == INDI_MACD_CROSS) + { + // Reuse MACD handle from filter section if not already created + if(g_handleMACD == INVALID_HANDLE) + g_handleMACD = iMACD(_Symbol, sigTF, InpFastEMAMACD, InpSlowEMAMACD, InpSMAMACD, InpAppliedPriceMACD); + } + + // ATR for CCBSN trailing + if(InpUseATRTrail) + { + g_handleATR = iATR(_Symbol, InpATRTimeframe, InpATRPeriod); + if(g_handleATR == INVALID_HANDLE) + { + Print("WARNING: Failed to create ATR indicator! Using fixed trail."); + } + } + + // EMA Filter (dùng TF riêng) + if(InpUseEMAFilter) + { + ENUM_TIMEFRAMES emaTF = (InpTFEMAFilter == PERIOD_CURRENT) ? PERIOD_M5 : InpTFEMAFilter; + g_handleEMAF = iMA(_Symbol, emaTF, InpEMAFast, 0, MODE_EMA, PRICE_CLOSE); + g_handleEMAS = iMA(_Symbol, emaTF, InpEMASlow, 0, MODE_EMA, PRICE_CLOSE); + if(g_handleEMAF == INVALID_HANDLE || g_handleEMAS == INVALID_HANDLE) + { + Print("FATAL: Failed to create EMA indicators!"); + return INIT_FAILED; + } + } + + // MACD Filter (dùng TF riêng) + if(InpUseMACDFilter) + { + ENUM_TIMEFRAMES macdTF = (InpTFMACDFilter == PERIOD_CURRENT) ? PERIOD_M5 : InpTFMACDFilter; + g_handleMACD = iMACD(_Symbol, macdTF, InpFastEMAMACD, InpSlowEMAMACD, InpSMAMACD, InpAppliedPriceMACD); + if(g_handleMACD == INVALID_HANDLE) + { + Print("FATAL: Failed to create MACD indicator!"); + return INIT_FAILED; + } + } + + // Init comment pool for anti-detect + InitCommentPool(); + + // Reset daily counters + g_dailyStartBalance = AccountInfoDouble(ACCOUNT_BALANCE); + + MqlDateTime dt; + TimeCurrent(dt); + g_lastDay = dt.day; + g_ordersOpenedToday = 0; + + Print("==========================================="); + Print("IchiDCA CCBSN MultiSignal v3.0 initialized"); + Print("Symbol: ", _Symbol, " | Magic: ", InpMagicID); + Print("Lots: ", InpLots, " | DCA Max: ", InpMaxDCAOrders); + Print("CCBSN: ", InpUsePartialClose ? "ON" : "OFF", " | Partial: ", InpPartialPercent, "%"); + Print("Trailing: ", InpUseTrailing ? "ON" : "OFF", " | ATR: ", InpUseATRTrail ? "ON" : "OFF"); + Print("Anti-Detect: ", InpAntiDetect ? "ON" : "OFF"); + Print("==========================================="); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(g_handleIchi != INVALID_HANDLE) IndicatorRelease(g_handleIchi); + if(g_handleEMAF != INVALID_HANDLE) IndicatorRelease(g_handleEMAF); + if(g_handleEMAS != INVALID_HANDLE) IndicatorRelease(g_handleEMAS); + if(g_handleMACD != INVALID_HANDLE) IndicatorRelease(g_handleMACD); + if(g_handleATR != INVALID_HANDLE) IndicatorRelease(g_handleATR); + if(g_handleRSI != INVALID_HANDLE) IndicatorRelease(g_handleRSI); + if(g_handleBB != INVALID_HANDLE) IndicatorRelease(g_handleBB); + if(g_handleCCI != INVALID_HANDLE) IndicatorRelease(g_handleCCI); + if(g_handleStoch != INVALID_HANDLE) IndicatorRelease(g_handleStoch); + if(g_handleMom != INVALID_HANDLE) IndicatorRelease(g_handleMom); + if(g_handleSigEF != INVALID_HANDLE) IndicatorRelease(g_handleSigEF); + if(g_handleSigES != INVALID_HANDLE) IndicatorRelease(g_handleSigES); + if(g_handleSTatr != INVALID_HANDLE) IndicatorRelease(g_handleSTatr); + + Print("IchiDCA CCBSN PropFirm EA deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Initialize random comment pool | +//+------------------------------------------------------------------+ +void InitCommentPool() +{ + ArrayResize(g_comments, 12); + g_comments[0] = "manual trade"; + g_comments[1] = "scalp entry"; + g_comments[2] = "trend follow"; + g_comments[3] = "breakout"; + g_comments[4] = "pullback"; + g_comments[5] = "retest"; + g_comments[6] = "momentum"; + g_comments[7] = "swing"; + g_comments[8] = "position"; + g_comments[9] = "dip buy"; + g_comments[10] = "rally sell"; + g_comments[11] = "range trade"; +} + +//+------------------------------------------------------------------+ +//| Get random comment (anti-detect) | +//+------------------------------------------------------------------+ +string GetRandomComment() +{ + if(!InpAntiDetect) return "IchiDCA"; + int idx = MathRand() % ArraySize(g_comments); + return g_comments[idx]; +} + +//+------------------------------------------------------------------+ +//| Get point value adjusted for broker digits | +//| Forex 4-digit: 1 pip = 0.0001 (_Point) | +//| Forex 5-digit: 1 pip = 0.0001 (_Point*10) | +//| XAUUSD 2-digit: 1 pip = 0.1 (_Point*10) | +//| XAUUSD 3-digit: 1 pip = 0.1 (_Point*100) | +//+------------------------------------------------------------------+ +double GetPipPoint() +{ + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // Detect metals/gold (typically 2 or 3 digits) + if(digits <= 3) + { + // XAUUSD, XAGUSD, etc. - 1 pip = 0.1 + if(digits == 2) return 0.1; // 2850.00 -> pip = 0.1 + if(digits == 3) return 0.1; // 2850.000 -> pip = 0.1 + if(digits == 1) return 1.0; // JPY pairs or similar + } + + // Forex pairs (4 or 5 digits) + if(digits == 5) return _Point * 10; // 1.12345 -> pip = 0.0001 + if(digits == 4) return _Point; // 1.1234 -> pip = 0.0001 + + return _Point; +} + +//+------------------------------------------------------------------+ +//| Get current spread in pips | +//+------------------------------------------------------------------+ +double GetSpreadPips() +{ + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + return (ask - bid) / GetPipPoint(); +} + +//+------------------------------------------------------------------+ +//| Normalize lot size | +//+------------------------------------------------------------------+ +double NormLots(double lots) +{ + double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + + lots = MathMax(lots, minLot); + lots = MathMin(lots, maxLot); + lots = MathRound(lots / step) * step; + return NormalizeDouble(lots, 2); +} + +//+------------------------------------------------------------------+ +//| Count orders by type (our magic only) | +//+------------------------------------------------------------------+ +int CountOrders(ENUM_POSITION_TYPE type) +{ + int count = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() == type) count++; + } + } + return count; +} + +//+------------------------------------------------------------------+ +//| Count all our orders | +//+------------------------------------------------------------------+ +int CountAllOrders() +{ + int count = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + count++; + } + } + return count; +} + +//+------------------------------------------------------------------+ +//| Get floating profit of our orders | +//+------------------------------------------------------------------+ +double GetFloatingProfit(ENUM_POSITION_TYPE type = -1) +{ + double total = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(type != -1 && g_posInfo.PositionType() != type) continue; + total += g_posInfo.Profit() + g_posInfo.Swap() + g_posInfo.Commission(); + } + } + return total; +} + +//+------------------------------------------------------------------+ +//| Get average entry price of orders | +//+------------------------------------------------------------------+ +double GetAveragePrice(ENUM_POSITION_TYPE type) +{ + double totalLots = 0; + double totalValue = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + + totalLots += g_posInfo.Volume(); + totalValue += g_posInfo.PriceOpen() * g_posInfo.Volume(); + } + } + + if(totalLots > 0) return totalValue / totalLots; + return 0; +} + +//+------------------------------------------------------------------+ +//| Get last entry price | +//+------------------------------------------------------------------+ +double GetLastEntryPrice(ENUM_POSITION_TYPE type) +{ + datetime lastTime = 0; + double lastPrice = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + + if(g_posInfo.Time() > lastTime) + { + lastTime = g_posInfo.Time(); + lastPrice = g_posInfo.PriceOpen(); + } + } + } + return lastPrice; +} + +//+------------------------------------------------------------------+ +//| Close all orders of a type | +//+------------------------------------------------------------------+ +void CloseAllByType(ENUM_POSITION_TYPE type) +{ + for(int retry = 0; retry < 3; retry++) + { + int remaining = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + if(!g_trade.PositionClose(g_posInfo.Ticket())) remaining++; + } + if(remaining == 0) break; + Sleep(500); + } + g_lastCloseTime = TimeCurrent(); +} + +//+------------------------------------------------------------------+ +//| Close ALL orders | +//+------------------------------------------------------------------+ +void CloseAllOrders() +{ + for(int retry = 0; retry < 3; retry++) + { + int remaining = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(!g_trade.PositionClose(g_posInfo.Ticket())) remaining++; + } + if(remaining == 0) break; + Sleep(500); + } + g_lastCloseTime = TimeCurrent(); +} + +//+------------------------------------------------------------------+ +//| CCBSN: Get ATR-based trail distance in price | +//+------------------------------------------------------------------+ +double GetATRTrailDistance() +{ + if(!InpUseATRTrail || g_handleATR == INVALID_HANDLE) + return InpTrailStepPips * GetPipPoint(); // fallback to fixed + + double atr[]; + ArraySetAsSeries(atr, true); + if(CopyBuffer(g_handleATR, 0, 0, 2, atr) < 2) + return InpTrailStepPips * GetPipPoint(); // fallback + + return atr[1] * InpATRMultiplier; +} + +//+------------------------------------------------------------------+ +//| CCBSN: Partial close X% lot of each position in a chain | +//+------------------------------------------------------------------+ +void PartialCloseByType(ENUM_POSITION_TYPE type) +{ + double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + int closedCount = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + + double currentLots = g_posInfo.Volume(); + double closeLots = currentLots * InpPartialPercent / 100.0; + + // Round to lot step + closeLots = MathFloor(closeLots / lotStep) * lotStep; + closeLots = NormalizeDouble(closeLots, 2); + + // Ensure minimum lot remains after close + if(closeLots < minLot) + { + // Lot quá nhỏ, không thể partial close → skip + Print("CCBSN: Lot too small for partial close. Ticket=", g_posInfo.Ticket(), + " current=", currentLots, " closeLots=", closeLots); + continue; + } + + // Ensure remaining lot >= minLot + double remainLots = currentLots - closeLots; + if(remainLots < minLot) + { + // Adjust: close less so remaining >= minLot + closeLots = currentLots - minLot; + closeLots = MathFloor(closeLots / lotStep) * lotStep; + closeLots = NormalizeDouble(closeLots, 2); + if(closeLots < minLot) continue; // skip if not possible + } + + ulong ticket = g_posInfo.Ticket(); + if(g_trade.PositionClosePartial(ticket, closeLots)) + { + closedCount++; + Print(">>> CCBSN PARTIAL CLOSE: ticket=", ticket, + " closed=", closeLots, " remain=", NormalizeDouble(currentLots - closeLots, 2)); + } + else + { + Print("CCBSN PARTIAL CLOSE FAILED: ticket=", ticket, + " error=", g_trade.ResultRetcode(), " - ", g_trade.ResultRetcodeDescription()); + } + } + + if(closedCount > 0) + { + // Mark partial close done + if(type == POSITION_TYPE_BUY) + { + g_buyPartialDone = true; + g_buyAvgAfterPartial = GetAveragePrice(POSITION_TYPE_BUY); + Print(">>> CCBSN BUY: Partial close done. Avg after=", + DoubleToString(g_buyAvgAfterPartial, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); + } + else + { + g_sellPartialDone = true; + g_sellAvgAfterPartial = GetAveragePrice(POSITION_TYPE_SELL); + Print(">>> CCBSN SELL: Partial close done. Avg after=", + DoubleToString(g_sellAvgAfterPartial, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); + } + + // Move SL to breakeven + if(InpMoveSLToBE) + MoveSLToBreakeven(type); + } +} + +//+------------------------------------------------------------------+ +//| CCBSN: Move SL to breakeven (avg price + offset) for remaining | +//+------------------------------------------------------------------+ +void MoveSLToBreakeven(ENUM_POSITION_TYPE type) +{ + double pip = GetPipPoint(); + double avgPrice = (type == POSITION_TYPE_BUY) ? g_buyAvgAfterPartial : g_sellAvgAfterPartial; + if(avgPrice <= 0) avgPrice = GetAveragePrice(type); + + double newSL; + if(type == POSITION_TYPE_BUY) + newSL = avgPrice + InpBEOffsetPips * pip; // SL trên avg = lock lời nhẹ + else + newSL = avgPrice - InpBEOffsetPips * pip; // SL dưới avg = lock lời nhẹ + + newSL = NormalizeDouble(newSL, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + + double currentSL = g_posInfo.StopLoss(); + double currentTP = g_posInfo.TakeProfit(); + + // Chỉ move SL nếu SL mới tốt hơn (gần giá hơn = bảo vệ tốt hơn) + bool shouldModify = false; + if(type == POSITION_TYPE_BUY) + shouldModify = (currentSL < newSL || currentSL == 0); + else + shouldModify = (currentSL > newSL || currentSL == 0); + + if(shouldModify) + { + if(g_trade.PositionModify(g_posInfo.Ticket(), newSL, 0)) // TP=0 để trailing quản lý + { + Print(">>> CCBSN BE: ticket=", g_posInfo.Ticket(), " SL=", DoubleToString(newSL, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); + } + else + { + Print("CCBSN BE FAILED: ticket=", g_posInfo.Ticket(), + " error=", g_trade.ResultRetcode(), " - ", g_trade.ResultRetcodeDescription()); + } + } + } + + // Initialize trail SL + if(type == POSITION_TYPE_BUY) + g_buyTrailSL = newSL; + else + g_sellTrailSL = newSL; +} + +//+------------------------------------------------------------------+ +//| CCBSN: Trailing Stop for remaining positions after partial close | +//+------------------------------------------------------------------+ +void ProcessTrailingStop() +{ + if(!InpUseTrailing) return; + + double pip = GetPipPoint(); + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + double trailDist = GetATRTrailDistance(); + double trailStartDist = InpTrailStartPips * pip; + + // === Trailing BUY === + if(g_buyPartialDone) + { + int buyCount = CountOrders(POSITION_TYPE_BUY); + if(buyCount == 0) + { + // Tất cả lệnh đã đóng (trailing SL hit hoặc manual) + ResetCCBSNState(POSITION_TYPE_BUY); + } + else + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double avgPrice = g_buyAvgAfterPartial; + double profitDist = bid - avgPrice; + + // Kích hoạt trailing khi profit đủ xa + if(profitDist >= trailStartDist) + { + double proposedSL = NormalizeDouble(bid - trailDist, digits); + + // Chỉ nâng SL, không hạ + if(proposedSL > g_buyTrailSL) + { + g_buyTrailSL = proposedSL; + + // Update SL cho tất cả lệnh Buy + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != POSITION_TYPE_BUY) continue; + + if(g_posInfo.StopLoss() < proposedSL || g_posInfo.StopLoss() == 0) + { + g_trade.PositionModify(g_posInfo.Ticket(), proposedSL, 0); + } + } + + Print(">>> CCBSN TRAIL BUY: SL=", DoubleToString(proposedSL, digits), + " bid=", DoubleToString(bid, digits), " dist=", DoubleToString(trailDist/pip, 1), "p"); + } + } + } + } + + // === Trailing SELL === + if(g_sellPartialDone) + { + int sellCount = CountOrders(POSITION_TYPE_SELL); + if(sellCount == 0) + { + ResetCCBSNState(POSITION_TYPE_SELL); + } + else + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double avgPrice = g_sellAvgAfterPartial; + double profitDist = avgPrice - ask; + + if(profitDist >= trailStartDist) + { + double proposedSL = NormalizeDouble(ask + trailDist, digits); + + // Chỉ hạ SL (cho Sell, SL thấp hơn = tốt hơn) + if(g_sellTrailSL == 0 || proposedSL < g_sellTrailSL) + { + g_sellTrailSL = proposedSL; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(!g_posInfo.SelectByIndex(i)) continue; + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != POSITION_TYPE_SELL) continue; + + if(g_posInfo.StopLoss() > proposedSL || g_posInfo.StopLoss() == 0) + { + g_trade.PositionModify(g_posInfo.Ticket(), proposedSL, 0); + } + } + + Print(">>> CCBSN TRAIL SELL: SL=", DoubleToString(proposedSL, digits), + " ask=", DoubleToString(ask, digits), " dist=", DoubleToString(trailDist/pip, 1), "p"); + } + } + } + } +} + +//+------------------------------------------------------------------+ +//| CCBSN: Reset state when all positions of a type are closed | +//+------------------------------------------------------------------+ +void ResetCCBSNState(ENUM_POSITION_TYPE type) +{ + if(type == POSITION_TYPE_BUY) + { + g_buyPartialDone = false; + g_buyTrailSL = 0; + g_buyAvgAfterPartial = 0; + Print(">>> CCBSN BUY: Chain closed. State reset."); + } + else + { + g_sellPartialDone = false; + g_sellTrailSL = 0; + g_sellAvgAfterPartial = 0; + Print(">>> CCBSN SELL: Chain closed. State reset."); + } + g_lastCloseTime = TimeCurrent(); +} + +//+------------------------------------------------------------------+ +//| Open order with anti-detect features | +//+------------------------------------------------------------------+ +bool PlaceOrder(ENUM_POSITION_TYPE type, double lots, double tpPips, double slPips, string comment) +{ + lots = NormLots(lots); + double pip = GetPipPoint(); + double price, tp = 0, sl = 0; + + if(type == POSITION_TYPE_BUY) + { + price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(tpPips > 0) tp = price + tpPips * pip; + if(slPips > 0) sl = price - slPips * pip; + + if(!g_trade.Buy(lots, _Symbol, price, sl, tp, comment)) + { + Print("BUY FAILED: ", g_trade.ResultRetcode(), " - ", g_trade.ResultRetcodeDescription()); + return false; + } + } + else + { + price = SymbolInfoDouble(_Symbol, SYMBOL_BID); + if(tpPips > 0) tp = price - tpPips * pip; + if(slPips > 0) sl = price + slPips * pip; + + if(!g_trade.Sell(lots, _Symbol, price, sl, tp, comment)) + { + Print("SELL FAILED: ", g_trade.ResultRetcode(), " - ", g_trade.ResultRetcodeDescription()); + return false; + } + } + + g_lastOrderTime = TimeCurrent(); + g_ordersOpenedToday++; + + Print((type == POSITION_TYPE_BUY ? "BUY" : "SELL"), + " opened: lots=", lots, " tp=", tpPips, "p sl=", slPips, "p"); + return true; +} + +//+------------------------------------------------------------------+ +//| RSI SIGNAL | +//+------------------------------------------------------------------+ +int GetRSISignal() +{ + if(g_handleRSI == INVALID_HANDLE) return 0; + double rsi[3]; ArraySetAsSeries(rsi, true); + if(CopyBuffer(g_handleRSI, 0, 0, 3, rsi) < 3) return 0; + if(rsi[2] < InpRSIOS && rsi[1] >= InpRSIOS) return +1; + if(rsi[2] > InpRSIOB && rsi[1] <= InpRSIOB) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| BB SIGNAL | +//+------------------------------------------------------------------+ +int GetBBSignal() +{ + if(g_handleBB == INVALID_HANDLE) return 0; + double bbU[3], bbL[3]; // 0=BASE, 1=UPPER, 2=LOWER + double lo[3], hi[3], cl[3], opn[3]; + ArraySetAsSeries(bbU, true); ArraySetAsSeries(bbL, true); + ArraySetAsSeries(lo, true); ArraySetAsSeries(hi, true); + ArraySetAsSeries(cl, true); ArraySetAsSeries(opn, true); + if(CopyBuffer(g_handleBB, 1, 0, 3, bbU) < 3) return 0; + if(CopyBuffer(g_handleBB, 2, 0, 3, bbL) < 3) return 0; + ENUM_TIMEFRAMES sigTF = (InpTFSignal == PERIOD_CURRENT) ? PERIOD_M5 : InpTFSignal; + if(CopyLow(_Symbol, sigTF, 0, 3, lo) < 3) return 0; + if(CopyHigh(_Symbol, sigTF, 0, 3, hi) < 3) return 0; + if(CopyClose(_Symbol, sigTF, 0, 3, cl) < 3) return 0; + if(CopyOpen(_Symbol, sigTF, 0, 3, opn) < 3) return 0; + if(lo[2] <= bbL[2] && cl[1] > bbL[1] && cl[1] > opn[1]) return +1; + if(hi[2] >= bbU[2] && cl[1] < bbU[1] && cl[1] < opn[1]) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| CCI SIGNAL | +//+------------------------------------------------------------------+ +int GetCCISignal() +{ + if(g_handleCCI == INVALID_HANDLE) return 0; + double cci[3]; ArraySetAsSeries(cci, true); + if(CopyBuffer(g_handleCCI, 0, 0, 3, cci) < 3) return 0; + if(cci[2] < InpCCIOS && cci[1] >= InpCCIOS) return +1; + if(cci[2] > InpCCIOB && cci[1] <= InpCCIOB) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| STOCHASTIC SIGNAL | +//+------------------------------------------------------------------+ +int GetStochSignal() +{ + if(g_handleStoch == INVALID_HANDLE) return 0; + double stK[3], stD[3]; + ArraySetAsSeries(stK, true); ArraySetAsSeries(stD, true); + if(CopyBuffer(g_handleStoch, 0, 0, 3, stK) < 3) return 0; + if(CopyBuffer(g_handleStoch, 1, 0, 3, stD) < 3) return 0; + if(stK[1] > stD[1] && stK[2] <= stD[2] && stK[1] < InpStochOS + 20) return +1; + if(stK[1] < stD[1] && stK[2] >= stD[2] && stK[1] > InpStochOB - 20) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| MOMENTUM SIGNAL | +//+------------------------------------------------------------------+ +int GetMomentumSignal() +{ + if(g_handleMom == INVALID_HANDLE) return 0; + double mom[3]; ArraySetAsSeries(mom, true); + if(CopyBuffer(g_handleMom, 0, 0, 3, mom) < 3) return 0; + if(mom[2] < InpMomentumOS && mom[1] >= InpMomentumOS) return +1; + if(mom[2] > InpMomentumOB && mom[1] <= InpMomentumOB) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| EMA CROSS SIGNAL | +//+------------------------------------------------------------------+ +int GetEMACrossSignal() +{ + if(g_handleSigEF == INVALID_HANDLE || g_handleSigES == INVALID_HANDLE) return 0; + double ef[3], es[3]; + ArraySetAsSeries(ef, true); ArraySetAsSeries(es, true); + if(CopyBuffer(g_handleSigEF, 0, 0, 3, ef) < 3) return 0; + if(CopyBuffer(g_handleSigES, 0, 0, 3, es) < 3) return 0; + if(ef[1] > es[1] && ef[2] <= es[2]) return +1; + if(ef[1] < es[1] && ef[2] >= es[2]) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| MACD CROSS SIGNAL (histogram cross zero) | +//+------------------------------------------------------------------+ +int GetMACDCrossSignal() +{ + if(g_handleMACD == INVALID_HANDLE) return 0; + double macdM[3], macdS[3]; + ArraySetAsSeries(macdM, true); ArraySetAsSeries(macdS, true); + if(CopyBuffer(g_handleMACD, 0, 0, 3, macdM) < 3) return 0; + if(CopyBuffer(g_handleMACD, 1, 0, 3, macdS) < 3) return 0; + double h1 = macdM[1] - macdS[1], h2 = macdM[2] - macdS[2]; + if(h1 > 0 && h2 <= 0) return +1; + if(h1 < 0 && h2 >= 0) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| SUPERTREND SIGNAL (ATR-based) | +//+------------------------------------------------------------------+ +int GetSupertrendSignal() +{ + if(g_handleSTatr == INVALID_HANDLE) return 0; + double atr[3]; ArraySetAsSeries(atr, true); + if(CopyBuffer(g_handleSTatr, 0, 0, 3, atr) < 3) return 0; + ENUM_TIMEFRAMES sigTF = (InpTFSignal == PERIOD_CURRENT) ? PERIOD_M5 : InpTFSignal; + double hi[3], lo[3], cl[3]; + ArraySetAsSeries(hi, true); ArraySetAsSeries(lo, true); ArraySetAsSeries(cl, true); + if(CopyHigh(_Symbol, sigTF, 0, 3, hi) < 3) return 0; + if(CopyLow(_Symbol, sigTF, 0, 3, lo) < 3) return 0; + if(CopyClose(_Symbol, sigTF, 0, 3, cl) < 3) return 0; + double mid2 = (hi[2]+lo[2])/2.0, mid1 = (hi[1]+lo[1])/2.0; + double upBand = mid1 - InpSTmultiplier * atr[1]; + double dnBand = mid1 + InpSTmultiplier * atr[1]; + // Simplified: price above upper = uptrend signal + if(cl[2] <= mid2 + InpSTmultiplier*atr[2] && cl[1] > upBand) return +1; + if(cl[2] >= mid2 - InpSTmultiplier*atr[2] && cl[1] < dnBand) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| MASTER SIGNAL DISPATCHER | +//+------------------------------------------------------------------+ +int GetMasterSignal() +{ + switch(InpIndiMode) + { + case INDI_ICHIMOKU: return GetIchimokuSignal(); + case INDI_EMA_CROSS: return GetEMACrossSignal(); + case INDI_RSI: return GetRSISignal(); + case INDI_BB: return GetBBSignal(); + case INDI_STOCH: return GetStochSignal(); + case INDI_CCI: return GetCCISignal(); + case INDI_MACD_CROSS: return GetMACDCrossSignal(); + case INDI_SUPERTREND: return GetSupertrendSignal(); + case INDI_MOMENTUM: return GetMomentumSignal(); + default: return GetIchimokuSignal(); + } +} + +//+------------------------------------------------------------------+ +//| ICHIMOKU SIGNAL: Cloud Break + Continuation Detection | +//+------------------------------------------------------------------+ +// Returns: +1 = BUY signal, -1 = SELL signal, 0 = no signal +int GetIchimokuSignal() +{ + // Ichimoku buffers: + // 0 = Tenkan-sen, 1 = Kijun-sen + // 2 = Senkou Span A, 3 = Senkou Span B + // 4 = Chikou Span + + double tenkan[], kijun[], spanA[], spanB[]; + ArraySetAsSeries(tenkan, true); + ArraySetAsSeries(kijun, true); + ArraySetAsSeries(spanA, true); + ArraySetAsSeries(spanB, true); + + if(CopyBuffer(g_handleIchi, 0, 0, 3, tenkan) < 3) return 0; + if(CopyBuffer(g_handleIchi, 1, 0, 3, kijun) < 3) return 0; + if(CopyBuffer(g_handleIchi, 2, 0, 3, spanA) < 3) return 0; + if(CopyBuffer(g_handleIchi, 3, 0, 3, spanB) < 3) return 0; + + double close[]; + ArraySetAsSeries(close, true); + if(CopyClose(_Symbol, PERIOD_M5, 0, 3, close) < 3) return 0; + + // Xác định mây (cloud) - mây trên và mây dưới + double cloudTop1 = MathMax(spanA[1], spanB[1]); + double cloudBot1 = MathMin(spanA[1], spanB[1]); + double cloudTop2 = MathMax(spanA[2], spanB[2]); + double cloudBot2 = MathMin(spanA[2], spanB[2]); + + // === BUY SIGNAL === + // Nến trước trong/dưới mây → nến hiện tại đóng TRÊN mây + // + Tenkan > Kijun (uptrend confirmation) + bool buyBreak = (close[2] <= cloudTop2) && (close[1] > cloudTop1); + bool buyTrend = (tenkan[1] > kijun[1]); + + if(buyBreak && buyTrend) return +1; + + // === SELL SIGNAL === + // Nến trước trong/trên mây → nến hiện tại đóng DƯỚI mây + // + Tenkan < Kijun (downtrend confirmation) + bool sellBreak = (close[2] >= cloudBot2) && (close[1] < cloudBot1); + bool sellTrend = (tenkan[1] < kijun[1]); + + if(sellBreak && sellTrend) return -1; + + return 0; +} + +//+------------------------------------------------------------------+ +//| Check Ichimoku trend (for DCA filter) | +//| Returns: +1 = uptrend, -1 = downtrend, 0 = no clear trend | +//+------------------------------------------------------------------+ +int GetIchimokuTrend() +{ + double spanA[], spanB[], close[]; + ArraySetAsSeries(spanA, true); + ArraySetAsSeries(spanB, true); + ArraySetAsSeries(close, true); + + if(CopyBuffer(g_handleIchi, 2, 0, 2, spanA) < 2) return 0; + if(CopyBuffer(g_handleIchi, 3, 0, 2, spanB) < 2) return 0; + if(CopyClose(_Symbol, PERIOD_M5, 0, 2, close) < 2) return 0; + + double cloudTop = MathMax(spanA[1], spanB[1]); + double cloudBot = MathMin(spanA[1], spanB[1]); + + if(close[1] > cloudTop) return +1; // Trên mây = uptrend + if(close[1] < cloudBot) return -1; // Dưới mây = downtrend + return 0; // Trong mây = sideway +} + +//+------------------------------------------------------------------+ +//| EMA TREND FILTER | +//| Returns: +1 = uptrend, -1 = downtrend, 0 = no filter/neutral | +//+------------------------------------------------------------------+ +int GetEMATrend() +{ + if(!InpUseEMAFilter) return 0; // Bypass + + double emaFast[], emaSlow[]; + ArraySetAsSeries(emaFast, true); + ArraySetAsSeries(emaSlow, true); + + if(CopyBuffer(g_handleEMAF, 0, 0, 2, emaFast) < 2) return 0; + if(CopyBuffer(g_handleEMAS, 0, 0, 2, emaSlow) < 2) return 0; + + if(emaFast[1] > emaSlow[1]) return +1; + if(emaFast[1] < emaSlow[1]) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| MACD TREND FILTER | +//| Returns: +1 = bullish, -1 = bearish, 0 = neutral/off | +//+------------------------------------------------------------------+ +int GetMACDTrend() +{ + if(!InpUseMACDFilter) return 0; // Bypass + if(g_handleMACD == INVALID_HANDLE) return 0; + + double macdMain[], macdSignal[]; + ArraySetAsSeries(macdMain, true); + ArraySetAsSeries(macdSignal, true); + + if(CopyBuffer(g_handleMACD, 0, 0, 2, macdMain) < 2) return 0; + if(CopyBuffer(g_handleMACD, 1, 0, 2, macdSignal) < 2) return 0; + + // MACD > Signal = bullish, MACD < Signal = bearish + if(macdMain[1] > macdSignal[1]) return +1; + if(macdMain[1] < macdSignal[1]) return -1; + return 0; +} + +//+------------------------------------------------------------------+ +//| CHECK SPREAD | +//+------------------------------------------------------------------+ +bool IsSpreadOK() +{ + return (GetSpreadPips() <= InpMaxSpread); +} + +//+------------------------------------------------------------------+ +//| CHECK TRADING TIME | +//+------------------------------------------------------------------+ +bool IsTradingTimeOK() +{ + // Rollover filter + MqlDateTime dt; + TimeCurrent(dt); + if(dt.hour < InpFilterStartHour || dt.hour > InpFilterEndHour) + return false; + + // Custom time window + if(!InpUseTradingTime) return true; + + string currentTime = StringFormat("%02d:%02d", dt.hour, dt.min); + return (currentTime >= InpStartTime && currentTime <= InpEndTime); +} + +//+------------------------------------------------------------------+ +//| ANTI-DETECT: Check if we can open new order | +//+------------------------------------------------------------------+ +bool CanOpenNewOrder() +{ + if(!InpAntiDetect) return true; + + // Daily order limit + if(g_ordersOpenedToday >= InpMaxOrdersPerDay) + { + return false; + } + + // Random delay between orders + if(TimeCurrent() < g_nextAllowedTime) + { + return false; + } + + // Delay after closing chain + if(g_lastCloseTime > 0 && (TimeCurrent() - g_lastCloseTime) < InpDelayAfterClose) + { + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| ANTI-DETECT: Set random delay for next order | +//+------------------------------------------------------------------+ +void SetRandomDelay() +{ + if(!InpAntiDetect) return; + int delay = InpMinDelaySeconds + (MathRand() % (InpMaxDelaySeconds - InpMinDelaySeconds + 1)); + g_nextAllowedTime = TimeCurrent() + delay; +} + +//+------------------------------------------------------------------+ +//| CHECK DAILY COUNTERS & RESET | +//+------------------------------------------------------------------+ +void CheckDailyReset() +{ + MqlDateTime dt; + TimeCurrent(dt); + + if(dt.day != g_lastDay) + { + g_lastDay = dt.day; + g_ordersOpenedToday = 0; + g_dailyStartBalance = AccountInfoDouble(ACCOUNT_BALANCE); + Print("NEW DAY: Reset counters. Balance start: $", DoubleToString(g_dailyStartBalance, 2)); + } +} + +//+------------------------------------------------------------------+ +//| CHECK RISK: Daily loss & Max drawdown | +//| Returns true if trading should STOP | +//+------------------------------------------------------------------+ +bool IsRiskBreached() +{ + double equity = AccountInfoDouble(ACCOUNT_EQUITY); + double balance = AccountInfoDouble(ACCOUNT_BALANCE); + + // Daily loss limit + if(InpDailyLossLimit > 0) + { + double dailyPnL = equity - g_dailyStartBalance; + if(dailyPnL <= -InpDailyLossLimit) + { + Print("!!! DAILY LOSS LIMIT REACHED: $", DoubleToString(-dailyPnL, 2), " >= $", DoubleToString(InpDailyLossLimit, 2)); + return true; + } + } + + // Max drawdown (money) + if(InpMaxDrawdownMoney > 0) + { + double dd = balance - equity; + if(dd >= InpMaxDrawdownMoney) + { + Print("!!! MAX DD MONEY: $", DoubleToString(dd, 2), " >= $", DoubleToString(InpMaxDrawdownMoney, 2), " -> CUT ALL"); + CloseAllOrders(); + return true; + } + } + + // Max drawdown (percent) + if(InpMaxDrawdownPct > 0 && balance > 0) + { + double ddPct = (balance - equity) / balance * 100.0; + if(ddPct >= InpMaxDrawdownPct) + { + Print("!!! MAX DD %: ", DoubleToString(ddPct, 2), "% >= ", DoubleToString(InpMaxDrawdownPct, 2), "% -> CUT ALL"); + CloseAllOrders(); + return true; + } + } + + // Daily profit target + if(InpDailyProfitTarget > 0) + { + double dailyPnL = equity - g_dailyStartBalance; + if(dailyPnL >= InpDailyProfitTarget) + { + Print("DAILY PROFIT TARGET REACHED: $", DoubleToString(dailyPnL, 2)); + return true; // Stop trading, don't close (keep profitable positions) + } + } + + return false; +} + +//+------------------------------------------------------------------+ +//| EQUITY TRAILING | +//+------------------------------------------------------------------+ +void ProcessEquityTrailing() +{ + if(!InpUseEquityTrail) return; + + double equity = AccountInfoDouble(ACCOUNT_EQUITY); + double balance = AccountInfoDouble(ACCOUNT_BALANCE); + + // Kích hoạt khi lời đủ + if(equity >= balance + InpEquityTrailStart) + { + if(g_maxEquity == 0 || equity > g_maxEquity) + g_maxEquity = equity; + } + + // Chốt khi tụt lùi + if(g_maxEquity > 0) + { + if(equity <= g_maxEquity - InpEquityTrailStep) + { + Print(">>> EQUITY TRAILING: Max=", DoubleToString(g_maxEquity, 2), " Now=", DoubleToString(equity, 2)); + CloseAllOrders(); + g_maxEquity = 0; + } + + if(CountAllOrders() == 0) g_maxEquity = 0; + } +} + +//+------------------------------------------------------------------+ +//| CHECK DCA TP: Close chain or trigger CCBSN partial close | +//+------------------------------------------------------------------+ +void CheckDCATP() +{ + if(InpDCATPPips <= 0) return; + double pip = GetPipPoint(); + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // Check BUY chain (chỉ khi chưa partial close) + int buyCount = CountOrders(POSITION_TYPE_BUY); + if(buyCount > 0 && !g_buyPartialDone) + { + double avgPrice = GetAveragePrice(POSITION_TYPE_BUY); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double profitPips = (bid - avgPrice) / pip; + + double tpTarget = (buyCount > 1) ? InpDCATPPips : InpTPPips; + if(tpTarget <= 0) tpTarget = InpDCATPPips; + + if(profitPips >= tpTarget) + { + Print(">>> TP BUY: orders=", buyCount, " avg=", DoubleToString(avgPrice, digits), + " bid=", DoubleToString(bid, digits), + " profit=", DoubleToString(profitPips, 1), "p target=", DoubleToString(tpTarget, 1)); + + // CCBSN: partial close thay vì close all + if(InpUsePartialClose) + { + Print(">>> CCBSN: Partial close BUY ", InpPartialPercent, "% + Trailing"); + PartialCloseByType(POSITION_TYPE_BUY); + } + else + { + CloseAllByType(POSITION_TYPE_BUY); + } + SetRandomDelay(); + } + } + + // Check SELL chain (chỉ khi chưa partial close) + int sellCount = CountOrders(POSITION_TYPE_SELL); + if(sellCount > 0 && !g_sellPartialDone) + { + double avgPrice = GetAveragePrice(POSITION_TYPE_SELL); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double profitPips = (avgPrice - ask) / pip; + + double tpTarget = (sellCount > 1) ? InpDCATPPips : InpTPPips; + if(tpTarget <= 0) tpTarget = InpDCATPPips; + + if(profitPips >= tpTarget) + { + Print(">>> TP SELL: orders=", sellCount, " avg=", DoubleToString(avgPrice, digits), + " ask=", DoubleToString(ask, digits), + " profit=", DoubleToString(profitPips, 1), "p target=", DoubleToString(tpTarget, 1)); + + if(InpUsePartialClose) + { + Print(">>> CCBSN: Partial close SELL ", InpPartialPercent, "% + Trailing"); + PartialCloseByType(POSITION_TYPE_SELL); + } + else + { + CloseAllByType(POSITION_TYPE_SELL); + } + SetRandomDelay(); + } + } +} + +//+------------------------------------------------------------------+ +//| PROCESS DCA: Add to position when conditions met | +//+------------------------------------------------------------------+ +void ProcessDCA() +{ + if(!InpUseDCA) return; + + + // CCBSN: Khong DCA khi dang trailing sau partial close + + if(g_buyPartialDone || g_sellPartialDone) return; + + if(!IsSpreadOK()) return; + if(!CanOpenNewOrder()) return; + + // Check giờ trade (cho phép DCA ngoài giờ nếu InpDCAOutTime=true) + if(!InpDCAOutTime && !IsTradingTimeOK()) return; + + double pip = GetPipPoint(); + + // DCA BUY + int buyCount = CountOrders(POSITION_TYPE_BUY); + if(buyCount > 0 && buyCount < InpMaxDCAOrders + 1) // +1 vì lệnh đầu không tính DCA + { + if(InpTradeDir != DIR_SELL) // Không DCA buy nếu chỉ cho sell + { + // Check DCA cùng trend (Ichimoku + EMA) + bool dcaBuyOK = true; + if(InpDCANeedSignal) + { + int trend = GetIchimokuTrend(); + if(trend != +1) dcaBuyOK = false; + // Dừng DCA khi EMA đảo chiều + int emaTrend = GetEMATrend(); + if(emaTrend == -1) dcaBuyOK = false; + } + + if(dcaBuyOK) + { + double lastPrice = GetLastEntryPrice(POSITION_TYPE_BUY); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + + // Tính khoảng cách DCA (tăng dần theo hệ số) + double distance = InpDCADistance; + for(int i = 1; i < buyCount; i++) + distance *= InpDCADistMulti; + + // Giá đi xuống đủ xa so với lệnh cuối -> DCA + if(lastPrice - ask >= distance * pip) + { + double dcaLots = InpLots; + // Tính lot DCA (nếu có multiplier) + for(int i = 0; i < buyCount; i++) + dcaLots *= InpDCALotMulti; + + string comment = GetRandomComment(); + Print(">>> DCA BUY #", buyCount + 1, " lots=", NormLots(dcaLots), " dist=", DoubleToString(distance, 1)); + PlaceOrder(POSITION_TYPE_BUY, dcaLots, 0, InpSLPips, comment); // TP=0 vì dùng TP chuỗi + SetRandomDelay(); + } + } + } + } + + // DCA SELL + int sellCount = CountOrders(POSITION_TYPE_SELL); + if(sellCount > 0 && sellCount < InpMaxDCAOrders + 1) + { + if(InpTradeDir != DIR_BUY) // Không DCA sell nếu chỉ cho buy + { + // Check DCA cùng trend (Ichimoku + EMA) + bool dcaSellOK = true; + if(InpDCANeedSignal) + { + int trend = GetIchimokuTrend(); + if(trend != -1) dcaSellOK = false; + // Dừng DCA khi EMA đảo chiều + int emaTrend = GetEMATrend(); + if(emaTrend == +1) dcaSellOK = false; + } + if(!dcaSellOK) return; + + double lastPrice = GetLastEntryPrice(POSITION_TYPE_SELL); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + double distance = InpDCADistance; + for(int i = 1; i < sellCount; i++) + distance *= InpDCADistMulti; + + // Giá đi lên đủ xa so với lệnh cuối -> DCA + if(bid - lastPrice >= distance * pip) + { + double dcaLots = InpLots; + for(int i = 0; i < sellCount; i++) + dcaLots *= InpDCALotMulti; + + string comment = GetRandomComment(); + Print(">>> DCA SELL #", sellCount + 1, " lots=", NormLots(dcaLots), " dist=", DoubleToString(distance, 1)); + PlaceOrder(POSITION_TYPE_SELL, dcaLots, 0, InpSLPips, comment); + SetRandomDelay(); + } + } + } +} + +//+------------------------------------------------------------------+ +//| PROCESS FIRST ORDER (NEW ENTRY) | +//+------------------------------------------------------------------+ +void ProcessFirstOrder() +{ + int buyCount = CountOrders(POSITION_TYPE_BUY); + int sellCount = CountOrders(POSITION_TYPE_SELL); + + // Chỉ mở lệnh mới khi chưa có chuỗi nào + if(buyCount > 0 || sellCount > 0) return; + + // Debug log mỗi 60 giây + static datetime lastDebug = 0; + bool doDebug = (TimeCurrent() - lastDebug >= 60); + + // Kiểm tra điều kiện + if(!IsSpreadOK()) + { + if(doDebug) { Print("DEBUG: Spread blocked. Current=", DoubleToString(GetSpreadPips(),1), " Max=", InpMaxSpread); lastDebug=TimeCurrent(); } + return; + } + if(!IsTradingTimeOK()) + { + if(doDebug) { Print("DEBUG: Trading time blocked."); lastDebug=TimeCurrent(); } + return; + } + if(!CanOpenNewOrder()) + { + if(doDebug) { Print("DEBUG: CanOpenNewOrder blocked. OrdersToday=", g_ordersOpenedToday); lastDebug=TimeCurrent(); } + return; + } + if(IsRiskBreached()) return; + + // Lấy tín hiệu Ichimoku (đã cache từ bar open) + int signal = g_cachedSignal; + if(signal == 0) + { + if(doDebug) { Print("DEBUG: No Ichimoku signal."); lastDebug=TimeCurrent(); } + return; + } + + // Kiểm tra EMA trend filter + int emaTrend = GetEMATrend(); + + // Kiểm tra MACD trend filter + int macdTrend = GetMACDTrend(); + + // BUY: signal > 0 && EMA uptrend (hoặc bypass) && MACD bullish (hoặc bypass) + if(signal > 0 && (emaTrend >= 0) && (macdTrend >= 0)) + { + if(InpTradeDir == DIR_BOTH || InpTradeDir == DIR_BUY) + { + double tp = InpTPPips; + // Nếu dùng DCA, lệnh đầu không có TP riêng (chờ TP chuỗi) + if(InpUseDCA) tp = 0; + + string comment = GetRandomComment(); + Print(">>> SIGNAL BUY: Ichimoku Cloud Break Up + EMA OK"); + PlaceOrder(POSITION_TYPE_BUY, InpLots, tp, InpSLPips, comment); + SetRandomDelay(); + } + } + + // SELL: signal < 0 && EMA downtrend (hoặc bypass) && MACD bearish (hoặc bypass) + if(signal < 0 && (emaTrend <= 0) && (macdTrend <= 0)) + { + if(InpTradeDir == DIR_BOTH || InpTradeDir == DIR_SELL) + { + double tp = InpTPPips; + if(InpUseDCA) tp = 0; + + string comment = GetRandomComment(); + Print(">>> SIGNAL SELL: Ichimoku Cloud Break Down + EMA OK"); + PlaceOrder(POSITION_TYPE_SELL, InpLots, tp, InpSLPips, comment); + SetRandomDelay(); + } + } +} + +//+------------------------------------------------------------------+ +//| PROCESS SNIPER: Tỉa lệnh đầu chuỗi khi DCA quá nhiều | +//+------------------------------------------------------------------+ +void ProcessSniper() +{ + if(!InpUseSniper) return; + + // Check BUY chain + int buyCount = CountOrders(POSITION_TYPE_BUY); + if(buyCount >= InpOrders2StartSniper) + SniperForType(POSITION_TYPE_BUY, buyCount); + + // Check SELL chain + int sellCount = CountOrders(POSITION_TYPE_SELL); + if(sellCount >= InpOrders2StartSniper) + SniperForType(POSITION_TYPE_SELL, sellCount); +} + +//+------------------------------------------------------------------+ +//| Sniper: Tỉa N lệnh đầu (lỗ nặng nhất) của 1 loại | +//+------------------------------------------------------------------+ +void SniperForType(ENUM_POSITION_TYPE type, int totalOrders) +{ + // Tính tổng floating profit của chuỗi + double floatingProfit = GetFloatingProfit(type); + + // Chỉ tỉa khi tổng floating profit đủ điều kiện + // Ví dụ: floating = -100$, cần >= 10% → cần floating >= -90$ (giảm lỗ 10%) + // Logic: đóng lệnh lỗ nặng nhất khi giá hồi đủ % + if(floatingProfit >= 0) return; // Chuỗi đang lời → không cần tỉa + + // Thu thập thông tin lệnh + struct OrderInfo + { + ulong ticket; + double profit; + datetime openTime; + }; + + OrderInfo orders[]; + ArrayResize(orders, 0); + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(g_posInfo.SelectByIndex(i)) + { + if(g_posInfo.Symbol() != _Symbol) continue; + if(g_posInfo.Magic() != InpMagicID) continue; + if(g_posInfo.PositionType() != type) continue; + + int size = ArraySize(orders); + ArrayResize(orders, size + 1); + orders[size].ticket = g_posInfo.Ticket(); + orders[size].profit = g_posInfo.Profit() + g_posInfo.Swap() + g_posInfo.Commission(); + orders[size].openTime = g_posInfo.Time(); + } + } + + if(ArraySize(orders) < InpOrders2StartSniper) return; + + // Sort by openTime (lệnh cũ nhất trước) - bubble sort đơn giản + for(int i = 0; i < ArraySize(orders) - 1; i++) + { + for(int j = i + 1; j < ArraySize(orders); j++) + { + if(orders[j].openTime < orders[i].openTime) + { + OrderInfo temp = orders[i]; + orders[i] = orders[j]; + orders[j] = temp; + } + } + } + + // Kiểm tra: tổng profit của N lệnh đầu cần tỉa + double firstOrdersProfit = 0; + int trimCount = MathMin(InpFirstOrdersSniper, ArraySize(orders)); + for(int i = 0; i < trimCount; i++) + firstOrdersProfit += orders[i].profit; + + // Tổng profit còn lại (sau khi tỉa) + double remainProfit = floatingProfit - firstOrdersProfit; + + // Điều kiện: phần còn lại phải >= InpPercentSniper% so với floating hiện tại + // Nghĩa là sau khi tỉa, lỗ phải giảm đi đáng kể + double threshold = MathAbs(floatingProfit) * InpPercentSniper / 100.0; + + if(MathAbs(firstOrdersProfit) <= threshold) + { + // Đóng các lệnh đầu + for(int i = 0; i < trimCount; i++) + { + g_trade.PositionClose(orders[i].ticket); + Print(">>> SNIPER: Closed ticket ", orders[i].ticket, " profit=", + DoubleToString(orders[i].profit, 2)); + } + + Print(">>> SNIPER: Trimmed ", trimCount, " orders. Old float=", + DoubleToString(floatingProfit, 2), " Est remain=", DoubleToString(remainProfit, 2)); + } +} + +//+------------------------------------------------------------------+ +//| DISPLAY PANEL | +//+------------------------------------------------------------------+ +void DisplayPanel() +{ + static datetime lastUpdate = 0; + if(TimeCurrent() - lastUpdate < 3) return; + lastUpdate = TimeCurrent(); + + int buyN = CountOrders(POSITION_TYPE_BUY); + int sellN = CountOrders(POSITION_TYPE_SELL); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + string info = ""; + info += "IchiDCA CCBSN v2.0 | " + _Symbol + "\n"; + info += "Magic: " + IntegerToString(InpMagicID) + " | Spread: " + DoubleToString(GetSpreadPips(), 1) + "p\n"; + info += "Buy: " + IntegerToString(buyN) + " | Sell: " + IntegerToString(sellN) + "\n"; + info += "Float: $" + DoubleToString(GetFloatingProfit(), 2) + "\n"; + info += "Orders today: " + IntegerToString(g_ordersOpenedToday) + "/" + IntegerToString(InpMaxOrdersPerDay) + "\n"; + + if(buyN > 1) + info += "BUY avg: " + DoubleToString(GetAveragePrice(POSITION_TYPE_BUY), digits) + "\n"; + if(sellN > 1) + info += "SELL avg: " + DoubleToString(GetAveragePrice(POSITION_TYPE_SELL), digits) + "\n"; + + // CCBSN Status + if(g_buyPartialDone) + info += "[CCBSN BUY] Trail SL: " + DoubleToString(g_buyTrailSL, digits) + "\n"; + if(g_sellPartialDone) + info += "[CCBSN SELL] Trail SL: " + DoubleToString(g_sellTrailSL, digits) + "\n"; + + if(!IsTradingTimeOK()) info += "[OUT OF TIME]\n"; + if(InpAntiDetect) info += "[STEALTH MODE]\n"; + + Comment(info); +} + +//+------------------------------------------------------------------+ +//| CHECK FRIDAY CLOSE | +//+------------------------------------------------------------------+ +void CheckFridayClose() +{ + if(!InpCloseFriday) return; + + MqlDateTime dt; + TimeCurrent(dt); + + if(dt.day_of_week == 5 && dt.hour >= InpCloseFridayHour) + { + if(CountAllOrders() > 0) + { + Print("FRIDAY EXIT: Closing all positions before weekend."); + CloseAllOrders(); + } + } +} + +//+------------------------------------------------------------------+ +//| Check if new M5 bar has formed | +//+------------------------------------------------------------------+ +bool IsNewBar() +{ + datetime barTime = iTime(_Symbol, PERIOD_M5, 0); + if(barTime == 0) return false; + + if(barTime != g_lastBarTime) + { + g_lastBarTime = barTime; + return true; + } + return false; +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Panel (throttled internally) + DisplayPanel(); + + // Daily reset + CheckDailyReset(); + + // === MỖI TICK: Risk & TP (phản ứng nhanh) === + + // Risk check (closes all if breached) + if(IsRiskBreached()) return; + + // Equity Trailing (cần check mỗi tick) + ProcessEquityTrailing(); + + // Friday close + CheckFridayClose(); + + // DCA TP check (đóng chuỗi ngay khi đạt target hoặc CCBSN partial close) + CheckDCATP(); + + // CCBSN: Trailing Stop cho phần còn lại (cần check mỗi tick) + ProcessTrailingStop(); + + // CCBSN: Reset state nếu tất cả lệnh đã đóng + if(g_buyPartialDone && CountOrders(POSITION_TYPE_BUY) == 0) + ResetCCBSNState(POSITION_TYPE_BUY); + if(g_sellPartialDone && CountOrders(POSITION_TYPE_SELL) == 0) + ResetCCBSNState(POSITION_TYPE_SELL); + + // === CHỈ KHI NẾN MỚI: Signal, DCA, Sniper === + if(!IsNewBar()) return; + + // Cache signal cho bar này + g_cachedSignal = GetMasterSignal(); + + // Sniper: tỉa lệnh khi chuỗi quá dài + ProcessSniper(); + + // DCA: thêm lệnh vào chuỗi hiện có + ProcessDCA(); + + // Mở lệnh mới (chỉ khi không có chuỗi nào đang chạy) + ProcessFirstOrder(); +} + +//+------------------------------------------------------------------+ +//| Tester function | +//+------------------------------------------------------------------+ +double OnTester() +{ + if(!MQLInfoInteger(MQL_TESTER)) return 0; + + double profit = TesterStatistics(STAT_PROFIT); + double dd = TesterStatistics(STAT_EQUITY_DD_RELATIVE); + + if(dd > 0.0001) return profit / dd; + return profit; +} +//+------------------------------------------------------------------+ diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa679b7 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# MQL5 MultiSignal DCA CCBSN EA + +> **Expert Advisor thông minh cho MetaTrader 5** — 9 loại tín hiệu × DCA đa tầng × CCBSN (Chốt Cắt Bán Sớm Nửa) × Sniper tỉa lệnh × Anti-Detect Prop Firm + +![MetaTrader 5](https://img.shields.io/badge/MetaTrader-5-blue) +![MQL5](https://img.shields.io/badge/Language-MQL5-orange) +![Python](https://img.shields.io/badge/Optimizer-Python-yellow) +![License](https://img.shields.io/badge/License-MIT-green) + +## 🎯 Tư Duy & Điểm Khác Biệt + +### Vấn đề của các bot DCA truyền thống +- ❌ Chỉ 1 loại signal (thường là MA cross) → bỏ lỡ nhiều cơ hội +- ❌ DCA lot cố định → cháy tài khoản khi sideway dài +- ❌ TP chuỗi rồi đóng hết → bỏ lỡ trend lớn +- ❌ Không anti-detect → bị prop firm phát hiện + +### Giải pháp: MultiSignal + CCBSN + ML + +``` +┌──────────────┐ ┌──────────┐ ┌──────────────┐ +│ 9 Signal │────▶│ DCA │────▶│ CCBSN │ +│ Modes │ │ Adaptive │ │ Partial Close│ +│ │ │ │ │ + Trailing │ +│ Ichimoku │ │ Distance │ │ │ +│ EMA Cross │ │ tăng dần │ │ Chốt 50% │ +│ RSI OB/OS │ │ │ │ → Move BE │ +│ BB Bounce │ │ Lot │ │ → Trail phần │ +│ Stochastic │ │ tăng dần │ │ còn lại │ +│ CCI │ │ │ │ │ +│ MACD Cross │ │ Filter │ │ = Maximize │ +│ Supertrend │ │ by trend │ │ profit per │ +│ Momentum │ │ │ │ chain │ +└──────────────┘ └──────────┘ └──────────────┘ + │ │ + ▼ ▼ +┌──────────────┐ ┌──────────────┐ +│ EMA + MACD │ │ Sniper │ +│ Trend Filter │ │ Tỉa lệnh │ +│ │ │ đầu chuỗi │ +└──────────────┘ └──────────────┘ +``` + +### Tư duy thiết kế +1. **Multi-Signal Selector**: Thay vì hardcode 1 indicator, user chọn signal phù hợp từng thị trường qua `InpIndiMode` → optimize trên Strategy Tester +2. **CCBSN**: Khi chuỗi DCA về TP → chỉ chốt 50%. Phần còn lại: breakeven + trailing → **bắt trend lớn thay vì chỉ scalp nhỏ** +3. **ML Optimizer** (Python): Train decision tree trên data lịch sử → tự tìm DCA distance + lot size tối ưu per volatility regime +4. **Wave Strategy**: Brute-force 2240+ combo indicators → tìm chiến lược có lợi nhất trên dữ liệu cũ + +## 📦 Cấu Trúc + +| File | Mô tả | +|------|--------| +| `IchiDCA_CCBSN_PropFirm_Fixed.mq5` | EA chính — 9 signals, DCA, CCBSN, Sniper, Anti-Detect | +| `wave_strategy_optimizer.py` | Brute-force 2240+ combos tìm chiến lược tốt nhất | +| `dca_ml_optimizer.py` | ML tối ưu DCA parameters theo volatility regime | + +## 🚀 Cài Đặt + +### 1. Copy vào MT5 +``` +Copy IchiDCA_CCBSN_PropFirm_Fixed.mq5 → + C:\Users\\AppData\Roaming\MetaQuotes\Terminal\\MQL5\Experts\ +``` + +### 2. Compile +Mở MetaEditor → Open file → Compile (F7) + +### 3. Chạy +Drag & drop EA lên chart XAUUSD M5 → Cấu hình inputs + +## ⚙️ Inputs Chính (85+ parameters) + +### Signal Selection +| Input | Giá trị | Mô tả | +|-------|---------|-------| +| `InpIndiMode` | 0-8 | Chọn loại tín hiệu | +| `InpTFSignal` | PERIOD_M5 | Timeframe cho signal | + +### DCA +| Input | Default | Mô tả | +|-------|---------|-------| +| `InpDCADistance` | 10.0 | Khoảng cách DCA (pips) | +| `InpDCADistMulti` | 1.2 | Hệ số nhân khoảng cách | +| `InpDCALotMulti` | 1.0 | Hệ số nhân lot | +| `InpDCATPPips` | 50.0 | TP chuỗi DCA | +| `InpMaxDCAOrders` | 5 | Max DCA orders | + +### CCBSN +| Input | Default | Mô tả | +|-------|---------|-------| +| `InpUsePartialClose` | true | Bật chốt nửa | +| `InpPartialPercent` | 50% | % lot chốt | +| `InpMoveSLToBE` | true | Move SL breakeven | +| `InpTrailStartPips` | 10.0 | Pips kích hoạt trail | + +## 🐍 Python Optimizer + +### Wave Strategy Optimizer +```bash +pip install numpy pandas scikit-learn + +# Export XAUUSD M5 data from MT5 → CSV +python wave_strategy_optimizer.py --data XAUUSD_M5.csv --output-dir ./ +``` + +### ML DCA Optimizer +```bash +python dca_ml_optimizer.py --data XAUUSD_M5.csv +# → Generates ml_params.mqh with optimized DCA parameters +``` + +## 🐛 Known Issues & Fixes + +| Issue | Fix | +|-------|-----| +| Exness: `ORDER_FILLING_IOC` not supported | `GetFillingType()` auto-detect | +| `iBands` buffer 0 ≠ UPPER | Buffer: 0=BASE, 1=UPPER, 2=LOWER | +| Close position fails | Retry 3x with Sleep(500) | +| EMA trend + BB bounce = 0 signals | Don't mix trend filter with mean reversion | + +## 🗺️ Hướng Phát Triển + +- [ ] Walk-forward optimization (train/test split) +- [ ] Multi-timeframe signal confluence +- [ ] Risk management per volatility regime (ML-driven) +- [ ] Dashboard web theo dõi performance real-time +- [ ] Thêm signal: Order Block, Fair Value Gap (ICT concepts) + +## 📄 License + +MIT License — Thoải mái sử dụng, chỉnh sửa, phân phối. + +--- + +## 💼 Bạn muốn Bot Trading tương tự? + +| Bạn cần | Chúng tôi đã làm ✅ | +|---------|---------------------| +| Bot DCA thông minh | ✅ 9 signal modes + adaptive DCA | +| Quản lý rủi ro tự động | ✅ CCBSN + Sniper + Equity Trailing | +| Tối ưu bằng ML | ✅ Python optimizer → hardcode MQ5 | +| Bot cho Prop Firm | ✅ Anti-Detect features built-in | +| Backtest & Optimize | ✅ 85+ inputs cho MT5 Strategy Tester | + +

+ Demo + Zalo + Email +

+ +### 🤖 Comarai — Companion for Marketing & AI Automation + +> *"Bạn không cần thuê 10 nhân viên. Bạn cần 4 AI Agents chạy 24/7."* + +| Em Sale | Em Content | Em Marketing | Em Trade | +|---------|-----------|-------------|----------| +| Tìm khách tự động | Viết content viral | Chạy campaign 24/7 | Trade bot thông minh | + +

+ Built by @hungpixi | Comarai.com +

diff --git a/dca_ml_optimizer.py b/dca_ml_optimizer.py new file mode 100644 index 0000000..b178518 --- /dev/null +++ b/dca_ml_optimizer.py @@ -0,0 +1,877 @@ +""" +DCA ML Optimizer - Machine Learning DCA Parameter Optimization +================================================================ +Phân tích dữ liệu nến lịch sử XAUUSD, tìm khoảng cách DCA + lot tối ưu +theo volatility regime, phát hiện pattern "blow-up". + +Usage: + python dca_ml_optimizer.py --data XAUUSD_M5.csv + python dca_ml_optimizer.py --data XAUUSD_M5.csv --export-mt5 + python dca_ml_optimizer.py --test (chạy với sample data) + +Output: + - ml_params.mqh (hardcoded params cho MQ5) + - optimization_log.csv (log chi tiết) + +Author: Comarai (https://comarai.com) - AI-powered trading optimization +""" + +import argparse +import os +import sys +import csv +import json +import math +from datetime import datetime, timedelta +from collections import defaultdict + +import numpy as np + +# Try import sklearn, fallback to simple heuristic if not available +try: + from sklearn.tree import DecisionTreeRegressor + from sklearn.ensemble import GradientBoostingRegressor + from sklearn.model_selection import cross_val_score + HAS_SKLEARN = True +except ImportError: + HAS_SKLEARN = False + print("[WARN] scikit-learn not installed. Using heuristic optimization.") + print(" Install: pip install scikit-learn numpy pandas") + + +# ============================================================================= +# CONSTANTS +# ============================================================================= +PIP_VALUE_XAUUSD = 0.1 # 1 pip = 0.1 cho XAUUSD (2 or 3 digit broker) + +# Default settings from 2-2-test.set (reference) +DEFAULT_DCA_DISTANCE = 10.0 # pips +DEFAULT_DCA_DIST_MULTI = 1.2 +DEFAULT_LOT = 0.19 +DEFAULT_LOT_MULTI = 1.0 # lot cố định +DEFAULT_MAX_DCA = 5 +DEFAULT_TP_DCA = 50.0 # pips + +# Regime thresholds (will be refined by ML) +ATR_PERCENTILES = [25, 50, 75, 90] # Low, Medium, High, Extreme + + +# ============================================================================= +# DATA LOADING +# ============================================================================= +def load_candle_data(filepath): + """Load candle data from CSV exported by MT5. + + Supports formats: + - MT5 default export: Date, Time, Open, High, Low, Close, TickVolume, Volume, Spread + - Custom: datetime, open, high, low, close, volume + """ + candles = [] + + with open(filepath, 'r', encoding='utf-8-sig') as f: + # Detect delimiter + first_line = f.readline() + f.seek(0) + + delimiter = '\t' if '\t' in first_line else ',' + reader = csv.reader(f, delimiter=delimiter) + + # Try to detect header + header = next(reader) + header_lower = [h.strip().lower() for h in header] + + # Map columns + col_map = {} + for i, h in enumerate(header_lower): + if h in ('date', 'datetime', ''): + col_map['date'] = i + elif h in ('time', '