mirror of
https://github.com/deepseek-ai/DeepSeek-Coder-V2.git
synced 2026-11-05 00:57:27 +00:00
[GH-ISSUE #86] test #75
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
deepseek-ai/DeepSeek-Coder-V2#75
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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;
}
// 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;
}
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, InpMinTPPointsPipPoint());
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.0MathMax(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 + RRgInitSLDist : 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();
}
// 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");
}
}
//+------------------------------------------------------------------+