[GH-ISSUE #86] test #75

Closed
opened 2026-10-26 21:07:51 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @haydu777 on GitHub (Sep 20, 2025).
Original GitHub issue: https://github.com/deepseek-ai/DeepSeek-Coder-V2/issues/86

//+------------------------------------------------------------------+
//| MASTER_EA_BTC_v1_ULTRA_CLEAN2.mq5 |
//| Hybrid: Breakout + Retest + OBI (clean & compile-safe) |
//+------------------------------------------------------------------+
#property strict
#property version "1.60"
#property description "Hybrid (Breakout + Retest + OBI). Clean, compile-safe; fixed loops/uppercases; robust spread/ATR guards."

#include <Trade/Trade.mqh>
CTrade Trade;

//============================ INPUTS ===============================//
// --- Signal Mode Selection
enum SignalMode
{
HYBRID_BREAKOUT_RETEST_OBI = 0,
PURE_OBI_ONLY = 1,
BREAKOUT_RETEST_ONLY = 2
};
input SignalMode InpSignalMode = HYBRID_BREAKOUT_RETEST_OBI;

// --- Risk
input ulong InpMagicNumber = 909090;
input bool InpUseRiskManagement = true;
input double InpRiskPercent = 0.50;
input double InpFixedLotSize = 0.10;

// --- Lot controls
input double InpMinLotSize = 0.01;
input double InpMaxLotSize = 1.00;
input double InpMaxRiskLoss = 250.0;

// --- Breakout/Retest
input int Inp5MLookbackBars = 3;
input double Inp5MBufferPoints = 0.20; // pips -> price via PipPoint()
input double InpWickMinPips = 2.0;
input double InpBodyMinPips = 1.0;
input bool InpCloseBeyondLevel = true;
input int InpRetestTimeoutSec = 180;

// --- OBI (tick imbalance)
input double InpOBI_MinMovePoints = 5.0; // points; 0=auto baseline
input int InpOBI_MaxTicks = 10;
input int InpTickConfirmSec = 2;
input int InpTickMinTicks = 2;

// --- Retest mode
enum RetestMode { RETEST_CLOSED_M1=0, RETEST_TICK_RAW=1, RETEST_TICK_FILTERED=2 };
input RetestMode InpRetestMode = RETEST_TICK_FILTERED;
input bool InpAutoAdaptiveMode = true;

// --- Retest robustness
input double InpSoftRetestPoints = 10.0; // points

// --- Filter extras
input int InpFilterMinTicks = 3;
input double InpFilterBodyPoints = 10; // points
input double InpFilterMinATR = 0.1; // price units; 0=auto baseline
input double InpFilterMaxSpread = 50; // crypto/gold optional cap

// --- Exits
input int InpATRPeriod = 10;
input bool InpUseATRBasedSLTP = true;
input double InpSL_ATR = 2.0;
input double InpTP_ATR = 3.0;
input int InpMinSLPoints = 30; // pips -> price via PipPoint()
input int InpMinTPPoints = 20; // pips -> price via PipPoint()

// --- SL Modes
enum ModeType {CONSERVATIVE=1, BALANCED=2, AGGRESSIVE=3, ULTRA_AGGRESSIVE=4};
input ModeType InpMode = CONSERVATIVE;
input double InpBreakEvenAtPct = 50.0;
input double InpTrailStartPct = 75.0;
input double InpTrailingSL_ATR = 0.8;
input double InpLockPercent = 50.0;
input double InpUltraAggressiveTrailPoints = 50.0; // points

// --- Extend TP
input bool InpExtendTPAfterLock = true;
input double InpExtendedTP_RR_Forex = 5.0;
input double InpExtendedTP_RR_Crypto = 12.0;

// --- Trail-only mode
input bool InpTrailOnlyMode = false;

// --- Limits / filters
input int InpMaxOrders = 1;
input int InpMaxOrdersPerDay = 50;
input int InpCoolDownSec = 60;
input double InpMaxSpreadPoints = 5.0; // FX only; 0=off
input ushort InpMaxDeviationPoints= 15;
input bool InpShowDebug = true;
input bool InpShowPLVisualization = true;

//=========================== GLOBALS ===============================//
struct OBIState {
double lastAsk, lastBid;
double buyStart, sellStart;
int buyTicks, sellTicks;
datetime buyStartTime, sellStartTime;
};

// Caches
double gPipPoint=0.0, gBufferSize=0.0, gMinWickSize=0.0, gMinBodySize=0.0;
double gSoftTolerance=0.0, gFilterBodyDistance=0.0;

// Indicators
int gAtrHandle=INVALID_HANDLE;
double gPreATR=0.0; datetime gLastATRTime=0;

// Levels
double gM5High=0.0, gM5Low=0.0; datetime gLastM5BarTime=0;

// Signal state
bool gWaitingForRetest=false;
int gSignalDirection=0; double gSignalLevel=0.0;
datetime gSignalTime=0, gLastFillTime=0;

// Daily count
int gOrdersToday=0; datetime gLastOrderCountUpdate=0;

// Risk
double gInitSLDist=0.0;

// Tick-retest trackers
bool gTickTouched=false; datetime gTickTouchTime=0; int gTickConfirmCount=0;
bool gRawConfirmed=false; datetime gRawConfirmTime=0;

// Effective (auto-tuned) parameters
double gEffOBIMinMovePts=0.0; // points
double gEffFilterMinATR =0.0; // price units

// OBI state
OBIState gOBI={0};

//=========================== HELPERS ===============================//
// safe uppercase copy (avoid StringToUpper by-ref with constants)
string UpperCopy(const string src){
string out = src;
int len = StringLen(out);
for(int i = 0; i < len; ++i){
uint ch = (uint)StringGetCharacter(out, i);
if(ch >= 'a' && ch <= 'z')
StringSetCharacter(out, i, (ushort)(ch - 32));
}
return out;
}

bool IsSymbolLike(const string needle){
return (StringFind(UpperCopy(_Symbol), UpperCopy(needle)) >= 0);
}

bool IsCryptoOrGold(){
return (IsSymbolLike("BTC") || IsSymbolLike("XAU") || IsSymbolLike("GOLD"));
}

double PipPoint(){
if(gPipPoint==0.0){
int d=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
gPipPoint = (d==3||d==5) ? 10.0*_Point : _Point;
}
return gPipPoint;
}

bool GetUltraFastPrices(double &ask,double &bid){
MqlTick t; if(!SymbolInfoTick(_Symbol,t)) return false;
ask=t.ask; bid=t.bid; return true;
}

// Spread filter: FX uses InpMaxSpreadPoints; Crypto/Gold can use InpFilterMaxSpread
bool SpreadOK(){
MqlTick t; if(!SymbolInfoTick(_Symbol,t)) return false;
double spr=(t.ask - t.bid)/_Point;
if(IsCryptoOrGold()){
if(InpFilterMaxSpread > 0.0 && spr > InpFilterMaxSpread) return false;
return true;
}
if(InpMaxSpreadPoints > 0.0 && spr > InpMaxSpreadPoints) return false;
return true;
}

// Robust position counter
int PositionsByMagic(const ulong mg){
int cnt=0, total=(int)PositionsTotal();
for(int i = 0; i < total; ++i){
if(!PositionSelectByIndex(i)) continue;
string sym=""; PositionGetString(POSITION_SYMBOL, sym);
ulong pmg=(ulong)PositionGetInteger(POSITION_MAGIC);
if(sym==_Symbol && pmg==mg) cnt++;
}
return cnt;
}

//======================= INDICATOR CACHE ===========================//
void UpdateATROncePerSec(){
datetime now=TimeCurrent();
if(now - gLastATRTime <= 1) return;
if(gAtrHandle==INVALID_HANDLE) return;
double tmp[1]; if(CopyBuffer(gAtrHandle,0,0,1,tmp)==1) gPreATR=tmp[0];
gLastATRTime=now;
}

void UpdateM5LevelsOnNewBar(){
datetime current=iTime(_Symbol,PERIOD_M5,0);
if(current==gLastM5BarTime) return;

double highs[], lows[];
ArraySetAsSeries(highs,true);
ArraySetAsSeries(lows ,true);

int gotH=CopyHigh(_Symbol,PERIOD_M5,1,Inp5MLookbackBars,highs);
int gotL=CopyLow (_Symbol,PERIOD_M5,1,Inp5MLookbackBars,lows);
if(gotH<Inp5MLookbackBars || gotL<Inp5MLookbackBars) return;

double H = highs[0];
double L = lows [0];

for(int i = 0; i < gotH; ++i) if(highs[i] > H) H = highs[i];
for(int j = 0; j < gotL; ++j) if(lows [j] < L) L = lows [j];

gM5High = H; gM5Low = L;
gLastM5BarTime=current;
}

//======================= EFFECTIVE PARAMS ==========================//
void UpdateEffectiveParams(){
if(!InpAutoAdaptiveMode){
gEffOBIMinMovePts = InpOBI_MinMovePoints;
gEffFilterMinATR = InpFilterMinATR;
return;
}
// baselines
if(IsSymbolLike("BTC")){ gEffOBIMinMovePts=5.0; gEffFilterMinATR=0.01; }
else if(IsSymbolLike("XAU")||IsSymbolLike("GOLD")){ gEffOBIMinMovePts=20.0; gEffFilterMinATR=0.05; }
else { gEffOBIMinMovePts=2.0; gEffFilterMinATR=0.02; }

// user overrides take precedence when >0
if(InpOBI_MinMovePoints > 0) gEffOBIMinMovePts = InpOBI_MinMovePoints;
if(InpFilterMinATR > 0) gEffFilterMinATR = InpFilterMinATR;

// precompute commonly used distances
double pp=PipPoint();
gBufferSize = Inp5MBufferPoints * pp;
gMinWickSize = InpWickMinPips * pp;
gMinBodySize = InpBodyMinPips * pp;
gSoftTolerance = InpSoftRetestPoints * _Point;
gFilterBodyDistance = InpFilterBodyPoints * _Point;
}

//======================= SIGNAL DETECTION ==========================//
int Get5MBreakoutSignal(){
double ask,bid; if(!GetUltraFastPrices(ask,bid)) return 0;
if(ask > (gM5High + gBufferSize)) return 1;
if(bid < (gM5Low - gBufferSize)) return -1;
return 0;
}

int GetOBISignal(){
double ask,bid; if(!GetUltraFastPrices(ask,bid)) return 0;
if(gOBI.lastAsk==0.0){ gOBI.lastAsk=ask; gOBI.lastBid=bid; return 0; }

const double noise = 2.0 * _Point;
const int maxT = MathMax(1, InpOBI_MaxTicks);
const double need = gEffOBIMinMovePts*_Point;
const bool useT = (InpTickConfirmSec>0);

// BUY sequence
if(ask > gOBI.lastAsk + noise){
if(gOBI.buyTicks==0){ gOBI.buyStart=gOBI.lastAsk; gOBI.buyStartTime=TimeCurrent(); }
gOBI.buyTicks++; gOBI.sellTicks=0; gOBI.sellStartTime=0;

bool timeOK = !useT || (TimeCurrent()-gOBI.buyStartTime<=InpTickConfirmSec);
if(timeOK && (ask - gOBI.buyStart >= need) && (gOBI.buyTicks <= maxT)){
  if(InpShowDebug) Print("OBI BUY ok (",gOBI.buyTicks,")");
  ZeroMemory(gOBI); gOBI.lastAsk=ask; gOBI.lastBid=bid; return 1;
}
if(gOBI.buyTicks>maxT || (useT && (TimeCurrent()-gOBI.buyStartTime>InpTickConfirmSec))){
  gOBI.buyTicks=0; gOBI.buyStartTime=0;
}

}
// SELL sequence
else if(bid < gOBI.lastBid - noise){
if(gOBI.sellTicks==0){ gOBI.sellStart=gOBI.lastBid; gOBI.sellStartTime=TimeCurrent(); }
gOBI.sellTicks++; gOBI.buyTicks=0; gOBI.buyStartTime=0;

bool timeOK = !useT || (TimeCurrent()-gOBI.sellStartTime<=InpTickConfirmSec);
if(timeOK && (gOBI.sellStart - bid >= need) && (gOBI.sellTicks <= maxT)){
  if(InpShowDebug) Print("OBI SELL ok (",gOBI.sellTicks,")");
  ZeroMemory(gOBI); gOBI.lastAsk=ask; gOBI.lastBid=bid; return -1;
}
if(gOBI.sellTicks>maxT || (useT && (TimeCurrent()-gOBI.sellStartTime>InpTickConfirmSec))){
  gOBI.sellTicks=0; gOBI.sellStartTime=0;
}

}

gOBI.lastAsk=ask; gOBI.lastBid=bid;
return 0;
}

//==================== RETEST CHECKS ================================//
bool CheckWickRetestClosedM1(){
MqlRates r[]; ArraySetAsSeries(r,true);
if(CopyRates(_Symbol,PERIOD_M1,1,1,r) < 1) return false;

double o=r[0].open, c=r[0].close, h=r[0].high, l=r[0].low;

if(gSignalDirection==1){
bool touched = (l <= gSignalLevel + gBufferSize + gSoftTolerance);
bool bullish = (c > o);
double body = MathAbs(c - o);
double wick = MathMin(o, c) - l;
bool beyond = InpCloseBeyondLevel ? (c >= gSignalLevel) : true;
return touched && bullish && (body>=gMinBodySize) && (wick>=gMinWickSize) && beyond;
}else if(gSignalDirection==-1){
bool touched = (h >= gSignalLevel - gBufferSize - gSoftTolerance);
bool bearish = (c < o);
double body = MathAbs(c - o);
double wick = h - MathMax(o, c);
bool beyond = InpCloseBeyondLevel ? (c <= gSignalLevel) : true;
return touched && bearish && (body>=gMinBodySize) && (wick>=gMinWickSize) && beyond;
}
return false;
}

bool CheckRetestTickRaw(){
double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false;
double tolZone = gSoftTolerance + gBufferSize;

if(!gTickTouched){
if(gSignalDirection==1){
if(bid <= gSignalLevel + tolZone && bid >= gSignalLevel - tolZone){
gTickTouched=true; gTickTouchTime=TimeCurrent(); gTickConfirmCount=0;
if(InpShowDebug) Print("[RAW] touch BUY zone");
}
}else if(gSignalDirection==-1){
if(ask >= gSignalLevel - tolZone && ask <= gSignalLevel + tolZone){
gTickTouched=true; gTickTouchTime=TimeCurrent(); gTickConfirmCount=0;
if(InpShowDebug) Print("[RAW] touch SELL zone");
}
}
return false;
}

if(TimeCurrent()-gTickTouchTime > InpTickConfirmSec){
gTickTouched=false; gTickConfirmCount=0;
if(InpShowDebug) Print("[RAW] touch expired");
return false;
}

if(gSignalDirection==1){
if(ask >= gSignalLevel + InpFilterBodyPoints*_Point){ // reuse body points as bounce
gTickConfirmCount++; if(gTickConfirmCount>=InpTickMinTicks){
gTickTouched=false; gTickConfirmCount=0; return true;
}
}
}else if(gSignalDirection==-1){
if(bid <= gSignalLevel - InpFilterBodyPoints*_Point){
gTickConfirmCount++; if(gTickConfirmCount>=InpTickMinTicks){
gTickTouched=false; gTickConfirmCount=0; return true;
}
}
}
return false;
}

bool CheckRetestTickFiltered(){
double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false;
if(gPreATR < gEffFilterMinATR) return false;

if(!gRawConfirmed){
if(!CheckRetestTickRaw()) return false;
gRawConfirmed=true; gRawConfirmTime=TimeCurrent(); gTickConfirmCount=0;
if(InpShowDebug) Print("[FIL] raw ok; waiting …");
return false;
}

if(TimeCurrent()-gRawConfirmTime > InpTickConfirmSec){
gRawConfirmed=false; gTickConfirmCount=0;
if(InpShowDebug) Print("[FIL] window expired");
return false;
}

gTickConfirmCount++; if(gTickConfirmCount < InpFilterMinTicks) return false;

if(gSignalDirection==1){
if(ask < gSignalLevel + InpFilterBodyPoints*_Point) return false;
}else{
if(bid > gSignalLevel - InpFilterBodyPoints*_Point) return false;
}
if(InpShowDebug) Print("[FIL] retest confirmed");
gRawConfirmed=false; gTickConfirmCount=0; return true;
}

//========================= LOT & EXEC ==============================//
double CalcLots(double atr){
if(!InpUseRiskManagement) return InpFixedLotSize;

double bal = AccountInfoDouble(ACCOUNT_BALANCE);
double tickVal= SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
double tickSz = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(bal<=0.0 || tickVal<=0.0 || tickSz<=0.0) return InpFixedLotSize;

double slDist = InpUseATRBasedSLTP ? (InpSL_ATRatr) : (100.0PipPoint());
slDist = MathMax(slDist, InpMinSLPoints*PipPoint());

double riskAmt = MathMin(bal*(InpRiskPercent/100.0), InpMaxRiskLoss);
double perLotRisk = (slDist / tickSz) * tickVal;
if(perLotRisk<=0.0) return InpFixedLotSize;

double lots = riskAmt / perLotRisk;

double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double vmin=MathMax(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN), InpMinLotSize);
double vmax=MathMin(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX), InpMaxLotSize);
if(step>0.0) lots=MathFloor(lots/step)*step;
lots=MathMin(MathMax(lots, vmin), vmax);
return lots;
}

