Files
EA2/smart-bot/smart-bot - Copy.mq5
2026-05-24 20:22:43 +07:00

4864 lines
436 KiB
Plaintext

//+------------------------------------------------------------------+
//| SmartBot.mq5 |
//| Advanced Multi-Timeframe Trading System with AI Assistance |
//| Features: Dashboard, Signal Validator, S/D Detector, News Filter|
//| Smart TP/SL, Trendline Recognition, Session Heatmap, Trade Log |
//| Adaptive Scalping/Swing Modes + AI Suggestions |
//+------------------------------------------------------------------+
#property strict
#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
#include <Trade/PositionInfo.mqh>
// Define WebRequest error constants if not already defined
#ifndef ERR_WEBREQUEST_INVALID_ADDRESS
#define ERR_WEBREQUEST_INVALID_ADDRESS 4014
#endif
#ifndef ERR_WEBREQUEST_CONNECT_FAILED
#define ERR_WEBREQUEST_CONNECT_FAILED 4015
#endif
#ifndef ERR_WEBREQUEST_REQUEST_FAILED
#define ERR_WEBREQUEST_REQUEST_FAILED 4016
#endif
#ifndef ERR_WEBREQUEST_TIMEOUT
#define ERR_WEBREQUEST_TIMEOUT 4017
#endif
#ifndef ERR_WEBREQUEST_INVALID_PARAMETER
#define ERR_WEBREQUEST_INVALID_PARAMETER 4018
#endif
#ifndef ERR_WEBREQUEST_NOT_ALLOWED
#define ERR_WEBREQUEST_NOT_ALLOWED 4019
#endif
CTrade trade;
CSymbolInfo symbolInfoGlobal;
//==================== Inputs ====================
enum ENUM_Mode { MODE_SCALPING=0, MODE_INTRADAY=1, MODE_SWING=2 };
// Multi Timeframe Confirmation Settings
input group "=== Multi Timeframe Confirmation ==="
input bool EnableMTFConfirmation = true; // Enable MTF Confirmation
input double MTF_MinScore = 20.0; // MTF Minimum Score (diturunkan dari 40 untuk lebih agresif)
input bool MTF_ApplyToXAUUSD = true; // Apply MTF to XAUUSD only
input bool MTF_ApplyToAllPairs = false; // Apply MTF to all pairs
input bool MTF_PreventOppositeEntry = true; // Prevent opposite entry when position is open
// Global variables for timeframe tracking
ENUM_TIMEFRAMES currentTimeframe = PERIOD_CURRENT;
bool timeframeChanged = false;
// Global variables untuk menyimpan nilai indikator sebelumnya untuk debugging
double lastRsi = 0;
double lastAdx = 0;
double lastEmaF = 0;
double lastEmaS = 0;
double lastStochK = 0;
double lastStochD = 0;
double lastVolume = 0;
// Global variables untuk toggle buttons
bool rsiEnabled = true;
bool adxEnabled = true;
bool stochEnabled = true;
bool mtfApplyToAllPairsEnabled = false; // Toggle untuk MTF_ApplyToAllPairs
bool sidewaysDisableTradingEnabled = false; // Toggle untuk Sideways_DisableTrading
bool breakoutConfirmationEnabled = false; // Toggle untuk Breakout Confirmation
bool engulfingConfirmationEnabled = false; // Toggle untuk Engulfing Confirmation
// Global variables untuk re-entry mechanism
int buyReEntryCount = 0;
int sellReEntryCount = 0;
datetime lastBuySignalTime = 0;
datetime lastSellSignalTime = 0;
// Global MTF Indicator Handles untuk real-time updates
int hEmaF_H1 = INVALID_HANDLE, hEmaS_H1 = INVALID_HANDLE, hRsi_H1 = INVALID_HANDLE, hAdx_H1 = INVALID_HANDLE, hStoch_H1 = INVALID_HANDLE;
int hEmaF_M15 = INVALID_HANDLE, hEmaS_M15 = INVALID_HANDLE, hRsi_M15 = INVALID_HANDLE, hAdx_M15 = INVALID_HANDLE, hStoch_M15 = INVALID_HANDLE;
int hEmaF_M5 = INVALID_HANDLE, hEmaS_M5 = INVALID_HANDLE, hRsi_M5 = INVALID_HANDLE, hAdx_M5 = INVALID_HANDLE, hStoch_M5 = INVALID_HANDLE;
int hEmaF_M1 = INVALID_HANDLE, hEmaS_M1 = INVALID_HANDLE, hRsi_M1 = INVALID_HANDLE, hAdx_M1 = INVALID_HANDLE, hStoch_M1 = INVALID_HANDLE;
// Global variables untuk auto spread adjustment
double averageSpread = 0;
int spreadSampleCount = 0;
// MTFConfirmation struct definition
struct MTFConfirmation {
bool h1_buy, h1_sell;
bool m15_buy, m15_sell;
bool m5_buy, m5_sell;
bool m1_buy, m1_sell;
double h1_buy_strength, h1_sell_strength;
double m15_buy_strength, m15_sell_strength;
double m5_buy_strength, m5_sell_strength;
double m1_buy_strength, m1_sell_strength;
double total_score;
double total_buy_score;
double total_sell_score;
double net_score;
string reason;
// Copy constructor to fix deprecation warnings
MTFConfirmation(const MTFConfirmation& other) {
h1_buy = other.h1_buy;
h1_sell = other.h1_sell;
m15_buy = other.m15_buy;
m15_sell = other.m15_sell;
m5_buy = other.m5_buy;
m5_sell = other.m5_sell;
m1_buy = other.m1_buy;
m1_sell = other.m1_sell;
h1_buy_strength = other.h1_buy_strength;
h1_sell_strength = other.h1_sell_strength;
m15_buy_strength = other.m15_buy_strength;
m15_sell_strength = other.m15_sell_strength;
m5_buy_strength = other.m5_buy_strength;
m5_sell_strength = other.m5_sell_strength;
m1_buy_strength = other.m1_buy_strength;
m1_sell_strength = other.m1_sell_strength;
total_score = other.total_score;
total_buy_score = other.total_buy_score;
total_sell_score = other.total_sell_score;
net_score = other.net_score;
reason = other.reason;
}
// Default constructor
MTFConfirmation() {
h1_buy = h1_sell = m15_buy = m15_sell = m5_buy = m5_sell = m1_buy = m1_sell = false;
h1_buy_strength = h1_sell_strength = m15_buy_strength = m15_sell_strength = m5_buy_strength = m5_sell_strength = m1_buy_strength = m1_sell_strength = 0;
total_score = 0;
total_buy_score = 0;
total_sell_score = 0;
net_score = 0;
reason = "";
}
};
// Global variables untuk MTF signal tracking dan position management
MTFConfirmation lastMTFSignal;
bool lastMTFSignalValid = false;
datetime lastMTFSignalTime = 0;
// Global variables untuk sideway market detection
bool isSidewaysMarket = false;
int sidewaysConfidence = 0; // 0-100, semakin tinggi semakin yakin sideway
string sidewaysReason = "";
datetime lastSidewaysCheck = 0;
//==================== Constants ====================
#define BUY 1
#define SELL -1
//==================== Breakout & Engulfing Structures ====================
// Support/Resistance Level Structure
struct SRLevel {
double price;
int strength; // Number of touches
datetime lastTouch;
bool isResistance;
int barIndex;
};
// Engulfing Pattern Types
enum ENUM_ENGULFING_TYPE {
BULLISH_ENGULFING,
BEARISH_ENGULFING,
DOJI_ENGULFING,
HAMMER_ENGULFING,
NO_ENGULFING
};
// Engulfing Pattern Structure
struct EngulfingPattern {
ENUM_ENGULFING_TYPE type;
double strength; // 0.0 to 1.0
bool isValid;
string reason;
int barIndex;
};
// Global arrays untuk S/R levels
SRLevel srLevels[];
int srLevelCount = 0;
//==================== Timeframe-Specific Confirmation ====================
// Timeframe awareness untuk confirmation
struct TimeframeCache {
datetime lastCheck;
datetime lastEngulfingCheck;
bool breakoutValid;
bool engulfingValid;
double breakoutLevel;
ENUM_ENGULFING_TYPE lastEngulfingType;
double engulfingStrength;
string engulfingReason;
int lastEngulfingDirection; // BUY or SELL
};
TimeframeCache tfCache;
// Function to reset all indicator handles when timeframe changes
void ResetIndicatorHandles() {
EssentialLog("🔄 ResetIndicatorHandles: Starting handle reset...");
// Release existing handles
if(hEmaF != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing EMA Fast handle " + IntegerToString(hEmaF));
IndicatorRelease(hEmaF);
hEmaF = INVALID_HANDLE;
}
if(hEmaS != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing EMA Slow handle " + IntegerToString(hEmaS));
IndicatorRelease(hEmaS);
hEmaS = INVALID_HANDLE;
}
if(hRsi != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing RSI handle " + IntegerToString(hRsi));
IndicatorRelease(hRsi);
hRsi = INVALID_HANDLE;
}
if(hAdx != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing ADX handle " + IntegerToString(hAdx));
IndicatorRelease(hAdx);
hAdx = INVALID_HANDLE;
}
if(hAtr != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing ATR handle " + IntegerToString(hAtr));
IndicatorRelease(hAtr);
hAtr = INVALID_HANDLE;
}
if(hStoch != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing Stochastic handle " + IntegerToString(hStoch));
IndicatorRelease(hStoch);
hStoch = INVALID_HANDLE;
}
if(hVolume != INVALID_HANDLE) {
EssentialLog("🔄 ResetIndicatorHandles: Releasing Volume handle " + IntegerToString(hVolume));
IndicatorRelease(hVolume);
hVolume = INVALID_HANDLE;
}
EssentialLog("✅ ResetIndicatorHandles: All handles reset for new timeframe: " + EnumToString(currentTimeframe));
// Reset MTF handles if enabled
if(EnableMTFConfirmation) {
EssentialLog("🔄 ResetIndicatorHandles: Resetting MTF handles...");
ReleaseMTFHandles();
InitializeMTFHandles();
}
// Force chart refresh to ensure new handles are properly initialized
ChartRedraw();
Sleep(100); // Small delay to ensure handles are properly released
}
// Function to initialize MTF indicator handles
void InitializeMTFHandles() {
if(!EnableMTFConfirmation) return;
EssentialLog("🔄 InitializeMTFHandles: Initializing MTF indicator handles...");
// Initialize H1 handles
hEmaF_H1 = iMA(_Symbol, PERIOD_H1, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
hEmaS_H1 = iMA(_Symbol, PERIOD_H1, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
hRsi_H1 = iRSI(_Symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE);
hAdx_H1 = iADX(_Symbol, PERIOD_H1, ADX_Period);
hStoch_H1 = iStochastic(_Symbol, PERIOD_H1, Stochastic_K, Stochastic_D, Stochastic_Slow, MODE_SMA, STO_LOWHIGH);
// Initialize M15 handles
hEmaF_M15 = iMA(_Symbol, PERIOD_M15, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
hEmaS_M15 = iMA(_Symbol, PERIOD_M15, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
hRsi_M15 = iRSI(_Symbol, PERIOD_M15, RSI_Period, PRICE_CLOSE);
hAdx_M15 = iADX(_Symbol, PERIOD_M15, ADX_Period);
hStoch_M15 = iStochastic(_Symbol, PERIOD_M15, Stochastic_K, Stochastic_D, Stochastic_Slow, MODE_SMA, STO_LOWHIGH);
// Initialize M5 handles
hEmaF_M5 = iMA(_Symbol, PERIOD_M5, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
hEmaS_M5 = iMA(_Symbol, PERIOD_M5, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
hRsi_M5 = iRSI(_Symbol, PERIOD_M5, RSI_Period, PRICE_CLOSE);
hAdx_M5 = iADX(_Symbol, PERIOD_M5, ADX_Period);
hStoch_M5 = iStochastic(_Symbol, PERIOD_M5, Stochastic_K, Stochastic_D, Stochastic_Slow, MODE_SMA, STO_LOWHIGH);
// Initialize M1 handles
hEmaF_M1 = iMA(_Symbol, PERIOD_M1, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
hEmaS_M1 = iMA(_Symbol, PERIOD_M1, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
hRsi_M1 = iRSI(_Symbol, PERIOD_M1, RSI_Period, PRICE_CLOSE);
hAdx_M1 = iADX(_Symbol, PERIOD_M1, ADX_Period);
hStoch_M1 = iStochastic(_Symbol, PERIOD_M1, Stochastic_K, Stochastic_D, Stochastic_Slow, MODE_SMA, STO_LOWHIGH);
EssentialLog("✅ InitializeMTFHandles: MTF handles initialized successfully");
}
// Function to release MTF indicator handles
void ReleaseMTFHandles() {
EssentialLog("🔄 ReleaseMTFHandles: Releasing MTF indicator handles...");
// Release H1 handles
if(hEmaF_H1 != INVALID_HANDLE) { IndicatorRelease(hEmaF_H1); hEmaF_H1 = INVALID_HANDLE; }
if(hEmaS_H1 != INVALID_HANDLE) { IndicatorRelease(hEmaS_H1); hEmaS_H1 = INVALID_HANDLE; }
if(hRsi_H1 != INVALID_HANDLE) { IndicatorRelease(hRsi_H1); hRsi_H1 = INVALID_HANDLE; }
if(hAdx_H1 != INVALID_HANDLE) { IndicatorRelease(hAdx_H1); hAdx_H1 = INVALID_HANDLE; }
if(hStoch_H1 != INVALID_HANDLE) { IndicatorRelease(hStoch_H1); hStoch_H1 = INVALID_HANDLE; }
// Release M15 handles
if(hEmaF_M15 != INVALID_HANDLE) { IndicatorRelease(hEmaF_M15); hEmaF_M15 = INVALID_HANDLE; }
if(hEmaS_M15 != INVALID_HANDLE) { IndicatorRelease(hEmaS_M15); hEmaS_M15 = INVALID_HANDLE; }
if(hRsi_M15 != INVALID_HANDLE) { IndicatorRelease(hRsi_M15); hRsi_M15 = INVALID_HANDLE; }
if(hAdx_M15 != INVALID_HANDLE) { IndicatorRelease(hAdx_M15); hAdx_M15 = INVALID_HANDLE; }
if(hStoch_M15 != INVALID_HANDLE) { IndicatorRelease(hStoch_M15); hStoch_M15 = INVALID_HANDLE; }
// Release M5 handles
if(hEmaF_M5 != INVALID_HANDLE) { IndicatorRelease(hEmaF_M5); hEmaF_M5 = INVALID_HANDLE; }
if(hEmaS_M5 != INVALID_HANDLE) { IndicatorRelease(hEmaS_M5); hEmaS_M5 = INVALID_HANDLE; }
if(hRsi_M5 != INVALID_HANDLE) { IndicatorRelease(hRsi_M5); hRsi_M5 = INVALID_HANDLE; }
if(hAdx_M5 != INVALID_HANDLE) { IndicatorRelease(hAdx_M5); hAdx_M5 = INVALID_HANDLE; }
if(hStoch_M5 != INVALID_HANDLE) { IndicatorRelease(hStoch_M5); hStoch_M5 = INVALID_HANDLE; }
// Release M1 handles
if(hEmaF_M1 != INVALID_HANDLE) { IndicatorRelease(hEmaF_M1); hEmaF_M1 = INVALID_HANDLE; }
if(hEmaS_M1 != INVALID_HANDLE) { IndicatorRelease(hEmaS_M1); hEmaS_M1 = INVALID_HANDLE; }
if(hRsi_M1 != INVALID_HANDLE) { IndicatorRelease(hRsi_M1); hRsi_M1 = INVALID_HANDLE; }
if(hAdx_M1 != INVALID_HANDLE) { IndicatorRelease(hAdx_M1); hAdx_M1 = INVALID_HANDLE; }
if(hStoch_M1 != INVALID_HANDLE) { IndicatorRelease(hStoch_M1); hStoch_M1 = INVALID_HANDLE; }
EssentialLog("✅ ReleaseMTFHandles: All MTF handles released");
}
// Function to create toggle buttons on chart
void CreateToggleButtons() {
if(!ShowToggleButtons) return;
// Calculate position at bottom of dashboard
int buttonY = 500; // Position at bottom
int buttonHeight = 25;
int buttonWidth = 85;
int buttonSpacing = 5;
int startX = 10;
// RSI Toggle Button
string rsiButtonName = "RSI_Toggle_Button";
string rsiButtonText = "RSI: " + (rsiEnabled ? "ON" : "OFF");
color rsiButtonColor = rsiEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, rsiButtonName) < 0) {
ObjectCreate(0, rsiButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, rsiButtonName, OBJPROP_TEXT, rsiButtonText);
ObjectSetInteger(0, rsiButtonName, OBJPROP_BGCOLOR, rsiButtonColor);
ObjectSetInteger(0, rsiButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, rsiButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, rsiButtonName, OBJPROP_XDISTANCE, startX);
ObjectSetInteger(0, rsiButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, rsiButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, rsiButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, rsiButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, rsiButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, rsiButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, rsiButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, rsiButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, rsiButtonName, OBJPROP_ZORDER, 1000);
// ADX Toggle Button
string adxButtonName = "ADX_Toggle_Button";
string adxButtonText = "ADX: " + (adxEnabled ? "ON" : "OFF");
color adxButtonColor = adxEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, adxButtonName) < 0) {
ObjectCreate(0, adxButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, adxButtonName, OBJPROP_TEXT, adxButtonText);
ObjectSetInteger(0, adxButtonName, OBJPROP_BGCOLOR, adxButtonColor);
ObjectSetInteger(0, adxButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, adxButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, adxButtonName, OBJPROP_XDISTANCE, startX + buttonWidth + buttonSpacing);
ObjectSetInteger(0, adxButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, adxButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, adxButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, adxButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, adxButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, adxButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, adxButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, adxButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, adxButtonName, OBJPROP_ZORDER, 1000);
// Stochastic Toggle Button
string stochButtonName = "Stoch_Toggle_Button";
string stochButtonText = "Stoch: " + (stochEnabled ? "ON" : "OFF");
color stochButtonColor = stochEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, stochButtonName) < 0) {
ObjectCreate(0, stochButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, stochButtonName, OBJPROP_TEXT, stochButtonText);
ObjectSetInteger(0, stochButtonName, OBJPROP_BGCOLOR, stochButtonColor);
ObjectSetInteger(0, stochButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, stochButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, stochButtonName, OBJPROP_XDISTANCE, startX + (buttonWidth + buttonSpacing) * 2);
ObjectSetInteger(0, stochButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, stochButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, stochButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, stochButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, stochButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, stochButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, stochButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, stochButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, stochButtonName, OBJPROP_ZORDER, 1000);
// MTF Apply to All Pairs Toggle Button
string mtfAllPairsButtonName = "MTF_AllPairs_Toggle_Button";
string mtfAllPairsButtonText = "MTF All: " + (mtfApplyToAllPairsEnabled ? "ON" : "OFF");
color mtfAllPairsButtonColor = mtfApplyToAllPairsEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, mtfAllPairsButtonName) < 0) {
ObjectCreate(0, mtfAllPairsButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, mtfAllPairsButtonName, OBJPROP_TEXT, mtfAllPairsButtonText);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_BGCOLOR, mtfAllPairsButtonColor);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_XDISTANCE, startX + (buttonWidth + buttonSpacing) * 3);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, mtfAllPairsButtonName, OBJPROP_ZORDER, 1000);
// Sideways Disable Trading Toggle Button
string sidewaysDisableButtonName = "Sideways_Disable_Toggle_Button";
string sidewaysDisableButtonText = "SDWY: " + (sidewaysDisableTradingEnabled ? "DISABLE" : "ENABLE");
color sidewaysDisableButtonColor = sidewaysDisableTradingEnabled ? clrRed : clrLimeGreen;
if(ObjectFind(0, sidewaysDisableButtonName) < 0) {
ObjectCreate(0, sidewaysDisableButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, sidewaysDisableButtonName, OBJPROP_TEXT, sidewaysDisableButtonText);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_BGCOLOR, sidewaysDisableButtonColor);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_XDISTANCE, startX + (buttonWidth + buttonSpacing) * 4);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, sidewaysDisableButtonName, OBJPROP_ZORDER, 1000);
// Breakout Confirmation Toggle Button
string breakoutButtonName = "Breakout_Toggle_Button";
string breakoutButtonText = "Breakout: " + (breakoutConfirmationEnabled ? "ON" : "OFF");
color breakoutButtonColor = breakoutConfirmationEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, breakoutButtonName) < 0) {
ObjectCreate(0, breakoutButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, breakoutButtonName, OBJPROP_TEXT, breakoutButtonText);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_BGCOLOR, breakoutButtonColor);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_XDISTANCE, startX + (buttonWidth + buttonSpacing) * 5);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, breakoutButtonName, OBJPROP_ZORDER, 1000);
// Engulfing Confirmation Toggle Button
string engulfingButtonName = "Engulfing_Toggle_Button";
string engulfingButtonText = "Engulfing: " + (engulfingConfirmationEnabled ? "ON" : "OFF");
color engulfingButtonColor = engulfingConfirmationEnabled ? clrLimeGreen : clrRed;
if(ObjectFind(0, engulfingButtonName) < 0) {
ObjectCreate(0, engulfingButtonName, OBJ_BUTTON, 0, 0, 0);
}
ObjectSetString(0, engulfingButtonName, OBJPROP_TEXT, engulfingButtonText);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_BGCOLOR, engulfingButtonColor);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_BORDER_COLOR, clrBlack);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_XDISTANCE, startX + (buttonWidth + buttonSpacing) * 6);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_YDISTANCE, buttonY);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_XSIZE, buttonWidth);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_YSIZE, buttonHeight);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_HIDDEN, false);
ObjectSetInteger(0, engulfingButtonName, OBJPROP_ZORDER, 1000);
ChartRedraw();
}
// Function to delete toggle buttons
void DeleteToggleButtons() {
ObjectDelete(0, "RSI_Toggle_Button");
ObjectDelete(0, "ADX_Toggle_Button");
ObjectDelete(0, "Stoch_Toggle_Button");
ObjectDelete(0, "MTF_AllPairs_Toggle_Button");
ObjectDelete(0, "Sideways_Disable_Toggle_Button");
ObjectDelete(0, "Breakout_Toggle_Button");
ObjectDelete(0, "Engulfing_Toggle_Button");
ChartRedraw();
}
// Function to handle button clicks
void HandleButtonClick(string objectName) {
if(objectName == "RSI_Toggle_Button") {
rsiEnabled = !rsiEnabled;
EssentialLog("🔄 RSI Toggle: " + (rsiEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "ADX_Toggle_Button") {
adxEnabled = !adxEnabled;
EssentialLog("🔄 ADX Toggle: " + (adxEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "Stoch_Toggle_Button") {
stochEnabled = !stochEnabled;
EssentialLog("🔄 Stochastic Toggle: " + (stochEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "MTF_AllPairs_Toggle_Button") {
mtfApplyToAllPairsEnabled = !mtfApplyToAllPairsEnabled;
EssentialLog("🔄 MTF Apply to All Pairs Toggle: " + (mtfApplyToAllPairsEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "Sideways_Disable_Toggle_Button") {
sidewaysDisableTradingEnabled = !sidewaysDisableTradingEnabled;
EssentialLog("🔄 Sideways Disable Trading Toggle: " + (sidewaysDisableTradingEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "Breakout_Toggle_Button") {
breakoutConfirmationEnabled = !breakoutConfirmationEnabled;
EssentialLog("🔄 Breakout Confirmation Toggle: " + (breakoutConfirmationEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
else if(objectName == "Engulfing_Toggle_Button") {
engulfingConfirmationEnabled = !engulfingConfirmationEnabled;
EssentialLog("🔄 Engulfing Confirmation Toggle: " + (engulfingConfirmationEnabled ? "ENABLED" : "DISABLED"));
CreateToggleButtons(); // Update button appearance
}
}
input ENUM_Mode Mode = MODE_SCALPING; // Mode Scalping, Intraday, Swing
input bool AutoTrade = true; // Auto Trade
input double RiskPercent = 1.0; // % equity per trade
input int Magic = 240812; // Magic Number`
// Multi-Timeframe Scanner
input bool EnableMTFScanner = true; // Enable MTF Scanner
input string PairsToScan = "EURUSD,GBPUSD,USDJPY,USDCHF,AUDUSD,NZDUSD,EURGBP,EURJPY"; // Pairs to scan
input int MaxPairsToShow = 8; // Max pairs to show
input group "=== VALIDATIONS ==="
input int EMA_Fast = 8; // EMA Fast
input int EMA_Slow = 13; // EMA Slow
input int RSI_Period = 10; // RSI Period (dinaikkan dari 8)
input int RSI_Overbought = 80; // RSI Overbought
input int RSI_Oversold = 20; // RSI Oversold
input int ADX_Period = 14; // ADX Period
input int ADX_MinStrength = 5; // ADX Min Strength (diturunkan dari 10 untuk lebih agresif)
input int ADX_MinStrength_Scalping = 3; // ADX Min Strength untuk Scalping Mode (diturunkan dari 8)
input int MinConfirmations_Scalping = 1; // Min Confirmations untuk Scalping (1 = lebih agresif)
input int MinConfirmations_Other = 1; // Min Confirmations untuk Mode Lain (diturunkan dari 2)
input int ATR_Period = 14; // ATR Period
input int Stochastic_K = 14; // Stochastic K
input int Stochastic_D = 3; // Stochastic D
input int Stochastic_Slow = 3; // Stochastic Slow
input int MaxSpreadPoints = 1000; // MaxSpread 1000 pt ~ 10 pips (5-digit) - lebih agresif
input group "=== SUPPORT & RESISTANCE ==="
input bool EnableSDDetection = true; // Enable S/D Detection
input int SD_Lookback = 100; // bars to look back
input int SD_MinTouch = 3; // minimum touches
input double SD_ZoneSize = 0.0010; // zone size in price
input color SD_SupplyColor = clrRed; // Supply Color
input color SD_DemandColor = clrGreen; // Demand Color
input group "=== SMART TP/SL ==="
input bool UseATR_TP_SL = true; // Use ATR TP/SL
input double ATR_SL_Multiplier = 1.5; // ATR SL Multiplier
input double ATR_TP_Multiplier = 2.0; // ATR TP Multiplier
input bool UseMultiTP = true; // Use Multi TP
input double TP1_Ratio = 0.5; // % of total TP
input double TP2_Ratio = 0.3; // % of total TP
input double TP3_Ratio = 0.2; // % of total TP
input group "=== TRAILING & LOCK PROFIT ==="
input int TrailStartPts = 150; // Trailing Start Points
input int TrailStepPts = 80; // Trailing Step Points
input int LockStartPts = 120; // when profit > this, lock
input int LockOffsetPts = 20; // lock distance from BE
// UseSpreadBuffer dihapus - sekarang selalu otomatis
// SpreadBufferMultiplier dihapus - sekarang otomatis dari spread realtime
// AutoCheckSpread dihapus - sekarang selalu otomatis
// MinStopMultiplier dihapus - sekarang otomatis dari spread dan broker stop level
input group "=== NEWS FILTER ==="
input bool NewsPauseEnable = true;
input datetime UpcomingNewsTime = D'1970.01.01 00:00'; // set manual
input int PauseBeforeMin = 15;
input int PauseAfterMin = 15;
input string HighImpactNews = "NFP,CPI,GDP,Interest Rate,Employment";
input group "=== SESSION TRADING ==="
input int TradeStartHour = 7; // broker time start
input int TradeEndHour = 22; // broker time
input bool EnableSessionFilter = true; // Enable Session Filter
input bool TradeAsia = true; // Trade Asia
input bool TradeLondon = true; // Trade London
input bool TradeNewYork = true; // Trade New York
input group "=== TRENDLINE RECOGNITION ==="
input bool EnableTrendlines = true; // Enable Trendline Recognition
input int TrendlineLookback = 50; // Trendline Lookback
input int TrendlineMinTouch = 2; // Trendline Min Touch
input color TrendlineColor = clrYellow; // Trendline Color
input group "=== TRADE JOURNAL ==="
input bool EnableTradeLog = true; // Enable Trade Log
input string LogFileName = "SmartBot_Trades.csv"; // Log File Name
input group "=== AI ASSIST ==="
input bool AI_Assist_Enable = false; // Enable AI Assist
input string AI_Endpoint_URL = ""; // contoh: http://127.0.0.1:8000/ai/trade
input string AI_API_Key = ""; // AI API Key
input int AI_TimeoutMs = 1200; // AI Timeout
input int AI_MaxChars = 600; // AI Max Chars
input bool AI_RequireApprove = false; // AI Require Approve
input group "=== DEEPSEEK AI ==="
input bool DeepSeek_Enable = false; // Enable DeepSeek AI
input string DeepSeek_API_Key = ""; // DeepSeek API Key
input string DeepSeek_Model = "deepseek-chat"; // DeepSeek Model
input int DeepSeek_Timeout = 5000; // DeepSeek Timeout (ms)
input int DeepSeek_MaxTokens = 500; // Max tokens for response
input bool DeepSeek_RequireApprove = true; // Require manual approval
input group "=== INDICATOR TOGGLE CONTROLS ==="
input bool EnableRSI = true; // Enable RSI Indicator
input bool EnableADX = true; // Enable ADX Indicator
input bool EnableStochastic = true; // Enable Stochastic Indicator
input bool ShowToggleButtons = true; // Show Toggle Buttons on Chart
input group "=== CHATGPT AI ==="
input bool ChatGPT_Enable = false; // Enable ChatGPT AI
input string ChatGPT_API_Key = ""; // ChatGPT API Key
input string ChatGPT_Model = "gpt-3.5-turbo"; // ChatGPT Model
input int ChatGPT_Timeout = 5000; // ChatGPT Timeout (ms)
input int ChatGPT_MaxTokens = 500; // Max tokens for response
input bool ChatGPT_RequireApprove = true; // Require manual approval
input group "=== RE-ENTRY MECHANISM ==="
input bool EnableReEntry = true; // Enable Re-Entry Mechanism
input int MaxReEntries = 3; // Maximum Re-Entries per direction
input double ReEntryLotMultiplier = 1.5; // Lot multiplier for re-entries
input int MinFloatingLossPts = 50; // Minimum floating loss points for re-entry
input double ConservativeTrailingMultiplier = 2.0; // Conservative trailing multiplier for profit protection
input bool UseConservativeTrailing = true; // Use conservative trailing to protect profits
input group "=== SIDEWAYS MARKET DETECTION ==="
input bool EnableSidewaysDetection = true; // Enable Sideways Market Detection
input int RSI_SidewaysUpper = 65; // RSI Upper bound for sideways
input int RSI_SidewaysLower = 35; // RSI Lower bound for sideways
input int ADX_SidewaysMax = 20; // ADX Max value for sideways (weak trend)
input int Stoch_SidewaysUpper = 70; // Stochastic Upper bound for sideways
input int Stoch_SidewaysLower = 30; // Stochastic Lower bound for sideways
input bool Sideways_DisableTrading = false; // Disable trading during sideways
input bool Sideways_UseRangeStrategy = true; // Use range strategy during sideways
input group "=== BREAKOUT & ENGULFING CONFIRMATION ==="
input bool EnableBreakoutConfirmation = true; // Enable Breakout Confirmation
input int BreakoutLookback = 20; // Bars to look back for S/R levels
input double BreakoutThreshold = 0.0001; // Minimum breakout distance
input int BreakoutConfirmationBars = 2; // Bars to confirm breakout
input bool RequireVolumeSpike = true; // Require volume spike on breakout
input double VolumeSpikeMultiplier = 1.5; // Volume spike threshold
input bool EnableEngulfingConfirmation = true; // Enable Engulfing Confirmation
input bool RequireStrongEngulfing = true; // Require strong engulfing (70%+)
input double EngulfingStrengthThreshold = 0.3; // Minimum engulfing strength (diturunkan untuk lebih fleksibel)
input bool CheckPreviousTrend = true; // Check previous trend direction
input int TrendLookback = 5; // Bars to check previous trend
input double MinEnhancedScore = 80.0; // Minimum enhanced score for entry
input group "=== DEBUG & LOGGING ==="
// ====== DEBUG & LOGGING ======
input bool EnableDebugLogs = false; // Enable verbose debug logging
input bool EnableEssentialLogs = true; // Enable essential logs (always on)
//==================== Globals ====================
double pt;
int hEmaF=-1,hEmaS=-1,hRsi=-1,hAdx=-1,hAtr=-1,hStoch=-1;
int hVolume=-1;
//==================== Helper Functions ====================
void DebugLog(string message) {
if(EnableDebugLogs) {
Print("[DEBUG] ", message);
}
}
void EssentialLog(string message) {
if(EnableEssentialLogs) {
Print("[INFO] ", message);
}
}
// Forward declarations
struct SignalPack;
bool ValidateSignalWithMTF(SignalPack &s);
//==================== AUTO SPREAD & BROKER ADJUSTMENT ====================
// Semua pengaturan otomatis berdasarkan spread realtime dan broker stop level
// Tidak perlu deteksi broker manual - semua dihitung otomatis
// Calculate dynamic spread buffer based on current spread (AUTO)
double CalculateDynamicSpreadBuffer() {
int currentSpread = SpreadPoints();
// Auto buffer berbasis spread saat ini
double dynamicBuffer = 1.5; // Base multiplier
if(currentSpread > 100) dynamicBuffer *= 1.5; // instrumen spread tinggi (mis. XAU)
else if(currentSpread > 50) dynamicBuffer *= 1.2; // spread menengah
else if(currentSpread < 10) dynamicBuffer *= 0.8; // spread sangat rendah
DebugLog("📊 Auto Spread Buffer: Current=" + IntegerToString(currentSpread) +
", Buffer=" + DoubleToString(dynamicBuffer, 2));
return dynamicBuffer;
}
// Get adjusted trailing step based on spread (AUTO)
int GetAdjustedTrailingStep(int baseTrailingStep) {
int spreadPts = SpreadPoints();
double dynamicBuffer = CalculateDynamicSpreadBuffer();
double adjustedStep = MathMax((double)baseTrailingStep, spreadPts * dynamicBuffer);
if(UseConservativeTrailing) adjustedStep *= ConservativeTrailingMultiplier;
// Minimal sesuai broker stop level
int minStepPts = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(adjustedStep < minStepPts) adjustedStep = minStepPts;
DebugLog("🎯 Auto Trailing Step: Base=" + IntegerToString(baseTrailingStep) +
", Spread=" + IntegerToString(spreadPts) +
", Buffer=" + DoubleToString(dynamicBuffer, 2) +
", Adjusted=" + IntegerToString((int)adjustedStep) +
", Cons=" + (UseConservativeTrailing ? "ON" : "OFF"));
return (int)adjustedStep;
}
// Get adjusted stop distance based on spread (AUTO)
int GetAdjustedStopDistance(int baseStopDistance) {
int spreadPts = SpreadPoints();
int brokerMinPts = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double dynamicBuffer = CalculateDynamicSpreadBuffer();
int adjusted = MathMax(baseStopDistance, brokerMinPts);
adjusted = MathMax(adjusted, (int)(spreadPts * dynamicBuffer));
DebugLog("🛑 Auto Stop Distance: Base=" + IntegerToString(baseStopDistance) +
", Spread=" + IntegerToString(spreadPts) +
", BrokerMin=" + IntegerToString(brokerMinPts) +
", Buffer=" + DoubleToString(dynamicBuffer, 2) +
", Adjusted=" + IntegerToString(adjusted));
return adjusted;
}
// Check if current spread is acceptable for trading (AUTO)
bool IsSpreadAcceptable() {
int currentSpread = SpreadPoints();
bool acceptable = currentSpread <= MaxSpreadPoints;
if(!acceptable) {
DebugLog("⚠️ Auto Spread Check: Too High " + IntegerToString(currentSpread) +
" > " + IntegerToString(MaxSpreadPoints));
}
return acceptable;
}
// Calculate safe trailing stop distance to protect profits
double CalculateSafeTrailingStop(double entryPrice, double currentPrice, int positionType, double minDistance) {
double safeDistance = minDistance;
// Calculate profit in points
double profitPoints = 0;
if(positionType == POSITION_TYPE_BUY) {
profitPoints = (currentPrice - entryPrice) / _Point;
} else {
profitPoints = (entryPrice - currentPrice) / _Point;
}
// If we have significant profit, use more conservative distance
if(profitPoints > 100) { // More than 100 points profit
safeDistance = MathMax(safeDistance, profitPoints * 0.3); // Keep at least 30% of profit
} else if(profitPoints > 50) { // More than 50 points profit
safeDistance = MathMax(safeDistance, profitPoints * 0.4); // Keep at least 40% of profit
} else if(profitPoints > 20) { // More than 20 points profit
safeDistance = MathMax(safeDistance, profitPoints * 0.5); // Keep at least 50% of profit
}
// Add extra buffer for high-spread instruments like XAUUSD
if(SpreadPoints() > 100) {
safeDistance += 20; // Add 20 points extra buffer
}
DebugLog("🛡️ Safe Trailing Distance: Profit=" + DoubleToString(profitPoints, 1) +
"pts, Min=" + DoubleToString(minDistance, 1) +
"pts, Safe=" + DoubleToString(safeDistance, 1) + "pts");
return safeDistance;
}
// Supply & Demand zones
struct SDZone {
double price;
double high, low;
int touches;
bool isSupply;
datetime lastTouch;
string name;
};
SDZone sdZones[];
int sdZoneCount = 0;
// Trendlines
struct Trendline {
double startPrice, endPrice;
datetime startTime, endTime;
bool isUptrend;
string name;
int touches;
};
Trendline trendlines[];
int trendlineCount = 0;
// Trade Journal
struct TradeRecord {
datetime openTime;
string pair;
int type;
double lot, openPrice, sl, tp;
string reason;
double closePrice;
datetime closeTime;
double profit;
string notes;
};
TradeRecord tradeHistory[];
int tradeHistoryCount = 0;
//==================== Utils ====================
int SpreadPoints() { return (int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); }
//==================== Breakout Detection Functions ====================
// Find Support/Resistance levels
void FindSRLevels() {
if(!EnableBreakoutConfirmation) return;
ArrayResize(srLevels, 0);
srLevelCount = 0;
double high[], low[], close[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
// Increased lookback for better S/R detection
int lookback = MathMax(BreakoutLookback, 50);
if(CopyHigh(_Symbol, _Period, 0, lookback, high) < lookback) return;
if(CopyLow(_Symbol, _Period, 0, lookback, low) < lookback) return;
if(CopyClose(_Symbol, _Period, 0, lookback, close) < lookback) return;
// More flexible threshold for S/R detection
double threshold = MathMax(BreakoutThreshold, 10 * pt);
// Find resistance levels (highs) - more flexible detection
for(int i = 3; i < lookback-3; i++) {
// Check if this is a significant high (more flexible)
bool isHigh = true;
for(int j = 1; j <= 2; j++) {
if(high[i] <= high[i-j] || high[i] <= high[i+j]) {
isHigh = false;
break;
}
}
if(isHigh) {
// Check for touches with more flexible threshold
int touches = 0;
for(int j = 0; j < lookback; j++) {
if(MathAbs(high[j] - high[i]) <= threshold * 2) { // Double threshold for touch detection
touches++;
}
}
if(touches >= 2) { // Minimum 2 touches
ArrayResize(srLevels, srLevelCount + 1);
srLevels[srLevelCount].price = high[i];
srLevels[srLevelCount].strength = touches;
srLevels[srLevelCount].lastTouch = iTime(_Symbol, _Period, i);
srLevels[srLevelCount].isResistance = true;
srLevels[srLevelCount].barIndex = i;
srLevelCount++;
}
}
}
// Find support levels (lows) - more flexible detection
for(int i = 3; i < lookback-3; i++) {
// Check if this is a significant low (more flexible)
bool isLow = true;
for(int j = 1; j <= 2; j++) {
if(low[i] >= low[i-j] || low[i] >= low[i+j]) {
isLow = false;
break;
}
}
if(isLow) {
// Check for touches with more flexible threshold
int touches = 0;
for(int j = 0; j < lookback; j++) {
if(MathAbs(low[j] - low[i]) <= threshold * 2) { // Double threshold for touch detection
touches++;
}
}
if(touches >= 2) { // Minimum 2 touches
ArrayResize(srLevels, srLevelCount + 1);
srLevels[srLevelCount].price = low[i];
srLevels[srLevelCount].strength = touches;
srLevels[srLevelCount].lastTouch = iTime(_Symbol, _Period, i);
srLevels[srLevelCount].isResistance = false;
srLevels[srLevelCount].barIndex = i;
srLevelCount++;
}
}
}
DebugLog("🔍 Found " + IntegerToString(srLevelCount) + " S/R levels (optimized detection)");
}
// Find nearest S/R level
SRLevel FindNearestSRLevel(int direction) {
SRLevel nearest;
nearest.price = 0;
nearest.strength = 0;
nearest.isResistance = false;
nearest.barIndex = -1;
if(srLevelCount == 0) return nearest;
double currentPrice = (direction == BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
double minDistance = 999999;
double maxDistance = 100 * pt; // Maximum distance to consider (100 points)
for(int i = 0; i < srLevelCount; i++) {
double distance = MathAbs(currentPrice - srLevels[i].price);
// Only consider levels within reasonable distance
if(distance > maxDistance) continue;
// For BUY, look for resistance levels above current price
if(direction == BUY && srLevels[i].isResistance && srLevels[i].price > currentPrice) {
if(distance < minDistance) {
minDistance = distance;
nearest = srLevels[i];
}
}
// For SELL, look for support levels below current price
else if(direction == SELL && !srLevels[i].isResistance && srLevels[i].price < currentPrice) {
if(distance < minDistance) {
minDistance = distance;
nearest = srLevels[i];
}
}
}
return nearest;
}
// Check if price broke through S/R level
bool IsBreakoutConfirmed(int direction) {
// Skip jika bukan timeframe yang tepat
if(!ShouldApplyBreakoutConfirmation()) return true;
// Hanya cek pada M1 dan M5
if(!IsEntryTimeframe() && !IsSetupTimeframe()) return true;
// Find S/R levels
FindSRLevels();
// Find nearest S/R level
SRLevel nearestLevel = FindNearestSRLevel(direction);
if(nearestLevel.barIndex == -1) {
DebugLog("🔍 No S/R level found for " + (direction == BUY ? "BUY" : "SELL") + " direction");
return true; // Allow entry if no S/R level found
}
double currentPrice = (direction == BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
// More flexible breakout threshold
double breakoutThreshold = MathMax(BreakoutThreshold, 5 * pt);
// Check if price broke through
bool priceBreakout = false;
if(direction == BUY) {
priceBreakout = (currentPrice > nearestLevel.price + breakoutThreshold);
} else {
priceBreakout = (currentPrice < nearestLevel.price - breakoutThreshold);
}
if(!priceBreakout) {
DebugLog("🔍 No price breakout - Current: " + DoubleToString(currentPrice, 5) +
" Level: " + DoubleToString(nearestLevel.price, 5) +
" Threshold: " + DoubleToString(breakoutThreshold, 5));
return false;
}
// Simplified confirmation bars check
bool confirmationBars = CheckBreakoutConfirmationBars(direction, nearestLevel.price);
// Volume spike is optional and less strict
bool volumeSpike = true; // Default to true
if(RequireVolumeSpike) {
volumeSpike = CheckVolumeSpike();
}
bool result = confirmationBars && volumeSpike;
DebugLog("🔍 Breakout result: " + (result ? "CONFIRMED" : "REJECTED") +
" - Price: " + (priceBreakout ? "YES" : "NO") +
" Bars: " + (confirmationBars ? "YES" : "NO") +
" Volume: " + (volumeSpike ? "YES" : "NO"));
return result;
}
//==================== Engulfing Pattern Detection Functions ====================
// Detect engulfing patterns with direction alignment
EngulfingPattern DetectEngulfingPattern(int direction) {
EngulfingPattern pattern;
pattern.type = NO_ENGULFING;
pattern.strength = 0.0;
pattern.isValid = false;
pattern.reason = "No pattern detected";
pattern.barIndex = 0;
// Skip jika bukan timeframe yang tepat
if(!ShouldApplyEngulfingConfirmation()) {
pattern.isValid = true;
pattern.reason = "Engulfing confirmation disabled for this timeframe";
return pattern;
}
// Hanya cek pada M1 dan M5
if(!IsEntryTimeframe() && !IsSetupTimeframe()) {
pattern.isValid = true;
pattern.reason = "Not entry/setup timeframe";
return pattern;
}
if(!EnableEngulfingConfirmation || !engulfingConfirmationEnabled) {
pattern.isValid = true;
pattern.reason = "Engulfing confirmation disabled";
return pattern;
}
double open[], high[], low[], close[];
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
if(CopyOpen(_Symbol, _Period, 0, 3, open) < 3) return pattern;
if(CopyHigh(_Symbol, _Period, 0, 3, high) < 3) return pattern;
if(CopyLow(_Symbol, _Period, 0, 3, low) < 3) return pattern;
if(CopyClose(_Symbol, _Period, 0, 3, close) < 3) return pattern;
// Check pattern sesuai dengan direction yang diminta
if(direction == BUY) {
// Untuk BUY signal, cari bullish patterns
if(IsBullishEngulfing(open, high, low, close)) {
pattern.type = BULLISH_ENGULFING;
pattern.strength = CalculateEngulfingStrength(BUY, open, high, low, close);
pattern.isValid = (pattern.strength >= EngulfingStrengthThreshold);
pattern.reason = "Bullish Engulfing - Strength: " + DoubleToString(pattern.strength, 2) + " (Min: " + DoubleToString(EngulfingStrengthThreshold, 2) + ")";
pattern.barIndex = 0;
DebugLog("🟢 BUY Direction - Bullish Engulfing detected - Strength: " + DoubleToString(pattern.strength, 2) + " Valid: " + (pattern.isValid ? "YES" : "NO"));
}
// Hammer juga bullish pattern
else if(IsHammerEngulfing(open, high, low, close)) {
pattern.type = HAMMER_ENGULFING;
pattern.strength = 0.6; // Medium-high strength for hammer
pattern.isValid = true;
pattern.reason = "Hammer Engulfing (Bullish) - Medium-high strength";
pattern.barIndex = 0;
DebugLog("🟢 BUY Direction - Hammer Engulfing detected - Valid: YES");
}
// Doji bisa bullish jika dalam uptrend
else if(IsDojiEngulfing(open, high, low, close)) {
pattern.type = DOJI_ENGULFING;
pattern.strength = 0.4; // Lower strength for doji in buy direction
pattern.isValid = true;
pattern.reason = "Doji Engulfing (Bullish) - Medium strength";
pattern.barIndex = 0;
DebugLog("🟢 BUY Direction - Doji Engulfing detected - Valid: YES");
}
} else if(direction == SELL) {
// Untuk SELL signal, cari bearish patterns
if(IsBearishEngulfing(open, high, low, close)) {
pattern.type = BEARISH_ENGULFING;
pattern.strength = CalculateEngulfingStrength(SELL, open, high, low, close);
pattern.isValid = (pattern.strength >= EngulfingStrengthThreshold);
pattern.reason = "Bearish Engulfing - Strength: " + DoubleToString(pattern.strength, 2) + " (Min: " + DoubleToString(EngulfingStrengthThreshold, 2) + ")";
pattern.barIndex = 0;
DebugLog("🔴 SELL Direction - Bearish Engulfing detected - Strength: " + DoubleToString(pattern.strength, 2) + " Valid: " + (pattern.isValid ? "YES" : "NO"));
}
// Inverted Hammer bisa bearish pattern
else if(IsInvertedHammerEngulfing(open, high, low, close)) {
pattern.type = HAMMER_ENGULFING;
pattern.strength = 0.6; // Medium-high strength for inverted hammer
pattern.isValid = true;
pattern.reason = "Inverted Hammer Engulfing (Bearish) - Medium-high strength";
pattern.barIndex = 0;
DebugLog("🔴 SELL Direction - Inverted Hammer Engulfing detected - Valid: YES");
}
// Doji bisa bearish jika dalam downtrend
else if(IsDojiEngulfing(open, high, low, close)) {
pattern.type = DOJI_ENGULFING;
pattern.strength = 0.4; // Lower strength for doji in sell direction
pattern.isValid = true;
pattern.reason = "Doji Engulfing (Bearish) - Medium strength";
pattern.barIndex = 0;
DebugLog("🔴 SELL Direction - Doji Engulfing detected - Valid: YES");
}
}
// Debug logging jika tidak ada pattern yang terdeteksi
if(pattern.type == NO_ENGULFING) {
string directionStr = (direction == BUY) ? "BUY" : "SELL";
DebugLog("🔍 No " + directionStr + " engulfing pattern detected - Current candle analysis completed");
}
return pattern;
}
// Check for Bullish Engulfing (more flexible)
bool IsBullishEngulfing(const double &open[], const double &high[], const double &low[], const double &close[]) {
// Current candle (index 0) must be bullish
if(close[0] <= open[0]) return false;
// Previous candle (index 1) must be bearish
if(close[1] >= open[1]) return false;
// Current candle must engulf previous candle body
bool bodyEngulfing = (open[0] < close[1] && close[0] > open[1]);
// More flexible: also check if current candle is significantly larger
double currentBody = close[0] - open[0];
double previousBody = open[1] - close[1]; // Previous was bearish
bool sizeEngulfing = (currentBody > previousBody * 1.5); // 50% larger
// Optional: Check if current candle also engulfs the high and low
bool fullEngulfing = (low[0] <= low[1] && high[0] >= high[1]);
return bodyEngulfing || sizeEngulfing || fullEngulfing;
}
// Check for Bearish Engulfing (more flexible)
bool IsBearishEngulfing(const double &open[], const double &high[], const double &low[], const double &close[]) {
// Current candle (index 0) must be bearish
if(close[0] >= open[0]) return false;
// Previous candle (index 1) must be bullish
if(close[1] <= open[1]) return false;
// Current candle must engulf previous candle body
bool bodyEngulfing = (open[0] > close[1] && close[0] < open[1]);
// More flexible: also check if current candle is significantly larger
double currentBody = open[0] - close[0];
double previousBody = close[1] - open[1]; // Previous was bullish
bool sizeEngulfing = (currentBody > previousBody * 1.5); // 50% larger
// Optional: Check if current candle also engulfs the high and low
bool fullEngulfing = (low[0] <= low[1] && high[0] >= high[1]);
return bodyEngulfing || sizeEngulfing || fullEngulfing;
}
// Check for Doji Engulfing
bool IsDojiEngulfing(const double &open[], const double &high[], const double &low[], const double &close[]) {
// Current candle must be a doji (very small body)
double bodySize = MathAbs(close[0] - open[0]);
double totalRange = high[0] - low[0];
if(totalRange == 0) return false;
double bodyRatio = bodySize / totalRange;
if(bodyRatio > 0.1) return false; // Body must be less than 10% of total range
// Previous candle must have a significant body
double prevBodySize = MathAbs(close[1] - open[1]);
double prevTotalRange = high[1] - low[1];
if(prevTotalRange == 0) return false;
double prevBodyRatio = prevBodySize / prevTotalRange;
if(prevBodyRatio < 0.3) return false; // Previous body must be at least 30%
return true;
}
// Check for Hammer Engulfing (Bullish)
bool IsHammerEngulfing(const double &open[], const double &high[], const double &low[], const double &close[]) {
// Current candle must be bullish
if(close[0] <= open[0]) return false;
double bodySize = MathAbs(close[0] - open[0]);
double totalRange = high[0] - low[0];
if(totalRange == 0) return false;
// Lower shadow must be at least 2x the body size
double lowerShadow = MathMin(open[0], close[0]) - low[0];
if(lowerShadow < bodySize * 2) return false;
// Upper shadow should be small
double upperShadow = high[0] - MathMax(open[0], close[0]);
if(upperShadow > bodySize * 0.5) return false;
return true;
}
// Check for Inverted Hammer Engulfing (Bearish)
bool IsInvertedHammerEngulfing(const double &open[], const double &high[], const double &low[], const double &close[]) {
// Current candle must be bearish
if(close[0] >= open[0]) return false;
double bodySize = MathAbs(close[0] - open[0]);
double totalRange = high[0] - low[0];
if(totalRange == 0) return false;
// Upper shadow must be at least 2x the body size
double upperShadow = high[0] - MathMax(open[0], close[0]);
if(upperShadow < bodySize * 2) return false;
// Lower shadow should be small
double lowerShadow = MathMin(open[0], close[0]) - low[0];
if(lowerShadow > bodySize * 0.5) return false;
return true;
}
// Calculate engulfing strength (more flexible)
double CalculateEngulfingStrength(int direction, const double &open[], const double &high[], const double &low[], const double &close[]) {
double currentBody = MathAbs(close[0] - open[0]);
double previousBody = MathAbs(close[1] - open[1]);
if(previousBody == 0) return 0.0;
// Calculate how much the current candle engulfs the previous one
double engulfingRatio = currentBody / previousBody;
// More flexible normalization: 1.0x = 50% strength, 2.0x = 75% strength, 3.0x = 100% strength
double strength = 0.0;
if(engulfingRatio >= 1.0) {
strength = 0.5 + (engulfingRatio - 1.0) * 0.25; // 1.0x = 50%, 2.0x = 75%, 3.0x = 100%
} else if(engulfingRatio >= 0.8) {
strength = engulfingRatio * 0.625; // 0.8x = 50%
} else {
strength = engulfingRatio * 0.5; // Linear scaling for smaller ratios
}
// Additional strength for full engulfing (high and low)
if(high[0] >= high[1] && low[0] <= low[1]) {
strength += 0.15; // Bonus for full engulfing (dikurangi dari 0.2)
}
// Check previous trend if enabled
if(CheckPreviousTrend) {
bool trendAligned = CheckPreviousTrendAlignment(direction);
if(trendAligned) {
strength += 0.1; // Bonus for trend alignment
}
}
DebugLog("🔍 Engulfing Strength Calc: Ratio=" + DoubleToString(engulfingRatio, 2) +
" Base=" + DoubleToString(strength, 2) +
" Full=" + ((high[0] >= high[1] && low[0] <= low[1]) ? "YES" : "NO") +
" Trend=" + (CheckPreviousTrend ? (CheckPreviousTrendAlignment(direction) ? "ALIGNED" : "NOT_ALIGNED") : "DISABLED"));
return MathMin(strength, 1.0); // Cap at 1.0
}
//==================== Timeframe-Specific Functions ====================
// Konfirmasi hanya pada timeframe entry (M1/M5)
bool IsEntryTimeframe() {
return (_Period == PERIOD_M1 || _Period == PERIOD_M5);
}
// Konfirmasi hanya pada timeframe setup (M5)
bool IsSetupTimeframe() {
return (_Period == PERIOD_M5);
}
// Konfirmasi hanya pada timeframe trend (H1)
bool IsTrendTimeframe() {
return (_Period == PERIOD_H1);
}
// Conditional confirmation logic
bool ShouldApplyBreakoutConfirmation() {
// Breakout hanya pada timeframe entry dan setup
return (EnableBreakoutConfirmation && breakoutConfirmationEnabled &&
(IsEntryTimeframe() || IsSetupTimeframe()));
}
bool ShouldApplyEngulfingConfirmation() {
// Engulfing hanya pada timeframe entry dan setup
return (EnableEngulfingConfirmation && engulfingConfirmationEnabled &&
(IsEntryTimeframe() || IsSetupTimeframe()));
}
// Cached detection untuk performance
bool IsBreakoutConfirmedCached(int direction) {
// Check cache validity (5 seconds)
if(TimeCurrent() - tfCache.lastCheck < 5) {
return tfCache.breakoutValid;
}
// Perform fresh detection
bool result = IsBreakoutConfirmed(direction);
// Update cache
tfCache.lastCheck = TimeCurrent();
tfCache.breakoutValid = result;
return result;
}
// Cached engulfing detection untuk performance
EngulfingPattern DetectEngulfingPatternCached(int direction) {
// Check cache validity (5 seconds) - but only if direction matches
if(TimeCurrent() - tfCache.lastEngulfingCheck < 5 && tfCache.lastEngulfingDirection == direction) {
// Return cached result if available
EngulfingPattern cachedPattern;
cachedPattern.type = tfCache.lastEngulfingType;
cachedPattern.isValid = tfCache.engulfingValid;
cachedPattern.strength = tfCache.engulfingStrength;
cachedPattern.reason = tfCache.engulfingReason;
cachedPattern.barIndex = 0;
return cachedPattern;
}
// Perform fresh detection
EngulfingPattern result = DetectEngulfingPattern(direction);
// Update cache
tfCache.lastEngulfingCheck = TimeCurrent();
tfCache.lastEngulfingDirection = direction;
tfCache.engulfingValid = result.isValid;
tfCache.lastEngulfingType = result.type;
tfCache.engulfingStrength = result.strength;
tfCache.engulfingReason = result.reason;
return result;
}
//==================== Enhanced Signal Strength Calculation ====================
// Log enhanced entry decisions
void LogEnhancedEntryDecision(const SignalPack &sp, int direction) {
string directionStr = (direction == BUY) ? "BUY" : "SELL";
EssentialLog("🎯 Enhanced Entry Decision - " + directionStr);
EssentialLog(" Base Score: " + DoubleToString(sp.signalStrength, 1));
EssentialLog(" Breakout: " + (sp.breakoutConfirmed ? "YES" : "NO") +
" (Strength: " + DoubleToString(sp.breakoutStrength, 2) + ")");
EssentialLog(" Engulfing: " + (sp.engulfingConfirmed ? "YES" : "NO") +
" (Strength: " + DoubleToString(sp.engulfingStrength, 2) + ")");
EssentialLog(" Total Score: " + DoubleToString(sp.totalConfirmationScore, 1));
EssentialLog(" Decision: " + (sp.totalConfirmationScore >= MinEnhancedScore ? "APPROVED" : "REJECTED"));
}
// Calculate enhanced signal strength with breakout and engulfing confirmations
void CalculateEnhancedSignalStrength(SignalPack &sp) {
double baseScore = sp.signalStrength;
double breakoutBonus = 0;
double engulfingBonus = 0;
// Breakout Bonus (0-30 points)
if(sp.breakoutConfirmed) {
breakoutBonus = 30 * sp.breakoutStrength;
}
// Engulfing Bonus (0-25 points)
if(sp.engulfingConfirmed) {
engulfingBonus = 25 * sp.engulfingStrength;
}
sp.totalConfirmationScore = baseScore + breakoutBonus + engulfingBonus;
DebugLog("🎯 Enhanced Score: Base=" + DoubleToString(baseScore, 1) +
" + Breakout=" + DoubleToString(breakoutBonus, 1) +
" + Engulfing=" + DoubleToString(engulfingBonus, 1) +
" = Total=" + DoubleToString(sp.totalConfirmationScore, 1));
}
// Enhanced entry validation
bool IsEnhancedEntryValid(const SignalPack &sp, int direction) {
// Base conditions - calculate minConfirmations based on mode
int minConfirmations = (Mode == MODE_SCALPING ? MinConfirmations_Scalping : MinConfirmations_Other);
bool baseConditions = (sp.confirmationCount >= minConfirmations);
// Breakout confirmation
bool breakoutOK = !EnableBreakoutConfirmation || !breakoutConfirmationEnabled || sp.breakoutConfirmed;
// Engulfing confirmation
bool engulfingOK = !EnableEngulfingConfirmation || !engulfingConfirmationEnabled || sp.engulfingConfirmed;
// Minimum total score
bool scoreOK = (sp.totalConfirmationScore >= MinEnhancedScore);
return baseConditions && breakoutOK && engulfingOK && scoreOK;
}
// Check previous trend alignment
bool CheckPreviousTrendAlignment(int direction) {
double close[];
ArraySetAsSeries(close, true);
if(CopyClose(_Symbol, _Period, 0, TrendLookback + 1, close) < TrendLookback + 1) {
return false;
}
// Calculate trend direction
double trendStart = close[TrendLookback];
double trendEnd = close[1]; // Previous candle
if(direction == BUY) {
return (trendEnd > trendStart); // Uptrend for bullish engulfing
} else {
return (trendEnd < trendStart); // Downtrend for bearish engulfing
}
}
// Check breakout confirmation bars (simplified)
bool CheckBreakoutConfirmationBars(int direction, double levelPrice) {
double close[];
ArraySetAsSeries(close, true);
if(CopyClose(_Symbol, _Period, 0, 3, close) < 3) return true; // Default to true if data unavailable
// Simplified confirmation: just check if current close is beyond the level
if(direction == BUY) {
return (close[0] > levelPrice);
} else {
return (close[0] < levelPrice);
}
}
// Check volume spike (simplified)
bool CheckVolumeSpike() {
if(!RequireVolumeSpike) return true;
long volume[];
ArraySetAsSeries(volume, true);
if(CopyTickVolume(_Symbol, _Period, 0, 5, volume) < 5) return true; // Default to true if data unavailable
// Calculate average volume
long avgVolume = 0;
for(int i = 1; i < 5; i++) {
avgVolume += volume[i];
}
avgVolume /= 4;
// Check if current volume is higher than average
bool volumeSpike = (volume[0] > avgVolume * 1.2); // 20% higher than average
DebugLog("🔍 Volume spike: " + (volumeSpike ? "YES" : "NO") +
" - Current: " + IntegerToString(volume[0]) +
" Average: " + IntegerToString(avgVolume));
return volumeSpike;
}
//==================== Sideways Market Detection ====================
// Detect sideways market condition based on RSI, ADX, and Stochastic
bool DetectSidewaysMarket() {
if(!EnableSidewaysDetection) return false;
// Check if we need to update (every 5 seconds)
if(TimeCurrent() - lastSidewaysCheck < 5) {
return isSidewaysMarket;
}
lastSidewaysCheck = TimeCurrent();
// Get current indicator values
double rsi = 0, adx = 0, stoch_k = 0, stoch_d = 0;
GetRSI(_Symbol, _Period, RSI_Period, rsi);
GetADXv(_Symbol, _Period, ADX_Period, adx);
GetStoch(_Symbol, _Period, stoch_k, stoch_d);
// Initialize confidence and reason
int confidence = 0;
string localReason = "";
// RSI Sideways Check (40% weight)
bool rsi_sideways = (rsi >= RSI_SidewaysLower && rsi <= RSI_SidewaysUpper);
if(rsi_sideways) {
confidence += 40;
localReason += "RSI(" + DoubleToString(rsi, 1) + ") ";
}
// ADX Sideways Check (35% weight) - weak trend
bool adx_sideways = (adx <= ADX_SidewaysMax);
if(adx_sideways) {
confidence += 35;
localReason += "ADX(" + DoubleToString(adx, 1) + ") ";
}
// Stochastic Sideways Check (25% weight)
bool stoch_sideways = (stoch_k >= Stoch_SidewaysLower && stoch_k <= Stoch_SidewaysUpper);
if(stoch_sideways) {
confidence += 25;
localReason += "Stoch(" + DoubleToString(stoch_k, 1) + ") ";
}
// Update global variables
sidewaysConfidence = confidence;
sidewaysReason = localReason;
// Market is considered sideways if confidence >= 70%
bool newSidewaysStatus = (confidence >= 70);
// Log status change
if(newSidewaysStatus != isSidewaysMarket) {
if(newSidewaysStatus) {
EssentialLog("🔄 Sideways Market DETECTED - Confidence: " + IntegerToString(confidence) + "% | " + localReason);
} else {
EssentialLog("🔄 Sideways Market ENDED - Confidence: " + IntegerToString(confidence) + "% | " + localReason);
}
}
isSidewaysMarket = newSidewaysStatus;
return isSidewaysMarket;
}
// Get sideways market status
bool IsSidewaysMarket() {
return DetectSidewaysMarket();
}
// Get sideways confidence level
int GetSidewaysConfidence() {
DetectSidewaysMarket();
return sidewaysConfidence;
}
// Get sideways reason
string GetSidewaysReason() {
DetectSidewaysMarket();
return sidewaysReason;
}
//==================== Re-Entry Functions ====================
// Check if there are floating loss positions in a specific direction with progressive distance
bool HasFloatingLossPositions(int direction) {
if(!EnableReEntry) return false;
int currentReEntryCount = GetReEntryCount(direction);
if(currentReEntryCount >= MaxReEntries) {
EssentialLog("⚠️ Re-Entry: Maximum re-entries (" + IntegerToString(MaxReEntries) + ") reached for " +
(direction == POSITION_TYPE_BUY ? "BUY" : "SELL") + " direction");
return false;
}
// Calculate required floating loss points based on re-entry count
// Re-entry 1: MinFloatingLossPts (e.g., 200 points)
// Re-entry 2: MinFloatingLossPts * 2 (e.g., 400 points)
// Re-entry 3: MinFloatingLossPts * 3 (e.g., 600 points)
int requiredLossPoints = MinFloatingLossPts * (currentReEntryCount + 1);
for(int i = 0; i < PositionsTotal(); i++) {
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == Magic) {
int posType = (int)PositionGetInteger(POSITION_TYPE);
double posProfit = PositionGetDouble(POSITION_PROFIT);
// Check if position is in the same direction and has floating loss
if(posType == direction && posProfit < 0) {
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = (direction == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int lossPoints = (int)MathAbs((currentPrice - openPrice) / pt);
if(lossPoints >= requiredLossPoints) {
EssentialLog("💰 Re-Entry: Found floating loss position - Direction: " +
(direction == POSITION_TYPE_BUY ? "BUY" : "SELL") +
" Re-Entry #" + IntegerToString(currentReEntryCount + 1) +
" Loss: " + DoubleToString(posProfit, 2) +
" Points: " + IntegerToString(lossPoints) +
" Required: " + IntegerToString(requiredLossPoints));
return true;
}
}
}
}
return false;
}
// Get current re-entry count for a direction
int GetReEntryCount(int direction) {
return (direction == POSITION_TYPE_BUY) ? buyReEntryCount : sellReEntryCount;
}
// Check if re-entry is allowed for a direction
bool IsReEntryAllowed(int direction) {
if(!EnableReEntry) return false;
int currentCount = GetReEntryCount(direction);
if(currentCount >= MaxReEntries) {
EssentialLog("⚠️ Re-Entry: Maximum re-entries (" + IntegerToString(MaxReEntries) + ") reached for " +
(direction == POSITION_TYPE_BUY ? "BUY" : "SELL") +
" direction. Count: " + IntegerToString(currentCount));
return false;
}
return true;
}
// Calculate lot size for re-entry with progressive multiplier
double CalculateReEntryLot(double baseLot, int direction) {
if(!EnableReEntry) return baseLot;
int currentReEntryCount = GetReEntryCount(direction);
// Calculate progressive lot multiplier
// Re-entry 1: ReEntryLotMultiplier^1 (e.g., 1.5)
// Re-entry 2: ReEntryLotMultiplier^2 (e.g., 2.25)
// Re-entry 3: ReEntryLotMultiplier^3 (e.g., 3.375)
double progressiveMultiplier = MathPow(ReEntryLotMultiplier, currentReEntryCount + 1);
double reEntryLot = baseLot * progressiveMultiplier;
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
// Ensure lot size is within valid range
reEntryLot = MathMax(minLot, MathMin(maxLot, reEntryLot));
// Round to nearest lot step
reEntryLot = MathRound(reEntryLot / lotStep) * lotStep;
EssentialLog("💰 Re-Entry: Calculated lot size - Direction: " +
(direction == POSITION_TYPE_BUY ? "BUY" : "SELL") +
" Re-Entry #" + IntegerToString(currentReEntryCount + 1) +
" Base: " + DoubleToString(baseLot, 2) +
" Multiplier: " + DoubleToString(progressiveMultiplier, 3) +
" Re-Entry: " + DoubleToString(reEntryLot, 2));
return reEntryLot;
}
// Check and reset re-entry counters when positions are closed
void CheckAndResetReEntryCounters() {
if(!EnableReEntry) return;
// Check if there are any BUY positions
int buyPositions = CountPositions(ORDER_TYPE_BUY);
if(buyPositions == 0 && buyReEntryCount > 0) {
EssentialLog("💰 Re-Entry: All BUY positions closed, resetting BUY counter from " + IntegerToString(buyReEntryCount) + " to 0");
buyReEntryCount = 0;
}
// Check if there are any SELL positions
int sellPositions = CountPositions(ORDER_TYPE_SELL);
if(sellPositions == 0 && sellReEntryCount > 0) {
EssentialLog("💰 Re-Entry: All SELL positions closed, resetting SELL counter from " + IntegerToString(sellReEntryCount) + " to 0");
sellReEntryCount = 0;
}
}
// Update re-entry counters
void UpdateReEntryCounters(int direction, bool isReEntry) {
if(!EnableReEntry) return;
if(isReEntry) {
if(direction == POSITION_TYPE_BUY) {
buyReEntryCount++;
EssentialLog("💰 Re-Entry: BUY re-entry count increased to " + IntegerToString(buyReEntryCount));
} else {
sellReEntryCount++;
EssentialLog("💰 Re-Entry: SELL re-entry count increased to " + IntegerToString(sellReEntryCount));
}
} else {
// Reset counters when new signal in opposite direction
if(direction == POSITION_TYPE_BUY) {
sellReEntryCount = 0;
EssentialLog("💰 Re-Entry: SELL counter reset due to new BUY signal");
} else {
buyReEntryCount = 0;
EssentialLog("💰 Re-Entry: BUY counter reset due to new SELL signal");
}
}
}
// Get detailed spread and stop level information
string GetSpreadInfo(){
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double spread = ask - bid;
int spreadPoints = (int)(spread / _Point);
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double minStopDistance = MathMax(minStopLevel, spread * 2);
return StringFormat("Spread: %.5f (%d pts) | MinStop: %.5f | MinDistance: %.5f",
spread, spreadPoints, minStopLevel, minStopDistance);
}
// Validate if stop loss is valid for current market conditions
bool IsValidStopLoss(double price, double stopLoss, int positionType){
double currentPrice = (positionType == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double currentSpread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID);
double minStopDistance = MathMax(minStopLevel, currentSpread * 2);
if(positionType == POSITION_TYPE_BUY){
return (currentPrice - stopLoss) >= minStopDistance;
} else {
return (stopLoss - currentPrice) >= minStopDistance;
}
}
double AccountEquity() { return AccountInfoDouble(ACCOUNT_EQUITY); }
bool NewBar(){ static datetime last=0; datetime t=(datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE); if(t!=last){ last=t; return true;} return false; }
string SessionName(int hour){
if(hour>=0 && hour<7) return "Asia";
if(hour>=7 && hour<13) return "London-Open";
if(hour>=13 && hour<21) return "NY";
return "Afterhours";
}
bool WithinTradingHours(){
MqlDateTime waktu;
TimeToStruct(TimeCurrent(), waktu);
int h = waktu.hour;
if(TradeStartHour <= TradeEndHour)
return (h >= TradeStartHour && h < TradeEndHour);
else
return (h >= TradeStartHour || h < TradeEndHour);
}
bool IsSessionActive(int hour) {
if(!EnableSessionFilter) return true;
if(hour >= 0 && hour < 7) return TradeAsia;
if(hour >= 7 && hour < 13) return TradeLondon;
if(hour >= 13 && hour < 21) return TradeNewYork;
return false;
}
bool NewsWindowActive(){
if(!NewsPauseEnable || UpcomingNewsTime==0) return false;
int dt=(int)MathAbs((int) (TimeCurrent()-UpcomingNewsTime))/60;
if(TimeCurrent()<UpcomingNewsTime) return (dt<=PauseBeforeMin);
else return (dt<=PauseAfterMin);
}
// lot by risk (aproksimasi konservatif)
double LotByRisk(double sl_points){
if(sl_points<=0) return SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double risk_money = AccountEquity()*RiskPercent/100.0;
double tick_val = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
double tick_sz = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
double per_lot_loss = (sl_points*pt/tick_sz)*tick_val;
if(per_lot_loss<=0.0) return SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double lots = risk_money/per_lot_loss;
double minlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double step =SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double maxlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
lots = MathMax(minlot, MathFloor((lots-minlot)/step)*step + minlot);
return MathMin(lots,maxlot);
}
//==================== Indicators ====================
bool EnsureIndicators(){
// EssentialLog("🔄 EnsureIndicators: Checking indicators for TF " + EnumToString(_Period) + " (Current: " + EnumToString(currentTimeframe) + ")");
// Force reload indicators if handles are invalid
if(hEmaF==-1 || hEmaF==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating EMA Fast handle for TF " + EnumToString(_Period) + "...");
hEmaF=iMA(_Symbol,_Period,EMA_Fast,0,MODE_EMA,PRICE_CLOSE);
if(hEmaF==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create EMA Fast handle");
else EssentialLog("✅ EnsureIndicators: EMA Fast handle created: " + IntegerToString(hEmaF) + " for TF: " + EnumToString(_Period));
}
if(hEmaS==-1 || hEmaS==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating EMA Slow handle...");
hEmaS=iMA(_Symbol,_Period,EMA_Slow,0,MODE_EMA,PRICE_CLOSE);
if(hEmaS==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create EMA Slow handle");
else EssentialLog("✅ EnsureIndicators: EMA Slow handle created: " + IntegerToString(hEmaS) + " for TF: " + EnumToString(_Period));
}
if(hRsi==-1 || hRsi==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating RSI handle for TF " + EnumToString(_Period) + "...");
hRsi=iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE);
if(hRsi==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create RSI handle");
else EssentialLog("✅ EnsureIndicators: RSI handle created: " + IntegerToString(hRsi) + " for TF: " + EnumToString(_Period));
}
if(hAdx==-1 || hAdx==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating ADX handle...");
hAdx=iADX(_Symbol, _Period, ADX_Period);
if(hAdx==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create ADX handle");
else EssentialLog("✅ EnsureIndicators: ADX handle created: " + IntegerToString(hAdx) + " for TF: " + EnumToString(_Period));
}
if(hAtr==-1 || hAtr==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating ATR handle...");
hAtr=iATR(_Symbol, _Period, ATR_Period);
if(hAtr==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create ATR handle");
else EssentialLog("✅ EnsureIndicators: ATR handle created: " + IntegerToString(hAtr) + " for TF: " + EnumToString(_Period));
}
if(hStoch==-1 || hStoch==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating Stochastic handle...");
hStoch=iStochastic(_Symbol, _Period, Stochastic_K, Stochastic_D, Stochastic_Slow, MODE_SMA, STO_LOWHIGH);
if(hStoch==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create Stochastic handle");
else EssentialLog("✅ EnsureIndicators: Stochastic handle created: " + IntegerToString(hStoch) + " for TF: " + EnumToString(_Period));
}
if(hVolume==-1 || hVolume==INVALID_HANDLE) {
EssentialLog("🔄 EnsureIndicators: Creating Volume handle...");
hVolume=iVolumes(_Symbol, _Period, VOLUME_TICK);
if(hVolume==INVALID_HANDLE) EssentialLog("❌ EnsureIndicators: Failed to create Volume handle");
else EssentialLog("✅ EnsureIndicators: Volume handle created: " + IntegerToString(hVolume) + " for TF: " + EnumToString(_Period));
}
bool allValid = (hEmaF!=-1 && hEmaS!=-1 && hRsi!=-1 && hAdx!=-1 && hAtr!=-1 && hStoch!=-1 && hVolume!=-1);
if(!allValid) {
EssentialLog("❌ EnsureIndicators: Some indicators failed - EMA_F=" + IntegerToString(hEmaF) + " EMA_S=" + IntegerToString(hEmaS) + " RSI=" + IntegerToString(hRsi) + " ADX=" + IntegerToString(hAdx) + " ATR=" + IntegerToString(hAtr) + " Stoch=" + IntegerToString(hStoch) + " Vol=" + IntegerToString(hVolume));
} else {
//EssentialLog("✅ EnsureIndicators: All indicators created successfully for TF " + EnumToString(_Period));
}
return allValid;
}
bool GetBuf(int handle,int buffer,int count,double &val){
double a[];
int copied = CopyBuffer(handle,buffer,0,count,a);
if(copied < count) {
EssentialLog("❌ GetBuf failed: handle=" + IntegerToString(handle) + " buffer=" + IntegerToString(buffer) + " copied=" + IntegerToString(copied) + " expected=" + IntegerToString(count));
return false;
}
val=a[0];
return true;
}
//==================== Multi-Timeframe Scanner ====================
struct TFRow{
string tf;
string trend;
string ema;
string rsi;
string adx;
string vol;
string stoch;
double strength;
};
bool GetEMA(string sym, ENUM_TIMEFRAMES tf, int period, double &v){
int h=iMA(sym,tf,period,0,MODE_EMA,PRICE_CLOSE);
if(h==INVALID_HANDLE) {
return false;
}
double a[];
int copied = CopyBuffer(h,0,0,1,a);
if(copied<1) {
return false;
}
v=a[0];
return true;
}
bool GetRSI(string sym, ENUM_TIMEFRAMES tf, int p, double &v){
int h=iRSI(sym,tf,p,PRICE_CLOSE);
if(h==INVALID_HANDLE) {
return false;
}
double a[];
int copied = CopyBuffer(h,0,0,1,a);
if(copied<1) {
return false;
}
v=a[0];
return true;
}
bool GetADXv(string sym, ENUM_TIMEFRAMES tf, int p, double &v){
int h=iADX(sym,tf,p);
if(h==INVALID_HANDLE) {
return false;
}
double a[];
int copied = CopyBuffer(h,2,0,1,a);
if(copied<1) {
return false;
}
v=a[0];
return true;
}
bool GetStoch(string sym, ENUM_TIMEFRAMES tf, double &k, double &d){
int h=iStochastic(sym,tf,Stochastic_K,Stochastic_D,Stochastic_Slow,MODE_SMA,STO_LOWHIGH);
if(h==INVALID_HANDLE) {
return false;
}
double a[], b[];
int copied1 = CopyBuffer(h,0,0,1,a);
int copied2 = CopyBuffer(h,1,0,1,b);
if(copied1<1 || copied2<1) {
return false;
}
k=a[0]; d=b[0];
return true;
}
string BuildScanner(){
if(!EnableMTFScanner) return "MTF Scanner: DISABLED\n";
ENUM_TIMEFRAMES tfs[4]={PERIOD_M1,PERIOD_M5,PERIOD_M15,PERIOD_H1};
string names[4]={"M1","M5","M15","H1"};
string out="TF Trend EMA8/13 RSI ADX Stoch Vol Strength\n";
// Debug log di Expert tab
// DebugLog("=== MTF SCANNER DEBUG START ===");
// DebugLog("Symbol: " + _Symbol + " | Current TF: " + EnumToString(_Period));
// DebugLog("EnableMTFScanner: " + (EnableMTFScanner ? "true" : "false"));
for(int i=0;i<4;i++){
// DebugLog("--- Processing " + names[i] + " ---");
double f,s,r,a,k,d;
bool okf=GetEMA(_Symbol,tfs[i],EMA_Fast,f);
bool oks=GetEMA(_Symbol,tfs[i],EMA_Slow,s);
bool okr=GetRSI(_Symbol,tfs[i],RSI_Period,r);
bool oka=GetADXv(_Symbol,tfs[i],ADX_Period,a);
bool oksc=GetStoch(_Symbol,tfs[i],k,d);
// Log setiap nilai yang didapat
// DebugLog(names[i] + " - EMA_F: " + (okf?DoubleToString(f,5):"FAIL") + " | EMA_S: " + (oks?DoubleToString(s,5):"FAIL"));
// DebugLog(names[i] + " - RSI: " + (okr?DoubleToString(r,2):"FAIL") + " | ADX: " + (oka?DoubleToString(a,2):"FAIL"));
// DebugLog(names[i] + " - Stoch_K: " + (oksc?DoubleToString(k,2):"FAIL") + " | Stoch_D: " + (oksc?DoubleToString(d,2):"FAIL"));
string tr="-"; string ema="?"; string vol="-"; string stoch="-";
double strength=0;
if(okf && oks){
if(f>s){ tr="BUY"; ema="OK"; strength+=25; }
else if(f<s){ tr="SELL"; ema="OK"; strength+=25; }
else { tr="FLAT"; ema="-"; }
}
if(!okr) r=50;
if(!oka) a=20;
if(!oksc) { k=50; d=50; }
// Standard RSI strength calculation
if(r <= 20 || r >= 80) strength+=20; // Extreme oversold/overbought
if(r <= 30 || r >= 70) strength+=15; // Oversold/overbought zones
// ADX strength
if(a>=25) strength+=25;
if(a>=35) strength+=10;
// Stochastic
if(k<20 || k>80) strength+=15;
if(d<20 || d>80) strength+=10;
stoch=(k<20?"Oversold":(k>80?"Overbought":"Neutral"));
vol=(a>=25?"High":"Med");
string line = StringFormat("%-5s %-6s %-7s %-5.2f %-5.0f %-8s %-5s %-8.0f\n",
names[i], tr, ema, r, a, stoch, vol, strength);
out += line;
// DebugLog(names[i] + " - Line generated: '" + line + "'");
// DebugLog(names[i] + " - Final: Trend=" + tr + " EMA=" + ema + " Strength=" + DoubleToString(strength,0));
}
// Add debug info if no data is showing
if(StringLen(out) <= StringLen("TF Trend EMA8/13 RSI ADX Stoch Vol Strength\n")) {
// DebugLog("=== NO DATA DETECTED - STARTING DETAILED DEBUG ===");
out += "DEBUG: No data retrieved - checking indicators...\n";
out += "Symbol: " + _Symbol + " | Current TF: " + EnumToString(_Period) + "\n";
out += "Data availability check:\n";
// Test data availability for each timeframe
for(int i=0;i<4;i++){
double test[];
int copied = CopyClose(_Symbol, tfs[i], 0, 1, test);
// DebugLog("CopyClose " + names[i] + ": copied=" + IntegerToString(copied) + " array_size=" + IntegerToString(ArraySize(test)));
if(copied < 1) {
out += " " + names[i] + ": NO DATA\n";
// DebugLog(" " + names[i] + ": NO DATA - CopyClose failed");
} else {
out += " " + names[i] + ": DATA OK (" + DoubleToString(test[0], 5) + ")\n";
// DebugLog(" " + names[i] + ": DATA OK (" + DoubleToString(test[0], 5) + ")");
}
}
// Additional debug for indicator functions
out += "Indicator function debug:\n";
for(int i=0;i<4;i++){
double f,s,r,a,k,d;
bool okf=GetEMA(_Symbol,tfs[i],EMA_Fast,f);
bool oks=GetEMA(_Symbol,tfs[i],EMA_Slow,s);
bool okr=GetRSI(_Symbol,tfs[i],RSI_Period,r);
bool oka=GetADXv(_Symbol,tfs[i],ADX_Period,a);
bool oksc=GetStoch(_Symbol,tfs[i],k,d);
out += " " + names[i] + ": EMA_F=" + (okf?"OK":"FAIL") + " EMA_S=" + (oks?"OK":"FAIL") +
" RSI=" + (okr?"OK":"FAIL") + " ADX=" + (oka?"OK":"FAIL") + " Stoch=" + (oksc?"OK":"FAIL") + "\n";
// DebugLog(" " + names[i] + " Debug: EMA_F=" + (okf?"OK":"FAIL") + " EMA_S=" + (oks?"OK":"FAIL") +
// " RSI=" + (okr?"OK":"FAIL") + " ADX=" + (oka?"OK":"FAIL") + " Stoch=" + (oksc?"OK":"FAIL"));
}
} else {
// DebugLog("=== MTF DATA SUCCESSFULLY GENERATED ===");
// DebugLog("Final output length: " + IntegerToString(StringLen(out)) + " characters");
// DebugLog("Final output preview: '" + StringSubstr(out, 0, 100) + "...'");
}
// DebugLog("=== MTF SCANNER DEBUG END ===");
return out;
}
// Helper to draw multi-line text as individual labels
int DrawMultiline(string prefix,int x,int y,string text,color clr,int font,int lineSpacing=14){
string lines[];
int cnt=StringSplit(text,'\n',lines);
if(cnt<=0){
DrawLabel(prefix,x,y,text,clr,font);
return 1;
}
for(int i=0;i<cnt;i++){
string nm = prefix + "_" + IntegerToString(i);
int yi = y + i*lineSpacing;
DrawLabel(nm,x,yi,lines[i],clr,font);
}
return cnt;
}
//==================== Signal Validation ====================
//==================== Signal Validation ====================
struct SignalPack{
bool buy;
bool sell;
double rsi,adx,atr,emaF,emaS,stochK,stochD,volume;
string reason;
int confirmationCount;
double signalStrength;
// Enhanced confirmation fields
bool breakoutConfirmed;
bool engulfingConfirmed;
double breakoutStrength;
double engulfingStrength;
string breakoutReason;
string engulfingReason;
double totalConfirmationScore; // Combined score
};
void BuildSignal(SignalPack &s){
s.buy=false; s.sell=false; s.rsi=50; s.adx=20; s.atr=0; s.emaF=0; s.emaS=0;
s.stochK=50; s.stochD=50; s.volume=0; s.reason=""; s.confirmationCount=0; s.signalStrength=0;
// Initialize enhanced confirmation fields
s.breakoutConfirmed = false;
s.engulfingConfirmed = false;
s.breakoutStrength = 0.0;
s.engulfingStrength = 0.0;
s.breakoutReason = "";
s.engulfingReason = "";
s.totalConfirmationScore = 0.0;
double rsi,adx,atr,emaF,emaS,stochK,stochD,volume;
if(GetBuf(hRsi,0,1,rsi)) s.rsi=rsi;
if(GetBuf(hAdx,2,1,adx)) s.adx=adx;
if(GetBuf(hAtr,0,1,atr)) s.atr=atr; else s.atr=pt*200;
if(GetBuf(hEmaF,0,1,emaF)) s.emaF=emaF; else s.emaF=SymbolInfoDouble(_Symbol,SYMBOL_BID);
if(GetBuf(hEmaS,0,1,emaS)) s.emaS=emaS; else s.emaS=SymbolInfoDouble(_Symbol,SYMBOL_BID);
if(GetBuf(hStoch,0,1,stochK)) s.stochK=stochK;
if(GetBuf(hStoch,1,1,stochD)) s.stochD=stochD;
if(GetBuf(hVolume,0,1,volume)) s.volume=volume;
// Log handle status for debugging
if(timeframeChanged) {
EssentialLog("🔍 BuildSignal: Handle status - RSI=" + IntegerToString(hRsi) + " ADX=" + IntegerToString(hAdx) +
" EMA_F=" + IntegerToString(hEmaF) + " EMA_S=" + IntegerToString(hEmaS) +
" Stoch=" + IntegerToString(hStoch) + " Volume=" + IntegerToString(hVolume));
}
// Log indicator values dan bandingkan dengan nilai sebelumnya untuk debugging timeframe change
if(timeframeChanged) {
EssentialLog("📊 BuildSignal: Indicator values for TF " + EnumToString(currentTimeframe) + " (Period: " + EnumToString(_Period) + "): RSI=" + DoubleToString(s.rsi,2) +
" ADX=" + DoubleToString(s.adx,2) + " EMA8=" + DoubleToString(s.emaF,5) +
" EMA13=" + DoubleToString(s.emaS,5) + " StochK=" + DoubleToString(s.stochK,2) +
" StochD=" + DoubleToString(s.stochD,2) + " Volume=" + DoubleToString(s.volume,0));
// Bandingkan dengan nilai sebelumnya untuk memastikan data berubah
if(MathAbs(s.rsi - lastRsi) > 0.01) EssentialLog("🔄 RSI changed: " + DoubleToString(lastRsi,2) + " → " + DoubleToString(s.rsi,2));
if(MathAbs(s.adx - lastAdx) > 0.01) EssentialLog("🔄 ADX changed: " + DoubleToString(lastAdx,2) + " → " + DoubleToString(s.adx,2));
if(MathAbs(s.emaF - lastEmaF) > 0.00001) EssentialLog("🔄 EMA8 changed: " + DoubleToString(lastEmaF,5) + " → " + DoubleToString(s.emaF,5));
if(MathAbs(s.emaS - lastEmaS) > 0.00001) EssentialLog("🔄 EMA13 changed: " + DoubleToString(lastEmaS,5) + " → " + DoubleToString(s.emaS,5));
if(MathAbs(s.stochK - lastStochK) > 0.01) EssentialLog("🔄 StochK changed: " + DoubleToString(lastStochK,2) + " → " + DoubleToString(s.stochK,2));
if(MathAbs(s.stochD - lastStochD) > 0.01) EssentialLog("🔄 StochD changed: " + DoubleToString(lastStochD,2) + " → " + DoubleToString(s.stochD,2));
if(MathAbs(s.volume - lastVolume) > 0.01) EssentialLog("🔄 Volume changed: " + DoubleToString(lastVolume,0) + " → " + DoubleToString(s.volume,0));
// Update nilai sebelumnya
lastRsi = s.rsi;
lastAdx = s.adx;
lastEmaF = s.emaF;
lastEmaS = s.emaS;
lastStochK = s.stochK;
lastStochD = s.stochD;
lastVolume = s.volume;
}
// Standard RSI validation (tanpa level filtering)
bool emaUp = (s.emaF > s.emaS);
bool emaDn = (s.emaF < s.emaS);
bool trendOk= (s.adx >= ADX_MinStrength);
bool rsiBuyOk = (rsiEnabled ? (s.rsi < 80) : true); // RSI buy condition: RSI < 80 (lebih agresif)
bool rsiSellOk = (rsiEnabled ? (s.rsi > 20) : true); // RSI sell condition: RSI > 20 (lebih agresif)
bool stochBuyOk = (stochEnabled ? (s.stochK < 95 && s.stochD < 95) : true); // Stochastic buy: < 95 (lebih agresif)
bool stochSellOk = (stochEnabled ? (s.stochK > 5 && s.stochD > 5) : true); // Stochastic sell: > 5 (lebih agresif)
bool volumeOk = (s.volume > 0); // Basic volume check
// Entry condition logging (reduced frequency)
static datetime lastDebugLog = 0;
if(TimeCurrent() - lastDebugLog > 30) { // Log setiap 30 detik
EssentialLog("🔍 BuildSignal: EMA=" + (emaUp ? "UP" : "DOWN") + " RSI=" + DoubleToString(s.rsi, 1) + " ADX=" + DoubleToString(s.adx, 1) + " Stoch=" + DoubleToString(s.stochK, 1));
lastDebugLog = TimeCurrent();
}
// Count confirmations untuk BUY dan SELL secara terpisah
int buyConfirmations = 0;
int sellConfirmations = 0;
// BUY confirmations
if(emaUp) buyConfirmations++;
if(adxEnabled && trendOk) buyConfirmations++;
if(rsiEnabled && rsiBuyOk) buyConfirmations++;
if(stochEnabled && stochBuyOk) buyConfirmations++;
if(volumeOk) buyConfirmations++;
// SELL confirmations
if(emaDn) sellConfirmations++;
if(adxEnabled && trendOk) sellConfirmations++;
if(rsiEnabled && rsiSellOk) sellConfirmations++;
if(stochEnabled && stochSellOk) sellConfirmations++;
if(volumeOk) sellConfirmations++;
// Use the higher confirmation count
s.confirmationCount = MathMax(buyConfirmations, sellConfirmations);
// Confirmation count logging (reduced frequency)
if(TimeCurrent() - lastDebugLog > 30) {
EssentialLog("🔍 BuildSignal: BUY=" + IntegerToString(buyConfirmations) + " SELL=" + IntegerToString(sellConfirmations) + " Final=" + IntegerToString(s.confirmationCount));
}
// Calculate signal strength dengan standard RSI bonus
s.signalStrength = s.confirmationCount * 20; // 20 points per confirmation
if(adxEnabled && s.adx >= 35) s.signalStrength += 10;
// RSI bonus (lebih agresif)
if(rsiEnabled) {
if(s.rsi <= 25 || s.rsi >= 75) s.signalStrength += 20; // Extreme oversold/overbought
if(s.rsi <= 35 || s.rsi >= 65) s.signalStrength += 15; // Oversold/overbought zones
}
if(stochEnabled && (s.stochK < 15 || s.stochK > 85)) s.signalStrength += 10; // Stochastic bonus (lebih agresif)
// Check for sideways market condition
bool isSideways = IsSidewaysMarket();
int sidewaysConf = GetSidewaysConfidence();
string localSidewaysReason = GetSidewaysReason();
// Generate signals based on mode - Menggunakan parameter yang dapat disesuaikan
int minConfirmations = (Mode == MODE_SCALPING ? MinConfirmations_Scalping : MinConfirmations_Other);
// Signal generation logging (reduced frequency)
if(TimeCurrent() - lastDebugLog > 30) {
EssentialLog("🔍 BuildSignal: Mode=" + (Mode == MODE_SCALPING ? "SCALPING" : "OTHER") + " MinConf=" + IntegerToString(minConfirmations) + " Strength=" + DoubleToString(s.signalStrength, 1));
if(isSideways) {
EssentialLog("🔄 BuildSignal: SIDEWAYS Market Detected - Confidence: " + IntegerToString(sidewaysConf) + "% | " + localSidewaysReason);
}
}
if(s.confirmationCount >= minConfirmations) {
// Handle sideways market condition
if(isSideways) {
if(sidewaysDisableTradingEnabled) {
EssentialLog("⚠️ BuildSignal: Trading DISABLED due to sideways market - Confidence: " + IntegerToString(sidewaysConf) + "%");
s.reason = "Sideways Market - Trading Disabled";
return; // Exit without generating signals
}
if(Sideways_UseRangeStrategy) {
// Use range strategy for sideways market
EssentialLog("🔄 BuildSignal: Using RANGE strategy for sideways market");
// Range strategy: Buy at oversold, Sell at overbought
if(s.rsi <= 30 && s.stochK <= 20) { // Oversold condition
s.buy = true;
s.reason = StringFormat("Sideways Range BUY - RSI: %.2f (Oversold), Stoch: %.2f (Oversold), Confidence: %d%%",
s.rsi, s.stochK, sidewaysConf);
EssentialLog("🟢 Sideways Range BUY Signal: " + s.reason);
}
else if(s.rsi >= 70 && s.stochK >= 80) { // Overbought condition
s.sell = true;
s.reason = StringFormat("Sideways Range SELL - RSI: %.2f (Overbought), Stoch: %.2f (Overbought), Confidence: %d%%",
s.rsi, s.stochK, sidewaysConf);
EssentialLog("🔴 Sideways Range SELL Signal: " + s.reason);
}
else {
s.reason = StringFormat("Sideways Market - No Range Signal (RSI: %.2f, Stoch: %.2f), Confidence: %d%%",
s.rsi, s.stochK, sidewaysConf);
EssentialLog("⚠️ Sideways Market - No range signal generated");
}
return; // Exit after range strategy
}
}
// Normal trend-following strategy (when not sideways or range strategy disabled)
bool trendOkScalping = (Mode == MODE_SCALPING ? (s.adx >= ADX_MinStrength_Scalping) : (s.adx >= ADX_MinStrength));
if(emaUp && rsiBuyOk && (adxEnabled ? trendOkScalping : true) && stochBuyOk){
s.buy=true;
string stochStatus = stochEnabled ? "Stoch OK" : "Stoch OFF";
s.reason=StringFormat("EMA8>EMA13, RSI: %.2f (Buy OK), ADX>%d, %s",
s.rsi, (Mode == MODE_SCALPING ? ADX_MinStrength_Scalping : ADX_MinStrength), stochStatus);
EssentialLog("🟢 BUY Signal Generated: " + s.reason);
}
if(emaDn && rsiSellOk && (adxEnabled ? trendOkScalping : true) && stochSellOk){
s.sell=true;
string stochStatus = stochEnabled ? "Stoch OK" : "Stoch OFF";
s.reason=StringFormat("EMA8<EMA13, RSI: %.2f (Sell OK), ADX>%d, %s",
s.rsi, (Mode == MODE_SCALPING ? ADX_MinStrength_Scalping : ADX_MinStrength), stochStatus);
EssentialLog("🔴 SELL Signal Generated: " + s.reason);
}
// Apply Multi Timeframe Confirmation
if(EnableMTFConfirmation) {
bool shouldApplyMTF = false;
if(MTF_ApplyToXAUUSD && (_Symbol == "XAUUSD" || _Symbol == "GOLD")) {
shouldApplyMTF = true;
}
if(mtfApplyToAllPairsEnabled) {
shouldApplyMTF = true;
}
if(shouldApplyMTF) {
EssentialLog("🔍 BuildSignal: Applying MTF Confirmation...");
bool mtfResult = ValidateSignalWithMTF(s);
EssentialLog("🔍 BuildSignal: MTF Result - Buy=" + (s.buy ? "YES" : "NO") + " Sell=" + (s.sell ? "YES" : "NO") + " Success=" + (mtfResult ? "YES" : "NO"));
if(!mtfResult) {
s.buy = false;
s.sell = false;
EssentialLog("❌ BuildSignal: MTF Confirmation REJECTED signal");
} else {
EssentialLog("✅ BuildSignal: MTF Confirmation APPROVED signal");
}
}
}
// Enhanced Breakout & Engulfing Confirmation (hanya pada timeframe entry)
if(s.buy || s.sell) {
int direction = s.buy ? BUY : SELL;
// Breakout Confirmation (hanya M1/M5) - menggunakan cached version untuk performa
if(ShouldApplyBreakoutConfirmation()) {
s.breakoutConfirmed = IsBreakoutConfirmedCached(direction);
if(s.breakoutConfirmed) {
s.breakoutStrength = 1.0;
s.breakoutReason = "Breakout confirmed on " + EnumToString(_Period);
} else {
s.breakoutStrength = 0.0;
s.breakoutReason = "No breakout on " + EnumToString(_Period);
}
} else {
s.breakoutConfirmed = true; // Skip for non-entry timeframes
s.breakoutStrength = 1.0;
s.breakoutReason = "Breakout not required for " + EnumToString(_Period);
}
// Engulfing Confirmation (hanya M1/M5) - menggunakan cached version untuk performa
if(ShouldApplyEngulfingConfirmation()) {
EngulfingPattern pattern = DetectEngulfingPatternCached(direction);
s.engulfingConfirmed = pattern.isValid;
s.engulfingStrength = pattern.strength;
s.engulfingReason = pattern.reason + " on " + EnumToString(_Period);
} else {
s.engulfingConfirmed = true; // Skip for non-entry timeframes
s.engulfingStrength = 1.0;
s.engulfingReason = "Engulfing not required for " + EnumToString(_Period);
}
// Calculate enhanced signal strength
CalculateEnhancedSignalStrength(s);
// Check if enhanced entry is valid
if(!IsEnhancedEntryValid(s, direction)) {
s.buy = false;
s.sell = false;
EssentialLog("❌ Enhanced confirmation REJECTED on " + EnumToString(_Period) +
" - Score: " + DoubleToString(s.totalConfirmationScore, 1));
} else {
EssentialLog("✅ Enhanced confirmation APPROVED on " + EnumToString(_Period) +
" - Score: " + DoubleToString(s.totalConfirmationScore, 1));
LogEnhancedEntryDecision(s, direction);
}
}
} else {
if(TimeCurrent() - lastDebugLog > 10) {
EssentialLog("⚠️ BuildSignal: Insufficient confirmations - " + IntegerToString(s.confirmationCount) + "/" + IntegerToString(minConfirmations));
}
}
}
//==================== Supply & Demand Detection ====================
void DetectSupplyDemand() {
if(!EnableSDDetection) return;
// Clear old zones
for(int i=0; i<sdZoneCount; i++) {
ObjectDelete(0, sdZones[i].name);
}
sdZoneCount = 0;
double high[], low[], close[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
if(CopyHigh(_Symbol, _Period, 0, SD_Lookback, high) < SD_Lookback) return;
if(CopyLow(_Symbol, _Period, 0, SD_Lookback, low) < SD_Lookback) return;
if(CopyClose(_Symbol, _Period, 0, SD_Lookback, close) < SD_Lookback) return;
// Find supply zones (resistance)
for(int i=2; i<SD_Lookback-2; i++) {
if(high[i] > high[i-1] && high[i] > high[i-2] &&
high[i] > high[i+1] && high[i] > high[i+2]) {
// Check for touches
int touches = 0;
for(int j=0; j<SD_Lookback; j++) {
if(MathAbs(high[j] - high[i]) <= SD_ZoneSize) {
touches++;
}
}
if(touches >= SD_MinTouch) {
ArrayResize(sdZones, sdZoneCount + 1);
sdZones[sdZoneCount].price = high[i];
sdZones[sdZoneCount].high = high[i] + SD_ZoneSize/2;
sdZones[sdZoneCount].low = high[i] - SD_ZoneSize/2;
sdZones[sdZoneCount].touches = touches;
sdZones[sdZoneCount].isSupply = true;
sdZones[sdZoneCount].lastTouch = TimeCurrent();
sdZones[sdZoneCount].name = "SD_Supply_" + IntegerToString(sdZoneCount);
// Draw zone
ObjectCreate(0, sdZones[sdZoneCount].name, OBJ_RECTANGLE, 0,
TimeCurrent() - SD_Lookback * PeriodSeconds(_Period), sdZones[sdZoneCount].high,
TimeCurrent(), sdZones[sdZoneCount].low);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_COLOR, SD_SupplyColor);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_FILL, true);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_BACK, true);
sdZoneCount++;
}
}
}
// Find demand zones (support)
for(int i=2; i<SD_Lookback-2; i++) {
if(low[i] < low[i-1] && low[i] < low[i-2] &&
low[i] < low[i+1] && low[i] < low[i+2]) {
// Check for touches
int touches = 0;
for(int j=0; j<SD_Lookback; j++) {
if(MathAbs(low[j] - low[i]) <= SD_ZoneSize) {
touches++;
}
}
if(touches >= SD_MinTouch) {
ArrayResize(sdZones, sdZoneCount + 1);
sdZones[sdZoneCount].price = low[i];
sdZones[sdZoneCount].high = low[i] + SD_ZoneSize/2;
sdZones[sdZoneCount].low = low[i] - SD_ZoneSize/2;
sdZones[sdZoneCount].touches = touches;
sdZones[sdZoneCount].isSupply = false;
sdZones[sdZoneCount].lastTouch = TimeCurrent();
sdZones[sdZoneCount].name = "SD_Demand_" + IntegerToString(sdZoneCount);
// Draw zone
ObjectCreate(0, sdZones[sdZoneCount].name, OBJ_RECTANGLE, 0,
TimeCurrent() - SD_Lookback * PeriodSeconds(_Period), sdZones[sdZoneCount].high,
TimeCurrent(), sdZones[sdZoneCount].low);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_COLOR, SD_DemandColor);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_FILL, true);
ObjectSetInteger(0, sdZones[sdZoneCount].name, OBJPROP_BACK, true);
sdZoneCount++;
}
}
}
}
//==================== Smart TP/SL Calculator ====================
void CalculateTPSL(int type, double entryPrice, double &sl, double &tp1, double &tp2, double &tp3) {
double atr_pts = 0;
if(UseATR_TP_SL && hAtr != -1) {
double atr;
if(GetBuf(hAtr, 0, 1, atr)) {
atr_pts = atr / pt;
}
}
if(atr_pts <= 0) atr_pts = 200; // Default fallback
// Get broker minimum stop level
long stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double minStopDistance = stopLevel * pt;
// Ensure minimum distance for SL/TP
double sl_pts = MathMax(ATR_SL_Multiplier * atr_pts, stopLevel * 1.5);
double tp_pts = MathMax(ATR_TP_Multiplier * atr_pts, stopLevel * 2.0);
if(type == ORDER_TYPE_BUY) {
sl = entryPrice - sl_pts * pt;
tp1 = entryPrice + tp_pts * pt * TP1_Ratio;
tp2 = entryPrice + tp_pts * pt * (TP1_Ratio + TP2_Ratio);
tp3 = entryPrice + tp_pts * pt;
} else {
sl = entryPrice + sl_pts * pt;
tp1 = entryPrice - tp_pts * pt * TP1_Ratio;
tp2 = entryPrice - tp_pts * pt * (TP1_Ratio + TP2_Ratio);
tp3 = entryPrice - tp_pts * pt;
}
// Debug log for SL/TP calculation
EssentialLog("🔧 SL/TP Calc: ATR=" + DoubleToString(atr_pts, 1) + " StopLevel=" + IntegerToString(stopLevel) +
" SL_pts=" + DoubleToString(sl_pts, 1) + " TP_pts=" + DoubleToString(tp_pts, 1));
}
//==================== AI Assist ====================
string BuildPayload(const SignalPack &sp,const string candidate){
string json="{";
json+="\"pair\":\""+_Symbol+"\",";
json+="\"tf\":\""+EnumToString(_Period)+"\",";
json+="\"spread\":"+IntegerToString(SpreadPoints())+",";
json+="\"atr\":"+DoubleToString(sp.atr,2)+",";
json+="\"indicators\":{";
json+="\"ema_fast\":"+DoubleToString(sp.emaF,5)+",";
json+="\"ema_slow\":"+DoubleToString(sp.emaS,5)+",";
json+="\"rsi\":"+DoubleToString(sp.rsi,2)+",";
json+="\"adx\":"+DoubleToString(sp.adx,2)+",";
json+="\"stoch_k\":"+DoubleToString(sp.stochK,2)+",";
json+="\"stoch_d\":"+DoubleToString(sp.stochD,2)+",";
json+="\"volume\":"+DoubleToString(sp.volume,2)+"},";
json+="\"candidate\":\""+candidate+"\",";
json+="\"mode\":\""+(Mode==MODE_SCALPING?"scalping":(Mode==MODE_INTRADAY?"intraday":"swing"))+"\",";
json+="\"confirmations\":"+IntegerToString(sp.confirmationCount)+",";
json+="\"signal_strength\":"+DoubleToString(sp.signalStrength,2);
json+="}";
return json;
}
//==================== DeepSeek AI ====================
string BuildDeepSeekPayload(const SignalPack &sp, const string candidate) {
string prompt = "You are a professional forex trading analyst. Analyze this trading signal and provide a clear recommendation.\n\n";
prompt += "Trading Signal Analysis:\n";
prompt += "- Pair: " + _Symbol + "\n";
prompt += "- Timeframe: " + EnumToString(_Period) + "\n";
prompt += "- Mode: " + (Mode==MODE_SCALPING?"Scalping":(Mode==MODE_INTRADAY?"Intraday":"Swing")) + "\n";
prompt += "- Candidate: " + candidate + "\n";
prompt += "- Spread: " + IntegerToString(SpreadPoints()) + " points\n";
prompt += "- ATR: " + DoubleToString(sp.atr, 2) + "\n";
prompt += "- Indicators:\n";
prompt += " * EMA Fast: " + DoubleToString(sp.emaF, 5) + "\n";
prompt += " * EMA Slow: " + DoubleToString(sp.emaS, 5) + "\n";
prompt += " * RSI: " + DoubleToString(sp.rsi, 2) + "\n";
prompt += " * ADX: " + DoubleToString(sp.adx, 2) + "\n";
prompt += " * Stochastic K: " + DoubleToString(sp.stochK, 2) + "\n";
prompt += " * Stochastic D: " + DoubleToString(sp.stochD, 2) + "\n";
prompt += " * Volume: " + DoubleToString(sp.volume, 2) + "\n";
prompt += "- Confirmations: " + IntegerToString(sp.confirmationCount) + "\n";
prompt += "- Signal Strength: " + DoubleToString(sp.signalStrength, 0) + "\n\n";
prompt += "Please analyze this signal and respond with ONLY one of these options:\n";
prompt += "1. CONFIRM_BUY - if you recommend taking this BUY signal\n";
prompt += "2. CONFIRM_SELL - if you recommend taking this SELL signal\n";
prompt += "3. REJECT - if you recommend NOT taking this signal\n";
prompt += "4. WAIT - if you recommend waiting for better conditions\n\n";
prompt += "Provide a brief reason for your decision (max 100 words).";
string json = "{";
json += "\"model\":\"" + DeepSeek_Model + "\",";
json += "\"messages\":[";
json += "{\"role\":\"user\",\"content\":\"" + prompt + "\"}";
json += "],";
json += "\"max_tokens\":" + IntegerToString(DeepSeek_MaxTokens) + ",";
json += "\"temperature\":0.3";
json += "}";
return json;
}
string CallDeepSeek(const string payload, string &err) {
err = "";
if(!DeepSeek_Enable || DeepSeek_API_Key == "") {
return "";
}
string url = "https://api.deepseek.com/v1/chat/completions";
uchar data[];
StringToCharArray(payload, data, 0, WHOLE_ARRAY, CP_UTF8);
string headers = "Content-Type: application/json\r\n";
headers += "Authorization: Bearer " + DeepSeek_API_Key + "\r\n";
uchar result[];
string result_headers = "";
ResetLastError();
EssentialLog("📡 Sending WebRequest to: " + url);
EssentialLog("🧾 Headers: " + headers);
EssentialLog("🧾 Payload: " + payload);
int code = WebRequest("POST", url, headers, DeepSeek_Timeout, data, result, result_headers);
if(code == -1) {
err = "WebRequest failed: " + IntegerToString(GetLastError());
return "";
}
if(code != 200) {
err = "HTTP " + IntegerToString(code);
return "";
}
string resp = CharArrayToString(result, 0, (int)ArraySize(result), CP_UTF8);
// Parse DeepSeek response
string content = ParseDeepSeekResponse(resp);
if(content == "") {
err = "Failed to parse DeepSeek response";
return "";
}
return content;
}
string ParseDeepSeekResponse(const string response) {
// Simple JSON parsing for DeepSeek response
int contentStart = StringFind(response, "\"content\":\"");
if(contentStart == -1) return "";
contentStart += 12; // Skip "content":"
int contentEnd = StringFind(response, "\"", contentStart);
if(contentEnd == -1) return "";
return StringSubstr(response, contentStart, contentEnd - contentStart);
}
bool DeepSeek_ConfirmBuy(const string response) {
return (StringFind(response, "CONFIRM_BUY") >= 0);
}
bool DeepSeek_ConfirmSell(const string response) {
return (StringFind(response, "CONFIRM_SELL") >= 0);
}
bool DeepSeek_Reject(const string response) {
return (StringFind(response, "REJECT") >= 0);
}
bool DeepSeek_Wait(const string response) {
return (StringFind(response, "WAIT") >= 0);
}
//==================== ChatGPT AI ====================
string EscapeJSONString(string str) {
string out = "";
for(int i = 0; i < StringLen(str); i++) {
ushort c = StringGetCharacter(str, i);
if (c == 34) out += "\\\""; // "
else if (c == 92) out += "\\\\"; // \
else if (c == 10) out += "\\n"; // newline
else if (c == 13) out += "\\r"; // carriage return
else out += (string)CharToString((uchar)c);
}
return out;
}
string BuildChatGPTPayload(const SignalPack &sp, const string candidate) {
string prompt = "You are a professional forex trading analyst. Analyze this trading signal and provide a clear recommendation.\n\n";
prompt += "Trading Signal Analysis:\n";
prompt += "- Pair: " + _Symbol + "\n";
prompt += "- Timeframe: " + EnumToString(_Period) + "\n";
prompt += "- Mode: " + (Mode==MODE_SCALPING?"Scalping":(Mode==MODE_INTRADAY?"Intraday":"Swing")) + "\n";
prompt += "- Candidate: " + candidate + "\n";
prompt += "- Spread: " + IntegerToString(SpreadPoints()) + " points\n";
prompt += "- ATR: " + DoubleToString(sp.atr, 2) + "\n";
prompt += "- Indicators:\n";
prompt += " * EMA Fast: " + DoubleToString(sp.emaF, 5) + "\n";
prompt += " * EMA Slow: " + DoubleToString(sp.emaS, 5) + "\n";
prompt += " * RSI: " + DoubleToString(sp.rsi, 2) + "\n";
prompt += " * ADX: " + DoubleToString(sp.adx, 2) + "\n";
prompt += " * Stochastic K: " + DoubleToString(sp.stochK, 2) + "\n";
prompt += " * Stochastic D: " + DoubleToString(sp.stochD, 2) + "\n";
prompt += " * Volume: " + DoubleToString(sp.volume, 2) + "\n";
prompt += "- Confirmations: " + IntegerToString(sp.confirmationCount) + "\n";
prompt += "- Signal Strength: " + DoubleToString(sp.signalStrength, 0) + "\n\n";
prompt += "Please analyze this signal and respond with ONLY one of these options:\n";
prompt += "1. CONFIRM_BUY - if you recommend taking this BUY signal\n";
prompt += "2. CONFIRM_SELL - if you recommend taking this SELL signal\n";
prompt += "3. REJECT - if you recommend NOT taking this signal\n";
prompt += "4. WAIT - if you recommend waiting for better conditions\n\n";
prompt += "Provide a brief reason for your decision (max 100 words).";
string safePrompt = EscapeJSONString(prompt);
string json = "{";
json += "\"model\":\"" + ChatGPT_Model + "\",";
json += "\"messages\":[";
json += "{\"role\":\"user\",\"content\":\"" + safePrompt + "\"}";
json += "],";
json += "\"max_tokens\":" + IntegerToString(ChatGPT_MaxTokens) + ",";
json += "\"temperature\":0.3";
json += "}";
return json;
}
string CallChatGPT(const string payload, string &err) {
err = "";
if(!ChatGPT_Enable || ChatGPT_API_Key == "") {
err = "ChatGPT disabled or API key empty";
return "";
}
string url = "https://api.openai.com/v1/chat/completions";
// --- Encode payload ke UTF-8 dan HAPUS terminator null ---
uchar data[];
ResetLastError();
// Pakai -1/WHOLE_ARRAY: MQL5 akan copy + terminator null di akhir
int bytes_copied = StringToCharArray(payload, data, 0, -1, CP_UTF8);
if(bytes_copied <= 0) {
err = "Failed to encode payload to UTF-8";
return "";
}
// Hapus byte null terakhir agar JSON murni (tanpa \0)
if(ArraySize(data) > 0) {
ArrayResize(data, ArraySize(data) - 1);
}
// --- Header HTTP ---
string headers =
"Content-Type: application/json\r\n"
"Accept: application/json\r\n"
"Authorization: Bearer " + ChatGPT_API_Key + "\r\n";
uchar result[];
string result_headers = "";
ResetLastError();
//EssentialLog("🌐 Calling ChatGPT API...");
//EssentialLog("Timeout: " + IntegerToString(ChatGPT_Timeout) + "ms");
//EssentialLog("Payload length (chars): " + IntegerToString(StringLen(payload)));
//EssentialLog("Payload bytes sent: " + IntegerToString(ArraySize(data)));
//EssentialLog("📡 Sending WebRequest to: " + url);
//EssentialLog("🧾 Headers: " + headers);
//EssentialLog("🧾 Payload: " + payload);
int code = WebRequest("POST", url, headers, ChatGPT_Timeout, data, result, result_headers);
if(code == -1) {
int lastError = GetLastError();
err = "WebRequest failed: " + IntegerToString(lastError);
switch(lastError) {
case ERR_WEBREQUEST_INVALID_ADDRESS: err += " (Invalid URL)"; break;
case ERR_WEBREQUEST_CONNECT_FAILED: err += " (Connection failed)"; break;
case ERR_WEBREQUEST_REQUEST_FAILED: err += " (Request failed)"; break;
case ERR_WEBREQUEST_TIMEOUT: err += " (Timeout)"; break;
case ERR_WEBREQUEST_INVALID_PARAMETER:err += " (Invalid parameter)"; break;
case ERR_WEBREQUEST_NOT_ALLOWED: err += " (WebRequest not allowed - check MT5 settings)"; break;
default: err += " (Unknown error)";
}
EssentialLog("❌ " + err);
return "";
}
EssentialLog("📡 HTTP Response Code: " + IntegerToString(code));
EssentialLog("📄 Response Headers: " + result_headers);
string resp = CharArrayToString(result, 0, (int)ArraySize(result), CP_UTF8);
if(code != 200) {
err = "HTTP " + IntegerToString(code) + " - " + resp;
EssentialLog("❌ " + err);
return "";
}
EssentialLog("✅ ChatGPT response received: " + IntegerToString(StringLen(resp)) + " chars");
string content = ParseChatGPTResponse(resp);
if(content == "") {
err = "Failed to parse ChatGPT response";
EssentialLog("❌ " + err);
EssentialLog("Raw response: " + resp);
return "";
}
EssentialLog("🎯 Parsed content: " + content);
return content;
}
string ParseChatGPTResponse(const string response) {
// Cari key "content":
int keyPos = StringFind(response, "\"content\":");
if (keyPos == -1)
return "";
// Cari quote pembuka value string
int openQuote = StringFind(response, "\"", keyPos + 10);
if (openQuote == -1)
return "";
string out = "";
bool esc = false;
// Mulai baca setelah quote pembuka
for (int i = openQuote + 1; i < (int)StringLen(response); i++) {
ushort ch = StringGetCharacter(response, i);
if (esc) {
// Tangani karakter escape standar JSON
if (ch == 'n') out += "\n";
else if (ch == 'r') out += "\r";
else if (ch == 't') out += "\t";
else if (ch == '\\') out += "\\";
else if (ch == '\"') out += "\"";
else out += (string)CharToString((uchar)ch);
esc = false;
} else {
if (ch == '\\') {
esc = true; // masuk mode escape untuk char berikutnya
} else if (ch == '\"') {
// ketemu quote penutup string "content"
break;
} else {
out += (string)CharToString((uchar)ch);
}
}
}
return out;
}
// Ubah ke huruf besar dengan aman (tanpa pass const-by-ref)
string ToUpperStr(const string text) {
string s = text; // salin agar bukan const
StringToUpper(s); // ubah in-place; return bool diabaikan
return s;
}
bool ChatGPT_ConfirmBuy(const string content) { string s = ToUpperStr(content); return (StringFind(s, "CONFIRM_BUY") >= 0); }
bool ChatGPT_ConfirmSell(const string content) { string s = ToUpperStr(content); return (StringFind(s, "CONFIRM_SELL") >= 0); }
bool ChatGPT_Reject(const string content) { string s = ToUpperStr(content); return (StringFind(s, "REJECT") >= 0); }
bool ChatGPT_Wait(const string content) { string s = ToUpperStr(content); return (StringFind(s, "WAIT") >= 0); }
// KEMBALIKAN "" jika AI OFF / URL kosong -> aman compile & run
string CallAI(const string endpoint,const string payload,const string apiKey,int timeout_ms,string &err)
{
err = "";
if(!AI_Assist_Enable || endpoint == "") // safety gate
return "";
uchar data[];
StringToCharArray(payload, data, 0, WHOLE_ARRAY, CP_UTF8);
string headers = "Content-Type: application/json\r\n";
if(StringLen(apiKey) > 0)
headers += "Authorization: Bearer " + apiKey + "\r\n";
uchar result[]; string result_headers = ""; ResetLastError();
int code = WebRequest("POST", endpoint, headers, timeout_ms, data, result, result_headers);
if(code == -1){ err = StringFormat("WebRequest:%d", GetLastError()); return ""; }
string resp = CharArrayToString(result, 0, (int)ArraySize(result), CP_UTF8);
if(code != 200){ err = StringFormat("HTTP %d", code); return ""; }
if(StringLen(resp) > AI_MaxChars) resp = StringSubstr(resp, 0, AI_MaxChars);
return resp;
}
bool AI_ConfirmBuy(const string resp){ return (StringFind(resp,"confirm_buy")>=0 || StringFind(resp,"\"verdict\":\"confirm_buy\"")>=0); }
bool AI_ConfirmSell(const string resp){ return (StringFind(resp,"confirm_sell")>=0 || StringFind(resp,"\"verdict\":\"confirm_sell\"")>=0); }
//==================== Trade Journal ====================
void LogTrade(const TradeRecord &record) {
if(!EnableTradeLog) return;
string filename = LogFileName;
int handle = FileOpen(filename, FILE_WRITE|FILE_CSV|FILE_ANSI, '\t');
if(handle == INVALID_HANDLE) {
DebugLog("Failed to open trade log file: " + filename);
return;
}
// Write header if file is empty
if(FileSize(handle) == 0) {
FileWrite(handle, "OpenTime", "Pair", "Type", "Lot", "OpenPrice", "SL", "TP", "Reason", "CloseTime", "ClosePrice", "Profit", "Notes");
}
string typeStr = (record.type == ORDER_TYPE_BUY) ? "BUY" : "SELL";
string openTimeStr = TimeToString(record.openTime);
string closeTimeStr = (record.closeTime > 0) ? TimeToString(record.closeTime) : "";
FileWrite(handle, openTimeStr, record.pair, typeStr,
DoubleToString(record.lot, 2), DoubleToString(record.openPrice, 5),
DoubleToString(record.sl, 5), DoubleToString(record.tp, 5),
record.reason, closeTimeStr, DoubleToString(record.closePrice, 5),
DoubleToString(record.profit, 2), record.notes);
FileClose(handle);
}
//==================== Trading Helpers ====================
int CountPositions(int type){
int c=0;
for(int i=0;i<PositionsTotal();i++){
ulong ticket=PositionGetTicket(i); if(ticket==0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if((int)PositionGetInteger(POSITION_MAGIC)!=Magic) continue;
int t=(int)PositionGetInteger(POSITION_TYPE);
if((type==ORDER_TYPE_BUY && t==POSITION_TYPE_BUY)||(type==ORDER_TYPE_SELL && t==POSITION_TYPE_SELL)) c++;
}
return c;
}
void ManageTrailing(){
for(int i=0;i<PositionsTotal();i++){
ulong ticket=PositionGetTicket(i); if(ticket==0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if((int)PositionGetInteger(POSITION_MAGIC)!=Magic) continue;
int type=(int)PositionGetInteger(POSITION_TYPE);
double open = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double cur = (type==POSITION_TYPE_BUY? SymbolInfoDouble(_Symbol,SYMBOL_BID): SymbolInfoDouble(_Symbol,SYMBOL_ASK));
// Calculate spread buffer and minimum stop level with broker-specific adjustments
int currentSpread = SpreadPoints();
int spreadBuffer = 0;
// Auto spread buffer selalu aktif
double dynamicSpreadBuffer = CalculateDynamicSpreadBuffer();
spreadBuffer = (int)(currentSpread * dynamicSpreadBuffer);
// Get minimum stop level from broker with auto-check spread and broker-specific adjustments
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double minStopDistance = minStopLevel;
// Auto check spread selalu aktif
int adjustedMinStop = GetAdjustedStopDistance((int)(currentSpread * 2.0)); // Auto multiplier
minStopDistance = MathMax(minStopLevel, adjustedMinStop * _Point);
// Lock profit dulu dengan spread buffer dan validasi minimum stop
double profit_pts_buy = (cur-open)/pt;
double profit_pts_sell= (open-cur)/pt;
if(type==POSITION_TYPE_BUY){
if(profit_pts_buy>LockStartPts){
double lock_sl=open + (LockOffsetPts + spreadBuffer)*pt;
// Validate minimum stop distance
if(cur - lock_sl >= minStopDistance) {
if(sl==0.0 || lock_sl>sl) {
if(trade.PositionModify(ticket, lock_sl, tp)) {
DebugLog("Lock profit BUY: SL=" + DoubleToString(lock_sl, _Digits) + " (min=" + DoubleToString(minStopDistance, _Digits) + ")");
} else {
DebugLog("Lock profit BUY failed: " + IntegerToString(GetLastError()) + " - SL=" + DoubleToString(lock_sl, _Digits));
}
}
} else {
DebugLog("Lock profit BUY: SL too close to price. Required=" + DoubleToString(minStopDistance, _Digits) + ", Actual=" + DoubleToString(cur - lock_sl, _Digits));
}
}
}
else{
if(profit_pts_sell>LockStartPts){
double lock_sl=open - (LockOffsetPts + spreadBuffer)*pt;
// Validate minimum stop distance
if(lock_sl - cur >= minStopDistance) {
if(sl==0.0 || lock_sl<sl) {
if(trade.PositionModify(ticket, lock_sl, tp)) {
DebugLog("Lock profit SELL: SL=" + DoubleToString(lock_sl, _Digits) + " (min=" + DoubleToString(minStopDistance, _Digits) + ")");
} else {
DebugLog("Lock profit SELL failed: " + IntegerToString(GetLastError()) + " - SL=" + DoubleToString(lock_sl, _Digits));
}
}
} else {
DebugLog("Lock profit SELL: SL too close to price. Required=" + DoubleToString(minStopDistance, _Digits) + ", Actual=" + DoubleToString(lock_sl - cur, _Digits));
}
}
}
// Lanjut trailing dengan spread buffer dan validasi minimum stop dengan broker-specific adjustments
if(type==POSITION_TYPE_BUY){
double profit_pts=profit_pts_buy;
if(profit_pts>TrailStartPts){
int adjustedTrailingStep = GetAdjustedTrailingStep(TrailStepPts);
// Calculate safe trailing distance to protect profits
double safeDistance = CalculateSafeTrailingStop(open, cur, POSITION_TYPE_BUY, minStopDistance);
double new_sl=cur - safeDistance*pt;
// Enhanced debugging for trailing stop calculation
EssentialLog("🔍 TRAILING BUY DEBUG:");
EssentialLog(" - Current Price: " + DoubleToString(cur, _Digits));
EssentialLog(" - Entry Price: " + DoubleToString(open, _Digits));
EssentialLog(" - Current SL: " + DoubleToString(sl, _Digits));
EssentialLog(" - Profit Points: " + DoubleToString(profit_pts, 1));
EssentialLog(" - Trail Start Points: " + IntegerToString(TrailStartPts));
EssentialLog(" - Base Trail Step: " + IntegerToString(TrailStepPts));
EssentialLog(" - Adjusted Trail Step: " + IntegerToString(adjustedTrailingStep));
EssentialLog(" - Current Spread: " + IntegerToString(currentSpread));
EssentialLog(" - Spread Buffer: " + IntegerToString(spreadBuffer));
EssentialLog(" - Safe Distance: " + DoubleToString(safeDistance, 1));
EssentialLog(" - Calculated New SL: " + DoubleToString(new_sl, _Digits));
EssentialLog(" - Distance from Price: " + DoubleToString(cur - new_sl, _Digits));
EssentialLog(" - Min Stop Distance: " + DoubleToString(minStopDistance, _Digits));
// Validate minimum stop distance
if(cur - new_sl >= minStopDistance) {
if((sl==0.0 || new_sl>sl) && new_sl<cur) {
if(trade.PositionModify(ticket,new_sl,tp)) {
EssentialLog("✅ Trailing BUY SUCCESS: SL=" + DoubleToString(new_sl, _Digits) + " (min=" + DoubleToString(minStopDistance, _Digits) + ", step=" + IntegerToString(adjustedTrailingStep) + ")");
} else {
EssentialLog("❌ Trailing BUY failed: " + IntegerToString(GetLastError()) + " - SL=" + DoubleToString(new_sl, _Digits));
}
} else {
EssentialLog("⚠️ Trailing BUY: SL not improved. Current=" + DoubleToString(sl, _Digits) + ", New=" + DoubleToString(new_sl, _Digits));
}
} else {
EssentialLog("❌ Trailing BUY: SL too close to price. Required=" + DoubleToString(minStopDistance, _Digits) + ", Actual=" + DoubleToString(cur - new_sl, _Digits));
}
}
}
else{
double profit_pts=profit_pts_sell;
if(profit_pts>TrailStartPts){
int adjustedTrailingStep = GetAdjustedTrailingStep(TrailStepPts);
// Calculate safe trailing distance to protect profits
double safeDistance = CalculateSafeTrailingStop(open, cur, POSITION_TYPE_SELL, minStopDistance);
double new_sl=cur + safeDistance*pt;
// Enhanced debugging for trailing stop calculation
EssentialLog("🔍 TRAILING SELL DEBUG:");
EssentialLog(" - Current Price: " + DoubleToString(cur, _Digits));
EssentialLog(" - Entry Price: " + DoubleToString(open, _Digits));
EssentialLog(" - Current SL: " + DoubleToString(sl, _Digits));
EssentialLog(" - Profit Points: " + DoubleToString(profit_pts, 1));
EssentialLog(" - Trail Start Points: " + IntegerToString(TrailStartPts));
EssentialLog(" - Base Trail Step: " + IntegerToString(TrailStepPts));
EssentialLog(" - Adjusted Trail Step: " + IntegerToString(adjustedTrailingStep));
EssentialLog(" - Current Spread: " + IntegerToString(currentSpread));
EssentialLog(" - Spread Buffer: " + IntegerToString(spreadBuffer));
EssentialLog(" - Safe Distance: " + DoubleToString(safeDistance, 1));
EssentialLog(" - Calculated New SL: " + DoubleToString(new_sl, _Digits));
EssentialLog(" - Distance from Price: " + DoubleToString(new_sl - cur, _Digits));
EssentialLog(" - Min Stop Distance: " + DoubleToString(minStopDistance, _Digits));
// Validate minimum stop distance
if(new_sl - cur >= minStopDistance) {
if((sl==0.0 || new_sl<sl) && new_sl>cur) {
if(trade.PositionModify(ticket,new_sl,tp)) {
EssentialLog("✅ Trailing SELL SUCCESS: SL=" + DoubleToString(new_sl, _Digits) + " (min=" + DoubleToString(minStopDistance, _Digits) + ", step=" + IntegerToString(adjustedTrailingStep) + ")");
} else {
EssentialLog("❌ Trailing SELL failed: " + IntegerToString(GetLastError()) + " - SL=" + DoubleToString(new_sl, _Digits));
}
} else {
EssentialLog("⚠️ Trailing SELL: SL not improved. Current=" + DoubleToString(sl, _Digits) + ", New=" + DoubleToString(new_sl, _Digits));
}
} else {
EssentialLog("❌ Trailing SELL: SL too close to price. Required=" + DoubleToString(minStopDistance, _Digits) + ", Actual=" + DoubleToString(new_sl - cur, _Digits));
}
}
}
}
// Check and reset re-entry counters after managing positions
CheckAndResetReEntryCounters();
}
//==================== HUD ====================
void DrawLabel(string name,int x,int y,string text,color clr,int font=10,ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT_UPPER){
// Force delete existing object first
if(ObjectFind(0,name)>=0) ObjectDelete(0,name);
// Create new object
if(ObjectCreate(0,name,OBJ_LABEL,0,0,0)) {
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
ObjectSetInteger(0,name,OBJPROP_ANCHOR,anchor);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font);
ObjectSetString(0,name,OBJPROP_FONT,"Consolas"); // monospaced for alignment
ObjectSetString(0,name,OBJPROP_TEXT,text);
ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,false);
ObjectSetInteger(0,name,OBJPROP_ZORDER,0);
// DebugLog("DrawLabel: Created object '" + name + "' at (" + IntegerToString(x) + "," + IntegerToString(y) + ") with text: '" + text + "'");
} else {
// DebugLog("DrawLabel: FAILED to create object '" + name + "' - Error: " + IntegerToString(GetLastError()));
}
}
void CheckObjectVisibility(string name) {
if(ObjectFind(0,name) >= 0) {
// DebugLog("Object '" + name + "' EXISTS and is visible");
string text = ObjectGetString(0,name,OBJPROP_TEXT);
int x = (int)ObjectGetInteger(0,name,OBJPROP_XDISTANCE);
int y = (int)ObjectGetInteger(0,name,OBJPROP_YDISTANCE);
DebugLog(" - Text: '" + text + "'");
DebugLog(" - Position: (" + IntegerToString(x) + "," + IntegerToString(y) + ")");
} else {
DebugLog("Object '" + name + "' NOT FOUND");
}
}
void ForceChartRefresh() {
ChartRedraw();
// DebugLog("Chart refresh forced");
}
void RenderHUD(const SignalPack &sp){
// Dashboard update tracking (reduced frequency)
static datetime lastDashboardLog = 0;
if(TimeCurrent() - lastDashboardLog > 60) { // Log setiap 60 detik
EssentialLog("🖥️ Dashboard: Updating with Buy=" + (sp.buy ? "YES" : "NO") + " Sell=" + (sp.sell ? "YES" : "NO"));
lastDashboardLog = TimeCurrent();
}
MqlDateTime waktu;
TimeToStruct(TimeCurrent(), waktu);
string sess = SessionName(waktu.hour);
string modeStr = (Mode==MODE_SCALPING?"Scalping":(Mode==MODE_INTRADAY?"Intraday":"Swing"));
string aiStatus = "";
if(DeepSeek_Enable) aiStatus = "DeepSeek:ON";
else if(ChatGPT_Enable) aiStatus = "ChatGPT:ON";
else if(AI_Assist_Enable) aiStatus = "AI:ON";
else aiStatus = "AI:OFF";
// Calculate spread buffer for display (auto mode)
int currentSpread = SpreadPoints();
int spreadBuffer = 0;
// Auto spread buffer selalu aktif
double dynamicSpreadBuffer = CalculateDynamicSpreadBuffer();
spreadBuffer = (int)(currentSpread * dynamicSpreadBuffer);
// Get detailed spread info for display (auto mode)
string spreadInfo = "";
double minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double minStopDistance = MathMax(minStopLevel, currentSpread * _Point * 2.0); // Auto multiplier
spreadInfo = StringFormat("[Spread:%d] [Buffer:%d] [MinStop:%.1f]",
SpreadPoints(), spreadBuffer, minStopDistance/_Point);
// Add timeframe change indicator (broker info hidden in auto mode)
string tfIndicator = timeframeChanged ? " 🔄" : "";
string brokerInfo = "";
// Shortened header to prevent truncation
string hdr = StringFormat("[%s] [TF:%s] %s [ATR:%.1f] %s %s %s",
_Symbol, EnumToString(_Period), spreadInfo, sp.atr, modeStr,
aiStatus, sess);
DrawLabel("hdr",10,10,hdr,clrWhite,10);
// Multi-timeframe scanner with enhanced debug info
// DebugLog("=== RENDERHUD DEBUG START ===");
string mtfData = BuildScanner();
// DebugLog("BuildScanner returned: '" + mtfData + "'");
// DebugLog("MTF Data length: " + IntegerToString(StringLen(mtfData)));
int yStart = 30;
// Enhanced debug info for MTF scanner
if(EnableMTFScanner) {
string debugInfo = "MTF Scanner: ENABLED - Data should be visible below";
DrawLabel("mtf_debug",10,yStart,debugInfo,clrYellow,8);
// Additional debug info for indicators
string indicatorDebugInfo = StringFormat("Indicators: EMA(%d,%d) RSI(%d) ADX(%d) Stoch(%d,%d,%d)",
hEmaF, hEmaS, hRsi, hAdx, hStoch, Stochastic_K, Stochastic_D);
DrawLabel("indicator_debug",10,yStart+15,indicatorDebugInfo,clrCyan,8);
// MTF handles debug info
if(EnableMTFConfirmation) {
string mtfHandlesInfo = StringFormat("MTF Handles: H1(EMA:%d,%d RSI:%d ADX:%d Stoch:%d) M15(EMA:%d,%d RSI:%d ADX:%d Stoch:%d)",
hEmaF_H1, hEmaS_H1, hRsi_H1, hAdx_H1, hStoch_H1,
hEmaF_M15, hEmaS_M15, hRsi_M15, hAdx_M15, hStoch_M15);
DrawLabel("mtf_handles_debug",10,yStart+45,mtfHandlesInfo,clrMagenta,8);
string mtfHandlesInfo2 = StringFormat("MTF Handles: M5(EMA:%d,%d RSI:%d ADX:%d Stoch:%d) M1(EMA:%d,%d RSI:%d ADX:%d Stoch:%d)",
hEmaF_M5, hEmaS_M5, hRsi_M5, hAdx_M5, hStoch_M5,
hEmaF_M1, hEmaS_M1, hRsi_M1, hAdx_M1, hStoch_M1);
DrawLabel("mtf_handles_debug2",10,yStart+60,mtfHandlesInfo2,clrMagenta,8);
}
// Show current symbol and timeframe with tracking status
string tfStatus = timeframeChanged ? " (CHANGED)" : " (TRACKING)";
string symbolInfo = StringFormat("Symbol: %s | TF: %s%s | Spread: %d%s",
_Symbol, EnumToString(_Period), tfStatus, SpreadPoints(), "");
DrawLabel("symbol_debug",10,yStart+75,symbolInfo,clrLightSteelBlue,8);
// Show MTF data length for debugging
string dataLengthInfo = StringFormat("MTF Data Length: %d characters", StringLen(mtfData));
DrawLabel("data_length_debug",10,yStart+90,dataLengthInfo,clrOrange,8);
// Display MTF data as multiline labels for reliability
int mtfY = yStart + 105;
// DebugLog("About to draw MTF data (multiline) at Y=" + IntegerToString(mtfY) + " with first line: '" + StringSubstr(mtfData, 0, 50) + "...'");
int linesDrawn = DrawMultiline("mtf",10,mtfY,mtfData,clrSilver,9,14);
//EssentialLog("MTF Dashboard updated successfully");
// Add separator line under the table
int sepY = mtfY + linesDrawn*14 + 2;
string separator = "==========================================";
DrawLabel("separator",10,sepY,separator,clrGray,8);
// Place the rest below the table
int baseY = sepY + 12;
string sig = (sp.buy?"BUY":(sp.sell?"SELL":"-"));
color sigColor = (sp.buy?clrLime:(sp.sell?clrTomato:clrGray));
// Standard RSI color coding
color rsiColor = clrWhite;
// Set color based on standard RSI thresholds
if(sp.rsi <= 30) {
rsiColor = clrLime; // Green for oversold
} else if(sp.rsi >= 70) {
rsiColor = clrTomato; // Red for overbought
} else if(sp.rsi > 30 && sp.rsi < 70) {
rsiColor = clrYellow; // Yellow for neutral
}
DrawLabel("sig",10,baseY,StringFormat("Signal: %s Strength: %.0f Confirmations: %d",sig,sp.signalStrength,sp.confirmationCount),sigColor,10);
// Show RSI status with standard thresholds
string rsiStatus = rsiEnabled ? StringFormat("RSI: %.2f (Buy<70, Sell>30)",sp.rsi) : "RSI: DISABLED";
DrawLabel("rsi_level",10,baseY+18,rsiStatus,rsiEnabled ? rsiColor : clrGray,9);
DrawLabel("reason",10,baseY+36,StringFormat("Reason: %s",sp.reason),clrLightSteelBlue,9);
// Sideways market status
if(EnableSidewaysDetection) {
bool isSideways = IsSidewaysMarket();
int sidewaysConf = GetSidewaysConfidence();
string localSidewaysReason = GetSidewaysReason();
string sidewaysStatus = isSideways ?
StringFormat("SIDEWAYS: %d%% | %s", sidewaysConf, localSidewaysReason) :
StringFormat("TRENDING: %d%% | %s", 100-sidewaysConf, localSidewaysReason);
color sidewaysColor = isSideways ? clrOrange : clrCyan;
DrawLabel("sideways_status",10,baseY+54,sidewaysStatus,sidewaysColor,9);
}
// Add MTF information below reason for better visibility
if(EnableMTFConfirmation) {
MTFConfirmation mtf = GetMTFConfirmation();
// Calculate total buy and sell strength for display
double total_buy_strength = mtf.h1_buy_strength + mtf.m15_buy_strength + mtf.m5_buy_strength + mtf.m1_buy_strength;
double total_sell_strength = mtf.h1_sell_strength + mtf.m15_sell_strength + mtf.m5_sell_strength + mtf.m1_sell_strength;
double max_strength = MathMax(total_buy_strength, total_sell_strength);
string mtfInfo = StringFormat("MTF: Score=%.1f (Min:%.1f) | Buy:%.1f Sell:%.1f | %s",
mtf.total_score, MTF_MinScore, mtf.total_buy_score, mtf.total_sell_score,
mtf.total_score >= MTF_MinScore ? "READY" : "WAITING");
color mtfColor = (mtf.total_score >= MTF_MinScore) ? clrLime : clrOrange;
DrawLabel("mtf_info",10,baseY+72,mtfInfo,mtfColor,9);
// Add MTF Dominant signal display
string dominantSignal = "";
color dominantColor = clrGray;
if(mtf.total_buy_score > mtf.total_sell_score) {
dominantSignal = StringFormat("MTF Dominant: BUY (%.1f > %.1f)", mtf.total_buy_score, mtf.total_sell_score);
dominantColor = clrLime;
} else if(mtf.total_sell_score > mtf.total_buy_score) {
dominantSignal = StringFormat("MTF Dominant: SELL (%.1f > %.1f)", mtf.total_sell_score, mtf.total_buy_score);
dominantColor = clrTomato;
} else {
dominantSignal = StringFormat("MTF Dominant: NEUTRAL (Buy:%.1f, Sell:%.1f)", mtf.total_buy_score, mtf.total_sell_score);
dominantColor = clrGray;
}
DrawLabel("mtf_dominant",10,baseY+90,dominantSignal,dominantColor,9);
// Adjust other elements position
double eq=AccountInfoDouble(ACCOUNT_EQUITY), bal=AccountInfoDouble(ACCOUNT_BALANCE), fl=AccountInfoDouble(ACCOUNT_PROFIT);
DrawLabel("pl",10,baseY+108,StringFormat("Eq:%.2f Bal:%.2f Float:%.2f Risk%%:%.2f", eq, bal, fl, RiskPercent),clrLightSteelBlue,9);
// News-safe status
string ns = (NewsWindowActive()?"PAUSE around NEWS":"OK");
DrawLabel("news",10,baseY+126,StringFormat("News: %s (upcoming: %s)", ns, (string)UpcomingNewsTime), clrYellow, 9);
// Session status
string sessionStatus = (IsSessionActive(waktu.hour)?"ACTIVE":"INACTIVE");
DrawLabel("session",10,baseY+144,StringFormat("Session: %s (%s) - %s", sess, sessionStatus, (WithinTradingHours()?"Trading Hours":"Outside Hours")), clrCyan, 9);
// Supply/Demand zones count
DrawLabel("sd",10,baseY+162,StringFormat("S/D Zones: %d Trendlines: %d", sdZoneCount, trendlineCount), clrOrange, 9);
// Indicator status summary
string indicatorStatus = StringFormat("Indicators: RSI(%s) ADX(%s) Stoch(%s)",
rsiEnabled ? "ON" : "OFF",
adxEnabled ? "ON" : "OFF",
stochEnabled ? "ON" : "OFF");
DrawLabel("indicator_status",10,baseY+180,indicatorStatus,clrLightSteelBlue,9);
// Re-Entry status
if(EnableReEntry) {
// Calculate required loss points for next re-entry
int buyRequiredLoss = buyReEntryCount < MaxReEntries ? MinFloatingLossPts * (buyReEntryCount + 1) : 0;
int sellRequiredLoss = sellReEntryCount < MaxReEntries ? MinFloatingLossPts * (sellReEntryCount + 1) : 0;
string reEntryStatus = StringFormat("Re-Entry: BUY(%d/%d) SELL(%d/%d) | Next: BUY=%dpts SELL=%dpts",
buyReEntryCount, MaxReEntries, sellReEntryCount, MaxReEntries, buyRequiredLoss, sellRequiredLoss);
color reEntryColor = (buyReEntryCount > 0 || sellReEntryCount > 0) ? clrOrange : clrLightSteelBlue;
DrawLabel("reentry_status",10,baseY+198,reEntryStatus,reEntryColor,9);
}
// Enhanced Confirmation Status
if(EnableBreakoutConfirmation || EnableEngulfingConfirmation) {
string breakoutStatus = sp.breakoutConfirmed ?
"✅ Breakout: " + sp.breakoutReason :
"❌ Breakout: " + sp.breakoutReason;
color breakoutColor = sp.breakoutConfirmed ? clrLime : clrRed;
DrawLabel("breakout_status",10,baseY+216,breakoutStatus,breakoutColor,9);
string engulfingStatus = sp.engulfingConfirmed ?
"✅ Engulfing: " + sp.engulfingReason :
"❌ Engulfing: " + sp.engulfingReason;
color engulfingColor = sp.engulfingConfirmed ? clrLime : clrRed;
DrawLabel("engulfing_status",10,baseY+234,engulfingStatus,engulfingColor,9);
string totalScore = StringFormat("Total Score: %.1f (Min: %.1f)",
sp.totalConfirmationScore, MinEnhancedScore);
color scoreColor = sp.totalConfirmationScore >= MinEnhancedScore ? clrLime : clrOrange;
DrawLabel("total_score",10,baseY+252,totalScore,scoreColor,9);
}
} else {
// Original positioning when MTF is disabled
double eq=AccountInfoDouble(ACCOUNT_EQUITY), bal=AccountInfoDouble(ACCOUNT_BALANCE), fl=AccountInfoDouble(ACCOUNT_PROFIT);
DrawLabel("pl",10,baseY+54,StringFormat("Eq:%.2f Bal:%.2f Float:%.2f Risk%%:%.2f", eq, bal, fl, RiskPercent),clrLightSteelBlue,9);
// Sideways market status (when MTF is disabled)
if(EnableSidewaysDetection) {
bool isSideways = IsSidewaysMarket();
int sidewaysConf = GetSidewaysConfidence();
string localSidewaysReason = GetSidewaysReason();
string sidewaysStatus = isSideways ?
StringFormat("SIDEWAYS: %d%% | %s", sidewaysConf, localSidewaysReason) :
StringFormat("TRENDING: %d%% | %s", 100-sidewaysConf, localSidewaysReason);
color sidewaysColor = isSideways ? clrOrange : clrCyan;
DrawLabel("sideways_status",10,baseY+72,sidewaysStatus,sidewaysColor,9);
}
// News-safe status
string ns = (NewsWindowActive()?"PAUSE around NEWS":"OK");
DrawLabel("news",10,baseY+90,StringFormat("News: %s (upcoming: %s)", ns, (string)UpcomingNewsTime), clrYellow, 9);
// Session status
string sessionStatus = (IsSessionActive(waktu.hour)?"ACTIVE":"INACTIVE");
DrawLabel("session",10,baseY+108,StringFormat("Session: %s (%s) - %s", sess, sessionStatus, (WithinTradingHours()?"Trading Hours":"Outside Hours")), clrCyan, 9);
// Supply/Demand zones count
DrawLabel("sd",10,baseY+126,StringFormat("S/D Zones: %d Trendlines: %d", sdZoneCount, trendlineCount), clrOrange, 9);
// Indicator status summary
string indicatorStatus = StringFormat("Indicators: RSI(%s) ADX(%s) Stoch(%s)",
rsiEnabled ? "ON" : "OFF",
adxEnabled ? "ON" : "OFF",
stochEnabled ? "ON" : "OFF");
DrawLabel("indicator_status",10,baseY+144,indicatorStatus,clrLightSteelBlue,9);
// Re-Entry status
if(EnableReEntry) {
// Calculate required loss points for next re-entry
int buyRequiredLoss = buyReEntryCount < MaxReEntries ? MinFloatingLossPts * (buyReEntryCount + 1) : 0;
int sellRequiredLoss = sellReEntryCount < MaxReEntries ? MinFloatingLossPts * (sellReEntryCount + 1) : 0;
string reEntryStatus = StringFormat("Re-Entry: BUY(%d/%d) SELL(%d/%d) | Next: BUY=%dpts SELL=%dpts",
buyReEntryCount, MaxReEntries, sellReEntryCount, MaxReEntries, buyRequiredLoss, sellRequiredLoss);
color reEntryColor = (buyReEntryCount > 0 || sellReEntryCount > 0) ? clrOrange : clrLightSteelBlue;
DrawLabel("reentry_status",10,baseY+162,reEntryStatus,reEntryColor,9);
}
// Enhanced Confirmation Status (when MTF is disabled)
if(EnableBreakoutConfirmation || EnableEngulfingConfirmation) {
string breakoutStatus = sp.breakoutConfirmed ?
"✅ Breakout: " + sp.breakoutReason :
"❌ Breakout: " + sp.breakoutReason;
color breakoutColor = sp.breakoutConfirmed ? clrLime : clrRed;
DrawLabel("breakout_status",10,baseY+180,breakoutStatus,breakoutColor,9);
string engulfingStatus = sp.engulfingConfirmed ?
"✅ Engulfing: " + sp.engulfingReason :
"❌ Engulfing: " + sp.engulfingReason;
color engulfingColor = sp.engulfingConfirmed ? clrLime : clrRed;
DrawLabel("engulfing_status",10,baseY+198,engulfingStatus,engulfingColor,9);
string totalScore = StringFormat("Total Score: %.1f (Min: %.1f)",
sp.totalConfirmationScore, MinEnhancedScore);
color scoreColor = sp.totalConfirmationScore >= MinEnhancedScore ? clrLime : clrOrange;
DrawLabel("total_score",10,baseY+216,totalScore,scoreColor,9);
}
}
} else {
string debugInfo = "MTF Scanner: DISABLED - Enable in settings";
DrawLabel("mtf_debug",10,yStart,debugInfo,clrRed,8);
}
// Check object visibility after drawing
// DebugLog("=== CHECKING OBJECT VISIBILITY ===");
CheckObjectVisibility("hdr");
CheckObjectVisibility("mtf_debug");
CheckObjectVisibility("mtf_0");
CheckObjectVisibility("sig");
// Force chart refresh
ForceChartRefresh();
// Update toggle buttons if they exist
if(ShowToggleButtons) {
CreateToggleButtons();
}
// DebugLog("=== RENDERHUD DEBUG END ===");
}
//==================== Core ====================
int OnInit(){
EssentialLog("🚀 SmartBot Initializing...");
EssentialLog("Symbol: " + _Symbol + " | Timeframe: " + EnumToString(_Period));
EssentialLog("Mode: " + (Mode==MODE_SCALPING?"Scalping":(Mode==MODE_INTRADAY?"Intraday":"Swing")));
EssentialLog("MTF Scanner: " + (EnableMTFScanner ? "ON" : "OFF"));
// Initialize timeframe tracking
currentTimeframe = Period();
timeframeChanged = false;
EssentialLog("📊 Timeframe tracking initialized: " + EnumToString(currentTimeframe));
pt = SymbolInfoDouble(_Symbol,SYMBOL_POINT);
EssentialLog("Point value: " + DoubleToString(pt, 5));
EssentialLog("📊 Loading indicators...");
if(!EnsureIndicators()) {
EssentialLog("❌ Failed to load indicators");
return INIT_FAILED;
}
EssentialLog("✅ All indicators loaded successfully");
// DebugLog("Indicator handles: EMA_F=" + IntegerToString(hEmaF) + " EMA_S=" + IntegerToString(hEmaS) + " RSI=" + IntegerToString(hRsi) + " ADX=" + IntegerToString(hAdx) + " ATR=" + IntegerToString(hAtr) + " Stoch=" + IntegerToString(hStoch) + " Vol=" + IntegerToString(hVolume));
// Initialize symbol info
symbolInfoGlobal.Name(_Symbol);
symbolInfoGlobal.RefreshRates();
EssentialLog("📈 Symbol info initialized");
// Set up trade object
trade.SetExpertMagicNumber(Magic);
trade.SetDeviationInPoints(10);
trade.SetTypeFilling(ORDER_FILLING_FOK);
EssentialLog("💼 Trade object configured");
// Initialize arrays
ArrayResize(sdZones, 0);
ArrayResize(trendlines, 0);
ArrayResize(tradeHistory, 0);
EssentialLog("📋 Arrays initialized");
// Enable chart events for timeframe change detection and button clicks
EssentialLog("📊 Enabling chart events...");
ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
EssentialLog("✅ Chart events enabled");
// Initialize toggle button states
rsiEnabled = EnableRSI;
adxEnabled = EnableADX;
stochEnabled = EnableStochastic;
mtfApplyToAllPairsEnabled = MTF_ApplyToAllPairs;
sidewaysDisableTradingEnabled = Sideways_DisableTrading;
breakoutConfirmationEnabled = EnableBreakoutConfirmation;
engulfingConfirmationEnabled = EnableEngulfingConfirmation;
EssentialLog("🎛️ Toggle states initialized - RSI:" + (rsiEnabled ? "ON" : "OFF") +
" ADX:" + (adxEnabled ? "ON" : "OFF") + " Stoch:" + (stochEnabled ? "ON" : "OFF") +
" MTF All:" + (mtfApplyToAllPairsEnabled ? "ON" : "OFF") +
" Sideways:" + (sidewaysDisableTradingEnabled ? "DISABLE" : "ENABLE") +
" Breakout:" + (breakoutConfirmationEnabled ? "ON" : "OFF") +
" Engulfing:" + (engulfingConfirmationEnabled ? "ON" : "OFF"));
// Log timeframe-specific confirmation scope
string tfScope = "";
if(IsEntryTimeframe()) {
tfScope = "Entry Timeframe (M1/M5) - Confirmation Active";
} else if(IsSetupTimeframe()) {
tfScope = "Setup Timeframe (M5) - Confirmation Active";
} else {
tfScope = "Trend Timeframe (H1) - Confirmation Skipped";
}
EssentialLog("🎯 Timeframe Confirmation Scope: " + tfScope);
// Broker detection disabled: all adjustments are auto from spread & broker stop level
// Initialize MTF handles if enabled
if(EnableMTFConfirmation) {
InitializeMTFHandles();
}
// Create toggle buttons on chart
CreateToggleButtons();
EssentialLog("🎛️ Toggle buttons created on chart");
// Ensure buttons are clickable and visible
EssentialLog("🎛️ Ensuring button clickability...");
ObjectSetInteger(0, "RSI_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "ADX_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "Stoch_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "MTF_AllPairs_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "Sideways_Disable_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "Breakout_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "Engulfing_Toggle_Button", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "RSI_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "ADX_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "Stoch_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "MTF_AllPairs_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "Sideways_Disable_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "Breakout_Toggle_Button", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "Engulfing_Toggle_Button", OBJPROP_HIDDEN, false);
ChartRedraw();
// Force immediate dashboard update
EssentialLog("🖥️ Building dashboard...");
SignalPack sp;
BuildSignal(sp);
RenderHUD(sp);
EssentialLog("✅ SmartBot initialized successfully");
EssentialLog("🎯 Ready for trading - Mode: " + (Mode==MODE_SCALPING?"Scalping":(Mode==MODE_INTRADAY?"Intraday":"Swing")));
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason){
string names[] = {"hdr","mtf","mtf_debug","indicator_debug","symbol_debug","data_length_debug","separator","sig","rsi_level","reason","pl","news","session","sd","indicator_status","reentry_status","mtf_handles_debug","mtf_handles_debug2","sideways_status","breakout_status","engulfing_status","total_score","tf_confirmation_scope"};
for(int i=0;i<ArraySize(names);i++){
if(ObjectFind(0,names[i])>=0) ObjectDelete(0,names[i]);
}
// remove multiline mtf_* labels generously
for(int i=0;i<30;i++){
string nm = "mtf_"+IntegerToString(i);
if(ObjectFind(0,nm)>=0) ObjectDelete(0,nm);
}
// Release MTF handles
ReleaseMTFHandles();
// Delete toggle buttons
DeleteToggleButtons();
EssentialLog("🎛️ Toggle buttons removed from chart");
}
void TryEntry(const SignalPack &sp){
EssentialLog("🎯 TryEntry: Function called - Buy=" + (sp.buy ? "YES" : "NO") + " Sell=" + (sp.sell ? "YES" : "NO"));
if(!AutoTrade) {
EssentialLog("❌ TryEntry: AutoTrade is DISABLED");
return;
}
if(!IsSpreadAcceptable()) {
EssentialLog("❌ TryEntry: Spread not acceptable - Current=" + IntegerToString(SpreadPoints()) + " Max=" + IntegerToString(MaxSpreadPoints));
return;
}
if(!WithinTradingHours()) {
EssentialLog("❌ TryEntry: Outside trading hours");
return;
}
if(NewsWindowActive()) {
EssentialLog("❌ TryEntry: News window active");
return;
}
MqlDateTime currentTime;
TimeToStruct(TimeCurrent(), currentTime);
if(!IsSessionActive(currentTime.hour)) {
EssentialLog("❌ TryEntry: Session not active - Hour=" + IntegerToString(currentTime.hour));
return;
}
EssentialLog("✅ TryEntry: All basic conditions passed");
// Calculate spread buffer for entry with broker-specific adjustments
int currentSpread = SpreadPoints();
int spreadBuffer = 0;
// Auto spread buffer selalu aktif
double dynamicSpreadBuffer = CalculateDynamicSpreadBuffer();
spreadBuffer = (int)(currentSpread * dynamicSpreadBuffer);
int dir=-1;
string candidate="";
bool isReEntry = false;
// Check for BUY signal
if(sp.buy) {
EssentialLog("🔍 TryEntry: Checking BUY signal...");
if(CountPositions(ORDER_TYPE_BUY) == 0) {
// New BUY signal - no existing positions
dir = ORDER_TYPE_BUY;
candidate = "BUY";
UpdateReEntryCounters(POSITION_TYPE_BUY, false); // Reset SELL counter
lastBuySignalTime = TimeCurrent();
EssentialLog("✅ TryEntry: New BUY signal - no existing positions");
} else if(EnableReEntry && HasFloatingLossPositions(POSITION_TYPE_BUY) && IsReEntryAllowed(POSITION_TYPE_BUY)) {
// Re-entry BUY signal - existing floating loss positions
dir = ORDER_TYPE_BUY;
candidate = "BUY RE-ENTRY";
isReEntry = true;
UpdateReEntryCounters(POSITION_TYPE_BUY, true);
lastBuySignalTime = TimeCurrent();
EssentialLog("✅ TryEntry: BUY RE-ENTRY signal");
} else {
EssentialLog("⚠️ TryEntry: BUY signal ignored - existing positions or re-entry not allowed");
}
}
// Check for SELL signal
if(sp.sell && dir == -1) {
EssentialLog("🔍 TryEntry: Checking SELL signal...");
if(CountPositions(ORDER_TYPE_SELL) == 0) {
// New SELL signal - no existing positions
dir = ORDER_TYPE_SELL;
candidate = "SELL";
UpdateReEntryCounters(POSITION_TYPE_SELL, false); // Reset BUY counter
lastSellSignalTime = TimeCurrent();
EssentialLog("✅ TryEntry: New SELL signal - no existing positions");
} else if(EnableReEntry && HasFloatingLossPositions(POSITION_TYPE_SELL) && IsReEntryAllowed(POSITION_TYPE_SELL)) {
// Re-entry SELL signal - existing floating loss positions
dir = ORDER_TYPE_SELL;
candidate = "SELL RE-ENTRY";
isReEntry = true;
UpdateReEntryCounters(POSITION_TYPE_SELL, true);
lastSellSignalTime = TimeCurrent();
EssentialLog("✅ TryEntry: SELL RE-ENTRY signal");
} else {
EssentialLog("⚠️ TryEntry: SELL signal ignored - existing positions or re-entry not allowed");
}
}
if(dir == -1) {
EssentialLog("❌ TryEntry: No valid signal direction determined");
return;
}
EssentialLog("🎯 Signal detected: " + candidate + " - Checking AI approval...");
// Check DeepSeek AI first
if(DeepSeek_Enable && DeepSeek_API_Key != "") {
EssentialLog("🤖 Calling DeepSeek AI for analysis...");
string err, resp = CallDeepSeek(BuildDeepSeekPayload(sp, candidate), err);
if(resp != "") {
EssentialLog("DeepSeek response: " + resp);
bool confirmed = false;
if(dir == ORDER_TYPE_BUY && DeepSeek_ConfirmBuy(resp)) {
confirmed = true;
} else if(dir == ORDER_TYPE_SELL && DeepSeek_ConfirmSell(resp)) {
confirmed = true;
}
if(DeepSeek_Reject(resp)) {
EssentialLog("❌ DeepSeek REJECTED the signal: " + resp);
return;
}
if(DeepSeek_Wait(resp)) {
EssentialLog("⏳ DeepSeek recommends WAITING: " + resp);
return;
}
if(!confirmed) {
EssentialLog("❌ DeepSeek did not confirm the signal: " + resp);
return;
}
if(DeepSeek_RequireApprove) {
EssentialLog("✅ DeepSeek confirmed, waiting manual approve");
return;
}
EssentialLog("✅ DeepSeek confirmed the signal, proceeding with trade");
} else {
EssentialLog("❌ DeepSeek call failed: " + err);
// Continue with ChatGPT if DeepSeek fails
}
}
// Check ChatGPT if enabled
if(ChatGPT_Enable && ChatGPT_API_Key != "") {
EssentialLog("🤖 Calling ChatGPT AI for analysis...");
string err, resp = CallChatGPT(BuildChatGPTPayload(sp, candidate), err);
if(resp != "") {
EssentialLog("ChatGPT response: " + resp);
bool confirmed = false;
if(dir == ORDER_TYPE_BUY && ChatGPT_ConfirmBuy(resp)) {
confirmed = true;
} else if(dir == ORDER_TYPE_SELL && ChatGPT_ConfirmSell(resp)) {
confirmed = true;
}
if(ChatGPT_Reject(resp)) {
EssentialLog("❌ ChatGPT REJECTED the signal: " + resp);
return;
}
if(ChatGPT_Wait(resp)) {
EssentialLog("⏳ ChatGPT recommends WAITING: " + resp);
return;
}
if(!confirmed) {
EssentialLog("❌ ChatGPT did not confirm the signal: " + resp);
return;
}
if(ChatGPT_RequireApprove) {
EssentialLog("✅ ChatGPT confirmed, waiting manual approve");
return;
}
EssentialLog("✅ ChatGPT confirmed the signal, proceeding with trade");
} else {
EssentialLog("❌ ChatGPT call failed: " + err);
// Continue with other AI if ChatGPT fails
}
}
// Fallback to other AI if enabled
if(AI_Assist_Enable && AI_Endpoint_URL!="" && !DeepSeek_Enable && !ChatGPT_Enable){
EssentialLog("🤖 Calling Legacy AI for analysis...");
string err,resp=CallAI(AI_Endpoint_URL,BuildPayload(sp,candidate),AI_API_Key,AI_TimeoutMs,err);
if(resp!=""){
EssentialLog("Legacy AI response: " + resp);
bool ok=(dir==ORDER_TYPE_BUY?AI_ConfirmBuy(resp):AI_ConfirmSell(resp));
if(!ok){ EssentialLog("❌ Legacy AI veto: " + resp); return; }
if(AI_RequireApprove){ EssentialLog("✅ Legacy AI confirmed, waiting manual approve"); return; }
EssentialLog("✅ Legacy AI confirmed the signal, proceeding with trade");
} else { EssentialLog("❌ Legacy AI call failed: " + err); }
}
EssentialLog("✅ TryEntry: All checks passed, executing trade");
// Calculate TP/SL
double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK), bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double price = (dir==ORDER_TYPE_BUY? ask: bid);
double sl, tp1, tp2, tp3;
CalculateTPSL(dir, price, sl, tp1, tp2, tp3);
double baseLot = LotByRisk(MathAbs(price - sl) / pt);
double lot = isReEntry ? CalculateReEntryLot(baseLot, dir == ORDER_TYPE_BUY ? POSITION_TYPE_BUY : POSITION_TYPE_SELL) : baseLot;
trade.SetExpertMagicNumber(Magic);
bool ok=false;
if(dir==ORDER_TYPE_BUY) {
ok=trade.Buy(lot,NULL,price,sl,tp1);
} else {
ok=trade.Sell(lot,NULL,price,sl,tp1);
}
if(ok) {
string tradeType = isReEntry ? "RE-ENTRY " : "";
string direction = (dir==ORDER_TYPE_BUY) ? "BUY" : "SELL";
EssentialLog("✅ Opened " + tradeType + direction + " lot=" + DoubleToString(lot,2) + " SL=" + DoubleToString(sl,5) + " TP1=" + DoubleToString(tp1,5));
if(isReEntry) {
EssentialLog("💰 Re-Entry: " + direction + " re-entry #" + IntegerToString(GetReEntryCount(dir == ORDER_TYPE_BUY ? POSITION_TYPE_BUY : POSITION_TYPE_SELL)) +
" opened with lot size " + DoubleToString(lot,2));
}
// Log trade
if(EnableTradeLog) {
TradeRecord record;
record.openTime = TimeCurrent();
record.pair = _Symbol;
record.type = dir;
record.lot = lot;
record.openPrice = price;
record.sl = sl;
record.tp = tp1;
record.reason = sp.reason;
record.closeTime = 0;
record.closePrice = 0;
record.profit = 0;
record.notes = "Signal Strength: " + DoubleToString(sp.signalStrength, 0) +
(isReEntry ? " | Re-Entry #" + IntegerToString(GetReEntryCount(dir == ORDER_TYPE_BUY ? POSITION_TYPE_BUY : POSITION_TYPE_SELL)) : "");
LogTrade(record);
}
} else {
EssentialLog("❌ Open failed: " + IntegerToString(GetLastError()));
}
}
ENUM_TIMEFRAMES changeTimeframe = NULL;
void OnTick(){
if(!EnsureIndicators()) return;
// Check and reset re-entry counters if positions are closed
CheckAndResetReEntryCounters();
// Reset MTF signal tracking if position is closed
ResetMTFSignalTracking();
// Check if timeframe has changed
ENUM_TIMEFRAMES newTimeframe = Period();
if(newTimeframe != changeTimeframe) {
EssentialLog("🔄 OnTick: Timeframe change detected: " + EnumToString(currentTimeframe) + " → " + EnumToString(newTimeframe));
changeTimeframe = newTimeframe;
timeframeChanged = true;
EssentialLog("🔄 OnTick: Timeframe changed to: " + EnumToString(currentTimeframe));
// Reset indicator handles to force reload with new timeframe
EssentialLog("🔄 OnTick: Calling ResetIndicatorHandles()...");
ResetIndicatorHandles();
// Force immediate indicator reload
EssentialLog("🔄 OnTick: Calling EnsureIndicators()...");
if(!EnsureIndicators()) {
EssentialLog("❌ OnTick: Failed to reload indicators for new timeframe");
return;
}
EssentialLog("✅ OnTick: Indicators reloaded successfully for new timeframe");
// Force immediate signal rebuild and dashboard update
EssentialLog("🔄 OnTick: Rebuilding signal and updating dashboard...");
SignalPack sp;
BuildSignal(sp);
RenderHUD(sp);
EssentialLog("✅ OnTick: Dashboard updated for new timeframe");
}
//EssentialLog("LOG : "+ timeframeChanged);
// Always update dashboard on every tick for better responsiveness
SignalPack sp;
BuildSignal(sp);
RenderHUD(sp);
// Entry condition check (reduced logging)
if(!AutoTrade) {
EssentialLog("❌ OnTick: AutoTrade is OFF - skipping entry");
return;
}
if(SpreadPoints() > MaxSpreadPoints) {
EssentialLog("❌ OnTick: Spread too high (" + IntegerToString(SpreadPoints()) + " > " + IntegerToString(MaxSpreadPoints) + ") - skipping entry");
return;
}
if(!WithinTradingHours()) {
EssentialLog("❌ OnTick: Outside trading hours - skipping entry");
return;
}
if(NewsWindowActive()) {
EssentialLog("❌ OnTick: News window active - skipping entry");
return;
}
if(NewBar() || timeframeChanged){
EssentialLog("🔄 OnTick: New bar detected, checking for entry...");
// CRITICAL DEBUG: Log signal details before TryEntry
EssentialLog("🎯 OnTick: Signal details:");
EssentialLog(" Buy Signal: " + (sp.buy ? "YES" : "NO"));
EssentialLog(" Sell Signal: " + (sp.sell ? "YES" : "NO"));
EssentialLog(" Signal Strength: " + DoubleToString(sp.signalStrength, 1));
EssentialLog(" Confirmation Count: " + IntegerToString(sp.confirmationCount));
EssentialLog(" Reason: " + sp.reason);
// Check if we have any signal at all
if(!sp.buy && !sp.sell) {
EssentialLog("❌ OnTick: NO SIGNAL GENERATED - skipping TryEntry");
} else {
EssentialLog("✅ OnTick: SIGNAL DETECTED - calling TryEntry");
TryEntry(sp);
}
// Update S/D zones periodically
static int sdUpdateCounter = 0;
sdUpdateCounter++;
if(sdUpdateCounter >= 10) { // Update every 10 bars
DetectSupplyDemand();
sdUpdateCounter = 0;
}
// Reset timeframe changed flag
timeframeChanged = false;
}
ManageTrailing();
}
//+------------------------------------------------------------------+
//| Chart Event Handler - Detects timeframe changes and other chart events |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) {
//EssentialLog("📊 OnChartEvent: Event ID=" + IntegerToString(id) + " detected");
// Handle chart timeframe change
if(id == CHARTEVENT_CHART_CHANGE) {
//EssentialLog("📊 OnChartEvent: CHARTEVENT_CHART_CHANGE detected");
ENUM_TIMEFRAMES newTimeframe = Period();
//EssentialLog("📊 OnChartEvent: Current TF=" + EnumToString(currentTimeframe) + " New TF=" + EnumToString(newTimeframe));
if(newTimeframe != currentTimeframe) {
// EssentialLog("🔄 OnChartEvent: Timeframe change detected: " + EnumToString(currentTimeframe) + " → " + EnumToString(newTimeframe));
currentTimeframe = newTimeframe;
timeframeChanged = true;
EssentialLog("🔄 OnChartEvent: Timeframe changed to: " + EnumToString(currentTimeframe));
// Reset indicator handles to force reload with new timeframe
EssentialLog("🔄 OnChartEvent: Calling ResetIndicatorHandles()...");
ResetIndicatorHandles();
// Reset MTF handles if enabled
if(EnableMTFConfirmation) {
EssentialLog("🔄 OnChartEvent: Calling ReleaseMTFHandles()...");
ReleaseMTFHandles();
EssentialLog("🔄 OnChartEvent: Calling InitializeMTFHandles()...");
InitializeMTFHandles();
}
// Force immediate indicator reload
EssentialLog("🔄 OnChartEvent: Calling EnsureIndicators()...");
if(!EnsureIndicators()) {
EssentialLog("❌ OnChartEvent: Failed to reload indicators for new timeframe");
return;
}
EssentialLog("✅ OnChartEvent: Indicators reloaded successfully");
// Force immediate dashboard update
EssentialLog("🔄 OnChartEvent: Updating dashboard...");
SignalPack sp;
BuildSignal(sp);
RenderHUD(sp);
EssentialLog("✅ OnChartEvent: Dashboard updated successfully");
}
}
// Handle button clicks
if(id == CHARTEVENT_OBJECT_CLICK) {
EssentialLog("🎛️ OnChartEvent: Object click detected - Object: " + sparam);
if(sparam == "RSI_Toggle_Button" || sparam == "ADX_Toggle_Button" || sparam == "Stoch_Toggle_Button" ||
sparam == "MTF_AllPairs_Toggle_Button" || sparam == "Sideways_Disable_Toggle_Button" ||
sparam == "Breakout_Toggle_Button" || sparam == "Engulfing_Toggle_Button") {
EssentialLog("🎛️ OnChartEvent: Toggle button clicked: " + sparam);
HandleButtonClick(sparam);
ChartRedraw(); // Force chart refresh after button click
}
}
// Handle mouse clicks as fallback (using CHARTEVENT_MOUSE_CLICK is not available in MQL5)
// Mouse clicks are handled automatically by CHARTEVENT_OBJECT_CLICK for chart objects
}
//==================== Multi Timeframe Confirmation System ====================
// Anti-repaint function for MTF data reading
int ShiftFor(ENUM_TIMEFRAMES tf) {
datetime t = iTime(_Symbol, PERIOD_CURRENT, 1); // bar closed
if(t == 0) t = iTime(_Symbol, PERIOD_CURRENT, 0);
int sh = iBarShift(_Symbol, tf, t, true);
return (sh < 1 ? 1 : sh);
}
MTFConfirmation GetMTFConfirmation() {
EssentialLog("🔍 GetMTFConfirmation: Function called");
MTFConfirmation mtf; // Uses default constructor
// Check if MTF handles are initialized
if(!EnableMTFConfirmation) {
EssentialLog("🔍 GetMTFConfirmation: MTF Confirmation is DISABLED, returning early");
return mtf;
}
// Debug: Log MTF handle status
static datetime lastMTFLog = 0;
if(TimeCurrent() - lastMTFLog > 5) { // Log every 5 seconds
EssentialLog("🔍 MTF Debug - Handles: H1(EMA:" + IntegerToString(hEmaF_H1) + "," + IntegerToString(hEmaS_H1) +
" RSI:" + IntegerToString(hRsi_H1) + " ADX:" + IntegerToString(hAdx_H1) + " Stoch:" + IntegerToString(hStoch_H1) + ")");
EssentialLog("🔍 MTF Debug - Handles: M15(EMA:" + IntegerToString(hEmaF_M15) + "," + IntegerToString(hEmaS_M15) +
" RSI:" + IntegerToString(hRsi_M15) + " ADX:" + IntegerToString(hAdx_M15) + " Stoch:" + IntegerToString(hStoch_M15) + ")");
EssentialLog("🔍 MTF Debug - Handles: M5(EMA:" + IntegerToString(hEmaF_M5) + "," + IntegerToString(hEmaS_M5) +
" RSI:" + IntegerToString(hRsi_M5) + " ADX:" + IntegerToString(hAdx_M5) + " Stoch:" + IntegerToString(hStoch_M5) + ")");
EssentialLog("🔍 MTF Debug - Handles: M1(EMA:" + IntegerToString(hEmaF_M1) + "," + IntegerToString(hEmaS_M1) +
" RSI:" + IntegerToString(hRsi_M1) + " ADX:" + IntegerToString(hAdx_M1) + " Stoch:" + IntegerToString(hStoch_M1) + ")");
lastMTFLog = TimeCurrent();
}
// H1 Analysis (40% weight) - Using global handles for real-time updates
double h1_ema_f = 0, h1_ema_s = 0, h1_rsi = 0, h1_adx = 0, h1_stoch_k = 0, h1_stoch_d = 0;
if(hEmaF_H1 != INVALID_HANDLE && hEmaS_H1 != INVALID_HANDLE) {
double h1_ema_f_buffer[1], h1_ema_s_buffer[1];
int shift = ShiftFor(PERIOD_H1);
int copied_f = CopyBuffer(hEmaF_H1, 0, shift, 1, h1_ema_f_buffer);
int copied_s = CopyBuffer(hEmaS_H1, 0, shift, 1, h1_ema_s_buffer);
EssentialLog("🔍 H1 EMA CopyBuffer: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
if(copied_f > 0 && copied_s > 0) {
h1_ema_f = h1_ema_f_buffer[0];
h1_ema_s = h1_ema_s_buffer[0];
EssentialLog("✅ H1 EMA Data: Fast=" + DoubleToString(h1_ema_f, 5) + " Slow=" + DoubleToString(h1_ema_s, 5));
} else {
EssentialLog("❌ H1 EMA CopyBuffer failed: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
}
} else {
EssentialLog("❌ H1 EMA Handles invalid: Fast=" + IntegerToString(hEmaF_H1) + " Slow=" + IntegerToString(hEmaS_H1));
}
if(hRsi_H1 != INVALID_HANDLE) {
double h1_rsi_buffer[1];
int shift = ShiftFor(PERIOD_H1);
int copied = CopyBuffer(hRsi_H1, 0, shift, 1, h1_rsi_buffer);
EssentialLog("🔍 H1 RSI CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
h1_rsi = h1_rsi_buffer[0];
EssentialLog("✅ H1 RSI Data: " + DoubleToString(h1_rsi, 2));
} else {
EssentialLog("❌ H1 RSI CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ H1 RSI Handle invalid: " + IntegerToString(hRsi_H1));
}
if(hAdx_H1 != INVALID_HANDLE) {
double h1_adx_buffer[1];
int shift = ShiftFor(PERIOD_H1);
int copied = CopyBuffer(hAdx_H1, 0, shift, 1, h1_adx_buffer);
EssentialLog("🔍 H1 ADX CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
h1_adx = h1_adx_buffer[0];
EssentialLog("✅ H1 ADX Data: " + DoubleToString(h1_adx, 2));
} else {
EssentialLog("❌ H1 ADX CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ H1 ADX Handle invalid: " + IntegerToString(hAdx_H1));
}
if(hStoch_H1 != INVALID_HANDLE) {
double h1_stoch_k_buffer[1], h1_stoch_d_buffer[1];
int shift = ShiftFor(PERIOD_H1);
int copied_k = CopyBuffer(hStoch_H1, 0, shift, 1, h1_stoch_k_buffer);
int copied_d = CopyBuffer(hStoch_H1, 1, shift, 1, h1_stoch_d_buffer);
EssentialLog("🔍 H1 Stochastic CopyBuffer: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
if(copied_k > 0 && copied_d > 0) {
h1_stoch_k = h1_stoch_k_buffer[0];
h1_stoch_d = h1_stoch_d_buffer[0];
EssentialLog("✅ H1 Stochastic Data: K=" + DoubleToString(h1_stoch_k, 2) + " D=" + DoubleToString(h1_stoch_d, 2));
} else {
EssentialLog("❌ H1 Stochastic CopyBuffer failed: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
}
} else {
EssentialLog("❌ H1 Stochastic Handle invalid: " + IntegerToString(hStoch_H1));
}
bool h1_ema_up = h1_ema_f > h1_ema_s;
bool h1_rsi_buy = (h1_rsi < 50); // RSI < 50 untuk buy (lebih fleksibel)
bool h1_rsi_sell = (h1_rsi > 50); // RSI > 50 untuk sell (lebih fleksibel)
bool h1_adx_buy = (h1_adx >= 10); // ADX >= 10 untuk buy (lebih fleksibel)
bool h1_adx_sell = (h1_adx >= 10); // ADX >= 10 untuk sell (lebih fleksibel)
bool h1_stoch_buy = (h1_stoch_k < 40); // Stoch < 40 untuk buy (lebih fleksibel)
bool h1_stoch_sell = (h1_stoch_k > 60); // Stoch > 60 untuk sell (lebih fleksibel)
EssentialLog("🔍 H1 Conditions: EMA=" + (h1_ema_up ? "UP" : "DOWN") + " RSI=" + DoubleToString(h1_rsi, 1) +
" ADX=" + DoubleToString(h1_adx, 1) + " Stoch=" + DoubleToString(h1_stoch_k, 1));
EssentialLog("🔍 H1 Signals: RSI_Buy=" + (h1_rsi_buy ? "YES" : "NO") + " RSI_Sell=" + (h1_rsi_sell ? "YES" : "NO") +
" ADX_Buy=" + (h1_adx_buy ? "YES" : "NO") + " ADX_Sell=" + (h1_adx_sell ? "YES" : "NO") + " Stoch_Buy=" + (h1_stoch_buy ? "YES" : "NO") + " Stoch_Sell=" + (h1_stoch_sell ? "YES" : "NO"));
// Log jumlah kondisi yang terpenuhi
int h1_buy_count = 0, h1_sell_count = 0;
if(h1_ema_up) h1_buy_count++;
if(h1_rsi_buy) h1_buy_count++;
if(h1_adx_buy) h1_buy_count++;
if(h1_stoch_buy) h1_buy_count++;
if(!h1_ema_up) h1_sell_count++;
if(h1_rsi_sell) h1_sell_count++;
if(h1_adx_sell) h1_sell_count++;
if(h1_stoch_sell) h1_sell_count++;
EssentialLog("🔍 H1 Condition Count: Buy=" + IntegerToString(h1_buy_count) + "/4 Sell=" + IntegerToString(h1_sell_count) + "/4");
// Perhitungan strength yang lebih dinamis untuk H1 dengan mutual exclusion
int h1_buy_conditions = 0;
if(h1_ema_up) h1_buy_conditions++;
if(h1_rsi_buy) h1_buy_conditions++;
if(h1_adx_buy) h1_buy_conditions++;
if(h1_stoch_buy) h1_buy_conditions++;
int h1_sell_conditions = 0;
if(!h1_ema_up) h1_sell_conditions++;
if(h1_rsi_sell) h1_sell_conditions++;
if(h1_adx_sell) h1_sell_conditions++;
if(h1_stoch_sell) h1_sell_conditions++;
// Mutual exclusion: Hanya ambil sinyal yang lebih kuat
if(h1_buy_conditions > h1_sell_conditions && h1_buy_conditions >= 1) {
mtf.h1_buy = true;
mtf.h1_sell = false;
// Score lebih dinamis: 40 untuk 4 kondisi, 30 untuk 3, 20 untuk 2, 10 untuk 1
mtf.h1_buy_strength = 40 * (h1_buy_conditions / 4.0);
mtf.h1_sell_strength = 0; // Reset sell strength
// Bonus untuk kondisi yang sangat kuat
if(h1_buy_conditions >= 3) mtf.h1_buy_strength += 10;
if(h1_buy_conditions == 4) mtf.h1_buy_strength += 10;
EssentialLog("🟢 H1 BUY Signal: Conditions=" + IntegerToString(h1_buy_conditions) + "/4 EMA=" + (h1_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(h1_rsi, 1) + " ADX=" + DoubleToString(h1_adx, 1) + " Stoch=" + DoubleToString(h1_stoch_k, 1));
} else if(h1_sell_conditions > h1_buy_conditions && h1_sell_conditions >= 1) {
mtf.h1_sell = true;
mtf.h1_buy = false;
// Score lebih dinamis: 40 untuk 4 kondisi, 30 untuk 3, 20 untuk 2, 10 untuk 1
mtf.h1_sell_strength = 40 * (h1_sell_conditions / 4.0);
mtf.h1_buy_strength = 0; // Reset buy strength
// Bonus untuk kondisi yang sangat kuat
if(h1_sell_conditions >= 3) mtf.h1_sell_strength += 10;
if(h1_sell_conditions == 4) mtf.h1_sell_strength += 10;
EssentialLog("🔴 H1 SELL Signal: Conditions=" + IntegerToString(h1_sell_conditions) + "/4 EMA=" + (h1_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(h1_rsi, 1) + " ADX=" + DoubleToString(h1_adx, 1) + " Stoch=" + DoubleToString(h1_stoch_k, 1));
} else if(h1_buy_conditions == h1_sell_conditions && h1_buy_conditions >= 2) {
// Jika sama dan cukup kuat, gunakan EMA sebagai tie-breaker
if(h1_ema_up) {
mtf.h1_buy = true;
mtf.h1_sell = false;
mtf.h1_buy_strength = 40 * (h1_buy_conditions / 4.0);
mtf.h1_sell_strength = 0;
if(h1_buy_conditions >= 3) mtf.h1_buy_strength += 10;
if(h1_buy_conditions == 4) mtf.h1_buy_strength += 10;
EssentialLog("🟢 H1 BUY Signal (Tie-breaker): Conditions=" + IntegerToString(h1_buy_conditions) + "/4");
} else {
mtf.h1_sell = true;
mtf.h1_buy = false;
mtf.h1_sell_strength = 40 * (h1_sell_conditions / 4.0);
mtf.h1_buy_strength = 0;
if(h1_sell_conditions >= 3) mtf.h1_sell_strength += 10;
if(h1_sell_conditions == 4) mtf.h1_sell_strength += 10;
EssentialLog("🔴 H1 SELL Signal (Tie-breaker): Conditions=" + IntegerToString(h1_sell_conditions) + "/4");
}
} else {
// Tidak ada sinyal yang jelas
mtf.h1_buy = false;
mtf.h1_sell = false;
mtf.h1_buy_strength = 0;
mtf.h1_sell_strength = 0;
EssentialLog("⚪ H1 NO Signal: Buy=" + IntegerToString(h1_buy_conditions) + " Sell=" + IntegerToString(h1_sell_conditions));
}
// M15 Analysis (30% weight) - Using global handles for real-time updates
double m15_ema_f = 0, m15_ema_s = 0, m15_rsi = 0, m15_adx = 0, m15_stoch_k = 0, m15_stoch_d = 0;
if(hEmaF_M15 != INVALID_HANDLE && hEmaS_M15 != INVALID_HANDLE) {
double m15_ema_f_buffer[1], m15_ema_s_buffer[1];
int shift = ShiftFor(PERIOD_M15);
int copied_f = CopyBuffer(hEmaF_M15, 0, shift, 1, m15_ema_f_buffer);
int copied_s = CopyBuffer(hEmaS_M15, 0, shift, 1, m15_ema_s_buffer);
EssentialLog("🔍 M15 EMA CopyBuffer: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
if(copied_f > 0 && copied_s > 0) {
m15_ema_f = m15_ema_f_buffer[0];
m15_ema_s = m15_ema_s_buffer[0];
EssentialLog("✅ M15 EMA Data: Fast=" + DoubleToString(m15_ema_f, 5) + " Slow=" + DoubleToString(m15_ema_s, 5));
} else {
EssentialLog("❌ M15 EMA CopyBuffer failed: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
}
} else {
EssentialLog("❌ M15 EMA Handles invalid: Fast=" + IntegerToString(hEmaF_M15) + " Slow=" + IntegerToString(hEmaS_M15));
}
if(hRsi_M15 != INVALID_HANDLE) {
double m15_rsi_buffer[1];
int shift = ShiftFor(PERIOD_M15);
int copied = CopyBuffer(hRsi_M15, 0, shift, 1, m15_rsi_buffer);
EssentialLog("🔍 M15 RSI CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m15_rsi = m15_rsi_buffer[0];
EssentialLog("✅ M15 RSI Data: " + DoubleToString(m15_rsi, 2));
} else {
EssentialLog("❌ M15 RSI CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M15 RSI Handle invalid: " + IntegerToString(hRsi_M15));
}
if(hAdx_M15 != INVALID_HANDLE) {
double m15_adx_buffer[1];
int shift = ShiftFor(PERIOD_M15);
int copied = CopyBuffer(hAdx_M15, 0, shift, 1, m15_adx_buffer);
EssentialLog("🔍 M15 ADX CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m15_adx = m15_adx_buffer[0];
EssentialLog("✅ M15 ADX Data: " + DoubleToString(m15_adx, 2));
} else {
EssentialLog("❌ M15 ADX CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M15 ADX Handle invalid: " + IntegerToString(hAdx_M15));
}
if(hStoch_M15 != INVALID_HANDLE) {
double m15_stoch_k_buffer[1], m15_stoch_d_buffer[1];
int shift = ShiftFor(PERIOD_M15);
int copied_k = CopyBuffer(hStoch_M15, 0, shift, 1, m15_stoch_k_buffer);
int copied_d = CopyBuffer(hStoch_M15, 1, shift, 1, m15_stoch_d_buffer);
EssentialLog("🔍 M15 Stochastic CopyBuffer: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
if(copied_k > 0 && copied_d > 0) {
m15_stoch_k = m15_stoch_k_buffer[0];
m15_stoch_d = m15_stoch_d_buffer[0];
EssentialLog("✅ M15 Stochastic Data: K=" + DoubleToString(m15_stoch_k, 2) + " D=" + DoubleToString(m15_stoch_d, 2));
} else {
EssentialLog("❌ M15 Stochastic CopyBuffer failed: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
}
} else {
EssentialLog("❌ M15 Stochastic Handle invalid: " + IntegerToString(hStoch_M15));
}
bool m15_ema_up = m15_ema_f > m15_ema_s;
bool m15_rsi_buy = (m15_rsi < 50); // RSI < 50 untuk buy (lebih fleksibel)
bool m15_rsi_sell = (m15_rsi > 50); // RSI > 50 untuk sell (lebih fleksibel)
bool m15_adx_buy = (m15_adx >= 8); // ADX >= 8 untuk buy (lebih fleksibel)
bool m15_adx_sell = (m15_adx >= 8); // ADX >= 8 untuk sell (lebih fleksibel)
bool m15_stoch_buy = (m15_stoch_k < 40); // Stoch < 40 untuk buy (lebih fleksibel)
bool m15_stoch_sell = (m15_stoch_k > 60); // Stoch > 60 untuk sell (lebih fleksibel)
EssentialLog("🔍 M15 Conditions: EMA=" + (m15_ema_up ? "UP" : "DOWN") + " RSI=" + DoubleToString(m15_rsi, 1) +
" ADX=" + DoubleToString(m15_adx, 1) + " Stoch=" + DoubleToString(m15_stoch_k, 1));
EssentialLog("🔍 M15 Signals: RSI_Buy=" + (m15_rsi_buy ? "YES" : "NO") + " RSI_Sell=" + (m15_rsi_sell ? "YES" : "NO") +
" ADX_Buy=" + (m15_adx_buy ? "YES" : "NO") + " ADX_Sell=" + (m15_adx_sell ? "YES" : "NO") + " Stoch_Buy=" + (m15_stoch_buy ? "YES" : "NO") + " Stoch_Sell=" + (m15_stoch_sell ? "YES" : "NO"));
// Perhitungan strength yang lebih dinamis untuk M15 dengan mutual exclusion
int m15_buy_conditions = 0;
if(m15_ema_up) m15_buy_conditions++;
if(m15_rsi_buy) m15_buy_conditions++;
if(m15_adx_buy) m15_buy_conditions++;
if(m15_stoch_buy) m15_buy_conditions++;
int m15_sell_conditions = 0;
if(!m15_ema_up) m15_sell_conditions++;
if(m15_rsi_sell) m15_sell_conditions++;
if(m15_adx_sell) m15_sell_conditions++;
if(m15_stoch_sell) m15_sell_conditions++;
// Mutual exclusion: Hanya ambil sinyal yang lebih kuat
if(m15_buy_conditions > m15_sell_conditions && m15_buy_conditions >= 1) {
mtf.m15_buy = true;
mtf.m15_sell = false;
// Score lebih dinamis: 30 untuk 4 kondisi, 22.5 untuk 3, 15 untuk 2, 7.5 untuk 1
mtf.m15_buy_strength = 30 * (m15_buy_conditions / 4.0);
mtf.m15_sell_strength = 0; // Reset sell strength
// Bonus untuk kondisi yang sangat kuat
if(m15_buy_conditions >= 3) mtf.m15_buy_strength += 7.5;
if(m15_buy_conditions == 4) mtf.m15_buy_strength += 7.5;
EssentialLog("🟢 M15 BUY Signal: Conditions=" + IntegerToString(m15_buy_conditions) + "/4 EMA=" + (m15_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m15_rsi, 1) + " ADX=" + DoubleToString(m15_adx, 1) + " Stoch=" + DoubleToString(m15_stoch_k, 1));
} else if(m15_sell_conditions > m15_buy_conditions && m15_sell_conditions >= 1) {
mtf.m15_sell = true;
mtf.m15_buy = false;
// Score lebih dinamis: 30 untuk 4 kondisi, 22.5 untuk 3, 15 untuk 2, 7.5 untuk 1
mtf.m15_sell_strength = 30 * (m15_sell_conditions / 4.0);
mtf.m15_buy_strength = 0; // Reset buy strength
// Bonus untuk kondisi yang sangat kuat
if(m15_sell_conditions >= 3) mtf.m15_sell_strength += 7.5;
if(m15_sell_conditions == 4) mtf.m15_sell_strength += 7.5;
EssentialLog("🔴 M15 SELL Signal: Conditions=" + IntegerToString(m15_sell_conditions) + "/4 EMA=" + (m15_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m15_rsi, 1) + " ADX=" + DoubleToString(m15_adx, 1) + " Stoch=" + DoubleToString(m15_stoch_k, 1));
} else if(m15_buy_conditions == m15_sell_conditions && m15_buy_conditions >= 2) {
// Jika sama dan cukup kuat, gunakan EMA sebagai tie-breaker
if(m15_ema_up) {
mtf.m15_buy = true;
mtf.m15_sell = false;
mtf.m15_buy_strength = 30 * (m15_buy_conditions / 4.0);
mtf.m15_sell_strength = 0;
if(m15_buy_conditions >= 3) mtf.m15_buy_strength += 7.5;
if(m15_buy_conditions == 4) mtf.m15_buy_strength += 7.5;
EssentialLog("🟢 M15 BUY Signal (Tie-breaker): Conditions=" + IntegerToString(m15_buy_conditions) + "/4");
} else {
mtf.m15_sell = true;
mtf.m15_buy = false;
mtf.m15_sell_strength = 30 * (m15_sell_conditions / 4.0);
mtf.m15_buy_strength = 0;
if(m15_sell_conditions >= 3) mtf.m15_sell_strength += 7.5;
if(m15_sell_conditions == 4) mtf.m15_sell_strength += 7.5;
EssentialLog("🔴 M15 SELL Signal (Tie-breaker): Conditions=" + IntegerToString(m15_sell_conditions) + "/4");
}
} else {
// Tidak ada sinyal yang jelas
mtf.m15_buy = false;
mtf.m15_sell = false;
mtf.m15_buy_strength = 0;
mtf.m15_sell_strength = 0;
EssentialLog("⚪ M15 NO Signal: Buy=" + IntegerToString(m15_buy_conditions) + " Sell=" + IntegerToString(m15_sell_conditions));
}
// M5 Analysis (20% weight) - Using global handles for real-time updates
double m5_ema_f = 0, m5_ema_s = 0, m5_rsi = 0, m5_adx = 0, m5_stoch_k = 0, m5_stoch_d = 0;
if(hEmaF_M5 != INVALID_HANDLE && hEmaS_M5 != INVALID_HANDLE) {
double m5_ema_f_buffer[1], m5_ema_s_buffer[1];
int shift = ShiftFor(PERIOD_M5);
int copied_f = CopyBuffer(hEmaF_M5, 0, shift, 1, m5_ema_f_buffer);
int copied_s = CopyBuffer(hEmaS_M5, 0, shift, 1, m5_ema_s_buffer);
EssentialLog("🔍 M5 EMA CopyBuffer: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
if(copied_f > 0 && copied_s > 0) {
m5_ema_f = m5_ema_f_buffer[0];
m5_ema_s = m5_ema_s_buffer[0];
EssentialLog("✅ M5 EMA Data: Fast=" + DoubleToString(m5_ema_f, 5) + " Slow=" + DoubleToString(m5_ema_s, 5));
} else {
EssentialLog("❌ M5 EMA CopyBuffer failed: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
}
} else {
EssentialLog("❌ M5 EMA Handles invalid: Fast=" + IntegerToString(hEmaF_M5) + " Slow=" + IntegerToString(hEmaS_M5));
}
if(hRsi_M5 != INVALID_HANDLE) {
double m5_rsi_buffer[1];
int shift = ShiftFor(PERIOD_M5);
int copied = CopyBuffer(hRsi_M5, 0, shift, 1, m5_rsi_buffer);
EssentialLog("🔍 M5 RSI CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m5_rsi = m5_rsi_buffer[0];
EssentialLog("✅ M5 RSI Data: " + DoubleToString(m5_rsi, 2));
} else {
EssentialLog("❌ M5 RSI CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M5 RSI Handle invalid: " + IntegerToString(hRsi_M5));
}
if(hAdx_M5 != INVALID_HANDLE) {
double m5_adx_buffer[1];
int shift = ShiftFor(PERIOD_M5);
int copied = CopyBuffer(hAdx_M5, 0, shift, 1, m5_adx_buffer);
EssentialLog("🔍 M5 ADX CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m5_adx = m5_adx_buffer[0];
EssentialLog("✅ M5 ADX Data: " + DoubleToString(m5_adx, 2));
} else {
EssentialLog("❌ M5 ADX CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M5 ADX Handle invalid: " + IntegerToString(hAdx_M5));
}
if(hStoch_M5 != INVALID_HANDLE) {
double m5_stoch_k_buffer[1], m5_stoch_d_buffer[1];
int shift = ShiftFor(PERIOD_M5);
int copied_k = CopyBuffer(hStoch_M5, 0, shift, 1, m5_stoch_k_buffer);
int copied_d = CopyBuffer(hStoch_M5, 1, shift, 1, m5_stoch_d_buffer);
EssentialLog("🔍 M5 Stochastic CopyBuffer: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
if(copied_k > 0 && copied_d > 0) {
m5_stoch_k = m5_stoch_k_buffer[0];
m5_stoch_d = m5_stoch_d_buffer[0];
EssentialLog("✅ M5 Stochastic Data: K=" + DoubleToString(m5_stoch_k, 2) + " D=" + DoubleToString(m5_stoch_d, 2));
} else {
EssentialLog("❌ M5 Stochastic CopyBuffer failed: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
}
} else {
EssentialLog("❌ M5 Stochastic Handle invalid: " + IntegerToString(hStoch_M5));
}
bool m5_ema_up = m5_ema_f > m5_ema_s;
bool m5_rsi_buy = (m5_rsi < 50); // RSI < 50 untuk buy (lebih fleksibel)
bool m5_rsi_sell = (m5_rsi > 50); // RSI > 50 untuk sell (lebih fleksibel)
bool m5_adx_buy = (m5_adx >= 6); // ADX >= 6 untuk buy (lebih fleksibel)
bool m5_adx_sell = (m5_adx >= 6); // ADX >= 6 untuk sell (lebih fleksibel)
bool m5_stoch_buy = (m5_stoch_k < 40); // Stoch < 40 untuk buy (lebih fleksibel)
bool m5_stoch_sell = (m5_stoch_k > 60); // Stoch > 60 untuk sell (lebih fleksibel)
EssentialLog("🔍 M5 Conditions: EMA=" + (m5_ema_up ? "UP" : "DOWN") + " RSI=" + DoubleToString(m5_rsi, 1) +
" ADX=" + DoubleToString(m5_adx, 1) + " Stoch=" + DoubleToString(m5_stoch_k, 1));
EssentialLog("🔍 M5 Signals: RSI_Buy=" + (m5_rsi_buy ? "YES" : "NO") + " RSI_Sell=" + (m5_rsi_sell ? "YES" : "NO") +
" ADX_Buy=" + (m5_adx_buy ? "YES" : "NO") + " ADX_Sell=" + (m5_adx_sell ? "YES" : "NO") + " Stoch_Buy=" + (m5_stoch_buy ? "YES" : "NO") + " Stoch_Sell=" + (m5_stoch_sell ? "YES" : "NO"));
// Perhitungan strength yang lebih dinamis untuk M5 dengan mutual exclusion
int m5_buy_conditions = 0;
if(m5_ema_up) m5_buy_conditions++;
if(m5_rsi_buy) m5_buy_conditions++;
if(m5_adx_buy) m5_buy_conditions++;
if(m5_stoch_buy) m5_buy_conditions++;
int m5_sell_conditions = 0;
if(!m5_ema_up) m5_sell_conditions++;
if(m5_rsi_sell) m5_sell_conditions++;
if(m5_adx_sell) m5_sell_conditions++;
if(m5_stoch_sell) m5_sell_conditions++;
// Mutual exclusion: Hanya ambil sinyal yang lebih kuat
if(m5_buy_conditions > m5_sell_conditions && m5_buy_conditions >= 1) {
mtf.m5_buy = true;
mtf.m5_sell = false;
// Score lebih dinamis: 20 untuk 4 kondisi, 15 untuk 3, 10 untuk 2, 5 untuk 1
mtf.m5_buy_strength = 20 * (m5_buy_conditions / 4.0);
mtf.m5_sell_strength = 0; // Reset sell strength
// Bonus untuk kondisi yang sangat kuat
if(m5_buy_conditions >= 3) mtf.m5_buy_strength += 5;
if(m5_buy_conditions == 4) mtf.m5_buy_strength += 5;
EssentialLog("🟢 M5 BUY Signal: Conditions=" + IntegerToString(m5_buy_conditions) + "/4 EMA=" + (m5_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m5_rsi, 1) + " ADX=" + DoubleToString(m5_adx, 1) + " Stoch=" + DoubleToString(m5_stoch_k, 1));
} else if(m5_sell_conditions > m5_buy_conditions && m5_sell_conditions >= 1) {
mtf.m5_sell = true;
mtf.m5_buy = false;
// Score lebih dinamis: 20 untuk 4 kondisi, 15 untuk 3, 10 untuk 2, 5 untuk 1
mtf.m5_sell_strength = 20 * (m5_sell_conditions / 4.0);
mtf.m5_buy_strength = 0; // Reset buy strength
// Bonus untuk kondisi yang sangat kuat
if(m5_sell_conditions >= 3) mtf.m5_sell_strength += 5;
if(m5_sell_conditions == 4) mtf.m5_sell_strength += 5;
EssentialLog("🔴 M5 SELL Signal: Conditions=" + IntegerToString(m5_sell_conditions) + "/4 EMA=" + (m5_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m5_rsi, 1) + " ADX=" + DoubleToString(m5_adx, 1) + " Stoch=" + DoubleToString(m5_stoch_k, 1));
} else if(m5_buy_conditions == m5_sell_conditions && m5_buy_conditions >= 2) {
// Jika sama dan cukup kuat, gunakan EMA sebagai tie-breaker
if(m5_ema_up) {
mtf.m5_buy = true;
mtf.m5_sell = false;
mtf.m5_buy_strength = 20 * (m5_buy_conditions / 4.0);
mtf.m5_sell_strength = 0;
if(m5_buy_conditions >= 3) mtf.m5_buy_strength += 5;
if(m5_buy_conditions == 4) mtf.m5_buy_strength += 5;
EssentialLog("🟢 M5 BUY Signal (Tie-breaker): Conditions=" + IntegerToString(m5_buy_conditions) + "/4");
} else {
mtf.m5_sell = true;
mtf.m5_buy = false;
mtf.m5_sell_strength = 20 * (m5_sell_conditions / 4.0);
mtf.m5_buy_strength = 0;
if(m5_sell_conditions >= 3) mtf.m5_sell_strength += 5;
if(m5_sell_conditions == 4) mtf.m5_sell_strength += 5;
EssentialLog("🔴 M5 SELL Signal (Tie-breaker): Conditions=" + IntegerToString(m5_sell_conditions) + "/4");
}
} else {
// Tidak ada sinyal yang jelas
mtf.m5_buy = false;
mtf.m5_sell = false;
mtf.m5_buy_strength = 0;
mtf.m5_sell_strength = 0;
EssentialLog("⚪ M5 NO Signal: Buy=" + IntegerToString(m5_buy_conditions) + " Sell=" + IntegerToString(m5_sell_conditions));
}
// M1 Analysis (10% weight) - Using global handles for real-time updates
double m1_ema_f = 0, m1_ema_s = 0, m1_rsi = 0, m1_adx = 0, m1_stoch_k = 0, m1_stoch_d = 0;
if(hEmaF_M1 != INVALID_HANDLE && hEmaS_M1 != INVALID_HANDLE) {
double m1_ema_f_buffer[1], m1_ema_s_buffer[1];
int shift = ShiftFor(PERIOD_M1);
int copied_f = CopyBuffer(hEmaF_M1, 0, shift, 1, m1_ema_f_buffer);
int copied_s = CopyBuffer(hEmaS_M1, 0, shift, 1, m1_ema_s_buffer);
EssentialLog("🔍 M1 EMA CopyBuffer: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
if(copied_f > 0 && copied_s > 0) {
m1_ema_f = m1_ema_f_buffer[0];
m1_ema_s = m1_ema_s_buffer[0];
EssentialLog("✅ M1 EMA Data: Fast=" + DoubleToString(m1_ema_f, 5) + " Slow=" + DoubleToString(m1_ema_s, 5));
} else {
EssentialLog("❌ M1 EMA CopyBuffer failed: Fast=" + IntegerToString(copied_f) + " Slow=" + IntegerToString(copied_s));
}
} else {
EssentialLog("❌ M1 EMA Handles invalid: Fast=" + IntegerToString(hEmaF_M1) + " Slow=" + IntegerToString(hEmaS_M1));
}
if(hRsi_M1 != INVALID_HANDLE) {
double m1_rsi_buffer[1];
int shift = ShiftFor(PERIOD_M1);
int copied = CopyBuffer(hRsi_M1, 0, shift, 1, m1_rsi_buffer);
EssentialLog("🔍 M1 RSI CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m1_rsi = m1_rsi_buffer[0];
EssentialLog("✅ M1 RSI Data: " + DoubleToString(m1_rsi, 2));
} else {
EssentialLog("❌ M1 RSI CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M1 RSI Handle invalid: " + IntegerToString(hRsi_M1));
}
if(hAdx_M1 != INVALID_HANDLE) {
double m1_adx_buffer[1];
int shift = ShiftFor(PERIOD_M1);
int copied = CopyBuffer(hAdx_M1, 0, shift, 1, m1_adx_buffer);
EssentialLog("🔍 M1 ADX CopyBuffer: " + IntegerToString(copied));
if(copied > 0) {
m1_adx = m1_adx_buffer[0];
EssentialLog("✅ M1 ADX Data: " + DoubleToString(m1_adx, 2));
} else {
EssentialLog("❌ M1 ADX CopyBuffer failed: " + IntegerToString(copied));
}
} else {
EssentialLog("❌ M1 ADX Handle invalid: " + IntegerToString(hAdx_M1));
}
if(hStoch_M1 != INVALID_HANDLE) {
double m1_stoch_k_buffer[1], m1_stoch_d_buffer[1];
int shift = ShiftFor(PERIOD_M1);
int copied_k = CopyBuffer(hStoch_M1, 0, shift, 1, m1_stoch_k_buffer);
int copied_d = CopyBuffer(hStoch_M1, 1, shift, 1, m1_stoch_d_buffer);
EssentialLog("🔍 M1 Stochastic CopyBuffer: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
if(copied_k > 0 && copied_d > 0) {
m1_stoch_k = m1_stoch_k_buffer[0];
m1_stoch_d = m1_stoch_d_buffer[0];
EssentialLog("✅ M1 Stochastic Data: K=" + DoubleToString(m1_stoch_k, 2) + " D=" + DoubleToString(m1_stoch_d, 2));
} else {
EssentialLog("❌ M1 Stochastic CopyBuffer failed: K=" + IntegerToString(copied_k) + " D=" + IntegerToString(copied_d));
}
} else {
EssentialLog("❌ M1 Stochastic Handle invalid: " + IntegerToString(hStoch_M1));
}
bool m1_ema_up = m1_ema_f > m1_ema_s;
bool m1_rsi_buy = (m1_rsi < 50); // RSI < 50 untuk buy (lebih fleksibel)
bool m1_rsi_sell = (m1_rsi > 50); // RSI > 50 untuk sell (lebih fleksibel)
bool m1_adx_buy = (m1_adx >= 5); // ADX >= 5 untuk buy (lebih fleksibel)
bool m1_adx_sell = (m1_adx >= 5); // ADX >= 5 untuk sell (lebih fleksibel)
bool m1_stoch_buy = (m1_stoch_k < 40); // Stoch < 40 untuk buy (lebih fleksibel)
bool m1_stoch_sell = (m1_stoch_k > 60); // Stoch > 60 untuk sell (lebih fleksibel)
EssentialLog("🔍 M1 Conditions: EMA=" + (m1_ema_up ? "UP" : "DOWN") + " RSI=" + DoubleToString(m1_rsi, 1) +
" ADX=" + DoubleToString(m1_adx, 1) + " Stoch=" + DoubleToString(m1_stoch_k, 1));
EssentialLog("🔍 M1 Signals: RSI_Buy=" + (m1_rsi_buy ? "YES" : "NO") + " RSI_Sell=" + (m1_rsi_sell ? "YES" : "NO") +
" ADX_Buy=" + (m1_adx_buy ? "YES" : "NO") + " ADX_Sell=" + (m1_adx_sell ? "YES" : "NO") + " Stoch_Buy=" + (m1_stoch_buy ? "YES" : "NO") + " Stoch_Sell=" + (m1_stoch_sell ? "YES" : "NO"));
// Perhitungan strength yang lebih dinamis untuk M1 dengan mutual exclusion
int m1_buy_conditions = 0;
if(m1_ema_up) m1_buy_conditions++;
if(m1_rsi_buy) m1_buy_conditions++;
if(m1_adx_buy) m1_buy_conditions++;
if(m1_stoch_buy) m1_buy_conditions++;
int m1_sell_conditions = 0;
if(!m1_ema_up) m1_sell_conditions++;
if(m1_rsi_sell) m1_sell_conditions++;
if(m1_adx_sell) m1_sell_conditions++;
if(m1_stoch_sell) m1_sell_conditions++;
// Mutual exclusion: Hanya ambil sinyal yang lebih kuat
if(m1_buy_conditions > m1_sell_conditions && m1_buy_conditions >= 1) {
mtf.m1_buy = true;
mtf.m1_sell = false;
// Score lebih dinamis: 10 untuk 4 kondisi, 7.5 untuk 3, 5 untuk 2, 2.5 untuk 1
mtf.m1_buy_strength = 10 * (m1_buy_conditions / 4.0);
mtf.m1_sell_strength = 0; // Reset sell strength
// Bonus untuk kondisi yang sangat kuat
if(m1_buy_conditions >= 3) mtf.m1_buy_strength += 2.5;
if(m1_buy_conditions == 4) mtf.m1_buy_strength += 2.5;
EssentialLog("🟢 M1 BUY Signal: Conditions=" + IntegerToString(m1_buy_conditions) + "/4 EMA=" + (m1_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m1_rsi, 1) + " ADX=" + DoubleToString(m1_adx, 1) + " Stoch=" + DoubleToString(m1_stoch_k, 1));
} else if(m1_sell_conditions > m1_buy_conditions && m1_sell_conditions >= 1) {
mtf.m1_sell = true;
mtf.m1_buy = false;
// Score lebih dinamis: 10 untuk 4 kondisi, 7.5 untuk 3, 5 untuk 2, 2.5 untuk 1
mtf.m1_sell_strength = 10 * (m1_sell_conditions / 4.0);
mtf.m1_buy_strength = 0; // Reset buy strength
// Bonus untuk kondisi yang sangat kuat
if(m1_sell_conditions >= 3) mtf.m1_sell_strength += 2.5;
if(m1_sell_conditions == 4) mtf.m1_sell_strength += 2.5;
EssentialLog("🔴 M1 SELL Signal: Conditions=" + IntegerToString(m1_sell_conditions) + "/4 EMA=" + (m1_ema_up ? "UP" : "DOWN") +
" RSI=" + DoubleToString(m1_rsi, 1) + " ADX=" + DoubleToString(m1_adx, 1) + " Stoch=" + DoubleToString(m1_stoch_k, 1));
} else if(m1_buy_conditions == m1_sell_conditions && m1_buy_conditions >= 2) {
// Jika sama dan cukup kuat, gunakan EMA sebagai tie-breaker
if(m1_ema_up) {
mtf.m1_buy = true;
mtf.m1_sell = false;
mtf.m1_buy_strength = 10 * (m1_buy_conditions / 4.0);
mtf.m1_sell_strength = 0;
if(m1_buy_conditions >= 3) mtf.m1_buy_strength += 2.5;
if(m1_buy_conditions == 4) mtf.m1_buy_strength += 2.5;
EssentialLog("🟢 M1 BUY Signal (Tie-breaker): Conditions=" + IntegerToString(m1_buy_conditions) + "/4");
} else {
mtf.m1_sell = true;
mtf.m1_buy = false;
mtf.m1_sell_strength = 10 * (m1_sell_conditions / 4.0);
mtf.m1_buy_strength = 0;
if(m1_sell_conditions >= 3) mtf.m1_sell_strength += 2.5;
if(m1_sell_conditions == 4) mtf.m1_sell_strength += 2.5;
EssentialLog("🔴 M1 SELL Signal (Tie-breaker): Conditions=" + IntegerToString(m1_sell_conditions) + "/4");
}
} else {
// Tidak ada sinyal yang jelas
mtf.m1_buy = false;
mtf.m1_sell = false;
mtf.m1_buy_strength = 0;
mtf.m1_sell_strength = 0;
EssentialLog("⚪ M1 NO Signal: Buy=" + IntegerToString(m1_buy_conditions) + " Sell=" + IntegerToString(m1_sell_conditions));
}
// Calculate Total Score dengan mutual exclusion
double buy_score = 0, sell_score = 0;
if(mtf.h1_buy) buy_score += mtf.h1_buy_strength;
if(mtf.m15_buy) buy_score += mtf.m15_buy_strength;
if(mtf.m5_buy) buy_score += mtf.m5_buy_strength;
if(mtf.m1_buy) buy_score += mtf.m1_buy_strength;
if(mtf.h1_sell) sell_score += mtf.h1_sell_strength;
if(mtf.m15_sell) sell_score += mtf.m15_sell_strength;
if(mtf.m5_sell) sell_score += mtf.m5_sell_strength;
if(mtf.m1_sell) sell_score += mtf.m1_sell_strength;
// Enhanced debugging untuk masalah score MTF
EssentialLog("🔍 MTF Score Debug - Buy Conditions: H1=" + (mtf.h1_buy ? "YES" : "NO") +
" M15=" + (mtf.m15_buy ? "YES" : "NO") + " M5=" + (mtf.m5_buy ? "YES" : "NO") +
" M1=" + (mtf.m1_buy ? "YES" : "NO"));
EssentialLog("🔍 MTF Score Debug - Sell Conditions: H1=" + (mtf.h1_sell ? "YES" : "NO") +
" M15=" + (mtf.m15_sell ? "YES" : "NO") + " M5=" + (mtf.m5_sell ? "YES" : "NO") +
" M1=" + (mtf.m1_sell ? "YES" : "NO"));
EssentialLog("🔍 MTF Score Debug - Buy Strengths: H1=" + DoubleToString(mtf.h1_buy_strength,1) +
" M15=" + DoubleToString(mtf.m15_buy_strength,1) + " M5=" + DoubleToString(mtf.m5_buy_strength,1) +
" M1=" + DoubleToString(mtf.m1_buy_strength,1));
EssentialLog("🔍 MTF Score Debug - Sell Strengths: H1=" + DoubleToString(mtf.h1_sell_strength,1) +
" M15=" + DoubleToString(mtf.m15_sell_strength,1) + " M5=" + DoubleToString(mtf.m5_sell_strength,1) +
" M1=" + DoubleToString(mtf.m1_sell_strength,1));
// Log detail kondisi untuk debugging
EssentialLog("🔍 MTF Signal Details - H1: " + (mtf.h1_buy ? "BUY" : (mtf.h1_sell ? "SELL" : "NONE")) +
" | M15: " + (mtf.m15_buy ? "BUY" : (mtf.m15_sell ? "SELL" : "NONE")) +
" | M5: " + (mtf.m5_buy ? "BUY" : (mtf.m5_sell ? "SELL" : "NONE")) +
" | M1: " + (mtf.m1_buy ? "BUY" : (mtf.m1_sell ? "SELL" : "NONE")));
// Debug: Log individual strengths
EssentialLog("🔍 MTF Strengths - H1: Buy=" + DoubleToString(mtf.h1_buy_strength, 1) + " Sell=" + DoubleToString(mtf.h1_sell_strength, 1) +
" | M15: Buy=" + DoubleToString(mtf.m15_buy_strength, 1) + " Sell=" + DoubleToString(mtf.m15_sell_strength, 1) +
" | M5: Buy=" + DoubleToString(mtf.m5_buy_strength, 1) + " Sell=" + DoubleToString(mtf.m5_sell_strength, 1) +
" | M1: Buy=" + DoubleToString(mtf.m1_buy_strength, 1) + " Sell=" + DoubleToString(mtf.m1_sell_strength, 1));
EssentialLog("🔍 MTF Total Scores - Buy=" + DoubleToString(buy_score, 1) + " Sell=" + DoubleToString(sell_score, 1));
// Assign new score fields
mtf.total_buy_score = buy_score;
mtf.total_sell_score = sell_score;
mtf.net_score = buy_score - sell_score;
mtf.total_score = buy_score + sell_score; // konfluensi tanpa arah
// Optional logging for new scores
EssentialLog("🔎 MTF Scores → BUY=" + DoubleToString(mtf.total_buy_score,1) +
" | SELL=" + DoubleToString(mtf.total_sell_score,1) +
" | NET=" + DoubleToString(mtf.net_score,1) +
" | TOTAL=" + DoubleToString(mtf.total_score,1));
// Build reason string
string buy_tfs = "", sell_tfs = "";
if(mtf.h1_buy) buy_tfs += "H1 ";
if(mtf.m15_buy) buy_tfs += "M15 ";
if(mtf.m5_buy) buy_tfs += "M5 ";
if(mtf.m1_buy) buy_tfs += "M1 ";
if(mtf.h1_sell) sell_tfs += "H1 ";
if(mtf.m15_sell) sell_tfs += "M15 ";
if(mtf.m5_sell) sell_tfs += "M5 ";
if(mtf.m1_sell) sell_tfs += "M1 ";
if(buy_score > sell_score && buy_score >= 10) { // Lebih agresif: score >= 10
mtf.reason = "MTF BUY: " + buy_tfs + "Score: " + DoubleToString(buy_score, 1) + " (Net: " + DoubleToString(buy_score - sell_score, 1) + ")";
} else if(sell_score > buy_score && sell_score >= 10) { // Lebih agresif: score >= 10
mtf.reason = "MTF SELL: " + sell_tfs + "Score: " + DoubleToString(sell_score, 1) + " (Net: " + DoubleToString(sell_score - buy_score, 1) + ")";
} else {
mtf.reason = "MTF: No clear signal (Buy: " + DoubleToString(buy_score, 1) + " Sell: " + DoubleToString(sell_score, 1) + ")";
}
// Debug: Log final MTF result
if(TimeCurrent() - lastMTFLog > 5) {
EssentialLog("📊 MTF Final Result: Score=" + DoubleToString(mtf.total_score, 1) + " | " + mtf.reason);
}
// Apply opposite entry prevention
MTFConfirmation filteredMTF = PreventOppositeEntry(mtf);
// Store current signal for future reference (only if it's valid)
if(filteredMTF.total_score >= MTF_MinScore) {
lastMTFSignal = filteredMTF;
lastMTFSignalValid = true;
lastMTFSignalTime = TimeCurrent();
EssentialLog("💾 GetMTFConfirmation: Stored valid signal for future reference");
}
EssentialLog("🔍 GetMTFConfirmation: Function completed, returning score=" + DoubleToString(filteredMTF.total_score, 1));
return filteredMTF;
}
// Function untuk mengecek apakah ada posisi terbuka
bool HasOpenPosition() {
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(PositionSelectByTicket(PositionGetTicket(i))) {
if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
return true;
}
}
}
return false;
}
// Function untuk mendapatkan direction posisi terbuka (1=BUY, -1=SELL, 0=NONE)
int GetOpenPositionDirection() {
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(PositionSelectByTicket(PositionGetTicket(i))) {
if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(posType == POSITION_TYPE_BUY) return 1;
if(posType == POSITION_TYPE_SELL) return -1;
}
}
}
return 0;
}
// Function untuk mencegah entry yang berlawanan dengan posisi terbuka
MTFConfirmation PreventOppositeEntry(MTFConfirmation &mtf) {
if(!MTF_PreventOppositeEntry) {
EssentialLog("🔍 PreventOppositeEntry: Feature disabled, allowing all signals");
return mtf;
}
if(!HasOpenPosition()) {
EssentialLog("🔍 PreventOppositeEntry: No open position, allowing all signals");
return mtf;
}
int openPosDirection = GetOpenPositionDirection();
if(openPosDirection == 0) {
EssentialLog("🔍 PreventOppositeEntry: No valid open position direction");
return mtf;
}
// Tentukan direction sinyal baru
int newSignalDirection = 0;
if(mtf.total_buy_score > mtf.total_sell_score && mtf.total_buy_score >= MTF_MinScore) {
newSignalDirection = 1; // BUY
} else if(mtf.total_sell_score > mtf.total_buy_score && mtf.total_sell_score >= MTF_MinScore) {
newSignalDirection = -1; // SELL
}
// Jika sinyal baru berlawanan dengan posisi terbuka
if(newSignalDirection != 0 && newSignalDirection != openPosDirection) {
EssentialLog("⚠️ PreventOppositeEntry: OPPOSITE SIGNAL DETECTED!");
EssentialLog("🔍 Current Position: " + (openPosDirection == 1 ? "BUY" : "SELL"));
EssentialLog("🔍 New Signal: " + (newSignalDirection == 1 ? "BUY" : "SELL"));
// Jika ada sinyal sebelumnya yang valid dan searah dengan posisi terbuka
if(lastMTFSignalValid && lastMTFSignalTime > 0) {
int lastSignalDirection = 0;
if(lastMTFSignal.total_buy_score > lastMTFSignal.total_sell_score && lastMTFSignal.total_buy_score >= MTF_MinScore) {
lastSignalDirection = 1; // BUY
} else if(lastMTFSignal.total_sell_score > lastMTFSignal.total_buy_score && lastMTFSignal.total_sell_score >= MTF_MinScore) {
lastSignalDirection = -1; // SELL
}
// Jika sinyal sebelumnya searah dengan posisi terbuka, gunakan sinyal sebelumnya
if(lastSignalDirection == openPosDirection) {
EssentialLog("✅ PreventOppositeEntry: Using previous signal to maintain position direction");
EssentialLog("🔍 Previous Signal: " + (lastSignalDirection == 1 ? "BUY" : "SELL") + " Score: " + DoubleToString(lastSignalDirection == 1 ? lastMTFSignal.total_buy_score : lastMTFSignal.total_sell_score, 1));
// Return sinyal sebelumnya dengan timestamp update
lastMTFSignalTime = TimeCurrent();
return lastMTFSignal;
}
}
// Jika tidak ada sinyal sebelumnya yang valid, block sinyal baru
EssentialLog("❌ PreventOppositeEntry: Blocking opposite signal - no valid previous signal");
mtf.total_buy_score = 0;
mtf.total_sell_score = 0;
mtf.net_score = 0;
mtf.total_score = 0;
mtf.reason = "MTF: Signal blocked - opposite to open position";
return mtf;
}
EssentialLog("✅ PreventOppositeEntry: Signal direction allowed or no signal");
return mtf;
}
// Function untuk reset MTF signal tracking ketika posisi ditutup
void ResetMTFSignalTracking() {
if(lastMTFSignalValid && !HasOpenPosition()) {
EssentialLog("🔄 ResetMTFSignalTracking: Position closed, resetting signal tracking");
lastMTFSignalValid = false;
lastMTFSignalTime = 0;
}
}
// Enhanced signal validation with MTF confirmation
bool ValidateSignalWithMTF(SignalPack &s) {
MTFConfirmation mtf = GetMTFConfirmation();
EssentialLog("🔍 ValidateSignalWithMTF: Starting validation with score=" + DoubleToString(mtf.total_score, 1) + " MinScore=" + DoubleToString(MTF_MinScore, 1));
// Check confluence threshold (total_score = buy + sell)
if(mtf.total_score < MTF_MinScore) {
s.reason += " | MTF Confluence too low: TOTAL=" + DoubleToString(mtf.total_score,1) +
" (Min:" + DoubleToString(MTF_MinScore,1) + ")";
EssentialLog("❌ ValidateSignalWithMTF: Confluence too low - " + DoubleToString(mtf.total_score, 1) + " < " + DoubleToString(MTF_MinScore, 1));
return false;
}
// Enhanced debugging untuk signal dominan
EssentialLog("🔍 ValidateSignalWithMTF: Signal Decision - Buy Score=" + DoubleToString(mtf.total_buy_score,1) +
" Sell Score=" + DoubleToString(mtf.total_sell_score,1) +
" Difference=" + DoubleToString(mtf.total_buy_score - mtf.total_sell_score,1));
// Sudah lolos konfluensi → tentukan arah
if(mtf.total_buy_score > mtf.total_sell_score) {
s.buy = true;
s.sell = false;
s.reason += " | MTF → BUY (Buy=" + DoubleToString(mtf.total_buy_score,1) +
", Sell=" + DoubleToString(mtf.total_sell_score,1) + ")";
EssentialLog("🟢 MTF Signal Generated: BUY (Buy: " + DoubleToString(mtf.total_buy_score, 1) + " > Sell: " + DoubleToString(mtf.total_sell_score, 1) + ")");
} else if(mtf.total_sell_score > mtf.total_buy_score) {
s.buy = false;
s.sell = true;
s.reason += " | MTF → SELL (Sell=" + DoubleToString(mtf.total_sell_score,1) +
", Buy=" + DoubleToString(mtf.total_buy_score,1) + ")";
EssentialLog("🔴 MTF Signal Generated: SELL (Sell: " + DoubleToString(mtf.total_sell_score, 1) + " > Buy: " + DoubleToString(mtf.total_buy_score, 1) + ")");
} else {
// Imbang → no trade / butuh filter tambahan
s.buy = s.sell = false;
s.reason += " | MTF → Balanced (no clear edge)";
EssentialLog("⚠️ MTF: Balanced scores (Buy: " + DoubleToString(mtf.total_buy_score, 1) + " = Sell: " + DoubleToString(mtf.total_sell_score, 1) + ")");
return false;
}
// Add MTF info to reason
s.reason += " | " + mtf.reason;
// Boost signal strength based on MTF confluence
s.signalStrength += (mtf.total_score - 60) * 2; // Bonus points for high MTF confluence
return true;
}