Add Order Blocks EA v1.07

This commit is contained in:
Brayan Rivas Campero
2026-05-30 20:44:59 -05:00
committed by GitHub
parent 3e6000efef
commit 0cfe101e74
+563
View File
@@ -0,0 +1,563 @@
//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
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<b); }
bool DeadBuy(datetime tbar)
{
datetime gmt5 = tbar - (datetime)((InpBrokerGMT+5)*3600);
MqlDateTime s; TimeToStruct(gmt5,s);
int m = s.hour*60 + s.min;
return( InRange(m,45,90) || InRange(m,1035,1140) );
}
bool DeadSell(datetime tbar)
{
datetime gmt5 = tbar - (datetime)((InpBrokerGMT+5)*3600);
MqlDateTime s; TimeToStruct(gmt5,s);
int m = s.hour*60 + s.min;
return( InRange(m,135,210) || InRange(m,945,1080) );
}
int CountPositions()
{
int c=0;
for(int i=PositionsTotal()-1;i>=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(lots<minv) lots=minv;
if(lots>maxv) 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;i<ArraySize(buyOBs);i++) ObjectSetInteger(0,buyOBs[i].name,OBJPROP_TIME,1,rt);
for(int j=0;j<ArraySize(sellOBs);j++) ObjectSetInteger(0,sellOBs[j].name,OBJPROP_TIME,1,rt);
}
void Lbl(string id,int row,string txt,color col)
{
string nm=PREF+"tbl_"+id;
if(ObjectFind(0,nm)<0) ObjectCreate(0,nm,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,nm,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
ObjectSetInteger(0,nm,OBJPROP_XDISTANCE,10);
ObjectSetInteger(0,nm,OBJPROP_YDISTANCE,12+row*18);
ObjectSetInteger(0,nm,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
ObjectSetString(0,nm,OBJPROP_TEXT,txt);
ObjectSetInteger(0,nm,OBJPROP_COLOR,col);
ObjectSetInteger(0,nm,OBJPROP_FONTSIZE,9);
}
void BuildTable()
{
string bg=PREF+"tbl_bg";
if(ObjectFind(0,bg)<0) ObjectCreate(0,bg,OBJ_RECTANGLE_LABEL,0,0,0);
ObjectSetInteger(0,bg,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
ObjectSetInteger(0,bg,OBJPROP_XDISTANCE,200);
ObjectSetInteger(0,bg,OBJPROP_YDISTANCE,5);
ObjectSetInteger(0,bg,OBJPROP_XSIZE,195);
ObjectSetInteger(0,bg,OBJPROP_YSIZE,80);
ObjectSetInteger(0,bg,OBJPROP_BGCOLOR,clrBlack);
ObjectSetInteger(0,bg,OBJPROP_BORDER_TYPE,BORDER_FLAT);
ObjectSetInteger(0,bg,OBJPROP_COLOR,clrBlack);
Lbl("dl",0,"DAILY LOSS CAP: "+DoubleToString(max_daily_loss,1)+"%",clrRed);
Lbl("rk",1,"RISK: "+DoubleToString(riesgo_per,1)+"%",clrOrange);
Lbl("wr",2,"WIN RATE: 62.75%",clrLime);
Lbl("ds",3,"DISCOUNT: "+DoubleToString(pd_threshold*100,1)+"%",clrYellow);
}
void OnTick()
{
datetime t0 = iTime(_Symbol,_Period,0);
if(t0==g_lastBarTime) return;
g_lastBarTime = t0;
OnNewBar();
}
void OnNewBar()
{
int needBars = MathMax(chop_len, MathMax(vol_ma_len, pd_lookback)) + 252;
if(Bars(_Symbol,_Period) < needBars) return;
int S = 1;
double atr_v = PineATR(S, atr_len);
double cuerpo_abs = MathAbs(C(S)-O(S));
double tam_vela = H(S)-L(S);
double denom = (tam_vela>0)?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<vol_ma_len;i++) volsum += (double)V(S+i);
double current_vol_ma = volsum/vol_ma_len;
bool vol_ok = !Use_Volumen ||
(((double)V(S)>=current_vol_ma*min_vol_mult) &&
((double)V(S)<=current_vol_ma*max_vol_mult));
double atr_sum=0.0;
for(int i=0;i<chop_len;i++)
{
int sh=S+i;
double tr = MathMax(H(sh)-L(sh), MathMax(MathAbs(H(sh)-C(sh+1)), MathAbs(L(sh)-C(sh+1))));
atr_sum += tr;
}
double hh=H(S), ll=L(S);
for(int i=0;i<chop_len;i++){ hh=MathMax(hh,H(S+i)); ll=MathMin(ll,L(S+i)); }
double hl_diff = hh-ll;
double chop_val = 100.0*MathLog10(atr_sum/((hl_diff>0)?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<pd_lookback;i++) inicio_impulso = MathMin(inicio_impulso,L(S+i));
if(C(S) > 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_conf_th);
bool st_confirm_sell = (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<ArraySize(sellOBs);j++)
{
double s_top=sellOBs[j].top, s_bot=sellOBs[j].bot;
double nivel_tp = s_bot + (s_top - s_bot)*pct_tp_ob;
if(nivel_tp > 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_ops<max_ops && en_descuento && !stop_por_drawdown;
if(InpDebug && touched && !entrar)
PrintFormat("[RECHAZO] %s OBbot=%s | cuerpo=%d vela=%d(body%%=%.1f sz/atr=%.2f) vol=%d(v=%.0f ma=%.0f) wicks=%d(t%%=%.1f b%%=%.1f) stoch=%d(k=%.1f) chop=%d(%.1f) adx=%d(%.1f) rsi=%d desc=%d ops=%d dd=%d",
TimeToString(T(S),TIME_DATE|TIME_MINUTES), DoubleToString(b_bot,_Digits),
cuerpo_solid_ok, vela_size_ok, body_pct, (atr_v>0?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_ops<max_ops), !stop_por_drawdown);
if(entrar)
{
double sl_fijo = b_bot - (atr_v*sl_atr_mult);
double tp_fijo = has_tp ? target_tp_ob : C(S) + (C(S)-sl_fijo)*tp_ratio;
DrawSLTP(sl_fijo,tp_fijo);
if(InpDebug)
PrintFormat("[ENTRADA] %s entry~%s sl=%s tp=%s",
TimeToString(T(S),TIME_DATE|TIME_MINUTES), DoubleToString(C(S),_Digits),
DoubleToString(sl_fijo,_Digits), DoubleToString(tp_fijo,_Digits));
double lots = CalcLots(C(S), sl_fijo);
if(lots>0) 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;
}
//+------------------------------------------------------------------+