564 lines
21 KiB
Plaintext
564 lines
21 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
}
|
|
//+------------------------------------------------------------------+
|