4864 lines
436 KiB
Plaintext
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;
|
|
}
|