bool ExecuteTrade(const int dir, const double atr){
if(!SpreadOK()){
if(InpShowDebug) Print("Skip: spread");
return false;
}

double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false;

double slDist = InpUseATRBasedSLTP ? (InpSL_ATRatr) : (100.0PipPoint());
double tpDist = InpUseATRBasedSLTP ? (InpTP_ATRatr) : (150.0PipPoint());
slDist = MathMax(slDist, InpMinSLPointsPipPoint());
tpDist = MathMax(tpDist, InpMinTPPoints
PipPoint());

double price=(dir==1)? ask : bid;
double sl =(dir==1)? price - slDist : price + slDist;
double tp =(dir==1)? price + tpDist : price - tpDist;
if(InpTrailOnlyMode) tp=0.0;

double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(tickSz>0.0){
sl=MathRound(sl/tickSz)*tickSz;
if(tp!=0.0) tp=MathRound(tp/tickSz)*tickSz;
}
sl=NormalizeDouble(sl,_Digits);
if(tp!=0.0) tp=NormalizeDouble(tp,_Digits);

double lots=CalcLots(atr); if(lots<=0.0) return false;

// send at market with price=0 to avoid invalid price/requote issues
bool ok = (dir==1) ? Trade.Buy (lots,_Symbol,0.0,sl,tp,"ULTRA BUY")
: Trade.Sell(lots,_Symbol,0.0,sl,tp,"ULTRA SELL");

if(ok){ gLastFillTime=TimeCurrent(); gInitSLDist=slDist; }
else if(InpShowDebug) Print("Order failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription());

return ok;
}

//====================== POSITION MANAGEMENT ========================//
void ManageAllPositions(){
UpdateATROncePerSec();
if(gPreATR<=0.0) return;

// manage only our magic on this symbol (simple single-position manager)
if(!PositionSelect(_Symbol)) return;
if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagicNumber) return;

int typ=(int)PositionGetInteger(POSITION_TYPE);
double op = PositionGetDouble(POSITION_PRICE_OPEN);
double cp = PositionGetDouble(POSITION_PRICE_CURRENT);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);

