566 lines
39 KiB
MQL5
566 lines
39 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| cashcow.mq5 (VIZION MASTER EA - HUD + Aggressive Re-Entry) |
|
|
//| - No candle objects (no lag). One HUD label top-right. |
|
|
//| - Trend confirm: EMA7 cross EMA50 (lookback) + stack agree |
|
|
//| - Entry core: EMA14 reclaim after confirm |
|
|
//| - Aggressive: re-enter on NEW confirmations (pullbacks/retests) |
|
|
//| - Close rules: trend flip OR MFIB rejection |
|
|
//| - Risk: 300 points SL + trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
#property strict
|
|
#property version "1.20"
|
|
|
|
#include <Trade/Trade.mqh>
|
|
CTrade trade;
|
|
|
|
//============================== INPUTS ==============================
|
|
input ENUM_TIMEFRAMES InpTF = PERIOD_H1;
|
|
|
|
// MA periods
|
|
input int InpEMA7 = 7;
|
|
input int InpEMA14 = 14;
|
|
input int InpEMA21 = 21;
|
|
input int InpEMA50 = 50;
|
|
input int InpEMA140 = 140;
|
|
input int InpEMA230 = 230;
|
|
input int InpEMA500 = 500;
|
|
input int InpEMA1400 = 1400;
|
|
|
|
// Aggression
|
|
input int InpBatchOrders = 4; // 4 trades per entry
|
|
input double InpLotsPerOrder = 0.01;
|
|
input int InpMaxBatchesPerTrendLeg = 6; // cap to avoid runaway stacking
|
|
input int InpMinBarsBetweenEntries = 1; // 1 = can enter each new bar
|
|
input int InpReentryCooldownBars = 0; // extra cooldown if needed (0 = off)
|
|
|
|
// Confirmation logic
|
|
input int InpTouchTolPoints = 35;
|
|
input int InpCrossLookbackBars = 18;
|
|
input bool InpRequireMicroStack = true; // 7>21>50 or 7<21<50
|
|
input bool InpEnterOn14Reclaim = true; // main entry preference
|
|
input bool InpAllowReentryOn50Reclaim = true;
|
|
input bool InpAllowReentryOn140Reclaim = true;
|
|
input bool InpAllowReentryOn230Reclaim = true;
|
|
|
|
// Risk / management
|
|
input int InpSL_Points = 300;
|
|
input bool InpUseTrailingStop = true;
|
|
input int InpTrailStartPoints = 250;
|
|
input int InpTrailDistancePoints = 200;
|
|
input bool InpCloseOppOnTrendFlip = true;
|
|
|
|
// MFIB
|
|
input bool InpUseMFIBBiasFilter = true;
|
|
input int InpMFIBLookbackBars = 2500;
|
|
input bool InpUseCloseForMFIBBreaks = true;
|
|
|
|
// HUD
|
|
input bool InpShowHUD = true;
|
|
|
|
//============================== COLORS ==============================
|
|
color ColHud = clrWhite;
|
|
|
|
//============================== HANDLES =============================
|
|
int h7=-1,h14=-1,h21=-1,h50=-1,h140=-1,h230=-1,h500=-1,h1400=-1;
|
|
|
|
//============================== BUFFERS =============================
|
|
double ema7[], ema14[], ema21[], ema50[], ema140[], ema230[], ema500[], ema1400[];
|
|
MqlRates rates[];
|
|
|
|
//============================== STATE ===============================
|
|
string HUD_NAME = "VIZION_HUD";
|
|
datetime g_lastBar = 0;
|
|
int g_lastEntryShift = 999999; // bars since last entry
|
|
datetime g_lastEntryBarTime = 0; // bar time of last entry
|
|
int g_batchesThisLeg = 0; // how many batches pressed in current trend leg
|
|
int g_lastSetupId = 0; // prevents duplicates (same setup repeated)
|
|
int g_lastDirection = 0; // +1 long, -1 short, 0 none
|
|
|
|
//============================== UTILS ===============================
|
|
bool SafeCopyBuffer(const int handle, const int buffer, const int start_pos, const int count, double &arr[])
|
|
{
|
|
if(handle <= 0) return false;
|
|
ArrayResize(arr, count);
|
|
ArraySetAsSeries(arr, true);
|
|
int copied = CopyBuffer(handle, buffer, start_pos, count, arr);
|
|
if(copied <= 0) return false;
|
|
if(copied < count) ArrayResize(arr, copied);
|
|
return true;
|
|
}
|
|
|
|
void EnsureHUD()
|
|
{
|
|
if(!InpShowHUD) return;
|
|
|
|
if(ObjectFind(0, HUD_NAME) < 0)
|
|
{
|
|
ObjectCreate(0, HUD_NAME, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, HUD_NAME, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
|
|
ObjectSetInteger(0, HUD_NAME, OBJPROP_XDISTANCE, 10);
|
|
ObjectSetInteger(0, HUD_NAME, OBJPROP_YDISTANCE, 12);
|
|
ObjectSetInteger(0, HUD_NAME, OBJPROP_FONTSIZE, 10);
|
|
ObjectSetString(0, HUD_NAME, OBJPROP_FONT, "Consolas");
|
|
ObjectSetInteger(0, HUD_NAME, OBJPROP_COLOR, ColHud);
|
|
}
|
|
}
|
|
|
|
void SetHUD(const string txt)
|
|
{
|
|
if(!InpShowHUD) return;
|
|
EnsureHUD();
|
|
ObjectSetString(0, HUD_NAME, OBJPROP_TEXT, txt);
|
|
}
|
|
|
|
int CountPositionsByType(const ENUM_POSITION_TYPE type)
|
|
{
|
|
int n = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
|
|
ENUM_POSITION_TYPE t = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
if(t == type) n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void CloseAllType(const ENUM_POSITION_TYPE type, const string reason)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
|
|
ENUM_POSITION_TYPE t = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
if(t != type) continue;
|
|
|
|
trade.PositionClose(ticket);
|
|
Print("Closed ", (type == POSITION_TYPE_BUY ? "BUY" : "SELL"), " due to: ", reason);
|
|
}
|
|
}
|
|
|
|
void ApplyTrailing()
|
|
{
|
|
if(!InpUseTrailingStop) return;
|
|
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(!PositionSelectByTicket(ticket)) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
|
|
|
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double sl = PositionGetDouble(POSITION_SL);
|
|
double tp = PositionGetDouble(POSITION_TP);
|
|
|
|
double profitPts = 0;
|
|
if(type == POSITION_TYPE_BUY) profitPts = (bid - openPrice) / _Point;
|
|
if(type == POSITION_TYPE_SELL) profitPts = (openPrice - ask) / _Point;
|
|
|
|
if(profitPts < InpTrailStartPoints) continue;
|
|
|
|
if(type == POSITION_TYPE_BUY)
|
|
{
|
|
double newSL = bid - InpTrailDistancePoints * _Point;
|
|
if(sl == 0.0 || newSL > sl)
|
|
trade.PositionModify(_Symbol, newSL, tp);
|
|
}
|
|
else
|
|
{
|
|
double newSL = ask + InpTrailDistancePoints * _Point;
|
|
if(sl == 0.0 || newSL < sl)
|
|
trade.PositionModify(_Symbol, newSL, tp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//============================== MFIB ================================
|
|
bool ComputeMFIB(const int shift, const int lookback, double &atl, double &ath)
|
|
{
|
|
int last = MathMin(shift + lookback, ArraySize(rates)-1);
|
|
if(last <= shift) return false;
|
|
|
|
atl = rates[shift].low;
|
|
ath = rates[shift].high;
|
|
|
|
for(int k=shift; k<=last; k++)
|
|
{
|
|
if(rates[k].low < atl) atl = rates[k].low;
|
|
if(rates[k].high > ath) ath = rates[k].high;
|
|
}
|
|
return (ath > atl);
|
|
}
|
|
|
|
double FibLevel(const double atl, const double ath, const double r)
|
|
{
|
|
return atl + (ath - atl) * r;
|
|
}
|
|
|
|
int MFIBBiasState(const int shift, int &stackCount)
|
|
{
|
|
stackCount = 0;
|
|
double atl, ath;
|
|
if(!ComputeMFIB(shift, InpMFIBLookbackBars, atl, ath)) return 0;
|
|
|
|
double f032 = FibLevel(atl,ath,0.32);
|
|
double f050 = FibLevel(atl,ath,0.50);
|
|
double f0618= FibLevel(atl,ath,0.618);
|
|
double f0786= FibLevel(atl,ath,0.786);
|
|
|
|
double bp = rates[shift].close; // close-based for stability
|
|
|
|
if(bp > f032)
|
|
{
|
|
if(bp > f050) stackCount = 1;
|
|
if(bp > f0618) stackCount = 2;
|
|
if(bp > f0786) stackCount = 3;
|
|
return (stackCount>=1 ? +1 : 0);
|
|
}
|
|
if(bp < f032)
|
|
{
|
|
if(bp < f050) stackCount = 1;
|
|
if(bp < f0618) stackCount = 2;
|
|
if(bp < f0786) stackCount = 3;
|
|
return (stackCount>=1 ? -1 : 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool MFIBRejectHigh(const int shift, double &whichLevel)
|
|
{
|
|
whichLevel = 0.0;
|
|
double atl, ath;
|
|
if(!ComputeMFIB(shift, InpMFIBLookbackBars, atl, ath)) return false;
|
|
|
|
double f0618= FibLevel(atl,ath,0.618);
|
|
double f0786= FibLevel(atl,ath,0.786);
|
|
|
|
if(rates[shift].high >= f0786 && rates[shift].close < f0786) { whichLevel = 0.786; return true; }
|
|
if(rates[shift].high >= f0618 && rates[shift].close < f0618) { whichLevel = 0.618; return true; }
|
|
return false;
|
|
}
|
|
|
|
bool MFIBRejectLow(const int shift, double &whichLevel)
|
|
{
|
|
whichLevel = 0.0;
|
|
double atl, ath;
|
|
if(!ComputeMFIB(shift, InpMFIBLookbackBars, atl, ath)) return false;
|
|
|
|
double f0618= FibLevel(atl,ath,0.618);
|
|
double f0786= FibLevel(atl,ath,0.786);
|
|
|
|
if(rates[shift].low <= f0786 && rates[shift].close > f0786) { whichLevel = 0.786; return true; }
|
|
if(rates[shift].low <= f0618 && rates[shift].close > f0618) { whichLevel = 0.618; return true; }
|
|
return false;
|
|
}
|
|
|
|
//============================== TREND ===============================
|
|
bool CrossUp7_50(const int shift)
|
|
{
|
|
if(shift+1 >= ArraySize(ema7) || shift+1 >= ArraySize(ema50)) return false;
|
|
double prevDiff = ema7[shift+1] - ema50[shift+1];
|
|
double currDiff = ema7[shift] - ema50[shift];
|
|
return (prevDiff <= 0.0 && currDiff > 0.0);
|
|
}
|
|
bool CrossDn7_50(const int shift)
|
|
{
|
|
if(shift+1 >= ArraySize(ema7) || shift+1 >= ArraySize(ema50)) return false;
|
|
double prevDiff = ema7[shift+1] - ema50[shift+1];
|
|
double currDiff = ema7[shift] - ema50[shift];
|
|
return (prevDiff >= 0.0 && currDiff < 0.0);
|
|
}
|
|
|
|
bool CrossedWithin(const int shift, const int lookback, const bool bull)
|
|
{
|
|
int maxShift = MathMin(shift + lookback, ArraySize(ema7) - 2);
|
|
for(int k=shift; k<=maxShift; k++)
|
|
{
|
|
if(bull && CrossUp7_50(k)) return true;
|
|
if(!bull && CrossDn7_50(k)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool StackLong(const int shift) { return (ema7[shift] > ema21[shift] && ema21[shift] > ema50[shift]); }
|
|
bool StackShort(const int shift) { return (ema7[shift] < ema21[shift] && ema21[shift] < ema50[shift]); }
|
|
|
|
bool TouchReclaimMA_Long(const int shift, const double ma)
|
|
{
|
|
double tol = InpTouchTolPoints * _Point;
|
|
return (rates[shift].low <= ma + tol && rates[shift].close > ma);
|
|
}
|
|
bool TouchReclaimMA_Short(const int shift, const double ma)
|
|
{
|
|
double tol = InpTouchTolPoints * _Point;
|
|
return (rates[shift].high >= ma - tol && rates[shift].close < ma);
|
|
}
|
|
|
|
//============================== ORDERS ==============================
|
|
void PlaceBatch(const bool isBuy)
|
|
{
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double entry = isBuy ? ask : bid;
|
|
|
|
double sl = isBuy ? entry - InpSL_Points * _Point
|
|
: entry + InpSL_Points * _Point;
|
|
|
|
trade.SetDeviationInPoints(30);
|
|
|
|
for(int k=0; k<InpBatchOrders; k++)
|
|
{
|
|
bool ok = isBuy
|
|
? trade.Buy(InpLotsPerOrder, _Symbol, 0.0, sl, 0.0, "VIZION_BATCH")
|
|
: trade.Sell(InpLotsPerOrder, _Symbol, 0.0, sl, 0.0, "VIZION_BATCH");
|
|
|
|
if(!ok) Print("Order failed: ", GetLastError());
|
|
}
|
|
}
|
|
|
|
//============================== SETUP IDs ===========================
|
|
// Setup IDs prevent duplicate re-entries on the same confirmation.
|
|
enum SETUP_ID
|
|
{
|
|
SETUP_NONE = 0,
|
|
SETUP_ENTRY_14 = 1,
|
|
SETUP_REENTRY_50 = 2,
|
|
SETUP_REENTRY_140 = 3,
|
|
SETUP_REENTRY_230 = 4
|
|
};
|
|
|
|
//============================== INIT ================================
|
|
int OnInit()
|
|
{
|
|
h7 = iMA(_Symbol, InpTF, InpEMA7, 0, MODE_EMA, PRICE_CLOSE);
|
|
h14 = iMA(_Symbol, InpTF, InpEMA14, 0, MODE_EMA, PRICE_CLOSE);
|
|
h21 = iMA(_Symbol, InpTF, InpEMA21, 0, MODE_EMA, PRICE_CLOSE);
|
|
h50 = iMA(_Symbol, InpTF, InpEMA50, 0, MODE_EMA, PRICE_CLOSE);
|
|
h140 = iMA(_Symbol, InpTF, InpEMA140, 0, MODE_EMA, PRICE_CLOSE);
|
|
h230 = iMA(_Symbol, InpTF, InpEMA230, 0, MODE_EMA, PRICE_CLOSE);
|
|
h500 = iMA(_Symbol, InpTF, InpEMA500, 0, MODE_EMA, PRICE_CLOSE);
|
|
h1400 = iMA(_Symbol, InpTF, InpEMA1400, 0, MODE_EMA, PRICE_CLOSE);
|
|
|
|
if(h7<0 || h14<0 || h21<0 || h50<0 || h140<0 || h230<0 || h500<0 || h1400<0)
|
|
return(INIT_FAILED);
|
|
|
|
EnsureHUD();
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//============================== TICK ================================
|
|
void OnTick()
|
|
{
|
|
// always manage trailing
|
|
ApplyTrailing();
|
|
|
|
// Run on new bar only (keeps it fast + consistent)
|
|
datetime curBar = iTime(_Symbol, InpTF, 0);
|
|
if(curBar == g_lastBar) return;
|
|
g_lastBar = curBar;
|
|
|
|
// Pull enough bars
|
|
int need = MathMax(InpEMA1400 + 50, InpMFIBLookbackBars + 50);
|
|
need = MathMin(need, 6000);
|
|
|
|
ArrayResize(rates, need);
|
|
ArraySetAsSeries(rates, true);
|
|
|
|
int gotRates = CopyRates(_Symbol, InpTF, 0, need, rates);
|
|
if(gotRates < 500) return;
|
|
|
|
int rates_total = gotRates;
|
|
|
|
// Copy buffers safely (no out-of-range)
|
|
if(!SafeCopyBuffer(h7,0,0,rates_total,ema7)) return;
|
|
if(!SafeCopyBuffer(h14,0,0,rates_total,ema14)) return;
|
|
if(!SafeCopyBuffer(h21,0,0,rates_total,ema21)) return;
|
|
if(!SafeCopyBuffer(h50,0,0,rates_total,ema50)) return;
|
|
if(!SafeCopyBuffer(h140,0,0,rates_total,ema140)) return;
|
|
if(!SafeCopyBuffer(h230,0,0,rates_total,ema230)) return;
|
|
if(!SafeCopyBuffer(h500,0,0,rates_total,ema500)) return;
|
|
if(!SafeCopyBuffer(h1400,0,0,rates_total,ema1400)) return;
|
|
|
|
// Use last closed bar
|
|
int i = 1;
|
|
|
|
//========================
|
|
// 1) Reversal management
|
|
//========================
|
|
bool flipBull = CrossUp7_50(i);
|
|
bool flipBear = CrossDn7_50(i);
|
|
|
|
if(InpCloseOppOnTrendFlip)
|
|
{
|
|
if(flipBull && CountPositionsByType(POSITION_TYPE_SELL) > 0)
|
|
{
|
|
CloseAllType(POSITION_TYPE_SELL, "TREND FLIP BULL (7>50)");
|
|
g_batchesThisLeg = 0;
|
|
g_lastSetupId = SETUP_NONE;
|
|
g_lastDirection = 0;
|
|
}
|
|
if(flipBear && CountPositionsByType(POSITION_TYPE_BUY) > 0)
|
|
{
|
|
CloseAllType(POSITION_TYPE_BUY, "TREND FLIP BEAR (7<50)");
|
|
g_batchesThisLeg = 0;
|
|
g_lastSetupId = SETUP_NONE;
|
|
g_lastDirection = 0;
|
|
}
|
|
}
|
|
|
|
// MFIB rejection closes
|
|
double rej=0.0;
|
|
if(CountPositionsByType(POSITION_TYPE_BUY) > 0 && MFIBRejectHigh(i, rej))
|
|
{
|
|
CloseAllType(POSITION_TYPE_BUY, "MFIB REJECT HIGH @ "+DoubleToString(rej,3));
|
|
g_batchesThisLeg = 0; g_lastSetupId = SETUP_NONE; g_lastDirection = 0;
|
|
}
|
|
if(CountPositionsByType(POSITION_TYPE_SELL) > 0 && MFIBRejectLow(i, rej))
|
|
{
|
|
CloseAllType(POSITION_TYPE_SELL, "MFIB REJECT LOW @ "+DoubleToString(rej,3));
|
|
g_batchesThisLeg = 0; g_lastSetupId = SETUP_NONE; g_lastDirection = 0;
|
|
}
|
|
|
|
//========================
|
|
// 2) Trend permission
|
|
//========================
|
|
bool longPerm = CrossedWithin(i, InpCrossLookbackBars, true);
|
|
bool shortPerm = CrossedWithin(i, InpCrossLookbackBars, false);
|
|
|
|
if(InpRequireMicroStack)
|
|
{
|
|
longPerm = longPerm && StackLong(i);
|
|
shortPerm = shortPerm && StackShort(i);
|
|
}
|
|
|
|
// MFIB bias permission
|
|
int stackCount=0;
|
|
int mfibBias = MFIBBiasState(i, stackCount);
|
|
|
|
if(InpUseMFIBBiasFilter)
|
|
{
|
|
if(longPerm && !(mfibBias > 0 && stackCount >= 1)) longPerm = false;
|
|
if(shortPerm && !(mfibBias < 0 && stackCount >= 1)) shortPerm = false;
|
|
}
|
|
|
|
//========================
|
|
// 3) Aggressive re-entry gating
|
|
//========================
|
|
// time-based gate
|
|
if(g_lastEntryBarTime != 0)
|
|
{
|
|
int barsSince = 0;
|
|
// compute bars since last entry using bar times (simple)
|
|
for(int k=1; k<MathMin(500, gotRates); k++)
|
|
{
|
|
if(rates[k].time == g_lastEntryBarTime) { barsSince = k-1; break; }
|
|
}
|
|
if(barsSince < InpMinBarsBetweenEntries) { SetHUD("Cooldown: waiting bars"); return; }
|
|
if(InpReentryCooldownBars > 0 && barsSince < InpReentryCooldownBars) { SetHUD("Reentry cooldown"); return; }
|
|
}
|
|
|
|
// batch cap per leg
|
|
if(g_batchesThisLeg >= InpMaxBatchesPerTrendLeg)
|
|
{
|
|
SetHUD("Max batches reached for this trend leg");
|
|
return;
|
|
}
|
|
|
|
//========================
|
|
// 4) Setup detection (pick ONE best setup per bar)
|
|
//========================
|
|
int setupId = SETUP_NONE;
|
|
int dir = 0;
|
|
string setupName = "NONE";
|
|
|
|
// Primary entry on EMA14 reclaim after confirm
|
|
if(longPerm && InpEnterOn14Reclaim && TouchReclaimMA_Long(i, ema14[i]))
|
|
{
|
|
setupId = SETUP_ENTRY_14; dir = +1; setupName = "ENTRY_14_RECLAIM";
|
|
}
|
|
else if(shortPerm && InpEnterOn14Reclaim && TouchReclaimMA_Short(i, ema14[i]))
|
|
{
|
|
setupId = SETUP_ENTRY_14; dir = -1; setupName = "ENTRY_14_RECLAIM";
|
|
}
|
|
// Aggressive continuation re-entries (only if trend confirmed)
|
|
else if(InpAllowReentryOn50Reclaim && longPerm && TouchReclaimMA_Long(i, ema50[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_50; dir = +1; setupName = "REENTRY_50_RECLAIM";
|
|
}
|
|
else if(InpAllowReentryOn50Reclaim && shortPerm && TouchReclaimMA_Short(i, ema50[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_50; dir = -1; setupName = "REENTRY_50_RECLAIM";
|
|
}
|
|
else if(InpAllowReentryOn140Reclaim && longPerm && TouchReclaimMA_Long(i, ema140[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_140; dir = +1; setupName = "REENTRY_140_RECLAIM";
|
|
}
|
|
else if(InpAllowReentryOn140Reclaim && shortPerm && TouchReclaimMA_Short(i, ema140[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_140; dir = -1; setupName = "REENTRY_140_RECLAIM";
|
|
}
|
|
else if(InpAllowReentryOn230Reclaim && longPerm && TouchReclaimMA_Long(i, ema230[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_230; dir = +1; setupName = "REENTRY_230_RECLAIM";
|
|
}
|
|
else if(InpAllowReentryOn230Reclaim && shortPerm && TouchReclaimMA_Short(i, ema230[i]))
|
|
{
|
|
setupId = SETUP_REENTRY_230; dir = -1; setupName = "REENTRY_230_RECLAIM";
|
|
}
|
|
|
|
// Duplicate prevention: same setup twice in a row, same direction → skip
|
|
if(setupId != SETUP_NONE)
|
|
{
|
|
if(g_lastSetupId == setupId && g_lastDirection == dir)
|
|
{
|
|
SetHUD("Duplicate setup blocked: " + setupName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//========================
|
|
// 5) Execute
|
|
//========================
|
|
if(setupId != SETUP_NONE)
|
|
{
|
|
// If we already have opposite positions (should be rare due to flip close), clean them.
|
|
if(dir > 0 && CountPositionsByType(POSITION_TYPE_SELL) > 0 && InpCloseOppOnTrendFlip)
|
|
CloseAllType(POSITION_TYPE_SELL, "Opposite cleanup before LONG");
|
|
|
|
if(dir < 0 && CountPositionsByType(POSITION_TYPE_BUY) > 0 && InpCloseOppOnTrendFlip)
|
|
CloseAllType(POSITION_TYPE_BUY, "Opposite cleanup before SHORT");
|
|
|
|
PlaceBatch(dir > 0);
|
|
|
|
g_lastEntryBarTime = rates[i].time;
|
|
g_batchesThisLeg++;
|
|
g_lastSetupId = setupId;
|
|
g_lastDirection = dir;
|
|
}
|
|
|
|
//========================
|
|
// 6) HUD update
|
|
//========================
|
|
string biasTxt = (mfibBias>0 ? "MFIB_BULL" : (mfibBias<0 ? "MFIB_BEAR" : "MFIB_NEUTRAL"));
|
|
string permTxt = (longPerm ? "LONG_OK" : (shortPerm ? "SHORT_OK" : "NO_PERM"));
|
|
|
|
string hud =
|
|
"VIZION CASHCOW\n"
|
|
"TF: " + EnumToString(InpTF) + "\n"
|
|
"Perm: " + permTxt + "\n"
|
|
"MFIB: " + biasTxt + " | Stack:" + IntegerToString(stackCount) + "\n"
|
|
"Setup: " + setupName + "\n"
|
|
"BatchesThisLeg: " + IntegerToString(g_batchesThisLeg) + "/" + IntegerToString(InpMaxBatchesPerTrendLeg);
|
|
|
|
SetHUD(hud);
|
|
}
|
|
//+------------------------------------------------------------------+
|