4cb86ecd5f
- 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
1838 lines
68 KiB
Plaintext
1838 lines
68 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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 <Trade\Trade.mqh>
|
||
#include <Trade\PositionInfo.mqh>
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| 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;
|
||
}
|
||
//+------------------------------------------------------------------+
|