double progress=0.0;
if(tp>0.0 && tp!=op){
if(typ==POSITION_TYPE_BUY && tp>op) progress=100.0*(cp-op)/(tp-op);
if(typ==POSITION_TYPE_SELL && tp<op) progress=100.0*(op-cp)/(op-tp);
}else{
double targetDist=InpTP_ATR * gPreATR;
if(targetDist>0.0){
if(typ==POSITION_TYPE_BUY ) progress=100.0MathMax(0.0,(cp-op))/targetDist;
if(typ==POSITION_TYPE_SELL) progress=100.0
MathMax(0.0,(op-cp))/targetDist;
}
}

double newSL=sl; bool modify=false;

if(InpMode==CONSERVATIVE){
if(progress>=InpBreakEvenAtPct && ((typ==POSITION_TYPE_BUY && sl<op)||(typ==POSITION_TYPE_SELL && sl>op))){
newSL=op; modify=true; if(InpShowDebug) Print("SL -> BE");
}
if(progress>=InpTrailStartPct){
if(tp>0.0 && tp!=op){
double lock=(InpLockPercent/100.0)MathAbs(tp-op);
newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, op+lock) : MathMin(newSL, op-lock);
modify=true;
}
if(InpExtendTPAfterLock && !InpTrailOnlyMode && tp>0.0){
double RR = IsCryptoOrGold()? InpExtendedTP_RR_Crypto : InpExtendedTP_RR_Forex;
if(gInitSLDist<=0.0 && sl!=0.0 && op!=sl) gInitSLDist=MathAbs(op-sl);
if(gInitSLDist>0.0){
double newTP=(typ==POSITION_TYPE_BUY)? op + RR
gInitSLDist : op - RR*gInitSLDist;
double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(tickSz>0.0) newTP=MathRound(newTP/tickSz)*tickSz;
newTP=NormalizeDouble(newTP,_Digits);
if(MathAbs(newTP - tp) > _Point){
if(!Trade.PositionModify(_Symbol,newSL,newTP) && InpShowDebug)
Print("Extend TP failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription());
}
return; // avoid double-mod this tick
}
}
}
}
else if(InpMode==BALANCED){
if(progress>=InpBreakEvenAtPct && ((typ==POSITION_TYPE_BUY && sl<op)||(typ==POSITION_TYPE_SELL && sl>op))){
newSL=op; modify=true;
}
if(progress>=InpTrailStartPct){
double tr=InpTrailingSL_ATR * gPreATR;
newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, cp - tr) : MathMin(newSL, cp + tr);
modify=true;
}
}
else if(InpMode==AGGRESSIVE){
double oneR_pct = 100.0 * (InpSL_ATR / MathMax(1e-9, InpTP_ATR));
if(progress>=oneR_pct){
double tr=InpTrailingSL_ATR * gPreATR;
newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, cp - tr) : MathMin(newSL, cp + tr);
modify=true;
}
}
else if(InpMode==ULTRA_AGGRESSIVE){
double trailPoints = InpUltraAggressiveTrailPoints * _Point;
newSL=(typ==POSITION_TYPE_BUY)? cp - trailPoints : cp + trailPoints;
modify=true;
}

