UA6-9V_VL6-N9/Logs/Indicators/Downloads/orb.mq5
LengKundee cb6d7a9024
2025-12-15 20:37:35 +07:00

1527 lines
No EOL
120 KiB
MQL5

//+------------------------------------------------------------------+
//| ORB.mq5 |
//| Copyright 2025 © YourRealName |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "© 2025 YourRealName"
#property link "https://www.mql5.com/en/users/yourprofile"
#property version "2.10"
#property description "Opening Range Breakout (ORB) Indicator with DST support"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
//| Enumeraciones |
//+------------------------------------------------------------------+
enum ENUM_ORB_PERIOD
{
ORB_5MIN = 5, // 5 minutos
ORB_15MIN = 15, // 15 minutos
ORB_30MIN = 30, // 30 minutos
ORB_CUSTOM = 0 // Personalizado
};
enum ENUM_MARKET_SESSION
{
SESSION_CUSTOM = 0, // Personalizado
SESSION_SYDNEY = 1, // Sydney (00:00-06:00 UTC)
SESSION_TOKYO = 2, // Tokyo (00:00-06:00 UTC)
SESSION_LONDON = 3, // London (08:00-16:30 UTC)
SESSION_NEWYORK = 4, // New York (14:30-21:00 UTC)
SESSION_FRANKFURT = 5 // Frankfurt (08:00-16:30 UTC)
};
enum ENUM_TIMEZONE
{
GMT_MINUS_11_MIDWAY = 0, // (GMT -11:00) Midway Island
GMT_MINUS_11_SAMOA = 1, // (GMT -11:00) Samoa
GMT_MINUS_10_HAWAII = 2, // (GMT -10:00) Hawaii
GMT_MINUS_8_ALASKA = 3, // (GMT -8:00) Alaska
GMT_MINUS_7_ARIZONA = 4, // (GMT -7:00) Arizona
GMT_MINUS_7_BAJA = 5, // (GMT -7:00) Baja California
GMT_MINUS_7_PACIFIC = 6, // (GMT -7:00) Pacific Time (US & Canada)
GMT_MINUS_6_CHIHUAHUA = 7, // (GMT -6:00) Chihuahua, La Paz, Mazatlan
GMT_MINUS_6_GUADALAJARA = 8, // (GMT -6:00) Guadalajara, Mexico City, Monterrey
GMT_MINUS_6_MOUNTAIN = 9, // (GMT -6:00) Mountain Time (US & Canada)
GMT_MINUS_5_BOGOTA = 10, // (GMT -5:00) Bogota, Lima, Quito
GMT_MINUS_5_CENTRAL_AMERICA = 11, // (GMT -5:00) Central America
GMT_MINUS_5_CENTRAL_US = 12, // (GMT -5:00) Central Time (US & Canada)
GMT_MINUS_5_SASKATCHEWAN = 13, // (GMT -5:00) Saskatchewan
GMT_MINUS_4_ASUNCION = 14, // (GMT -4:00) Asuncion
GMT_MINUS_4_CARACAS = 15, // (GMT -4:00) Caracas
GMT_MINUS_4_CUIABA = 16, // (GMT -4:00) Cuiaba
GMT_MINUS_4_EASTERN_US = 17, // (GMT -4:00) Eastern Time (US & Canada)
GMT_MINUS_4_GEORGETOWN = 18, // (GMT -4:00) Georgetown, La Paz, Manaus, San Juan
GMT_MINUS_4_INDIANA = 19, // (GMT -4:00) Indiana (East)
GMT_MINUS_4_SANTIAGO = 20, // (GMT -4:00) Santiago
GMT_MINUS_3_ATLANTIC = 21, // (GMT -3:00) Atlantic Time (Canada)
GMT_MINUS_3_BRASILIA = 22, // (GMT -3:00) Brasilia
GMT_MINUS_3_BUENOS_AIRES = 23, // (GMT -3:00) Buenos Aires
GMT_MINUS_3_CAYENNE = 24, // (GMT -3:00) Cayenne, Fortaleza
GMT_MINUS_3_MONTEVIDEO = 25, // (GMT -3:00) Montevideo
GMT_MINUS_2_30_NEWFOUNDLAND = 26, // (GMT -2:30) Newfoundland
GMT_MINUS_2_GREENLAND = 27, // (GMT -2:00) Greenland
GMT_MINUS_1_CAPE_VERDE = 28, // (GMT -1:00) Cape Verde Is.
GMT_0_AZORES = 29, // (GMT) Azores
GMT_0_UTC = 30, // (GMT) Coordinated Universal Time
GMT_0_MONROVIA = 31, // (GMT) Monrovia, Reykjavik
GMT_PLUS_1_CASABLANCA = 32, // (GMT +1:00) Casablanca
GMT_PLUS_1_DUBLIN = 33, // (GMT +1:00) Dublin, Edinburgh, Lisbon, London
GMT_PLUS_1_LAGOS = 34, // (GMT +1:00) Lagos
GMT_PLUS_1_WEST_AFRICA = 35, // (GMT +1:00) West Central Africa
GMT_PLUS_2_AMSTERDAM = 36, // (GMT +2:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
GMT_PLUS_2_BELGRADE = 37, // (GMT +2:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
GMT_PLUS_2_BRUSSELS = 38, // (GMT +2:00) Brussels, Copenhagen, Madrid, Paris
GMT_PLUS_2_CAIRO = 39, // (GMT +2:00) Cairo
GMT_PLUS_2_JOHANNESBURG = 40, // (GMT +02:00) Johannesburg
GMT_PLUS_2_SARAJEVO = 41, // (GMT +2:00) Sarajevo, Skopje, Warsaw, Zagreb
GMT_PLUS_2_WINDHOEK = 42, // (GMT +2:00) Windhoek
GMT_PLUS_3_AMMAN = 43, // (GMT +3:00) Amman
GMT_PLUS_3_BAGHDAD = 44, // (GMT +3:00) Baghdad
GMT_PLUS_3_BEIRUT = 45, // (GMT +3:00) Beirut
GMT_PLUS_3_DAMASCUS = 46, // (GMT +3:00) Damascus
GMT_PLUS_3_HELSINKI = 47, // (GMT +3:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius
GMT_PLUS_3_ISTANBUL = 48, // (GMT +3:00) Istanbul
GMT_PLUS_3_JERUSALEM = 49, // (GMT +3:00) Jerusalem
GMT_PLUS_3_KUWAIT = 50, // (GMT +3:00) Kuwait, Riyadh
GMT_PLUS_3_MOSCOW = 51, // (GMT +3:00) Moscow, St. Petersburg, Volgograd
GMT_PLUS_3_NAIROBI = 52, // (GMT +3:00) Nairobi
GMT_PLUS_3_30_TEHRAN = 53, // (GMT +3:30) Tehran
GMT_PLUS_4_ABU_DHABI = 54, // (GMT +4:00) Abu Dhabi, Dubai, Muscat
GMT_PLUS_4_BAKU = 55, // (GMT +4:00) Baku
GMT_PLUS_4_30_KABUL = 56, // (GMT +4:30) Kabul
GMT_PLUS_5_EKATERINBURG = 57, // (GMT +5:00) Ekaterinburg
GMT_PLUS_5_KARACHI = 58, // (GMT +5:00) Karachi
GMT_PLUS_5_30_CHENNAI = 59, // (GMT +5:30) Chennai, Kolkata, Mumbai, New Delhi
GMT_PLUS_5_30_COLOMBO = 60, // (GMT +5:30) Colombo
GMT_PLUS_5_45_KATHMANDU = 61, // (GMT +5:45) Kathmandu
GMT_PLUS_6_DHAKA = 62, // (GMT +6:00) Dhaka
GMT_PLUS_6_30_YANGON = 63, // (GMT +6:30) Yangon (Rangoon)
GMT_PLUS_7_BANGKOK = 64, // (GMT +7:00) Bangkok, Hanoi, Jakarta
GMT_PLUS_8_BEIJING = 65, // (GMT +8:00) Beijing, Chongqing, Hong Kong, Urumqi
GMT_PLUS_8_MANILA = 66, // (GMT +08:00) Manila
GMT_PLUS_8_SINGAPORE = 67, // (GMT +8:00) Singapore
GMT_PLUS_9_OSAKA = 68, // (GMT +9:00) Osaka, Sapporo, Tokyo
GMT_PLUS_9_SEOUL = 69, // (GMT +9:00) Seoul
GMT_PLUS_9_30_ADELAIDE = 70, // (GMT +9:30) Adelaide
GMT_PLUS_10_BRISBANE = 71, // (GMT +10:00) Brisbane
GMT_PLUS_10_CANBERRA = 72, // (GMT +10:00) Canberra, Melbourne, Sydney
GMT_PLUS_10_VLADIVOSTOK = 73, // (GMT +10:00) Vladivostok
GMT_PLUS_11_SOLOMON = 74, // (GMT +11:00) Solomon Is., New Caledonia
GMT_PLUS_12_AUCKLAND = 75, // (GMT +12:00) Auckland, Wellington
GMT_PLUS_12_ENIWETOK = 76 // (GMT +12:00) Eniwetok, Kwajalein
};
//+------------------------------------------------------------------+
//| Inputs del Indicador |
//+------------------------------------------------------------------+
input group "=== Configuración ORB ==="
input ENUM_ORB_PERIOD InpORBPeriod = ORB_5MIN; // ORB period
input ENUM_MARKET_SESSION InpMarketSession = SESSION_NEWYORK; // Market session
input string InpCustomTime = "08:00-08:05"; // Custom time (only if Custom)
input ENUM_TIMEZONE InpTimeZone = GMT_MINUS_5_BOGOTA; // GMT time zone
input bool InpAlertBreakoutsOnly = false; // Alerts only on confirmed breakouts
input bool InpDebugMode = false; // DEBUG MODE
input group "=== Visualización ==="
input bool InpShowLabels = true; // Show ORB labels
input bool InpShowPreviousDays = true; // Show previous days' ORBs
input bool InpShowPriceTargets = true; // Show basic targets (50%, 100%)
input bool InpShowExtendedTargets = false; // Show extended targets
input bool InpShowFibTargets = false; // Show Fibonacci targets (27.2%, 61.8%)
input bool InpShowMidPoint = false; // Show midpoint
input bool InpShowShadedBox = true; // ENABLE/DISABLE shaded box
input int InpBoxBorderSize = 2; // Box border thickness
input int InpMaxDaysBack = 5; // Maximum days back
input group "=== Breakout Detection ==="
input bool InpUseVolumeConfirmation = false; // Use volume confirmation
input int InpVolumePeriod = 20; // Period for volume SMA
input group "=== Colores y Estilo ==="
input color InpORBHighColor = clrLimeGreen; // ORB High line color
input color InpORBLowColor = clrRed; // ORB Low line color
input color InpShadeColor = clrTeal; // Shaded box color
input color InpBorderColor = clrDarkSlateGray; // Box border color
input color InpTarget50Color = clrMagenta; // 50% target color
input color InpTarget100Color = clrBlue; // 100% target color
input color InpTargetOtherColor = clrTeal; // Other targets color
input color InpFibColor = clrOrange; // Fibonacci targets color
input color InpMidPointColor = clrGray; // Midpoint color
input int InpLineWidth = 2; // Line thickness
input ENUM_LINE_STYLE InpLineStyle = STYLE_SOLID; // Line style
//+------------------------------------------------------------------+
//| Estructura para datos ORB |
//+------------------------------------------------------------------+
struct ORBData
{
datetime date;
double high;
double low;
double range;
datetime startTime;
datetime endTime;
datetime candleTime;
bool calculated;
int barsFound;
bool isFirstCandleMode;
// 🔹 NUEVOS CAMPOS PARA BREAKOUT TRACKING
bool inBreakout;
int breakoutDirection; // 1 = alcista, -1 = bajista, 0 = none
datetime breakoutTime;
double breakoutPrice;
};
//+------------------------------------------------------------------+
//| Estructura para configuración de sesión |
//+------------------------------------------------------------------+
struct SessionConfig
{
string name;
string startTime;
string endTime;
double timezoneOffset;
bool isDST; // 🔹 NUEVO: Indicador de horario de verano
};
//+------------------------------------------------------------------+
//| Estructura para datos de breakout |
//+------------------------------------------------------------------+
struct BreakoutData
{
bool highCross;
bool lowCross;
bool highCrossBO;
bool lowCrossBO;
bool isRetestOrbHigh;
bool isRetestOrbLow;
bool failedRetest;
bool volumeSpike;
};
//+------------------------------------------------------------------+
//| Variables Globales |
//+------------------------------------------------------------------+
ORBData orbDataArray[];
string orbPrefix = "ORB_";
int orbPeriodMinutes = 15;
int chartTimeframeMinutes = 0;
SessionConfig currentSession;
bool currentInBreakout = false;
datetime lastAlertTime = 0;
// 🔹 NUEVAS VARIABLES PARA TRACKING DE BREAKOUTS
double volumeSMA[];
int volumePeriod = 20;
//+------------------------------------------------------------------+
//| Función auxiliar para debug |
//+------------------------------------------------------------------+
void DebugPrint(string message)
{
if(InpDebugMode)
Print("🔍 [ORB DEBUG] ", message);
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: Detectar si una fecha está en horario de verano |
//+------------------------------------------------------------------+
bool IsDaylightSavingTime(datetime date, ENUM_MARKET_SESSION session)
{
MqlDateTime dt;
TimeToStruct(date, dt);
switch(session)
{
case SESSION_NEWYORK:
// Estados Unidos: segundo domingo de marzo hasta primer domingo de noviembre
return IsUSADaylightSavingTime(dt);
case SESSION_LONDON:
case SESSION_FRANKFURT:
// Europa: último domingo de marzo hasta último domingo de octubre
return IsEuropeDaylightSavingTime(dt);
case SESSION_SYDNEY:
// Australia: primer domingo de octubre hasta primer domingo de abril (inverso)
return IsAustraliaDaylightSavingTime(dt);
case SESSION_TOKYO:
// Japón no usa horario de verano
return false;
default:
return false;
}
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: DST para Estados Unidos |
//+------------------------------------------------------------------+
bool IsUSADaylightSavingTime(const MqlDateTime &dt)
{
// Segundo domingo de marzo hasta primer domingo de noviembre
// Antes de marzo = horario estándar
if(dt.mon < 3) return false;
// Después de noviembre = horario estándar
if(dt.mon > 11) return false;
// Diciembre = horario estándar
if(dt.mon == 12) return false;
// Entre abril y octubre = horario de verano
if(dt.mon >= 4 && dt.mon <= 10) return true;
// Marzo: segundo domingo
if(dt.mon == 3)
{
int secondSunday = GetNthSunday(dt.year, 3, 2);
return dt.day >= secondSunday;
}
// Noviembre: primer domingo
if(dt.mon == 11)
{
int firstSunday = GetNthSunday(dt.year, 11, 1);
return dt.day < firstSunday;
}
return false;
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: DST para Europa |
//+------------------------------------------------------------------+
bool IsEuropeDaylightSavingTime(const MqlDateTime &dt)
{
// Último domingo de marzo hasta último domingo de octubre
// Antes de marzo = horario estándar
if(dt.mon < 3) return false;
// Después de octubre = horario estándar
if(dt.mon > 10) return false;
// Entre abril y septiembre = horario de verano
if(dt.mon >= 4 && dt.mon <= 9) return true;
// Marzo: último domingo
if(dt.mon == 3)
{
int lastSunday = GetLastSunday(dt.year, 3);
return dt.day >= lastSunday;
}
// Octubre: último domingo
if(dt.mon == 10)
{
int lastSunday = GetLastSunday(dt.year, 10);
return dt.day < lastSunday;
}
return false;
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: DST para Australia |
//+------------------------------------------------------------------+
bool IsAustraliaDaylightSavingTime(const MqlDateTime &dt)
{
// Primer domingo de octubre hasta primer domingo de abril (inverso)
// Entre noviembre y marzo = horario de verano
if(dt.mon >= 11 || dt.mon <= 3)
{
if(dt.mon >= 11) return true; // Nov-Dic
if(dt.mon <= 2) return true; // Ene-Feb
// Marzo: primer domingo
if(dt.mon == 3)
{
int firstSunday = GetNthSunday(dt.year, 3, 1);
return dt.day < firstSunday;
}
}
// Octubre: primer domingo
if(dt.mon == 10)
{
int firstSunday = GetNthSunday(dt.year, 10, 1);
return dt.day >= firstSunday;
}
// Entre mayo y septiembre = horario estándar
return false;
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: Obtener el N-ésimo domingo de un mes |
//+------------------------------------------------------------------+
int GetNthSunday(int year, int month, int n)
{
datetime firstDay = StringToTime(StringFormat("%04d.%02d.01", year, month));
MqlDateTime dt;
TimeToStruct(firstDay, dt);
// Encontrar el primer domingo
int daysToFirstSunday = (7 - dt.day_of_week) % 7;
int firstSunday = 1 + daysToFirstSunday;
// Calcular el N-ésimo domingo
return firstSunday + (n - 1) * 7;
}
//+------------------------------------------------------------------+
//| 🔹 NUEVA FUNCIÓN: Obtener el último domingo de un mes |
//+------------------------------------------------------------------+
int GetLastSunday(int year, int month)
{
// Último día del mes
datetime lastDay = StringToTime(StringFormat("%04d.%02d.01", year, month + 1)) - 86400;
MqlDateTime dt;
TimeToStruct(lastDay, dt);
// Retroceder hasta el domingo
return dt.day - dt.day_of_week;
}
//+------------------------------------------------------------------+
//| Obtener offset de zona horaria |
//+------------------------------------------------------------------+
double GetTimezoneOffset()
{
switch(InpTimeZone)
{
case GMT_MINUS_11_MIDWAY: return -11.0;
case GMT_MINUS_11_SAMOA: return -11.0;
case GMT_MINUS_10_HAWAII: return -10.0;
case GMT_MINUS_8_ALASKA: return -8.0;
case GMT_MINUS_7_ARIZONA: return -7.0;
case GMT_MINUS_7_BAJA: return -7.0;
case GMT_MINUS_7_PACIFIC: return -7.0;
case GMT_MINUS_6_CHIHUAHUA: return -6.0;
case GMT_MINUS_6_GUADALAJARA: return -6.0;
case GMT_MINUS_6_MOUNTAIN: return -6.0;
case GMT_MINUS_5_BOGOTA: return -5.0;
case GMT_MINUS_5_CENTRAL_AMERICA: return -5.0;
case GMT_MINUS_5_CENTRAL_US: return -5.0;
case GMT_MINUS_5_SASKATCHEWAN: return -5.0;
case GMT_MINUS_4_ASUNCION: return -4.0;
case GMT_MINUS_4_CARACAS: return -4.0;
case GMT_MINUS_4_CUIABA: return -4.0;
case GMT_MINUS_4_EASTERN_US: return -4.0;
case GMT_MINUS_4_GEORGETOWN: return -4.0;
case GMT_MINUS_4_INDIANA: return -4.0;
case GMT_MINUS_4_SANTIAGO: return -4.0;
case GMT_MINUS_3_ATLANTIC: return -3.0;
case GMT_MINUS_3_BRASILIA: return -3.0;
case GMT_MINUS_3_BUENOS_AIRES: return -3.0;
case GMT_MINUS_3_CAYENNE: return -3.0;
case GMT_MINUS_3_MONTEVIDEO: return -3.0;
case GMT_MINUS_2_30_NEWFOUNDLAND: return -2.5;
case GMT_MINUS_2_GREENLAND: return -2.0;
case GMT_MINUS_1_CAPE_VERDE: return -1.0;
case GMT_0_AZORES: return 0.0;
case GMT_0_UTC: return 0.0;
case GMT_0_MONROVIA: return 0.0;
case GMT_PLUS_1_CASABLANCA: return 1.0;
case GMT_PLUS_1_DUBLIN: return 1.0;
case GMT_PLUS_1_LAGOS: return 1.0;
case GMT_PLUS_1_WEST_AFRICA: return 1.0;
case GMT_PLUS_2_AMSTERDAM: return 2.0;
case GMT_PLUS_2_BELGRADE: return 2.0;
case GMT_PLUS_2_BRUSSELS: return 2.0;
case GMT_PLUS_2_CAIRO: return 2.0;
case GMT_PLUS_2_JOHANNESBURG: return 2.0;
case GMT_PLUS_2_SARAJEVO: return 2.0;
case GMT_PLUS_2_WINDHOEK: return 2.0;
case GMT_PLUS_3_AMMAN: return 3.0;
case GMT_PLUS_3_BAGHDAD: return 3.0;
case GMT_PLUS_3_BEIRUT: return 3.0;
case GMT_PLUS_3_DAMASCUS: return 3.0;
case GMT_PLUS_3_HELSINKI: return 3.0;
case GMT_PLUS_3_ISTANBUL: return 3.0;
case GMT_PLUS_3_JERUSALEM: return 3.0;
case GMT_PLUS_3_KUWAIT: return 3.0;
case GMT_PLUS_3_MOSCOW: return 3.0;
case GMT_PLUS_3_NAIROBI: return 3.0;
case GMT_PLUS_3_30_TEHRAN: return 3.5;
case GMT_PLUS_4_ABU_DHABI: return 4.0;
case GMT_PLUS_4_BAKU: return 4.0;
case GMT_PLUS_4_30_KABUL: return 4.5;
case GMT_PLUS_5_EKATERINBURG: return 5.0;
case GMT_PLUS_5_KARACHI: return 5.0;
case GMT_PLUS_5_30_CHENNAI: return 5.5;
case GMT_PLUS_5_30_COLOMBO: return 5.5;
case GMT_PLUS_5_45_KATHMANDU: return 5.75;
case GMT_PLUS_6_DHAKA: return 6.0;
case GMT_PLUS_6_30_YANGON: return 6.5;
case GMT_PLUS_7_BANGKOK: return 7.0;
case GMT_PLUS_8_BEIJING: return 8.0;
case GMT_PLUS_8_MANILA: return 8.0;
case GMT_PLUS_8_SINGAPORE: return 8.0;
case GMT_PLUS_9_OSAKA: return 9.0;
case GMT_PLUS_9_SEOUL: return 9.0;
case GMT_PLUS_9_30_ADELAIDE: return 9.5;
case GMT_PLUS_10_BRISBANE: return 10.0;
case GMT_PLUS_10_CANBERRA: return 10.0;
case GMT_PLUS_10_VLADIVOSTOK: return 10.0;
case GMT_PLUS_11_SOLOMON: return 11.0;
case GMT_PLUS_12_AUCKLAND: return 12.0;
case GMT_PLUS_12_ENIWETOK: return 12.0;
default: return 0.0;
}
}
//+------------------------------------------------------------------+
//| 🔹 FUNCIÓN CORREGIDA: Configurar sesión de mercado con DST |
//+------------------------------------------------------------------+
void SetupMarketSession()
{
// Detectar si estamos en horario de verano para la fecha actual
bool isDST = IsDaylightSavingTime(TimeCurrent(), InpMarketSession);
switch(InpMarketSession)
{
case SESSION_SYDNEY:
currentSession.name = "Sydney";
if(isDST) // AEDT (UTC+11)
{
currentSession.startTime = "23:00"; // 10:00 AEDT
currentSession.endTime = "05:00"; // 16:00 AEDT
}
else // AEST (UTC+10)
{
currentSession.startTime = "00:00"; // 10:00 AEST
currentSession.endTime = "06:00"; // 16:00 AEST
}
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = isDST;
break;
case SESSION_TOKYO:
currentSession.name = "Tokyo";
currentSession.startTime = "00:00"; // 09:00 JST
currentSession.endTime = "06:00"; // 15:00 JST (sin lunch break para ORB)
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = false; // Japón no usa DST
break;
case SESSION_LONDON:
currentSession.name = "London";
if(isDST) // BST (UTC+1)
{
currentSession.startTime = "07:00"; // 08:00 BST
currentSession.endTime = "15:30"; // 16:30 BST
}
else // GMT (UTC+0)
{
currentSession.startTime = "08:00"; // 08:00 GMT
currentSession.endTime = "16:30"; // 16:30 GMT
}
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = isDST;
break;
case SESSION_NEWYORK:
currentSession.name = "New York";
if(isDST) // EDT (UTC-4)
{
currentSession.startTime = "13:30"; // 09:30 EDT
currentSession.endTime = "20:00"; // 16:00 EDT
}
else // EST (UTC-5)
{
currentSession.startTime = "14:30"; // 09:30 EST
currentSession.endTime = "21:00"; // 16:00 EST
}
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = isDST;
break;
case SESSION_FRANKFURT:
currentSession.name = "Frankfurt";
if(isDST) // CEST (UTC+2)
{
currentSession.startTime = "07:00"; // 09:00 CEST
currentSession.endTime = "15:30"; // 17:30 CEST
}
else // CET (UTC+1)
{
currentSession.startTime = "08:00"; // 09:00 CET
currentSession.endTime = "16:30"; // 17:30 CET
}
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = isDST;
break;
case SESSION_CUSTOM:
default:
currentSession.name = "Custom";
currentSession.startTime = "";
currentSession.endTime = "";
currentSession.timezoneOffset = GetTimezoneOffset();
currentSession.isDST = false;
break;
}
DebugPrint(StringFormat("📍 Market Session: %s (%s - %s UTC%+.1f) %s",
currentSession.name, currentSession.startTime, currentSession.endTime,
currentSession.timezoneOffset, isDST ? "[DST]" : "[STD]"));
}
//+------------------------------------------------------------------+
//| Obtener timeframe del gráfico en minutos |
//+------------------------------------------------------------------+
int GetChartTimeframeMinutes()
{
ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)Period();
switch(tf)
{
case PERIOD_M1: return 1;
case PERIOD_M2: return 2;
case PERIOD_M3: return 3;
case PERIOD_M4: return 4;
case PERIOD_M5: return 5;
case PERIOD_M6: return 6;
case PERIOD_M10: return 10;
case PERIOD_M12: return 12;
case PERIOD_M15: return 15;
case PERIOD_M20: return 20;
case PERIOD_M30: return 30;
case PERIOD_H1: return 60;
case PERIOD_H2: return 120;
case PERIOD_H3: return 180;
case PERIOD_H4: return 240;
case PERIOD_H6: return 360;
case PERIOD_H8: return 480;
case PERIOD_H12: return 720;
case PERIOD_D1: return 1440;
case PERIOD_W1: return 10080;
case PERIOD_MN1: return 43200;
default: return 15;
}
}
//+------------------------------------------------------------------+
//| Determinar si debe usar modo de primera vela |
//+------------------------------------------------------------------+
bool ShouldUseFirstCandleMode()
{
if(orbPeriodMinutes == 0) return false;
bool useFirstCandle = (chartTimeframeMinutes == orbPeriodMinutes);
DebugPrint(StringFormat("📊 Chart TF: %d min, ORB Period: %d min → Mode: %s",
chartTimeframeMinutes, orbPeriodMinutes,
useFirstCandle ? "FIRST CANDLE" : "RANGE"));
return useFirstCandle;
}
//+------------------------------------------------------------------+
//| Calcular SMA de volumen |
//+------------------------------------------------------------------+
void CalculateVolumeSMA(const long &volume[], int rates_total)
{
ArrayResize(volumeSMA, rates_total);
for(int i = 0; i < rates_total; i++)
{
if(i < volumePeriod)
{
volumeSMA[i] = 0;
continue;
}
double sum = 0;
for(int j = 0; j < volumePeriod; j++)
{
sum += (double)volume[i - j];
}
volumeSMA[i] = sum / volumePeriod;
}
}
//+------------------------------------------------------------------+
//| Detectar breakout avanzado con confirmación |
//+------------------------------------------------------------------+
BreakoutData DetectAdvancedBreakout(const datetime &time[], const double &high[],
const double &low[], const double &close[],
const long &volume[], int index, const ORBData &orb)
{
BreakoutData breakout;
ZeroMemory(breakout);
if(index < 2) return breakout;
// 🔹 LÓGICA DE BREAKOUT AVANZADA (como en Pine Script)
// highCrossBO = (low[2] < orbHighPrice and close[2] > orbHighPrice and low[1] > orbHighPrice and close[1] > orbHighPrice and close > low[1] and low > orbHighPrice)
breakout.highCrossBO = (low[index-2] < orb.high && close[index-2] > orb.high &&
low[index-1] > orb.high && close[index-1] > orb.high &&
close[index] > low[index-1] && low[index] > orb.high);
// lowCrossBO = (high[2] > orbLowPrice and close[2] < orbLowPrice and high[1] < orbLowPrice and close[1] < orbLowPrice and close < high[1] and high < orbLowPrice)
breakout.lowCrossBO = (high[index-2] > orb.low && close[index-2] < orb.low &&
high[index-1] < orb.low && close[index-1] < orb.low &&
close[index] < high[index-1] && high[index] < orb.low);
// 🔹 CRUCES SIMPLES
breakout.highCross = (close[index-1] <= orb.high && close[index] > orb.high);
breakout.lowCross = (close[index-1] >= orb.low && close[index] < orb.low);
// 🔹 DETECCIÓN DE RETESTS
breakout.isRetestOrbHigh = (close[index-1] > orb.high && low[index] <= orb.high && close[index] >= orb.high);
breakout.isRetestOrbLow = (close[index-1] < orb.low && high[index] >= orb.low && close[index] <= orb.low);
// 🔹 CONFIRMACIÓN DE VOLUMEN (si está habilitada)
if(InpUseVolumeConfirmation && index < ArraySize(volumeSMA))
{
breakout.volumeSpike = ((double)volume[index-2] > volumeSMA[index-2] ||
(double)volume[index-1] > volumeSMA[index-1] ||
(double)volume[index] > volumeSMA[index]);
}
else
{
breakout.volumeSpike = true; // Si no usamos volumen, siempre es true
}
return breakout;
}
//+------------------------------------------------------------------+
//| Inicialización del Indicador |
//+------------------------------------------------------------------+
int OnInit()
{
SetupMarketSession();
chartTimeframeMinutes = GetChartTimeframeMinutes();
volumePeriod = InpVolumePeriod;
if(InpORBPeriod == ORB_CUSTOM)
{
orbPeriodMinutes = 0;
DebugPrint("Using CUSTOM time period: " + InpCustomTime);
}
else
{
orbPeriodMinutes = (int)InpORBPeriod;
DebugPrint("Using predefined period: " + IntegerToString(orbPeriodMinutes) + " minutes");
}
bool firstCandleMode = ShouldUseFirstCandleMode();
string indicatorName = StringFormat("ORB%s_%s_%s%s%s%s",
(orbPeriodMinutes > 0) ? IntegerToString(orbPeriodMinutes) : "Custom",
currentSession.name,
firstCandleMode ? "1C" : "RANGE",
currentSession.isDST ? "_DST" : "_STD",
InpShowFibTargets ? "_FIB" : "",
InpUseVolumeConfirmation ? "_VOL" : "");
IndicatorSetString(INDICATOR_SHORTNAME, indicatorName);
ArrayResize(orbDataArray, 0);
Print("🚀 ORB Indicator initialized - Version 2.1 WITH DST SUPPORT");
Print("📊 Chart Timeframe: ", chartTimeframeMinutes, " minutes");
Print("📊 ORB Period: ", orbPeriodMinutes, " minutes");
Print("📍 Market Session: ", currentSession.name);
Print("🎯 Mode: ", firstCandleMode ? "FIRST CANDLE" : "FULL RANGE");
Print("🌍 Timezone: ", EnumToString(InpTimeZone), " (UTC", currentSession.timezoneOffset, ")");
Print("⏰ DST Status: ", currentSession.isDST ? "DAYLIGHT SAVING TIME" : "STANDARD TIME");
Print("📦 Shaded Box: ", InpShowShadedBox ? "ENABLED" : "DISABLED");
Print("📐 Fibonacci Targets: ", InpShowFibTargets ? "ENABLED" : "DISABLED");
Print("📊 Volume Confirmation: ", InpUseVolumeConfirmation ? "ENABLED" : "DISABLED");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Desinicialización del Indicador |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
DeleteAllORBObjects();
Print("👋 ORB Indicator deinitialized");
}
//+------------------------------------------------------------------+
//| Función principal de cálculo |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < 100) return(0);
// 🔹 CALCULAR SMA DE VOLUMEN SI ESTÁ HABILITADO
if(InpUseVolumeConfirmation)
{
CalculateVolumeSMA(volume, rates_total);
}
CalculateAllORBs(time, high, low, rates_total);
DrawAllORBElements();
// 🔹 DETECTAR BREAKOUTS EN TIEMPO REAL
DetectRealtimeBreakouts(time, high, low, close, volume, rates_total);
return(rates_total);
}
//+------------------------------------------------------------------+
//| Detectar breakouts en tiempo real |
//+------------------------------------------------------------------+
void DetectRealtimeBreakouts(const datetime &time[], const double &high[],
const double &low[], const double &close[],
const long &volume[], int rates_total)
{
if(rates_total < 3) return;
// Buscar ORB del día actual
ORBData currentORB;
bool foundCurrentORB = false;
int arraySize = ArraySize(orbDataArray);
for(int i = arraySize - 1; i >= 0; i--)
{
if(IsToday(orbDataArray[i].date) && orbDataArray[i].calculated)
{
currentORB = orbDataArray[i];
foundCurrentORB = true;
break;
}
}
if(!foundCurrentORB) return;
// Detectar breakouts en la vela actual
int currentIndex = rates_total - 1;
BreakoutData breakout = DetectAdvancedBreakout(time, high, low, close, volume, currentIndex, currentORB);
// 🔹 PROCESAR ALERTAS
ProcessBreakoutAlerts(breakout, currentORB, time[currentIndex]);
// 🔹 ACTUALIZAR ESTADO DE BREAKOUT
UpdateBreakoutState(breakout, currentORB);
}
//+------------------------------------------------------------------+
//| Procesar alertas de breakout |
//+------------------------------------------------------------------+
void ProcessBreakoutAlerts(const BreakoutData &breakout, const ORBData &orb, datetime currentTime)
{
// Evitar alertas duplicadas en la misma vela
if(currentTime == lastAlertTime) return;
string orbName = StringFormat("ORB%s_%s",
(orbPeriodMinutes > 0) ? IntegerToString(orbPeriodMinutes) : "Custom",
currentSession.name);
if(!InpAlertBreakoutsOnly)
{
// Alertas para cruces simples
if(breakout.highCross)
{
string message = StringFormat("%s: Price crossing HIGH level %.5f", orbName, orb.high);
SendORBAlert(message);
lastAlertTime = currentTime;
}
if(breakout.lowCross)
{
string message = StringFormat("%s: Price crossing LOW level %.5f", orbName, orb.low);
SendORBAlert(message);
lastAlertTime = currentTime;
}
}
else
{
// Alertas solo para breakouts confirmados
if(breakout.highCrossBO && breakout.volumeSpike)
{
string message = StringFormat("%s: BREAKOUT above HIGH %.5f - Look for Retest", orbName, orb.high);
SendORBAlert(message);
lastAlertTime = currentTime;
}
if(breakout.lowCrossBO && breakout.volumeSpike)
{
string message = StringFormat("%s: BREAKOUT below LOW %.5f - Look for Retest", orbName, orb.low);
SendORBAlert(message);
}
}
// 🔹 ALERTAS DE RETEST
if(breakout.isRetestOrbHigh)
{
string message = StringFormat("%s: RETEST of HIGH level %.5f", orbName, orb.high);
SendORBAlert(message);
}
if(breakout.isRetestOrbLow)
{
string message = StringFormat("%s: RETEST of LOW level %.5f", orbName, orb.low);
SendORBAlert(message);
}
}
//+------------------------------------------------------------------+
//| Actualizar estado de breakout |
//+------------------------------------------------------------------+
void UpdateBreakoutState(const BreakoutData &breakout, const ORBData &orb)
{
// Lógica similar al Pine Script para tracking de breakouts
if(breakout.highCrossBO || breakout.lowCrossBO)
{
currentInBreakout = true;
}
if(currentInBreakout && (breakout.isRetestOrbHigh || breakout.isRetestOrbLow || breakout.failedRetest))
{
currentInBreakout = false;
}
}
//+------------------------------------------------------------------+
//| Calcular ORBs para todos los días |
//+------------------------------------------------------------------+
void CalculateAllORBs(const datetime &time[], const double &high[], const double &low[], int rates_total)
{
ArrayResize(orbDataArray, 0);
datetime firstVisibleTime = time[MathMax(0, rates_total - 1000)];
datetime lastTime = time[rates_total - 1];
MqlDateTime dtFirst, dtLast;
TimeToStruct(firstVisibleTime, dtFirst);
TimeToStruct(lastTime, dtLast);
DebugPrint("Processing dates from " + TimeToString(firstVisibleTime) + " to " + TimeToString(lastTime));
int daysProcessed = 0;
for(datetime currentDate = StringToTime(StringFormat("%04d.%02d.%02d", dtFirst.year, dtFirst.mon, dtFirst.day));
currentDate <= StringToTime(StringFormat("%04d.%02d.%02d", dtLast.year, dtLast.mon, dtLast.day));
currentDate += 86400)
{
MqlDateTime dt;
TimeToStruct(currentDate, dt);
if(dt.day_of_week == 0 || dt.day_of_week == 6) continue;
ORBData orb;
if(CalculateORBForDate(currentDate, time, high, low, rates_total, orb))
{
int size = ArraySize(orbDataArray);
ArrayResize(orbDataArray, size + 1);
orbDataArray[size] = orb;
daysProcessed++;
DebugPrint(StringFormat("✅ ORB calculated for %s: H=%.5f, L=%.5f, Range=%.5f, Bars=%d, Mode=%s",
TimeToString(currentDate, TIME_DATE), orb.high, orb.low, orb.range, orb.barsFound,
orb.isFirstCandleMode ? "FirstCandle" : "Range"));
}
}
Print("📈 Calculated ORBs for ", daysProcessed, " days");
}
//+------------------------------------------------------------------+
//| 🔹 FUNCIÓN MEJORADA: Calcular ORB para una fecha específica con DST |
//+------------------------------------------------------------------+
bool CalculateORBForDate(datetime date, const datetime &time[], const double &high[],
const double &low[], int rates_total, ORBData &orb)
{
datetime sessionStart, sessionEnd;
GetORBSessionForDate(date, sessionStart, sessionEnd);
orb.date = date;
orb.startTime = sessionStart;
orb.endTime = sessionEnd;
orb.high = 0.0;
orb.low = DBL_MAX;
orb.calculated = false;
orb.barsFound = 0;
orb.candleTime = 0;
orb.inBreakout = false;
orb.breakoutDirection = 0;
orb.breakoutTime = 0;
orb.breakoutPrice = 0;
bool useFirstCandle = ShouldUseFirstCandleMode();
orb.isFirstCandleMode = useFirstCandle;
if(useFirstCandle)
{
DebugPrint("🕯️ Using FIRST CANDLE mode for " + TimeToString(date, TIME_DATE));
return FindFirstCandleORB(time, high, low, rates_total, sessionStart, orb);
}
else
{
DebugPrint("📊 Using RANGE mode for " + TimeToString(date, TIME_DATE));
return FindRangeORB(time, high, low, rates_total, sessionStart, sessionEnd, orb);
}
}
//+------------------------------------------------------------------+
//| Buscar ORB de la primera vela solamente |
//+------------------------------------------------------------------+
bool FindFirstCandleORB(const datetime &time[], const double &high[], const double &low[],
int rates_total, datetime sessionStart, ORBData &orb)
{
int bestIndex = -1;
int minTimeDiff = INT_MAX;
for(int i = 0; i < rates_total; i++)
{
int timeDiff = (int)MathAbs(time[i] - sessionStart);
if(timeDiff < minTimeDiff)
{
minTimeDiff = timeDiff;
bestIndex = i;
}
if(timeDiff == 0) break;
}
if(bestIndex >= 0 && minTimeDiff <= 300)
{
orb.high = high[bestIndex];
orb.low = low[bestIndex];
orb.range = orb.high - orb.low;
orb.calculated = true;
orb.barsFound = 1;
orb.candleTime = time[bestIndex];
DebugPrint(StringFormat("🎯 Found FIRST CANDLE at %s: H=%.5f, L=%.5f, TimeDiff=%d sec",
TimeToString(time[bestIndex]), high[bestIndex], low[bestIndex], minTimeDiff));
return true;
}
DebugPrint("❌ No suitable first candle found near " + TimeToString(sessionStart));
return false;
}
//+------------------------------------------------------------------+
//| Buscar ORB en rango completo |
//+------------------------------------------------------------------+
bool FindRangeORB(const datetime &time[], const double &high[], const double &low[],
int rates_total, datetime sessionStart, datetime sessionEnd, ORBData &orb)
{
bool foundData = false;
DebugPrint(StringFormat("🔍 Searching RANGE from %s to %s",
TimeToString(sessionStart), TimeToString(sessionEnd)));
for(int i = 0; i < rates_total; i++)
{
if(time[i] >= sessionStart && time[i] <= sessionEnd)
{
if(!foundData)
{
orb.high = high[i];
orb.low = low[i];
foundData = true;
orb.candleTime = time[i];
DebugPrint(StringFormat("📍 First bar in range at %s: H=%.5f, L=%.5f",
TimeToString(time[i]), high[i], low[i]));
}
else
{
if(high[i] > orb.high)
{
orb.high = high[i];
DebugPrint(StringFormat("⬆️ New HIGH: %.5f at %s", high[i], TimeToString(time[i])));
}
if(low[i] < orb.low)
{
orb.low = low[i];
DebugPrint(StringFormat("⬇️ New LOW: %.5f at %s", low[i], TimeToString(time[i])));
}
}
orb.barsFound++;
}
}
if(foundData && orb.barsFound > 0)
{
orb.range = orb.high - orb.low;
orb.calculated = true;
DebugPrint(StringFormat("✅ RANGE ORB complete: H=%.5f, L=%.5f, Range=%.5f, Bars=%d",
orb.high, orb.low, orb.range, orb.barsFound));
return true;
}
DebugPrint("❌ No data found in range");
return false;
}
//+------------------------------------------------------------------+
//| 🔹 FUNCIÓN MEJORADA: Obtener horarios de sesión con DST |
//+------------------------------------------------------------------+
void GetORBSessionForDate(datetime date, datetime &startTime, datetime &endTime)
{
MqlDateTime dt;
TimeToStruct(date, dt);
string sessionStart, sessionEnd;
double timezoneOffset = currentSession.timezoneOffset;
// 🔹 DETECTAR DST PARA LA FECHA ESPECÍFICA
bool isDST = IsDaylightSavingTime(date, InpMarketSession);
if(InpMarketSession == SESSION_CUSTOM)
{
if(orbPeriodMinutes == 0)
{
string times[];
if(StringSplit(InpCustomTime, '-', times) == 2)
{
sessionStart = times[0];
sessionEnd = times[1];
}
else
{
sessionStart = "09:30";
sessionEnd = "09:45";
}
}
else
{
sessionStart = "09:30";
switch(orbPeriodMinutes)
{
case 5: sessionEnd = "09:35"; break;
case 15: sessionEnd = "09:45"; break;
case 30: sessionEnd = "10:00"; break;
default: sessionEnd = "09:45"; break;
}
}
}
else
{
// 🔹 USAR HORARIOS AJUSTADOS POR DST
switch(InpMarketSession)
{
case SESSION_SYDNEY:
if(isDST) // AEDT
{
sessionStart = "23:00"; // 10:00 AEDT
sessionEnd = "05:00"; // 16:00 AEDT
}
else // AEST
{
sessionStart = "00:00"; // 10:00 AEST
sessionEnd = "06:00"; // 16:00 AEST
}
break;
case SESSION_TOKYO:
sessionStart = "00:00"; // 09:00 JST
sessionEnd = "06:00"; // 15:00 JST
break;
case SESSION_LONDON:
if(isDST) // BST
{
sessionStart = "07:00"; // 08:00 BST
sessionEnd = "15:30"; // 16:30 BST
}
else // GMT
{
sessionStart = "08:00"; // 08:00 GMT
sessionEnd = "16:30"; // 16:30 GMT
}
break;
case SESSION_NEWYORK:
if(isDST) // EDT
{
sessionStart = "13:30"; // 09:30 EDT
sessionEnd = "20:00"; // 16:00 EDT
}
else // EST
{
sessionStart = "14:30"; // 09:30 EST
sessionEnd = "21:00"; // 16:00 EST
}
break;
case SESSION_FRANKFURT:
if(isDST) // CEST
{
sessionStart = "07:00"; // 09:00 CEST
sessionEnd = "15:30"; // 17:30 CEST
}
else // CET
{
sessionStart = "08:00"; // 09:00 CET
sessionEnd = "16:30"; // 17:30 CET
}
break;
}
// 🔹 AJUSTAR SEGÚN PERÍODO ORB
if(orbPeriodMinutes > 0)
{
string startParts[];
StringSplit(sessionStart, ':', startParts);
int startHour = (int)StringToInteger(startParts[0]);
int startMinute = (int)StringToInteger(startParts[1]);
int totalStartMinutes = startHour * 60 + startMinute;
int totalEndMinutes = totalStartMinutes + orbPeriodMinutes;
int endHour = totalEndMinutes / 60;
int endMinute = totalEndMinutes % 60;
if(endHour >= 24)
{
endHour -= 24;
}
sessionEnd = StringFormat("%02d:%02d", endHour, endMinute);
}
}
string dateStr = StringFormat("%04d.%02d.%02d", dt.year, dt.mon, dt.day);
startTime = StringToTime(dateStr + " " + sessionStart) + (int)(timezoneOffset * 3600);
// 🔹 MANEJAR CRUCE DE MEDIANOCHE PARA SYDNEY
if(InpMarketSession == SESSION_SYDNEY && isDST && StringCompare(sessionEnd, "05:00") == 0)
{
datetime nextDay = date + 86400;
MqlDateTime dtNext;
TimeToStruct(nextDay, dtNext);
string nextDayStr = StringFormat("%04d.%02d.%02d", dtNext.year, dtNext.mon, dtNext.day);
endTime = StringToTime(nextDayStr + " " + sessionEnd) + (int)(timezoneOffset * 3600);
}
else
{
endTime = StringToTime(dateStr + " " + sessionEnd) + (int)(timezoneOffset * 3600);
}
DebugPrint(StringFormat("📅 Session for %s (%s): %s to %s (UTC%+.1f) %s",
dateStr, currentSession.name, TimeToString(startTime), TimeToString(endTime),
timezoneOffset, isDST ? "[DST]" : "[STD]"));
}
//+------------------------------------------------------------------+
//| Dibujar todos los elementos ORB |
//+------------------------------------------------------------------+
void DrawAllORBElements()
{
DeleteAllORBObjects();
int arraySize = ArraySize(orbDataArray);
if(arraySize == 0)
{
DebugPrint("⚠️ No ORB data to draw");
return;
}
for(int i = 0; i < arraySize; i++)
{
if(!orbDataArray[i].calculated) continue;
bool isToday = IsToday(orbDataArray[i].date);
if(!InpShowPreviousDays && !isToday) continue;
DrawORBForDay(orbDataArray[i], i);
}
}
//+------------------------------------------------------------------+
//| Dibujar ORB para un día específico |
//+------------------------------------------------------------------+
void DrawORBForDay(const ORBData &orb, int dayIndex)
{
string suffix = "_" + IntegerToString(dayIndex);
datetime lineEndTime = orb.endTime + 86400;
datetime boxStartTime = orb.isFirstCandleMode ? orb.candleTime : orb.startTime;
// 🔹 CAJA SOMBREADA
if(InpShowShadedBox)
{
string boxName = orbPrefix + "Box" + suffix;
ObjectDelete(0, boxName);
if(ObjectCreate(0, boxName, OBJ_RECTANGLE, 0, boxStartTime, orb.high, lineEndTime, orb.low))
{
ObjectSetInteger(0, boxName, OBJPROP_COLOR, InpShadeColor);
ObjectSetInteger(0, boxName, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, boxName, OBJPROP_WIDTH, InpBoxBorderSize);
ObjectSetInteger(0, boxName, OBJPROP_FILL, true);
ObjectSetInteger(0, boxName, OBJPROP_BACK, true);
ObjectSetInteger(0, boxName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, boxName, OBJPROP_SELECTABLE, false);
}
}
// 🔹 LÍNEAS ORB
DrawORBLine("High" + suffix, orb.endTime, lineEndTime, orb.high, InpORBHighColor);
DrawORBLine("Low" + suffix, orb.endTime, lineEndTime, orb.low, InpORBLowColor);
// 🔹 OBJETIVOS DE PRECIO
if(InpShowPriceTargets)
DrawPriceTargetsForDay(orb, suffix, lineEndTime);
// 🔹 OBJETIVOS FIBONACCI
if(InpShowFibTargets)
DrawFibonacciTargetsForDay(orb, suffix, lineEndTime);
// 🔹 PUNTO MEDIO
if(InpShowMidPoint)
DrawMidPointForDay(orb, suffix, lineEndTime);
// 🔹 ETIQUETAS
if(InpShowLabels && IsToday(orb.date))
DrawLabelsForDay(orb, suffix);
}
//+------------------------------------------------------------------+
//| Dibujar línea ORB |
//+------------------------------------------------------------------+
void DrawORBLine(string name, datetime startTime, datetime endTime, double price, color lineColor)
{
string objName = orbPrefix + name;
if(ObjectCreate(0, objName, OBJ_TREND, 0, startTime, price, endTime, price))
{
ObjectSetInteger(0, objName, OBJPROP_COLOR, lineColor);
ObjectSetInteger(0, objName, OBJPROP_STYLE, InpLineStyle);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, InpLineWidth);
ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
}
}
//+------------------------------------------------------------------+
//| Dibujar objetivos de precio |
//+------------------------------------------------------------------+
void DrawPriceTargetsForDay(const ORBData &orb, string suffix, datetime endTime)
{
DrawTargetLineForDay("Target_High_50" + suffix, orb.high + (orb.range * 0.5), InpTarget50Color, endTime);
DrawTargetLineForDay("Target_High_100" + suffix, orb.high + (orb.range * 1.0), InpTarget100Color, endTime);
DrawTargetLineForDay("Target_Low_50" + suffix, orb.low - (orb.range * 0.5), InpTarget50Color, endTime);
DrawTargetLineForDay("Target_Low_100" + suffix, orb.low - (orb.range * 1.0), InpTarget100Color, endTime);
if(InpShowExtendedTargets)
{
double multipliers[] = {1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0};
for(int i = 0; i < ArraySize(multipliers); i++)
{
DrawTargetLineForDay("Target_High_" + DoubleToString(multipliers[i], 1) + suffix,
orb.high + (orb.range * multipliers[i]), InpTargetOtherColor, endTime);
DrawTargetLineForDay("Target_Low_" + DoubleToString(multipliers[i], 1) + suffix,
orb.low - (orb.range * multipliers[i]), InpTargetOtherColor, endTime);
}
}
}
//+------------------------------------------------------------------+
//| Dibujar objetivos Fibonacci |
//+------------------------------------------------------------------+
void DrawFibonacciTargetsForDay(const ORBData &orb, string suffix, datetime endTime)
{
// 🔹 NIVELES FIBONACCI: 27.2% y 61.8%
DrawTargetLineForDay("Fib_High_272" + suffix, orb.high + (orb.range * 0.272), InpFibColor, endTime);
DrawTargetLineForDay("Fib_High_618" + suffix, orb.high + (orb.range * 0.618), InpFibColor, endTime);
DrawTargetLineForDay("Fib_Low_272" + suffix, orb.low - (orb.range * 0.272), InpFibColor, endTime);
DrawTargetLineForDay("Fib_Low_618" + suffix, orb.low - (orb.range * 0.618), InpFibColor, endTime);
// 🔹 ETIQUETAS FIBONACCI (solo para el día actual)
if(IsToday(orb.date))
{
DrawPriceTargetLabel(orb.high, 0.272, "FIB 27.2%", InpFibColor, suffix);
DrawPriceTargetLabel(orb.high, 0.618, "FIB 61.8%", InpFibColor, suffix);
DrawPriceTargetLabel(orb.low, -0.272, "FIB 27.2%", InpFibColor, suffix);
DrawPriceTargetLabel(orb.low, -0.618, "FIB 61.8%", InpFibColor, suffix);
}
}
//+------------------------------------------------------------------+
//| Dibujar línea de objetivo |
//+------------------------------------------------------------------+
void DrawTargetLineForDay(string name, double price, color lineColor, datetime endTime)
{
string objName = orbPrefix + name;
datetime startTime = endTime - 86400;
if(ObjectCreate(0, objName, OBJ_TREND, 0, startTime, price, endTime, price))
{
ObjectSetInteger(0, objName, OBJPROP_COLOR, lineColor);
ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
}
}
//+------------------------------------------------------------------+
//| Dibujar punto medio |
//+------------------------------------------------------------------+
void DrawMidPointForDay(const ORBData &orb, string suffix, datetime endTime)
{
double midPrice = orb.low + (orb.range * 0.5);
DrawTargetLineForDay("MidPoint" + suffix, midPrice, InpMidPointColor, endTime);
}
//+------------------------------------------------------------------+
//| Dibujar etiqueta de objetivo de precio |
//+------------------------------------------------------------------+
void DrawPriceTargetLabel(double fromPrice, double level, string name, color labelColor, string suffix)
{
double targetPrice = fromPrice + (level * (ArraySize(orbDataArray) > 0 ? orbDataArray[ArraySize(orbDataArray)-1].range : 10.0));
string labelName = orbPrefix + "Label_" + name + suffix;
datetime labelTime = TimeCurrent() + 3600; // 1 hora en el futuro
if(ObjectCreate(0, labelName, OBJ_TEXT, 0, labelTime, targetPrice))
{
string orbName = StringFormat("ORB%s_%s",
(orbPeriodMinutes > 0) ? IntegerToString(orbPeriodMinutes) : "Custom",
currentSession.name);
ObjectSetString(0, labelName, OBJPROP_TEXT, orbName + " " + name);
ObjectSetInteger(0, labelName, OBJPROP_COLOR, labelColor);
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
ObjectSetString(0, labelName, OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(0, labelName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, labelName, OBJPROP_SELECTABLE, false);
}
}
//+------------------------------------------------------------------+
//| Dibujar etiquetas |
//+------------------------------------------------------------------+
void DrawLabelsForDay(const ORBData &orb, string suffix)
{
string orbName = StringFormat("ORB%s_%s%s",
(orbPeriodMinutes > 0) ? IntegerToString(orbPeriodMinutes) : "Custom",
currentSession.name,
orb.isFirstCandleMode ? "_1C" : "");
string labelHighName = orbPrefix + "Label_High" + suffix;
if(ObjectCreate(0, labelHighName, OBJ_TEXT, 0, orb.endTime + 600, orb.high))
{
ObjectSetString(0, labelHighName, OBJPROP_TEXT, orbName + " HIGH");
ObjectSetInteger(0, labelHighName, OBJPROP_COLOR, InpORBHighColor);
ObjectSetInteger(0, labelHighName, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, labelHighName, OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(0, labelHighName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, labelHighName, OBJPROP_SELECTABLE, false);
}
string labelLowName = orbPrefix + "Label_Low" + suffix;
if(ObjectCreate(0, labelLowName, OBJ_TEXT, 0, orb.endTime + 600, orb.low))
{
ObjectSetString(0, labelLowName, OBJPROP_TEXT, orbName + " LOW");
ObjectSetInteger(0, labelLowName, OBJPROP_COLOR, InpORBLowColor);
ObjectSetInteger(0, labelLowName, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, labelLowName, OBJPROP_FONT, "Arial Bold");
ObjectSetInteger(0, labelLowName, OBJPROP_SELECTED, false);
ObjectSetInteger(0, labelLowName, OBJPROP_SELECTABLE, false);
}
// 🔹 ETIQUETAS DE OBJETIVOS BÁSICOS
if(InpShowPriceTargets)
{
DrawPriceTargetLabel(orb.high, 0.5, "PT 50%", InpTarget50Color, suffix);
DrawPriceTargetLabel(orb.high, 1.0, "PT 100%", InpTarget100Color, suffix);
DrawPriceTargetLabel(orb.low, -0.5, "PT 50%", InpTarget50Color, suffix);
DrawPriceTargetLabel(orb.low, -1.0, "PT 100%", InpTarget100Color, suffix);
if(InpShowExtendedTargets)
{
double multipliers[] = {1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0};
for(int i = 0; i < ArraySize(multipliers); i++)
{
string ptName = StringFormat("PT %.0f%%", multipliers[i] * 100);
DrawPriceTargetLabel(orb.high, multipliers[i], ptName, InpTargetOtherColor, suffix);
DrawPriceTargetLabel(orb.low, -multipliers[i], ptName, InpTargetOtherColor, suffix);
}
}
}
// 🔹 PUNTO MEDIO
if(InpShowMidPoint)
{
DrawPriceTargetLabel(orb.low, 0.5, "MIDPOINT", InpMidPointColor, suffix);
}
}
//+------------------------------------------------------------------+
//| Verificar si es hoy |
//+------------------------------------------------------------------+
bool IsToday(datetime date)
{
datetime today = StringToTime(TimeToString(TimeCurrent(), TIME_DATE));
datetime checkDate = StringToTime(TimeToString(date, TIME_DATE));
return today == checkDate;
}
//+------------------------------------------------------------------+
//| Eliminar todos los objetos ORB |
//+------------------------------------------------------------------+
void DeleteAllORBObjects()
{
for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
{
string objName = ObjectName(0, i);
if(StringFind(objName, orbPrefix) == 0)
ObjectDelete(0, objName);
}
}
//+------------------------------------------------------------------+
//| Enviar alerta ORB |
//+------------------------------------------------------------------+
void SendORBAlert(string message)
{
Alert("🔔 ORB Alert: " + message);
Print("🔔 ORB Alert: " + message);
// 🔹 OPCIONAL: Enviar notificación push si está habilitado
if(TerminalInfoInteger(TERMINAL_NOTIFICATIONS_ENABLED))
{
SendNotification("ORB: " + message);
}
}
//+------------------------------------------------------------------+
//| Timer |
//+------------------------------------------------------------------+
void OnTimer()
{
// Implementar lógica de timer si es necesario
}
//+------------------------------------------------------------------+
//| Eventos del gráfico |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_OBJECT_CLICK)
{
DebugPrint("Objeto clickeado: " + sparam);
}
}