//+------------------------------------------------------------------+ //| Multi Timeframe Harmony Index v2.2 | //| fix: char encoding, swing order, fallback | //+------------------------------------------------------------------+ #property copyright " Christian Benjamin" #property link "https://www.mql5.com/en/users/lynnchris" #property version "2.40" #property strict //+------------------------------------------------------------------+ //| INPUTS | //+------------------------------------------------------------------+ input group "=== SLOT 1 ===" input ENUM_TIMEFRAMES TF1 = PERIOD_M15; input bool TF1_Enable = true; input double TF1_Weight = 0.15; input group "=== SLOT 2 ===" input ENUM_TIMEFRAMES TF2 = PERIOD_H1; input bool TF2_Enable = true; input double TF2_Weight = 0.25; input group "=== SLOT 3 ===" input ENUM_TIMEFRAMES TF3 = PERIOD_H4; input bool TF3_Enable = true; input double TF3_Weight = 0.60; input group "=== SLOT 4 ===" input ENUM_TIMEFRAMES TF4 = PERIOD_D1; input bool TF4_Enable = false; // D1 OFF por default input double TF4_Weight = 0.00; input group "=== SLOT 5 ===" input ENUM_TIMEFRAMES TF5 = PERIOD_W1; input bool TF5_Enable = false; input double TF5_Weight = 0.00; input group "=== SLOT 6 ===" input ENUM_TIMEFRAMES TF6 = PERIOD_MN1; input bool TF6_Enable = false; input double TF6_Weight = 0.00; input group "=== MÉTODO DE SESGO ===" input int SwingLookback = 3; // Reducido: 3 barras a cada lado del swing input bool UseATRFilter = true; input double ATRFilterMult = 0.25; input int ATRPeriod = 14; input bool StructureOnlyMode = false; // false = permite fallback a closes (más señales) input group "=== UMBRALES ===" input double StrongThreshold = 0.70; input double ModerateThreshold = 0.35; input double ConflictThreshold = 0.35; input group "=== SUAVIZADO EMA ===" input int HI_EMA_Period = 8; input int EMA_WarmupBars = 15; input group "=== SEÑALES ===" input bool EnableSignals = true; input bool SignalsOnBarClose = true; input int MaxSignals = 150; input double ArrowOffsetPoints = 15.0; input group "=== ALERTAS ===" input bool EnableAlerts = true; input bool AlertOnStrongThreshold = true; input bool AlertOnHTFFlip = true; input bool AlertOnConflict = true; input bool EnablePushNotif = false; input int AlertCooldownSec = 60; input group "=== FILTRO DE SESION ===" input bool UseSessionFilter = false; // OFF: señales en cualquier hora input int SessionStartHour = 8; input int SessionEndHour = 20; input group "=== PANEL ===" input ENUM_BASE_CORNER PanelCorner = CORNER_LEFT_UPPER; input int PanelX = 12; input int PanelY = 12; input int UpdateIntervalSec = 3; input group "=== INTEGRACION ===" input bool ExportGlobals = true; input string GlobalPrefix = "HI2_"; //+------------------------------------------------------------------+ //| CONSTANTES DE DISEÑO — NO TOCAR | //+------------------------------------------------------------------+ #define PANEL_W 295 #define FONT_MAIN "Courier New" #define FONT_SZ_T 11 #define FONT_SZ_B 9 #define LINE_H 17 #define PAD_X 10 #define PAD_Y 8 #define SEP_CHAR "-" // ASCII puro, sin problemas de encoding #define SEP_LEN 38 // Paleta de colores color CLR_BG() { return ColorRGB(16,16,20); } color CLR_BORDER() { return ColorRGB(55,55,65); } color CLR_TITLE() { return ColorRGB(200,200,225); } color CLR_SUB() { return ColorRGB(90,90,110); } color CLR_SEP() { return ColorRGB(50,50,60); } color CLR_BULL_S() { return ColorRGB(30,210,80); } color CLR_BULL_M() { return ColorRGB(100,185,120); } color CLR_BEAR_S() { return ColorRGB(220,50,50); } color CLR_BEAR_M() { return ColorRGB(185,100,100); } color CLR_CONFLICT() { return ColorRGB(220,170,30); } color CLR_NEUTRAL() { return ColorRGB(140,140,100); } color CLR_DIM() { return ColorRGB(85,85,105); } color CLR_WHITE() { return ColorRGB(225,225,240); } color ColorRGB(int r,int g,int b) { return (color)((r)|(g<<8)|(b<<16)); } //+------------------------------------------------------------------+ //| TIPOS | //+------------------------------------------------------------------+ enum PanelState { PS_STRONG_BULL, PS_MOD_BULL, PS_CONFLICT, PS_NEUTRAL, PS_MOD_BEAR, PS_STRONG_BEAR }; struct TFData { ENUM_TIMEFRAMES tf; bool enabled; double weight; string label; // "M15" "H1 " "H4 " "D1 " int bias; double contrib; }; // Índices de línea del panel (orden de aparición, de arriba hacia abajo) enum LineID { LN_TITLE=0, LN_SUB, LN_SEP1, LN_HI, LN_EMA, LN_BAR, LN_SEP2, LN_CONFLICT, LN_SEP3, LN_TF0, LN_TF1, LN_TF2, LN_TF3, LN_TF4, LN_TF5, LN_SEP4, LN_STATE, LN_TIME, LN_COUNT }; //+------------------------------------------------------------------+ //| VARIABLES GLOBALES DEL EA | //+------------------------------------------------------------------+ TFData tfs[6]; int atrH[6]; // handles ATR por TF string gPfx = ""; // prefijo único de objetos string pObjs[LN_COUNT]; // nombre de cada label del panel double hi_raw = 0.0; double hi_ema = 0.0; double hi_conflict = 0.0; double prev_hi = 0.0; int prev_bias[6]; bool ema_ok = false; int ema_cnt = 0; string arrows[]; int lastSigDir = 0; datetime lastSigBar = 0; datetime lastAlrt[5]; //+------------------------------------------------------------------+ //| TFLabel — nombre corto de 4 chars para cualquier ENUM_TIMEFRAMES| //+------------------------------------------------------------------+ string TFLabel(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1 "; case PERIOD_M2: return "M2 "; case PERIOD_M3: return "M3 "; case PERIOD_M4: return "M4 "; case PERIOD_M5: return "M5 "; case PERIOD_M6: return "M6 "; case PERIOD_M10: return "M10 "; case PERIOD_M12: return "M12 "; case PERIOD_M15: return "M15 "; case PERIOD_M20: return "M20 "; case PERIOD_M30: return "M30 "; case PERIOD_H1: return "H1 "; case PERIOD_H2: return "H2 "; case PERIOD_H3: return "H3 "; case PERIOD_H4: return "H4 "; case PERIOD_H6: return "H6 "; case PERIOD_H8: return "H8 "; case PERIOD_H12: return "H12 "; case PERIOD_D1: return "D1 "; case PERIOD_W1: return "W1 "; case PERIOD_MN1: return "MN1 "; default: return "?? "; } } //+------------------------------------------------------------------+ //| INIT / DEINIT | //+------------------------------------------------------------------+ int OnInit() { gPfx = "HI2_" + _Symbol + "_" + IntegerToString(Period()) + "_"; // Cargar slots configurables — cualquier TF, cualquier peso ENUM_TIMEFRAMES slot_tf[] = {TF1, TF2, TF3, TF4, TF5, TF6}; bool slot_en[] = {TF1_Enable, TF2_Enable,TF3_Enable,TF4_Enable,TF5_Enable,TF6_Enable}; double slot_wt[] = {TF1_Weight, TF2_Weight,TF3_Weight,TF4_Weight,TF5_Weight,TF6_Weight}; for(int i=0;i<6;i++) { tfs[i].tf = slot_tf[i]; tfs[i].enabled = slot_en[i] && slot_wt[i]>0.0; tfs[i].weight = slot_wt[i]; // Label: nombre corto del TF tfs[i].label = TFLabel(slot_tf[i]); tfs[i].bias = 0; tfs[i].contrib = 0; } bool any=false; for(int i=0;i<6;i++) if(tfs[i].enabled){any=true;break;} if(!any){tfs[1].enabled=true;tfs[1].weight=1.0;} // fallback H1 for(int i=0;i<6;i++) { atrH[i]=INVALID_HANDLE; if(tfs[i].enabled && UseATRFilter) atrH[i]=iATR(_Symbol,tfs[i].tf,ATRPeriod); } for(int i=0;i<5;i++) lastAlrt[i]=0; for(int i=0;i<6;i++) prev_bias[i]=0; ArrayResize(arrows,0); BuildPanel(); EventSetTimer(MathMax(1,UpdateIntervalSec)); UpdateAll(true); Print("HI2 v2.2 iniciado: ",_Symbol," ",EnumToString(Period())); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); // Panel for(int i=0;i0)?MathMax(-1.0,MathMin(1.0,sw/wt)):0.0; hi_conflict=CalcConflict(); // EMA con warm-up por SMA acumulativa if(init||!ema_ok) { hi_ema=hi; ema_cnt=1; ema_ok=(hi!=0.0||init); } else { double alpha=2.0/(HI_EMA_Period+1.0); if(ema_cnt fallback"); return FallbackBias(tf,idx); } // Fallback: buscar en closes recientes IGNORANDO barras flat int FallbackBias(ENUM_TIMEFRAMES tf, int idx) { // Buscar las últimas 10 closes con movimiento real double closes[]; ArrayResize(closes,0); int cnt=0; for(int shift=1; shift<=50 && cnt<5; shift++) { double h=iHigh(_Symbol,tf,shift); double l=iLow(_Symbol,tf,shift); if(h-l<=0) continue; // barra flat — saltar double c=iClose(_Symbol,tf,shift); if(c==0) continue; int s=ArraySize(closes); ArrayResize(closes,s+1); closes[s]=c; cnt++; } if(cnt<2) return 0; // closes[0]=más reciente, closes[1]=anterior, etc. // Secuencia de 3 if(cnt>=3) { if(closes[0]>closes[1]&&closes[1]>closes[2]) return 1; if(closes[0]closes[1]) return 1; if(closes[0]0) bull+=tfs[i].weight; else if(tfs[i].bias<0) bear+=tfs[i].weight; tot+=tfs[i].weight; } if(tot<=0) return 0; return MathMax(0,MathMin(1,2.0*MathMin(bull,bear)/tot)); } PanelState GetState() { if(hi_conflict>=ConflictThreshold) return PS_CONFLICT; if(hi_raw>= StrongThreshold) return PS_STRONG_BULL; if(hi_raw>= ModerateThreshold) return PS_MOD_BULL; if(hi_raw<=-StrongThreshold) return PS_STRONG_BEAR; if(hi_raw<=-ModerateThreshold) return PS_MOD_BEAR; return PS_NEUTRAL; } //+------------------------------------------------------------------+ //| SEÑALES | //+------------------------------------------------------------------+ void CheckSignal() { if(!EnableSignals) return; // Usar la barra cerrada más reciente (shift 1) datetime bar = iTime(_Symbol,Period(),1); if(bar==0) return; // Filtro de sesión — usar tiempo de la barra, no TimeCurrent() if(UseSessionFilter) { MqlDateTime dt; TimeToStruct(bar,dt); if(dt.hour=SessionEndHour) return; } // No procesar si ya colocamos señal en esta barra if(lastSigBar==bar) return; // Condición de señal — DOS casos: // A) Cruce: prev_hi estaba bajo el umbral y ahora lo supera // B) Primera vez en esta racha alcista/bajista (lastSigDir distinto) // Captura el caso donde el cruce ocurrió pero fue bloqueado (sesión, etc.) bool crossBull = (prev_hi < StrongThreshold && hi_raw >= StrongThreshold); bool crossBear = (prev_hi > -StrongThreshold && hi_raw <= -StrongThreshold); bool levelBull = (hi_raw >= StrongThreshold && lastSigDir != 1); bool levelBear = (hi_raw <= -StrongThreshold && lastSigDir != -1); bool buy = (crossBull || levelBull); bool sell = (crossBear || levelBear); // Suprimir señales en conflicto if(hi_conflict>=ConflictThreshold){buy=false;sell=false;} // De-duplicación: si ya señalamos en esta misma barra, no repetir // (la de-dup por dirección ya está en levelBull/levelBear con lastSigDir) if(buy) { double p=iLow(_Symbol,Period(),1)-ArrowOffsetPoints*SymbolInfoDouble(_Symbol,SYMBOL_POINT); PutArrow(true,bar,p); lastSigDir=1; lastSigBar=bar; Print("HI2 SEÑAL BUY @ ",TimeToString(bar)," HI=",DoubleToString(hi_raw,4)); } if(sell) { double p=iHigh(_Symbol,Period(),1)+ArrowOffsetPoints*SymbolInfoDouble(_Symbol,SYMBOL_POINT); PutArrow(false,bar,p); lastSigDir=-1; lastSigBar=bar; Print("HI2 SEÑAL SELL @ ",TimeToString(bar)," HI=",DoubleToString(hi_raw,4)); } } void PutArrow(bool buy,datetime t,double p) { string nm=gPfx+(buy?"BUY":"SEL")+"_"+IntegerToString((int)t); int c=0; string u=nm; while(ObjectFind(0,u)!=-1&&c<10){c++;u=nm+"_"+IntegerToString(c);} if(!ObjectCreate(0,u,OBJ_ARROW,0,t,p)) return; ObjectSetInteger(0,u,OBJPROP_ARROWCODE,buy?233:234); ObjectSetInteger(0,u,OBJPROP_COLOR, buy?CLR_BULL_S():CLR_BEAR_S()); ObjectSetInteger(0,u,OBJPROP_WIDTH, 2); ObjectSetInteger(0,u,OBJPROP_BACK, false); ObjectSetInteger(0,u,OBJPROP_SELECTABLE,true); int n=ArraySize(arrows);ArrayResize(arrows,n+1);arrows[n]=u; while(ArraySize(arrows)>MaxSignals) { if(ObjectFind(0,arrows[0])!=-1)ObjectDelete(0,arrows[0]); int m=ArraySize(arrows); for(int i=1;i= StrongThreshold); bool ts=(prev_hi>-StrongThreshold && hi_raw<=-StrongThreshold); if((tb||ts)&&now-lastAlrt[0]>=AlertCooldownSec) Fire(StringFormat("[HI2] %s %s HI=%+.3f Conf=%.0f%%", _Symbol,tb?"BULL FUERTE":"BEAR FUERTE",hi_raw,hi_conflict*100),0); } if(AlertOnHTFFlip) { // Alertar en cualquier slot con TF >= H4 que cambie de sesgo for(int k=0;k<6;k++) { if(!tfs[k].enabled) continue; if(tfs[k].tf < PERIOD_H4) continue; // solo HTF if(tfs[k].bias!=prev_bias[k] && now-lastAlrt[1]>=AlertCooldownSec) { Fire(StringFormat("[HI2] %s %s->%s (HI=%+.3f)", _Symbol, tfs[k].label, tfs[k].bias>0?"BULL":tfs[k].bias<0?"BEAR":"Ntrl", hi_raw), 1); } } } if(AlertOnConflict&&hi_conflict>=ConflictThreshold&&now-lastAlrt[3]>=AlertCooldownSec) Fire(StringFormat("[HI2] %s CONFLICTO %.0f%% HI=%+.3f",_Symbol,hi_conflict*100,hi_raw),3); } void Fire(string msg,int idx) { Print(msg); Alert(msg); if(EnablePushNotif) SendNotification(msg); lastAlrt[idx]=TimeCurrent(); } //+------------------------------------------------------------------+ //| EXPORTAR | //+------------------------------------------------------------------+ void DoExport() { string b=GlobalPrefix+_Symbol; GlobalVariableSet(b+"_HI", hi_raw); GlobalVariableSet(b+"_EMA", hi_ema); GlobalVariableSet(b+"_CONFLICT", hi_conflict); GlobalVariableSet(b+"_STATE", (double)GetState()); for(int i=0;i<6;i++) if(tfs[i].enabled) GlobalVariableSet(b+"_TF"+IntegerToString(i+1)+"_"+tfs[i].label, (double)tfs[i].bias); } //+------------------------------------------------------------------+ //| PANEL — BuildPanel crea objetos UNA SOLA VEZ | //+------------------------------------------------------------------+ void BuildPanel() { int panelH=PAD_Y*2+LN_COUNT*LINE_H+4; string rn=gPfx+"RECT"; if(ObjectFind(0,rn)!=-1)ObjectDelete(0,rn); if(ObjectCreate(0,rn,OBJ_RECTANGLE_LABEL,0,0,0)) { ObjectSetInteger(0,rn,OBJPROP_CORNER, (int)PanelCorner); ObjectSetInteger(0,rn,OBJPROP_XDISTANCE, PanelX); ObjectSetInteger(0,rn,OBJPROP_YDISTANCE, PanelY); ObjectSetInteger(0,rn,OBJPROP_XSIZE, PANEL_W); ObjectSetInteger(0,rn,OBJPROP_YSIZE, panelH); ObjectSetInteger(0,rn,OBJPROP_BGCOLOR, CLR_BG()); ObjectSetInteger(0,rn,OBJPROP_COLOR, CLR_BORDER()); ObjectSetInteger(0,rn,OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0,rn,OBJPROP_BACK, false); ObjectSetInteger(0,rn,OBJPROP_HIDDEN, false); } for(int i=0;i= StrongThreshold) hiClr=CLR_BULL_S(); else if(hi_raw>= ModerateThreshold) hiClr=CLR_BULL_M(); else if(hi_raw<=-StrongThreshold) hiClr=CLR_BEAR_S(); else if(hi_raw<=-ModerateThreshold) hiClr=CLR_BEAR_M(); else if(hi_conflict>=ConflictThreshold) hiClr=CLR_CONFLICT(); else hiClr=CLR_NEUTRAL(); // Barra de fuerza ASCII pura — 24 chars, centro en 12 string bar=MakeBar(hi_raw,24); // Barra de conflicto — 12 chars string cbar=MakeFill(hi_conflict,12); color cclr=(hi_conflict>=ConflictThreshold)?CLR_CONFLICT():CLR_DIM(); // Separador con guiones simples — ASCII 100% seguro string sep=RepStr("-",SEP_LEN); // Escribir todas las líneas TL(LN_TITLE, " HARMONY INDEX v2.3", CLR_TITLE()); TL(LN_SUB, StringFormat(" %-20s %s",_Symbol,EnumToString(Period())), CLR_SUB()); TL(LN_SEP1, sep, CLR_SEP()); TL(LN_HI, StringFormat(" HI : %+.4f",hi_raw), hiClr); TL(LN_EMA, StringFormat(" EMA: %+.4f [per=%d]",hi_ema,HI_EMA_Period),CLR_DIM()); TL(LN_BAR, StringFormat(" [%s]",bar), hiClr); TL(LN_SEP2, sep, CLR_SEP()); TL(LN_CONFLICT,StringFormat(" Conf: [%s] %3.0f%%",cbar,hi_conflict*100.0),cclr); TL(LN_SEP3, sep, CLR_SEP()); // Líneas de TF — campos de ancho FIJO para alineación perfecta en Courier New // " M15 BULL ^ w=0.10 [+0.10]" // " H1 BEAR v w=0.20 [-0.20]" // " H4 Ntrl - w=0.30 [+0.00]" // " D1 BULL ^ w=0.40 [+0.40]" for(int i=0;i<6;i++) { int li=LN_TF0+i; if(!tfs[i].enabled) { TL(li,StringFormat(" %-4s %-4s - w=%.2f [+0.00]","---","off",tfs[i].weight),CLR_DIM()); continue; } int b = tfs[i].bias; string bstr = (b>0)?"BULL":(b<0)?"BEAR":"Ntrl"; string icon = (b>0)?"^":(b<0)?"v":"-"; string ctrb = StringFormat("%+.2f",tfs[i].contrib); // Formato: " LLLL BBBB I w=W.WW [CC.CC]" string line = StringFormat(" %-4s %s %s w=%.2f [%s]", tfs[i].label,bstr,icon,tfs[i].weight,ctrb); color bclr = (b>0)?CLR_BULL_S():(b<0)?CLR_BEAR_S():CLR_DIM(); TL(li,line,bclr); } TL(LN_SEP4, sep, CLR_SEP()); TL(LN_STATE,stateStr, stateClr); TL(LN_TIME, StringFormat(" %s",TimeToString(TimeCurrent(),TIME_DATE|TIME_MINUTES)), CLR_DIM()); ChartRedraw(0); } // Asignar texto y color a una línea del panel void TL(int id,string txt,color clr) { if(id<0||id>=LN_COUNT) return; string nm=pObjs[id]; if(ObjectFind(0,nm)==-1) return; ObjectSetString(0, nm,OBJPROP_TEXT, txt); ObjectSetInteger(0,nm,OBJPROP_COLOR,clr); } //+------------------------------------------------------------------+ //| UTILIDADES | //+------------------------------------------------------------------+ // Barra bidireccional -1..+1 en N chars // Diseño: [.........|===O.......] bull [.......O===|.........] bear // Centro siempre en posición half (el '|') // El marcador 'O' se mueve a la derecha (bull) o izquierda (bear) del centro string MakeBar(double val,int n) { int half = n/2; // pos del marcador: 0=extremo izq (-1), n-1=extremo der (+1) int pos = (int)MathRound((val+1.0)/2.0*(double)(n-1)); pos = MathMax(0,MathMin(n-1,pos)); string s=""; for(int i=0;i0 && i>half && ipos && i