if(modify){
double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
if(tickSz>0.0) newSL=MathRound(newSL/tickSz)*tickSz;
newSL=NormalizeDouble(newSL,_Digits);
if(sl==0.0 || MathAbs(newSL - sl) > _Point){
if(!Trade.PositionModify(_Symbol,newSL,tp) && InpShowDebug)
Print("SL modify failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription());
}
}
}

//====================== P/L VISUALIZATION ==========================//
void UpdatePLVisualization(){
if(!InpShowPLVisualization){ Comment(""); return; }
if(!PositionSelect(_Symbol) || (ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagicNumber){
Comment("No active positions"); return;
}

int typ=(int)PositionGetInteger(POSITION_TYPE);
double entry=PositionGetDouble(POSITION_PRICE_OPEN);
double current=PositionGetDouble(POSITION_PRICE_CURRENT);
double sl=PositionGetDouble(POSITION_SL);
double tp=PositionGetDouble(POSITION_TP);

string l1,l2;
if(typ==POSITION_TYPE_BUY){
double profit=(current-entry)/PipPoint();
double tpPips=(tp>0.0)? (tp-entry)/PipPoint() : 0.0;
double slPips=(sl>0.0)? (entry-sl)/PipPoint() : 0.0;
l1=StringFormat("BUY | %+0.1f pips (TP %.0f / SL %.0f)", profit,tpPips,slPips);
}else{
double profit=(entry-current)/PipPoint();
double tpPips=(tp>0.0)? (entry-tp)/PipPoint() : 0.0;
double slPips=(sl>0.0)? (sl-entry)/PipPoint() : 0.0;
l1=StringFormat("SELL | %+0.1f pips (TP %.0f / SL %.0f)", profit,tpPips,slPips);
}
l2=StringFormat("Entry %.*f SL %.*f TP %s", _Digits,entry,_Digits,sl,(tp>0.0?DoubleToString(tp,_Digits):"none"));
Comment(l1+"\n"+l2);
}

//================== DAILY COUNT / TRANSACTIONS =====================//
void UpdateDailyOrderCount(){
datetime todayBarOpen = iTime(_Symbol, PERIOD_D1, 0);
if(gLastOrderCountUpdate < todayBarOpen){
gOrdersToday = 0;
gLastOrderCountUpdate = TimeCurrent();
}
}

void InitializeDailyOrders(){
datetime today_start = iTime(_Symbol, PERIOD_D1, 0);
datetime today_end = today_start + PeriodSeconds(PERIOD_D1) - 1;

gOrdersToday = 0;
if(HistorySelect(today_start, today_end)){
int total = (int)HistoryDealsTotal();
for(int i = 0; i < total; ++i){
ulong deal = HistoryDealGetTicket(i);
if(deal==0) continue;
if(HistoryDealGetInteger(deal,DEAL_ENTRY) == DEAL_ENTRY_IN &&
(ulong)HistoryDealGetInteger(deal,DEAL_MAGIC) == InpMagicNumber &&
(string)HistoryDealGetString(deal, DEAL_SYMBOL) == _Symbol)
{
gOrdersToday++;
}
}
}
}

void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &req,
const MqlTradeResult &res)
{
UpdateDailyOrderCount();
if(trans.type==TRADE_TRANSACTION_DEAL_ADD){
ulong deal = trans.deal;
if(HistoryDealGetInteger(deal,DEAL_ENTRY)==DEAL_ENTRY_IN &&
(ulong)HistoryDealGetInteger(deal,DEAL_MAGIC)==InpMagicNumber &&
(string)HistoryDealGetString(deal,DEAL_SYMBOL)==_Symbol)
{
gOrdersToday++;
gLastFillTime = TimeCurrent(); // cooldown on actual fills
if(InpShowDebug) Print("Daily orders: ", gOrdersToday);
}
}
}

//========================== LIFECYCLE ==============================//
int OnInit(){
gAtrHandle=iATR(_Symbol,PERIOD_M1, InpATRPeriod);
if(gAtrHandle==INVALID_HANDLE) return INIT_FAILED;

Trade.SetExpertMagicNumber(InpMagicNumber);
Trade.SetDeviationInPoints((uint)InpMaxDeviationPoints);

ZeroMemory(gOBI);
InitializeDailyOrders();
if(InpShowDebug) Print("ULTRA_CLEAN2 (v1.60) initialized");
return INIT_SUCCEEDED;
}

void OnDeinit(const int reason){
if(gAtrHandle!=INVALID_HANDLE) IndicatorRelease(gAtrHandle);
Comment("");
}

//============================= CORE ================================//
void OnTick(){
// Manage open trade & HUD
ManageAllPositions();
UpdatePLVisualization();

// Housekeeping
UpdateATROncePerSec();
UpdateM5LevelsOnNewBar();
UpdateEffectiveParams();
UpdateDailyOrderCount();

// Throttles / gates
if(PositionsByMagic(InpMagicNumber)>=InpMaxOrders) return;
if(TimeCurrent() - gLastFillTime < InpCoolDownSec) return;
if(gOrdersToday >= InpMaxOrdersPerDay) return;
if(gM5High==0.0 || gM5Low==0.0 || gPreATR<=0.0) return;

bool readyToTrade=false; int signalDir=0;

switch(InpSignalMode){
case HYBRID_BREAKOUT_RETEST_OBI:
{
if(!gWaitingForRetest){
int dir=Get5MBreakoutSignal();
if(dir!=0){
gWaitingForRetest=true;
gSignalDirection=dir; gSignalLevel=(dir==1)? gM5High : gM5Low;
gSignalTime=TimeCurrent();
if(InpShowDebug) Print("Breakout detected. Waiting for retest + OBI");
}
}else{
bool ret=false;
if(InpRetestMode==RETEST_CLOSED_M1) ret=CheckWickRetestClosedM1();
else if(InpRetestMode==RETEST_TICK_RAW) ret=CheckRetestTickRaw();
else ret=CheckRetestTickFiltered();

    if(ret){
      int obi=GetOBISignal();
      if(obi==gSignalDirection){ readyToTrade=true; signalDir=gSignalDirection; }
    }
  }
  break;
}

case PURE_OBI_ONLY:
{
  signalDir=GetOBISignal();
  if(signalDir!=0) readyToTrade=true;
  break;
}

case BREAKOUT_RETEST_ONLY:
{
  if(!gWaitingForRetest){
    int dir=Get5MBreakoutSignal();
    if(dir!=0){
      gWaitingForRetest=true; gSignalDirection=dir;
      gSignalLevel=(dir==1)? gM5High : gM5Low;
      gSignalTime=TimeCurrent();
      if(InpShowDebug) Print("Breakout detected. Waiting for retest");
    }
  }else{
    bool ret=false;
    if(InpRetestMode==RETEST_CLOSED_M1)      ret=CheckWickRetestClosedM1();
    else if(InpRetestMode==RETEST_TICK_RAW)  ret=CheckRetestTickRaw();
    else                                     ret=CheckRetestTickFiltered();

    if(ret){ readyToTrade=true; signalDir=gSignalDirection; }
  }
  break;
}

}

// Execute if ready
if(readyToTrade){
if(ExecuteTrade(signalDir, gPreATR)){
gWaitingForRetest=false; gTickTouched=false; gTickConfirmCount=0; gRawConfirmed=false;
if(InpShowDebug) Print("Entry executed");
}
}

// Timeouts
if(gWaitingForRetest && TimeCurrent()-gSignalTime>InpRetestTimeoutSec){
gWaitingForRetest=false; gSignalDirection=0;
gTickTouched=false; gTickConfirmCount=0; gRawConfirmed=false;
if(InpShowDebug) Print("Signal timeout");
}
}
//+------------------------------------------------------------------+

