//+------------------------------------------------------------------+ //| OB EURUSD 30 MINS 2026 (Conversion Pine Script v6 -> MQL5) | //| V1.07 - + Interruptores por bloque para calibracion modular | //+------------------------------------------------------------------+ #property copyright "Conversion Pine -> MQL5" #property version "1.07" #property strict #include CTrade trade; input group "Configuracion PineConnector" input string pc_id = "8769131446485"; // License ID input string pc_symbol = "EURUSD"; // Simbolo MT5 input group "Gestion de Capital" input double riesgo_per = 2.0; // Riesgo por Operacion (%) input int max_ops = 2; // Max. Operaciones Abiertas input double max_daily_loss = 2.1; // Perdida Maxima Diaria (%) input group "Filtros Tecnicos (Osciladores)" input bool Use_Osciladores = true; // ON/OFF bloque completo (RSI+ADX+CHOP) input int atr_len = 13; // Periodo ATR input int rsi_len = 7; // Periodo RSI input double rsi_min = 10.0; // RSI Minimo input double rsi_max = 36.4; // RSI Maximo input int adx_len = 11; // Periodo ADX/DMI input double adx_min = 11.2; // ADX Minimo input double adx_max = 60.0; // ADX Maximo input int chop_len = 10; // Periodo CHOP input double chop_min = 26.7; // CHOP Minimo input double chop_max = 72.2; // CHOP Maximo input group "Confirmacion de Velas" input bool Use_Wicks = true; // ON/OFF filtro de mechas input bool Use_Body = true; // ON/OFF filtro de cuerpo solido input bool Use_VelaSize = true; // ON/OFF filtro de tamano de vela vs ATR input bool Use_Volumen = true; // ON/OFF filtro de volumen input double min_wick_top = 0.0; // Min. Mecha Superior (%) input double max_wick_top = 39.0; // Max. Mecha Superior (%) input double min_wick_bot = 0.0; // Min. Mecha Inferior (%) input double max_wick_bot = 50.1; // Max. Mecha Inferior (%) input double min_body_pct = 38.7; // Min. Cuerpo Solido (%) input double max_body_pct = 100.0; // Max. Cuerpo Solido (%) input double min_atr_size = 0.0; // Min. Tamano Vela (Mult. ATR) input double max_atr_size = 1.8; // Max. Tamano Vela (Mult. ATR) input int vol_ma_len = 20; // Periodo Media Volumen input double min_vol_mult = 0.0; // Min. Volumen (Mult. Media) input double max_vol_mult = 1.5; // Max. Volumen (Mult. Media) input group "Filtro Premium/Discount" input bool use_pd_filter = true; // Activar Filtro Descuento input int pd_lookback = 4; // Velas atras (Calibrado) input double pd_threshold = 0.25; // Nivel Descuento (Calibrado) input group "OB DE COMPRA (BULLISH)" input bool Use_StochBuy = true; // ON/OFF confirmacion estocastica de compra input double st_nivel_buy = 28.8; // Estocastico Compra input double st_conf_th = 30.0; // Umbral Confirmacion Stoch input double ob_mult_buy = 0.8; // Fuerza Impulso Compra (ATR) input double sl_atr_mult = 0.5; // Mult. SL (ATR) input double tp_ratio = 1.5; // Ratio TP Respaldo input double ob_buy_off = 0.1; // Offset de Entrada (Mult. ATR) input group "OB DE VENTA (BEARISH / TP)" input double st_nivel_sell = 24.5; // Estocastico Venta input double ob_mult_sell = 0.5; // Fuerza Impulso Venta (ATR) input double pct_tp_ob_in = 1.1; // Alcance TP en OB Venta (%) input group "Configuracion MT5 (zona horaria / magic)" input int InpBrokerGMT = 2; // Offset GMT del broker (ajustar: +2/+3) input long InpMagic = 20260001; // Numero magico input bool InpKeepBoxes = true; // Conservar cajas OB congeladas (auditoria) input bool InpDeleteOnExit = false; // Borrar dibujos al detener EA/Tester input bool InpUseDeadHours = true; // Aplicar filtro de horarios muertos (GMT-5) input bool InpDebug = false; // Modo diagnostico: log de OB y rechazos input group "Criterios Minimos (OnTester / Optimizacion)" input bool InpUseCustomMax = true; // Activar filtro custom max en optimizacion input int InpMinTrades = 300; // Minimo de operaciones input double InpMaxDDpct = 20.0; // DD maximo de equity permitido (%) input double InpMinPF = 1.5; // Profit Factor minimo input double InpMinProfit = 10000.0;// Ganancia neta minima input double InpMinSharpe = 1.0; // Sharpe Ratio minimo input double InpMinRecovery = 3.0; // Recovery Factor minimo double pct_tp_ob; struct SOB { double top; double bot; string name; }; SOB buyOBs[]; SOB sellOBs[]; int hRSI, hADX; datetime g_lastBarTime = 0; double g_equity_day0 = 0.0; int g_last_day = -1; double g_final_impulso = 0.0; long g_objCnt = 0; string PREF = "OBEU_"; double H(int s){ return iHigh(_Symbol,_Period,s); } double L(int s){ return iLow(_Symbol,_Period,s); } double C(int s){ return iClose(_Symbol,_Period,s);} double O(int s){ return iOpen(_Symbol,_Period,s); } long V(int s){ return iTickVolume(_Symbol,_Period,s); } datetime T(int s){ return iTime(_Symbol,_Period,s); } double IndVal(int handle,int buffer,int shift) { double a[]; if(CopyBuffer(handle,buffer,shift,1,a)==1) return a[0]; return 0.0; } // PINE ATR (RMA - Wilder) double PineATR(int bar_index, int length) { double rma = 0.0; int start_bar = bar_index + 250; for(int i = start_bar; i >= bar_index; i--) { double tr = MathMax(H(i)-L(i), MathMax(MathAbs(H(i)-C(i+1)), MathAbs(L(i)-C(i+1)))); if(rma == 0.0) rma = tr; else rma = (rma * (length - 1) + tr) / length; } return rma; } // PINE STOCHASTIC (%K Smoothed con SMA directa) double PineStoch(int bar_index, int length, int smooth) { double sum_stoch = 0.0; for(int s = 0; s < smooth; s++) { int current_b = bar_index + s; double ll = L(current_b); double hh = H(current_b); for(int i = 0; i < length; i++) { ll = MathMin(ll, L(current_b + i)); hh = MathMax(hh, H(current_b + i)); } double stoch = 0.0; if(hh - ll != 0) stoch = 100.0 * (C(current_b) - ll) / (hh - ll); sum_stoch += stoch; } return sum_stoch / smooth; } int OnInit() { pct_tp_ob = pct_tp_ob_in/100.0; ObjectsDeleteAll(0,PREF); hRSI = iRSI(_Symbol,_Period,rsi_len,PRICE_CLOSE); hADX = iADX(_Symbol,_Period,adx_len); if(hRSI==INVALID_HANDLE || hADX==INVALID_HANDLE) { Print("Error creando handles de indicadores"); return(INIT_FAILED); } trade.SetExpertMagicNumber(InpMagic); trade.SetTypeFillingBySymbol(_Symbol); g_equity_day0 = AccountInfoDouble(ACCOUNT_EQUITY); MqlDateTime st; TimeToStruct(TimeCurrent(),st); g_last_day = st.day; BuildTable(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(!MQLInfoInteger(MQL_TESTER) && InpDeleteOnExit) ObjectsDeleteAll(0,PREF); } bool InRange(int m,int a,int b){ return (m>=a && m=0;i--) { ulong tk=PositionGetTicket(i); if(PositionSelectByTicket(tk)) if(PositionGetString(POSITION_SYMBOL)==_Symbol && PositionGetInteger(POSITION_MAGIC)==InpMagic) c++; } return c; } double CalcLots(double entry,double sl) { double riskAmt = AccountInfoDouble(ACCOUNT_EQUITY)*riesgo_per/100.0; double slDist = MathAbs(entry-sl); if(slDist<=0) return 0.0; double tickVal = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(tickSize<=0 || tickVal<=0) return 0.0; double lossPerLot = (slDist/tickSize)*tickVal; if(lossPerLot<=0) return 0.0; double lots = riskAmt/lossPerLot; double step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); double minv = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); double maxv = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); if(step>0) lots = MathFloor(lots/step)*step; if(lotsmaxv) lots=maxv; return lots; } void AddBuyOB(double top,double bot,datetime lt,datetime rt) { int n=ArraySize(buyOBs); ArrayResize(buyOBs,n+1); string nm=PREF+"buyOB_"+(string)(g_objCnt++); buyOBs[n].top=top; buyOBs[n].bot=bot; buyOBs[n].name=nm; ObjectCreate(0,nm,OBJ_RECTANGLE,0,lt,top,rt,bot); ObjectSetInteger(0,nm,OBJPROP_COLOR,clrGreen); ObjectSetInteger(0,nm,OBJPROP_FILL,true); ObjectSetInteger(0,nm,OBJPROP_BACK,true); ObjectSetInteger(0,nm,OBJPROP_WIDTH,1); } void AddSellOB(double top,double bot,datetime lt,datetime rt) { int n=ArraySize(sellOBs); ArrayResize(sellOBs,n+1); string nm=PREF+"sellOB_"+(string)(g_objCnt++); sellOBs[n].top=top; sellOBs[n].bot=bot; sellOBs[n].name=nm; ObjectCreate(0,nm,OBJ_RECTANGLE,0,lt,top,rt,bot); ObjectSetInteger(0,nm,OBJPROP_COLOR,clrRed); ObjectSetInteger(0,nm,OBJPROP_FILL,true); ObjectSetInteger(0,nm,OBJPROP_BACK,true); ObjectSetInteger(0,nm,OBJPROP_WIDTH,1); } void RetireBuyOB(int i,bool triggered) { if(InpKeepBoxes) { string nm = buyOBs[i].name; ObjectSetInteger(0,nm,OBJPROP_TIME,1,T(0)); ObjectSetInteger(0,nm,OBJPROP_FILL,false); ObjectSetInteger(0,nm,OBJPROP_COLOR, triggered ? clrAqua : clrGray); ObjectSetInteger(0,nm,OBJPROP_STYLE, triggered ? STYLE_SOLID : STYLE_DOT); } else ObjectDelete(0,buyOBs[i].name); ArrayRemove(buyOBs,i,1); } void RetireSellOB(int i) { if(InpKeepBoxes) { string nm = sellOBs[i].name; ObjectSetInteger(0,nm,OBJPROP_TIME,1,T(0)); ObjectSetInteger(0,nm,OBJPROP_FILL,false); ObjectSetInteger(0,nm,OBJPROP_COLOR,clrGray); ObjectSetInteger(0,nm,OBJPROP_STYLE,STYLE_DOT); } else ObjectDelete(0,sellOBs[i].name); ArrayRemove(sellOBs,i,1); } void DrawSLTP(double sl_fijo,double tp_fijo) { datetime t0 = T(1); datetime t1 = t0 + (datetime)(20*PeriodSeconds()); string nSL = PREF+"SLline_"+(string)g_objCnt; ObjectCreate(0,nSL,OBJ_TREND,0,t0,sl_fijo,t1,sl_fijo); ObjectSetInteger(0,nSL,OBJPROP_COLOR,clrRed); ObjectSetInteger(0,nSL,OBJPROP_WIDTH,2); ObjectSetInteger(0,nSL,OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(0,nSL,OBJPROP_RAY_RIGHT,false); string nTP = PREF+"TPline_"+(string)g_objCnt; ObjectCreate(0,nTP,OBJ_TREND,0,t0,tp_fijo,t1,tp_fijo); ObjectSetInteger(0,nTP,OBJPROP_COLOR,clrLime); ObjectSetInteger(0,nTP,OBJPROP_WIDTH,2); ObjectSetInteger(0,nTP,OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(0,nTP,OBJPROP_RAY_RIGHT,false); string nSLt = PREF+"SLtxt_"+(string)g_objCnt; ObjectCreate(0,nSLt,OBJ_TEXT,0,t1,sl_fijo); ObjectSetString(0,nSLt,OBJPROP_TEXT,"SL: "+DoubleToString(sl_fijo,_Digits)); ObjectSetInteger(0,nSLt,OBJPROP_COLOR,clrRed); ObjectSetInteger(0,nSLt,OBJPROP_ANCHOR,ANCHOR_LEFT); string nTPt = PREF+"TPtxt_"+(string)g_objCnt; ObjectCreate(0,nTPt,OBJ_TEXT,0,t1,tp_fijo); ObjectSetString(0,nTPt,OBJPROP_TEXT,"TP: "+DoubleToString(tp_fijo,_Digits)); ObjectSetInteger(0,nTPt,OBJPROP_COLOR,clrLime); ObjectSetInteger(0,nTPt,OBJPROP_ANCHOR,ANCHOR_LEFT); g_objCnt++; } void UpdateBoxesRight() { datetime rt = T(0); for(int i=0;i0)?tam_vela:1; double wick_top_pct = (H(S)-MathMax(O(S),C(S)))/denom*100.0; double wick_bot_pct = (MathMin(O(S),C(S))-L(S))/denom*100.0; bool wicks_ok = !Use_Wicks || ((wick_top_pct>=min_wick_top && wick_top_pct<=max_wick_top) && (wick_bot_pct>=min_wick_bot && wick_bot_pct<=max_wick_bot)); double body_pct = cuerpo_abs/denom*100.0; bool cuerpo_solid_ok = !Use_Body || (body_pct>=min_body_pct && body_pct<=max_body_pct); bool vela_size_ok = !Use_VelaSize || ((cuerpo_abs>=atr_v*min_atr_size) && (cuerpo_abs<=atr_v*max_atr_size)); double volsum=0.0; for(int i=0;i=current_vol_ma*min_vol_mult) && ((double)V(S)<=current_vol_ma*max_vol_mult)); double atr_sum=0.0; for(int i=0;i0)?hl_diff:1))/MathLog10(chop_len); bool chop_ok = (chop_val>=chop_min && chop_val<=chop_max); double rsi_v = IndVal(hRSI,0,S); double rsi_v1 = IndVal(hRSI,0,S+1); bool rsi_ok = ((rsi_v>=rsi_min && rsi_v<=rsi_max) || (rsi_v1>=rsi_min && rsi_v1<=rsi_max)); double adx_v = IndVal(hADX,0,S); bool adx_ok = (adx_v>=adx_min && adx_v<=adx_max); bool filtros_ok = !Use_Osciladores || (chop_ok && adx_ok && rsi_ok); bool is_dead_buy = InpUseDeadHours && DeadBuy(T(S)); bool is_dead_sell = InpUseDeadHours && DeadSell(T(S)); double inicio_impulso = L(S); for(int i=0;i H(S+1)) g_final_impulso = H(S); double nivel_descuento = inicio_impulso + (g_final_impulso - inicio_impulso)*pd_threshold; bool en_descuento = (!use_pd_filter) || (L(S) <= nivel_descuento); double k_val = PineStoch(S, 14, 3); bool st_confirm_buy = !Use_StochBuy || (k_val<=st_nivel_buy && k_val=st_nivel_sell); double equity = AccountInfoDouble(ACCOUNT_EQUITY); MqlDateTime nowst; TimeToStruct(TimeCurrent(),nowst); if(nowst.day != g_last_day){ g_equity_day0 = equity; g_last_day = nowst.day; } bool stop_por_drawdown = ((g_equity_day0 - equity)/g_equity_day0)*100.0 >= max_daily_loss; datetime lt = T(S+1); datetime rt = T(S); if(C(S) > H(S+1) && (C(S)-O(S)) > (atr_v*ob_mult_buy) && !is_dead_buy) { AddBuyOB(H(S+1), L(S+1), lt, rt); if(InpDebug) PrintFormat("[OB-BUY ] %s top=%s bot=%s atr=%s", TimeToString(T(S),TIME_DATE|TIME_MINUTES), DoubleToString(H(S+1),_Digits), DoubleToString(L(S+1),_Digits), DoubleToString(atr_v,_Digits)); } if(C(S) < L(S+1) && (O(S)-C(S)) > (atr_v*ob_mult_sell) && st_confirm_sell && !is_dead_sell) { AddSellOB(H(S+1), L(S+1), lt, rt); if(InpDebug) PrintFormat("[OB-SELL] %s top=%s bot=%s", TimeToString(T(S),TIME_DATE|TIME_MINUTES), DoubleToString(H(S+1),_Digits), DoubleToString(L(S+1),_Digits)); } UpdateBoxesRight(); double target_tp_ob = 0.0; bool has_tp=false; double min_dist = 1e10; for(int j=0;j C(S) && (nivel_tp - C(S)) < min_dist) { min_dist = nivel_tp - C(S); target_tp_ob = nivel_tp; has_tp=true; } } int actuales_ops = CountPositions(); for(int i=ArraySize(buyOBs)-1; i>=0; i--) { double b_top=buyOBs[i].top, b_bot=buyOBs[i].bot; bool touched = (L(S) <= b_top && L(S) >= (b_bot-(atr_v*ob_buy_off))); bool entrar = touched && cuerpo_solid_ok && vela_size_ok && vol_ok && wicks_ok && st_confirm_buy && filtros_ok && actuales_ops0?cuerpo_abs/atr_v:0), vol_ok, (double)V(S), current_vol_ma, wicks_ok, wick_top_pct, wick_bot_pct, st_confirm_buy, k_val, chop_ok, chop_val, adx_ok, adx_v, rsi_ok, en_descuento, (actuales_ops0) trade.Buy(lots,_Symbol,0.0,sl_fijo,tp_fijo,"Long_"+(string)T(S)); RetireBuyOB(i,true); break; } else if(C(S) < (b_bot - (atr_v*0.5))) { RetireBuyOB(i,false); } } for(int j=ArraySize(sellOBs)-1; j>=0; j--) if(C(S) > sellOBs[j].top) RetireSellOB(j); BuildTable(); } //==================================================================== // ONTESTER - CRITERIOS MINIMOS PARA OPTIMIZACION (CUSTOM MAX) // - Si NO cumple cualquier minimo -> devuelve 0 (descarta la corrida) // - Si cumple todos -> maximiza el FACTOR DE RECUPERACION: // recuperacion = ganancia_neta / DD_equity_en_dinero // El DD de 20% se evalua sobre el DD RELATIVO de EQUITY (%). //==================================================================== double OnTester() { // Metricas estandar del Probador int trades = (int)TesterStatistics(STAT_TRADES); double profit = TesterStatistics(STAT_PROFIT); // ganancia neta double pf = TesterStatistics(STAT_PROFIT_FACTOR); // profit factor double dd_pct = TesterStatistics(STAT_EQUITYDD_PERCENT); // DD relativo de equity (%) double dd_money = TesterStatistics(STAT_EQUITY_DD); // DD de equity en dinero double sharpe = TesterStatistics(STAT_SHARPE_RATIO); // Sharpe Ratio double recovery = TesterStatistics(STAT_RECOVERY_FACTOR); // Recovery Factor if(InpDebug) PrintFormat("[OnTester] trades=%d profit=%.2f PF=%.2f DDeq=%.2f%% DD$=%.2f Sharpe=%.2f Recovery=%.2f", trades, profit, pf, dd_pct, dd_money, sharpe, recovery); // Si el filtro custom max esta desactivado, optimiza por recuperacion sin filtrar if(!InpUseCustomMax) return( dd_money>0.0 ? profit/dd_money : profit ); // --- FILTRO DURO: cualquier incumplimiento descarta la corrida --- if(trades < InpMinTrades) return 0.0; if(dd_pct > InpMaxDDpct) return 0.0; // DD de equity por encima del tope if(pf < InpMinPF) return 0.0; if(profit < InpMinProfit) return 0.0; if(sharpe < InpMinSharpe) return 0.0; if(recovery < InpMinRecovery) return 0.0; // --- METRICA A MAXIMIZAR: factor de recuperacion (ganancia / DD$) --- // Mayor es mejor: mas ganancia por cada unidad de drawdown sufrido. double recuperacion = (dd_money > 0.0) ? (profit / dd_money) : profit; return recuperacion; } //+------------------------------------------------------------------+