Originally created by @haydu777 on GitHub (Sep 20, 2025). Original GitHub issue: https://github.com/deepseek-ai/DeepSeek-Coder-V2/issues/86 //+------------------------------------------------------------------+ //| MASTER_EA_BTC_v1_ULTRA_CLEAN2.mq5 | //| Hybrid: Breakout + Retest + OBI (clean & compile-safe) | //+------------------------------------------------------------------+ #property strict #property version "1.60" #property description "Hybrid (Breakout + Retest + OBI). Clean, compile-safe; fixed loops/uppercases; robust spread/ATR guards." #include <Trade/Trade.mqh> CTrade Trade; //============================ INPUTS ===============================// // --- Signal Mode Selection enum SignalMode { HYBRID_BREAKOUT_RETEST_OBI = 0, PURE_OBI_ONLY = 1, BREAKOUT_RETEST_ONLY = 2 }; input SignalMode InpSignalMode = HYBRID_BREAKOUT_RETEST_OBI; // --- Risk input ulong InpMagicNumber = 909090; input bool InpUseRiskManagement = true; input double InpRiskPercent = 0.50; input double InpFixedLotSize = 0.10; // --- Lot controls input double InpMinLotSize = 0.01; input double InpMaxLotSize = 1.00; input double InpMaxRiskLoss = 250.0; // --- Breakout/Retest input int Inp5MLookbackBars = 3; input double Inp5MBufferPoints = 0.20; // pips -> price via PipPoint() input double InpWickMinPips = 2.0; input double InpBodyMinPips = 1.0; input bool InpCloseBeyondLevel = true; input int InpRetestTimeoutSec = 180; // --- OBI (tick imbalance) input double InpOBI_MinMovePoints = 5.0; // points; 0=auto baseline input int InpOBI_MaxTicks = 10; input int InpTickConfirmSec = 2; input int InpTickMinTicks = 2; // --- Retest mode enum RetestMode { RETEST_CLOSED_M1=0, RETEST_TICK_RAW=1, RETEST_TICK_FILTERED=2 }; input RetestMode InpRetestMode = RETEST_TICK_FILTERED; input bool InpAutoAdaptiveMode = true; // --- Retest robustness input double InpSoftRetestPoints = 10.0; // points // --- Filter extras input int InpFilterMinTicks = 3; input double InpFilterBodyPoints = 10; // points input double InpFilterMinATR = 0.1; // price units; 0=auto baseline input double InpFilterMaxSpread = 50; // crypto/gold optional cap // --- Exits input int InpATRPeriod = 10; input bool InpUseATRBasedSLTP = true; input double InpSL_ATR = 2.0; input double InpTP_ATR = 3.0; input int InpMinSLPoints = 30; // pips -> price via PipPoint() input int InpMinTPPoints = 20; // pips -> price via PipPoint() // --- SL Modes enum ModeType {CONSERVATIVE=1, BALANCED=2, AGGRESSIVE=3, ULTRA_AGGRESSIVE=4}; input ModeType InpMode = CONSERVATIVE; input double InpBreakEvenAtPct = 50.0; input double InpTrailStartPct = 75.0; input double InpTrailingSL_ATR = 0.8; input double InpLockPercent = 50.0; input double InpUltraAggressiveTrailPoints = 50.0; // points // --- Extend TP input bool InpExtendTPAfterLock = true; input double InpExtendedTP_RR_Forex = 5.0; input double InpExtendedTP_RR_Crypto = 12.0; // --- Trail-only mode input bool InpTrailOnlyMode = false; // --- Limits / filters input int InpMaxOrders = 1; input int InpMaxOrdersPerDay = 50; input int InpCoolDownSec = 60; input double InpMaxSpreadPoints = 5.0; // FX only; 0=off input ushort InpMaxDeviationPoints= 15; input bool InpShowDebug = true; input bool InpShowPLVisualization = true; //=========================== GLOBALS ===============================// struct OBIState { double lastAsk, lastBid; double buyStart, sellStart; int buyTicks, sellTicks; datetime buyStartTime, sellStartTime; }; // Caches double gPipPoint=0.0, gBufferSize=0.0, gMinWickSize=0.0, gMinBodySize=0.0; double gSoftTolerance=0.0, gFilterBodyDistance=0.0; // Indicators int gAtrHandle=INVALID_HANDLE; double gPreATR=0.0; datetime gLastATRTime=0; // Levels double gM5High=0.0, gM5Low=0.0; datetime gLastM5BarTime=0; // Signal state bool gWaitingForRetest=false; int gSignalDirection=0; double gSignalLevel=0.0; datetime gSignalTime=0, gLastFillTime=0; // Daily count int gOrdersToday=0; datetime gLastOrderCountUpdate=0; // Risk double gInitSLDist=0.0; // Tick-retest trackers bool gTickTouched=false; datetime gTickTouchTime=0; int gTickConfirmCount=0; bool gRawConfirmed=false; datetime gRawConfirmTime=0; // Effective (auto-tuned) parameters double gEffOBIMinMovePts=0.0; // points double gEffFilterMinATR =0.0; // price units // OBI state OBIState gOBI={0}; //=========================== HELPERS ===============================// // safe uppercase copy (avoid StringToUpper by-ref with constants) string UpperCopy(const string src){ string out = src; int len = StringLen(out); for(int i = 0; i < len; ++i){ uint ch = (uint)StringGetCharacter(out, i); if(ch >= 'a' && ch <= 'z') StringSetCharacter(out, i, (ushort)(ch - 32)); } return out; } bool IsSymbolLike(const string needle){ return (StringFind(UpperCopy(_Symbol), UpperCopy(needle)) >= 0); } bool IsCryptoOrGold(){ return (IsSymbolLike("BTC") || IsSymbolLike("XAU") || IsSymbolLike("GOLD")); } double PipPoint(){ if(gPipPoint==0.0){ int d=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); gPipPoint = (d==3||d==5) ? 10.0*_Point : _Point; } return gPipPoint; } bool GetUltraFastPrices(double &ask,double &bid){ MqlTick t; if(!SymbolInfoTick(_Symbol,t)) return false; ask=t.ask; bid=t.bid; return true; } // Spread filter: FX uses InpMaxSpreadPoints; Crypto/Gold can use InpFilterMaxSpread bool SpreadOK(){ MqlTick t; if(!SymbolInfoTick(_Symbol,t)) return false; double spr=(t.ask - t.bid)/_Point; if(IsCryptoOrGold()){ if(InpFilterMaxSpread > 0.0 && spr > InpFilterMaxSpread) return false; return true; } if(InpMaxSpreadPoints > 0.0 && spr > InpMaxSpreadPoints) return false; return true; } // Robust position counter int PositionsByMagic(const ulong mg){ int cnt=0, total=(int)PositionsTotal(); for(int i = 0; i < total; ++i){ if(!PositionSelectByIndex(i)) continue; string sym=""; PositionGetString(POSITION_SYMBOL, sym); ulong pmg=(ulong)PositionGetInteger(POSITION_MAGIC); if(sym==_Symbol && pmg==mg) cnt++; } return cnt; } //======================= INDICATOR CACHE ===========================// void UpdateATROncePerSec(){ datetime now=TimeCurrent(); if(now - gLastATRTime <= 1) return; if(gAtrHandle==INVALID_HANDLE) return; double tmp[1]; if(CopyBuffer(gAtrHandle,0,0,1,tmp)==1) gPreATR=tmp[0]; gLastATRTime=now; } void UpdateM5LevelsOnNewBar(){ datetime current=iTime(_Symbol,PERIOD_M5,0); if(current==gLastM5BarTime) return; double highs[], lows[]; ArraySetAsSeries(highs,true); ArraySetAsSeries(lows ,true); int gotH=CopyHigh(_Symbol,PERIOD_M5,1,Inp5MLookbackBars,highs); int gotL=CopyLow (_Symbol,PERIOD_M5,1,Inp5MLookbackBars,lows); if(gotH<Inp5MLookbackBars || gotL<Inp5MLookbackBars) return; double H = highs[0]; double L = lows [0]; for(int i = 0; i < gotH; ++i) if(highs[i] > H) H = highs[i]; for(int j = 0; j < gotL; ++j) if(lows [j] < L) L = lows [j]; gM5High = H; gM5Low = L; gLastM5BarTime=current; } //======================= EFFECTIVE PARAMS ==========================// void UpdateEffectiveParams(){ if(!InpAutoAdaptiveMode){ gEffOBIMinMovePts = InpOBI_MinMovePoints; gEffFilterMinATR = InpFilterMinATR; return; } // baselines if(IsSymbolLike("BTC")){ gEffOBIMinMovePts=5.0; gEffFilterMinATR=0.01; } else if(IsSymbolLike("XAU")||IsSymbolLike("GOLD")){ gEffOBIMinMovePts=20.0; gEffFilterMinATR=0.05; } else { gEffOBIMinMovePts=2.0; gEffFilterMinATR=0.02; } // user overrides take precedence when >0 if(InpOBI_MinMovePoints > 0) gEffOBIMinMovePts = InpOBI_MinMovePoints; if(InpFilterMinATR > 0) gEffFilterMinATR = InpFilterMinATR; // precompute commonly used distances double pp=PipPoint(); gBufferSize = Inp5MBufferPoints * pp; gMinWickSize = InpWickMinPips * pp; gMinBodySize = InpBodyMinPips * pp; gSoftTolerance = InpSoftRetestPoints * _Point; gFilterBodyDistance = InpFilterBodyPoints * _Point; } //======================= SIGNAL DETECTION ==========================// int Get5MBreakoutSignal(){ double ask,bid; if(!GetUltraFastPrices(ask,bid)) return 0; if(ask > (gM5High + gBufferSize)) return 1; if(bid < (gM5Low - gBufferSize)) return -1; return 0; } int GetOBISignal(){ double ask,bid; if(!GetUltraFastPrices(ask,bid)) return 0; if(gOBI.lastAsk==0.0){ gOBI.lastAsk=ask; gOBI.lastBid=bid; return 0; } const double noise = 2.0 * _Point; const int maxT = MathMax(1, InpOBI_MaxTicks); const double need = gEffOBIMinMovePts*_Point; const bool useT = (InpTickConfirmSec>0); // BUY sequence if(ask > gOBI.lastAsk + noise){ if(gOBI.buyTicks==0){ gOBI.buyStart=gOBI.lastAsk; gOBI.buyStartTime=TimeCurrent(); } gOBI.buyTicks++; gOBI.sellTicks=0; gOBI.sellStartTime=0; bool timeOK = !useT || (TimeCurrent()-gOBI.buyStartTime<=InpTickConfirmSec); if(timeOK && (ask - gOBI.buyStart >= need) && (gOBI.buyTicks <= maxT)){ if(InpShowDebug) Print("OBI BUY ok (",gOBI.buyTicks,")"); ZeroMemory(gOBI); gOBI.lastAsk=ask; gOBI.lastBid=bid; return 1; } if(gOBI.buyTicks>maxT || (useT && (TimeCurrent()-gOBI.buyStartTime>InpTickConfirmSec))){ gOBI.buyTicks=0; gOBI.buyStartTime=0; } } // SELL sequence else if(bid < gOBI.lastBid - noise){ if(gOBI.sellTicks==0){ gOBI.sellStart=gOBI.lastBid; gOBI.sellStartTime=TimeCurrent(); } gOBI.sellTicks++; gOBI.buyTicks=0; gOBI.buyStartTime=0; bool timeOK = !useT || (TimeCurrent()-gOBI.sellStartTime<=InpTickConfirmSec); if(timeOK && (gOBI.sellStart - bid >= need) && (gOBI.sellTicks <= maxT)){ if(InpShowDebug) Print("OBI SELL ok (",gOBI.sellTicks,")"); ZeroMemory(gOBI); gOBI.lastAsk=ask; gOBI.lastBid=bid; return -1; } if(gOBI.sellTicks>maxT || (useT && (TimeCurrent()-gOBI.sellStartTime>InpTickConfirmSec))){ gOBI.sellTicks=0; gOBI.sellStartTime=0; } } gOBI.lastAsk=ask; gOBI.lastBid=bid; return 0; } //==================== RETEST CHECKS ================================// bool CheckWickRetestClosedM1(){ MqlRates r[]; ArraySetAsSeries(r,true); if(CopyRates(_Symbol,PERIOD_M1,1,1,r) < 1) return false; double o=r[0].open, c=r[0].close, h=r[0].high, l=r[0].low; if(gSignalDirection==1){ bool touched = (l <= gSignalLevel + gBufferSize + gSoftTolerance); bool bullish = (c > o); double body = MathAbs(c - o); double wick = MathMin(o, c) - l; bool beyond = InpCloseBeyondLevel ? (c >= gSignalLevel) : true; return touched && bullish && (body>=gMinBodySize) && (wick>=gMinWickSize) && beyond; }else if(gSignalDirection==-1){ bool touched = (h >= gSignalLevel - gBufferSize - gSoftTolerance); bool bearish = (c < o); double body = MathAbs(c - o); double wick = h - MathMax(o, c); bool beyond = InpCloseBeyondLevel ? (c <= gSignalLevel) : true; return touched && bearish && (body>=gMinBodySize) && (wick>=gMinWickSize) && beyond; } return false; } bool CheckRetestTickRaw(){ double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false; double tolZone = gSoftTolerance + gBufferSize; if(!gTickTouched){ if(gSignalDirection==1){ if(bid <= gSignalLevel + tolZone && bid >= gSignalLevel - tolZone){ gTickTouched=true; gTickTouchTime=TimeCurrent(); gTickConfirmCount=0; if(InpShowDebug) Print("[RAW] touch BUY zone"); } }else if(gSignalDirection==-1){ if(ask >= gSignalLevel - tolZone && ask <= gSignalLevel + tolZone){ gTickTouched=true; gTickTouchTime=TimeCurrent(); gTickConfirmCount=0; if(InpShowDebug) Print("[RAW] touch SELL zone"); } } return false; } if(TimeCurrent()-gTickTouchTime > InpTickConfirmSec){ gTickTouched=false; gTickConfirmCount=0; if(InpShowDebug) Print("[RAW] touch expired"); return false; } if(gSignalDirection==1){ if(ask >= gSignalLevel + InpFilterBodyPoints*_Point){ // reuse body points as bounce gTickConfirmCount++; if(gTickConfirmCount>=InpTickMinTicks){ gTickTouched=false; gTickConfirmCount=0; return true; } } }else if(gSignalDirection==-1){ if(bid <= gSignalLevel - InpFilterBodyPoints*_Point){ gTickConfirmCount++; if(gTickConfirmCount>=InpTickMinTicks){ gTickTouched=false; gTickConfirmCount=0; return true; } } } return false; } bool CheckRetestTickFiltered(){ double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false; if(gPreATR < gEffFilterMinATR) return false; if(!gRawConfirmed){ if(!CheckRetestTickRaw()) return false; gRawConfirmed=true; gRawConfirmTime=TimeCurrent(); gTickConfirmCount=0; if(InpShowDebug) Print("[FIL] raw ok; waiting …"); return false; } if(TimeCurrent()-gRawConfirmTime > InpTickConfirmSec){ gRawConfirmed=false; gTickConfirmCount=0; if(InpShowDebug) Print("[FIL] window expired"); return false; } gTickConfirmCount++; if(gTickConfirmCount < InpFilterMinTicks) return false; if(gSignalDirection==1){ if(ask < gSignalLevel + InpFilterBodyPoints*_Point) return false; }else{ if(bid > gSignalLevel - InpFilterBodyPoints*_Point) return false; } if(InpShowDebug) Print("[FIL] retest confirmed"); gRawConfirmed=false; gTickConfirmCount=0; return true; } //========================= LOT & EXEC ==============================// double CalcLots(double atr){ if(!InpUseRiskManagement) return InpFixedLotSize; double bal = AccountInfoDouble(ACCOUNT_BALANCE); double tickVal= SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE); double tickSz = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(bal<=0.0 || tickVal<=0.0 || tickSz<=0.0) return InpFixedLotSize; double slDist = InpUseATRBasedSLTP ? (InpSL_ATR*atr) : (100.0*PipPoint()); slDist = MathMax(slDist, InpMinSLPoints*PipPoint()); double riskAmt = MathMin(bal*(InpRiskPercent/100.0), InpMaxRiskLoss); double perLotRisk = (slDist / tickSz) * tickVal; if(perLotRisk<=0.0) return InpFixedLotSize; double lots = riskAmt / perLotRisk; double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); double vmin=MathMax(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN), InpMinLotSize); double vmax=MathMin(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX), InpMaxLotSize); if(step>0.0) lots=MathFloor(lots/step)*step; lots=MathMin(MathMax(lots, vmin), vmax); return lots; } bool ExecuteTrade(const int dir, const double atr){ if(!SpreadOK()){ if(InpShowDebug) Print("Skip: spread"); return false; } double ask,bid; if(!GetUltraFastPrices(ask,bid)) return false; double slDist = InpUseATRBasedSLTP ? (InpSL_ATR*atr) : (100.0*PipPoint()); double tpDist = InpUseATRBasedSLTP ? (InpTP_ATR*atr) : (150.0*PipPoint()); slDist = MathMax(slDist, InpMinSLPoints*PipPoint()); tpDist = MathMax(tpDist, InpMinTPPoints*PipPoint()); double price=(dir==1)? ask : bid; double sl =(dir==1)? price - slDist : price + slDist; double tp =(dir==1)? price + tpDist : price - tpDist; if(InpTrailOnlyMode) tp=0.0; double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(tickSz>0.0){ sl=MathRound(sl/tickSz)*tickSz; if(tp!=0.0) tp=MathRound(tp/tickSz)*tickSz; } sl=NormalizeDouble(sl,_Digits); if(tp!=0.0) tp=NormalizeDouble(tp,_Digits); double lots=CalcLots(atr); if(lots<=0.0) return false; // send at market with price=0 to avoid invalid price/requote issues bool ok = (dir==1) ? Trade.Buy (lots,_Symbol,0.0,sl,tp,"ULTRA BUY") : Trade.Sell(lots,_Symbol,0.0,sl,tp,"ULTRA SELL"); if(ok){ gLastFillTime=TimeCurrent(); gInitSLDist=slDist; } else if(InpShowDebug) Print("Order failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription()); return ok; } //====================== POSITION MANAGEMENT ========================// void ManageAllPositions(){ UpdateATROncePerSec(); if(gPreATR<=0.0) return; // manage only our magic on this symbol (simple single-position manager) if(!PositionSelect(_Symbol)) return; if((ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagicNumber) return; int typ=(int)PositionGetInteger(POSITION_TYPE); double op = PositionGetDouble(POSITION_PRICE_OPEN); double cp = PositionGetDouble(POSITION_PRICE_CURRENT); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double progress=0.0; if(tp>0.0 && tp!=op){ if(typ==POSITION_TYPE_BUY && tp>op) progress=100.0*(cp-op)/(tp-op); if(typ==POSITION_TYPE_SELL && tp<op) progress=100.0*(op-cp)/(op-tp); }else{ double targetDist=InpTP_ATR * gPreATR; if(targetDist>0.0){ if(typ==POSITION_TYPE_BUY ) progress=100.0*MathMax(0.0,(cp-op))/targetDist; if(typ==POSITION_TYPE_SELL) progress=100.0*MathMax(0.0,(op-cp))/targetDist; } } double newSL=sl; bool modify=false; if(InpMode==CONSERVATIVE){ if(progress>=InpBreakEvenAtPct && ((typ==POSITION_TYPE_BUY && sl<op)||(typ==POSITION_TYPE_SELL && sl>op))){ newSL=op; modify=true; if(InpShowDebug) Print("SL -> BE"); } if(progress>=InpTrailStartPct){ if(tp>0.0 && tp!=op){ double lock=(InpLockPercent/100.0)*MathAbs(tp-op); newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, op+lock) : MathMin(newSL, op-lock); modify=true; } if(InpExtendTPAfterLock && !InpTrailOnlyMode && tp>0.0){ double RR = IsCryptoOrGold()? InpExtendedTP_RR_Crypto : InpExtendedTP_RR_Forex; if(gInitSLDist<=0.0 && sl!=0.0 && op!=sl) gInitSLDist=MathAbs(op-sl); if(gInitSLDist>0.0){ double newTP=(typ==POSITION_TYPE_BUY)? op + RR*gInitSLDist : op - RR*gInitSLDist; double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(tickSz>0.0) newTP=MathRound(newTP/tickSz)*tickSz; newTP=NormalizeDouble(newTP,_Digits); if(MathAbs(newTP - tp) > _Point){ if(!Trade.PositionModify(_Symbol,newSL,newTP) && InpShowDebug) Print("Extend TP failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription()); } return; // avoid double-mod this tick } } } } else if(InpMode==BALANCED){ if(progress>=InpBreakEvenAtPct && ((typ==POSITION_TYPE_BUY && sl<op)||(typ==POSITION_TYPE_SELL && sl>op))){ newSL=op; modify=true; } if(progress>=InpTrailStartPct){ double tr=InpTrailingSL_ATR * gPreATR; newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, cp - tr) : MathMin(newSL, cp + tr); modify=true; } } else if(InpMode==AGGRESSIVE){ double oneR_pct = 100.0 * (InpSL_ATR / MathMax(1e-9, InpTP_ATR)); if(progress>=oneR_pct){ double tr=InpTrailingSL_ATR * gPreATR; newSL=(typ==POSITION_TYPE_BUY)? MathMax(newSL, cp - tr) : MathMin(newSL, cp + tr); modify=true; } } else if(InpMode==ULTRA_AGGRESSIVE){ double trailPoints = InpUltraAggressiveTrailPoints * _Point; newSL=(typ==POSITION_TYPE_BUY)? cp - trailPoints : cp + trailPoints; modify=true; } if(modify){ double tickSz=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(tickSz>0.0) newSL=MathRound(newSL/tickSz)*tickSz; newSL=NormalizeDouble(newSL,_Digits); if(sl==0.0 || MathAbs(newSL - sl) > _Point){ if(!Trade.PositionModify(_Symbol,newSL,tp) && InpShowDebug) Print("SL modify failed: ", Trade.ResultRetcode(), " ", Trade.ResultRetcodeDescription()); } } } //====================== P/L VISUALIZATION ==========================// void UpdatePLVisualization(){ if(!InpShowPLVisualization){ Comment(""); return; } if(!PositionSelect(_Symbol) || (ulong)PositionGetInteger(POSITION_MAGIC)!=InpMagicNumber){ Comment("No active positions"); return; } int typ=(int)PositionGetInteger(POSITION_TYPE); double entry=PositionGetDouble(POSITION_PRICE_OPEN); double current=PositionGetDouble(POSITION_PRICE_CURRENT); double sl=PositionGetDouble(POSITION_SL); double tp=PositionGetDouble(POSITION_TP); string l1,l2; if(typ==POSITION_TYPE_BUY){ double profit=(current-entry)/PipPoint(); double tpPips=(tp>0.0)? (tp-entry)/PipPoint() : 0.0; double slPips=(sl>0.0)? (entry-sl)/PipPoint() : 0.0; l1=StringFormat("BUY | %+0.1f pips (TP %.0f / SL %.0f)", profit,tpPips,slPips); }else{ double profit=(entry-current)/PipPoint(); double tpPips=(tp>0.0)? (entry-tp)/PipPoint() : 0.0; double slPips=(sl>0.0)? (sl-entry)/PipPoint() : 0.0; l1=StringFormat("SELL | %+0.1f pips (TP %.0f / SL %.0f)", profit,tpPips,slPips); } l2=StringFormat("Entry %.*f SL %.*f TP %s", _Digits,entry,_Digits,sl,(tp>0.0?DoubleToString(tp,_Digits):"none")); Comment(l1+"\n"+l2); } //================== DAILY COUNT / TRANSACTIONS =====================// void UpdateDailyOrderCount(){ datetime todayBarOpen = iTime(_Symbol, PERIOD_D1, 0); if(gLastOrderCountUpdate < todayBarOpen){ gOrdersToday = 0; gLastOrderCountUpdate = TimeCurrent(); } } void InitializeDailyOrders(){ datetime today_start = iTime(_Symbol, PERIOD_D1, 0); datetime today_end = today_start + PeriodSeconds(PERIOD_D1) - 1; gOrdersToday = 0; if(HistorySelect(today_start, today_end)){ int total = (int)HistoryDealsTotal(); for(int i = 0; i < total; ++i){ ulong deal = HistoryDealGetTicket(i); if(deal==0) continue; if(HistoryDealGetInteger(deal,DEAL_ENTRY) == DEAL_ENTRY_IN && (ulong)HistoryDealGetInteger(deal,DEAL_MAGIC) == InpMagicNumber && (string)HistoryDealGetString(deal, DEAL_SYMBOL) == _Symbol) { gOrdersToday++; } } } } void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &req, const MqlTradeResult &res) { UpdateDailyOrderCount(); if(trans.type==TRADE_TRANSACTION_DEAL_ADD){ ulong deal = trans.deal; if(HistoryDealGetInteger(deal,DEAL_ENTRY)==DEAL_ENTRY_IN && (ulong)HistoryDealGetInteger(deal,DEAL_MAGIC)==InpMagicNumber && (string)HistoryDealGetString(deal,DEAL_SYMBOL)==_Symbol) { gOrdersToday++; gLastFillTime = TimeCurrent(); // cooldown on actual fills if(InpShowDebug) Print("Daily orders: ", gOrdersToday); } } } //========================== LIFECYCLE ==============================// int OnInit(){ gAtrHandle=iATR(_Symbol,PERIOD_M1, InpATRPeriod); if(gAtrHandle==INVALID_HANDLE) return INIT_FAILED; Trade.SetExpertMagicNumber(InpMagicNumber); Trade.SetDeviationInPoints((uint)InpMaxDeviationPoints); ZeroMemory(gOBI); InitializeDailyOrders(); if(InpShowDebug) Print("ULTRA_CLEAN2 (v1.60) initialized"); return INIT_SUCCEEDED; } void OnDeinit(const int reason){ if(gAtrHandle!=INVALID_HANDLE) IndicatorRelease(gAtrHandle); Comment(""); } //============================= CORE ================================// void OnTick(){ // Manage open trade & HUD ManageAllPositions(); UpdatePLVisualization(); // Housekeeping UpdateATROncePerSec(); UpdateM5LevelsOnNewBar(); UpdateEffectiveParams(); UpdateDailyOrderCount(); // Throttles / gates if(PositionsByMagic(InpMagicNumber)>=InpMaxOrders) return; if(TimeCurrent() - gLastFillTime < InpCoolDownSec) return; if(gOrdersToday >= InpMaxOrdersPerDay) return; if(gM5High==0.0 || gM5Low==0.0 || gPreATR<=0.0) return; bool readyToTrade=false; int signalDir=0; switch(InpSignalMode){ case HYBRID_BREAKOUT_RETEST_OBI: { if(!gWaitingForRetest){ int dir=Get5MBreakoutSignal(); if(dir!=0){ gWaitingForRetest=true; gSignalDirection=dir; gSignalLevel=(dir==1)? gM5High : gM5Low; gSignalTime=TimeCurrent(); if(InpShowDebug) Print("Breakout detected. Waiting for retest + OBI"); } }else{ bool ret=false; if(InpRetestMode==RETEST_CLOSED_M1) ret=CheckWickRetestClosedM1(); else if(InpRetestMode==RETEST_TICK_RAW) ret=CheckRetestTickRaw(); else ret=CheckRetestTickFiltered(); if(ret){ int obi=GetOBISignal(); if(obi==gSignalDirection){ readyToTrade=true; signalDir=gSignalDirection; } } } break; } case PURE_OBI_ONLY: { signalDir=GetOBISignal(); if(signalDir!=0) readyToTrade=true; break; } case BREAKOUT_RETEST_ONLY: { if(!gWaitingForRetest){ int dir=Get5MBreakoutSignal(); if(dir!=0){ gWaitingForRetest=true; gSignalDirection=dir; gSignalLevel=(dir==1)? gM5High : gM5Low; gSignalTime=TimeCurrent(); if(InpShowDebug) Print("Breakout detected. Waiting for retest"); } }else{ bool ret=false; if(InpRetestMode==RETEST_CLOSED_M1) ret=CheckWickRetestClosedM1(); else if(InpRetestMode==RETEST_TICK_RAW) ret=CheckRetestTickRaw(); else ret=CheckRetestTickFiltered(); if(ret){ readyToTrade=true; signalDir=gSignalDirection; } } break; } } // Execute if ready if(readyToTrade){ if(ExecuteTrade(signalDir, gPreATR)){ gWaitingForRetest=false; gTickTouched=false; gTickConfirmCount=0; gRawConfirmed=false; if(InpShowDebug) Print("Entry executed"); } } // Timeouts if(gWaitingForRetest && TimeCurrent()-gSignalTime>InpRetestTimeoutSec){ gWaitingForRetest=false; gSignalDirection=0; gTickTouched=false; gTickConfirmCount=0; gRawConfirmed=false; if(InpShowDebug) Print("Signal timeout"); } } //+------------------------------------------------------------------+
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
deepseek-ai/DeepSeek-Coder-V2#75
No description provided.