379 lines
12 KiB
Plaintext
379 lines
12 KiB
Plaintext
#property strict
|
|
#property version "1.00"
|
|
|
|
#include <Trade/Trade.mqh>
|
|
|
|
input group "=== Common ==="
|
|
input string InpSymbol = "BTCUSD";
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
|
input int InpSlippagePoints = 30;
|
|
input int InpPivotLookbackBars = 120;
|
|
input int InpMinSwingPoints = 500;
|
|
|
|
input group "=== Robot 1: Fibonacci Retracement ==="
|
|
input bool FR_Enabled = true;
|
|
input int FR_Magic = 920101;
|
|
input double FR_Lots = 0.01;
|
|
input bool FR_BuyAt618 = true;
|
|
input bool FR_BuyAt500 = false;
|
|
input bool FR_UseHardSLTP = true;
|
|
input double FR_SL_BufferPoints = 400;
|
|
input double FR_TP_BufferPoints = 400;
|
|
input int FR_MaxHoldingBars = 96; // time-stop safety
|
|
input bool FR_CloseOnStructureBreak = true; // close if recent swing low breaks
|
|
|
|
input group "=== Robot 2: Fibonacci Trend Extension ==="
|
|
input bool FE_Enabled = true;
|
|
input int FE_Magic = 920202;
|
|
input double FE_Lots = 0.01;
|
|
input bool FE_UseHardSLTP = true;
|
|
input double FE_SL_BufferPoints = 400;
|
|
input double FE_ExtensionLevel = 1.272; // Common values: 1.272 / 1.618
|
|
input int FE_MinBarsBetweenTrades = 6;
|
|
input double FE_MinStopPoints = 3000;
|
|
input int FE_AtrPeriod = 14;
|
|
input double FE_MinStopAtrMult = 1.2;
|
|
input double FE_MinRR = 1.5;
|
|
|
|
CTrade trade;
|
|
datetime g_lastBarTime = 0;
|
|
datetime g_lastFEEntryTime = 0;
|
|
datetime g_lastFREntryTime = 0;
|
|
|
|
bool IsNewBar(const string symbol, ENUM_TIMEFRAMES tf)
|
|
{
|
|
datetime t = iTime(symbol, tf, 0);
|
|
if(t <= 0 || t == g_lastBarTime)
|
|
return false;
|
|
g_lastBarTime = t;
|
|
return true;
|
|
}
|
|
|
|
bool GetLowestLow(const string symbol, ENUM_TIMEFRAMES tf, const int bars, int &idx, double &price)
|
|
{
|
|
idx = iLowest(symbol, tf, MODE_LOW, bars, 1);
|
|
if(idx < 0)
|
|
return false;
|
|
price = iLow(symbol, tf, idx);
|
|
return (price > 0.0);
|
|
}
|
|
|
|
bool GetHighestHigh(const string symbol, ENUM_TIMEFRAMES tf, const int bars, int &idx, double &price)
|
|
{
|
|
idx = iHighest(symbol, tf, MODE_HIGH, bars, 1);
|
|
if(idx < 0)
|
|
return false;
|
|
price = iHigh(symbol, tf, idx);
|
|
return (price > 0.0);
|
|
}
|
|
|
|
double GetAtrPrice(const string symbol, ENUM_TIMEFRAMES tf, const int period)
|
|
{
|
|
int hAtr = iATR(symbol, tf, period);
|
|
if(hAtr == INVALID_HANDLE)
|
|
return 0.0;
|
|
double b[1];
|
|
if(CopyBuffer(hAtr, 0, 1, 1, b) <= 0)
|
|
{
|
|
IndicatorRelease(hAtr);
|
|
return 0.0;
|
|
}
|
|
IndicatorRelease(hAtr);
|
|
return b[0];
|
|
}
|
|
|
|
bool PositionExistsByMagic(const string symbol, const int magic)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
|
{
|
|
ulong t = PositionGetTicket(i);
|
|
if(t == 0)
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
|
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GetPositionByMagic(const string symbol, const int magic, ulong &ticket, ENUM_POSITION_TYPE &posType, datetime &openTime)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
|
{
|
|
ulong t = PositionGetTicket(i);
|
|
if(t == 0)
|
|
continue;
|
|
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
|
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
|
{
|
|
ticket = t;
|
|
posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
double NormalizePrice(const string symbol, const double price)
|
|
{
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
return NormalizeDouble(price, digits);
|
|
}
|
|
|
|
bool ValidateAndAdjustStops(const bool isBuy, double &sl, double &tp)
|
|
{
|
|
if(sl == 0.0 && tp == 0.0)
|
|
return true;
|
|
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(InpSymbol, tick))
|
|
return false;
|
|
|
|
int stopsLevelPts = (int)SymbolInfoInteger(InpSymbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
int freezeLevelPts = (int)SymbolInfoInteger(InpSymbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
|
double minDist = (double)MathMax(stopsLevelPts, freezeLevelPts) * _Point + 2.0 * _Point;
|
|
|
|
if(isBuy)
|
|
{
|
|
if(sl > 0.0 && sl >= tick.bid - minDist)
|
|
sl = tick.bid - minDist;
|
|
if(tp > 0.0 && tp <= tick.ask + minDist)
|
|
tp = tick.ask + minDist;
|
|
if(sl > 0.0 && sl >= tick.bid)
|
|
return false;
|
|
if(tp > 0.0 && tp <= tick.ask)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(sl > 0.0 && sl <= tick.ask + minDist)
|
|
sl = tick.ask + minDist;
|
|
if(tp > 0.0 && tp >= tick.bid - minDist)
|
|
tp = tick.bid - minDist;
|
|
if(sl > 0.0 && sl <= tick.ask)
|
|
return false;
|
|
if(tp > 0.0 && tp >= tick.bid)
|
|
return false;
|
|
}
|
|
|
|
if(sl > 0.0)
|
|
sl = NormalizePrice(InpSymbol, sl);
|
|
if(tp > 0.0)
|
|
tp = NormalizePrice(InpSymbol, tp);
|
|
return true;
|
|
}
|
|
|
|
bool OpenBuy(const int magic, const double lots, const string comment, const double sl, const double tp)
|
|
{
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(InpSymbol, tick))
|
|
return false;
|
|
double useSL = sl, useTP = tp;
|
|
if(!ValidateAndAdjustStops(true, useSL, useTP))
|
|
return false;
|
|
trade.SetExpertMagicNumber(magic);
|
|
bool ok = trade.Buy(lots, InpSymbol, tick.ask, useSL, useTP, comment);
|
|
if(ok && magic == FR_Magic)
|
|
g_lastFREntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
|
if(ok && magic == FE_Magic)
|
|
g_lastFEEntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
|
return ok;
|
|
}
|
|
|
|
bool OpenSell(const int magic, const double lots, const string comment, const double sl, const double tp)
|
|
{
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(InpSymbol, tick))
|
|
return false;
|
|
double useSL = sl, useTP = tp;
|
|
if(!ValidateAndAdjustStops(false, useSL, useTP))
|
|
return false;
|
|
trade.SetExpertMagicNumber(magic);
|
|
bool ok = trade.Sell(lots, InpSymbol, tick.bid, useSL, useTP, comment);
|
|
if(ok && magic == FE_Magic)
|
|
g_lastFEEntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
|
return ok;
|
|
}
|
|
|
|
void RunFibonacciRetracement()
|
|
{
|
|
if(!FR_Enabled)
|
|
return;
|
|
if(PositionExistsByMagic(InpSymbol, FR_Magic))
|
|
return;
|
|
|
|
int idxLow = -1, idxHigh = -1;
|
|
double swingLow = 0.0, swingHigh = 0.0;
|
|
if(!GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow))
|
|
return;
|
|
if(!GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
|
return;
|
|
|
|
double rangePts = (swingHigh - swingLow) / _Point;
|
|
if(rangePts < InpMinSwingPoints)
|
|
return;
|
|
|
|
// Uptrend retracement model: low appears before high.
|
|
bool upSwing = (idxLow > idxHigh);
|
|
if(!upSwing)
|
|
return;
|
|
|
|
double fib50 = swingHigh - (swingHigh - swingLow) * 0.500;
|
|
double fib61 = swingHigh - (swingHigh - swingLow) * 0.618;
|
|
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(InpSymbol, tick))
|
|
return;
|
|
|
|
double sl = 0.0, tp = 0.0;
|
|
if(FR_UseHardSLTP)
|
|
{
|
|
// Positional levels: SL below swing low, TP near prior swing high breakout.
|
|
sl = swingLow - FR_SL_BufferPoints * _Point;
|
|
tp = swingHigh + FR_TP_BufferPoints * _Point;
|
|
}
|
|
|
|
if(FR_BuyAt618 && tick.ask <= fib61)
|
|
OpenBuy(FR_Magic, FR_Lots, "FiboRetrace-61.8 Buy", sl, tp);
|
|
else if(FR_BuyAt500 && tick.ask <= fib50)
|
|
OpenBuy(FR_Magic, FR_Lots, "FiboRetrace-50.0 Buy", sl, tp);
|
|
}
|
|
|
|
void ManageFibonacciRetracementExit()
|
|
{
|
|
if(!FR_Enabled)
|
|
return;
|
|
|
|
ulong ticket = 0;
|
|
ENUM_POSITION_TYPE posType = WRONG_VALUE;
|
|
datetime openTime = 0;
|
|
if(!GetPositionByMagic(InpSymbol, FR_Magic, ticket, posType, openTime))
|
|
return;
|
|
|
|
int tfSec = PeriodSeconds(InpTimeframe);
|
|
if(tfSec <= 0)
|
|
tfSec = 60;
|
|
int barsHeld = (int)((iTime(InpSymbol, InpTimeframe, 0) - openTime) / tfSec);
|
|
|
|
// 1) Time stop: force close stale retracement trades.
|
|
if(FR_MaxHoldingBars > 0 && barsHeld >= FR_MaxHoldingBars)
|
|
{
|
|
trade.PositionClose(ticket);
|
|
return;
|
|
}
|
|
|
|
// 2) Structure invalidation: if latest swing violates the trade idea, exit.
|
|
if(FR_CloseOnStructureBreak)
|
|
{
|
|
int idxLow = -1, idxHigh = -1;
|
|
double swingLow = 0.0, swingHigh = 0.0;
|
|
if(GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow) &&
|
|
GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
|
{
|
|
MqlTick tick;
|
|
if(SymbolInfoTick(InpSymbol, tick))
|
|
{
|
|
double invalidateBuffer = FR_SL_BufferPoints * _Point;
|
|
if(posType == POSITION_TYPE_BUY && tick.bid < (swingLow - invalidateBuffer))
|
|
trade.PositionClose(ticket);
|
|
else if(posType == POSITION_TYPE_SELL && tick.ask > (swingHigh + invalidateBuffer))
|
|
trade.PositionClose(ticket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RunFibonacciExtension()
|
|
{
|
|
if(!FE_Enabled)
|
|
return;
|
|
if(PositionExistsByMagic(InpSymbol, FE_Magic))
|
|
return;
|
|
if(g_lastFEEntryTime > 0)
|
|
{
|
|
int tfSec = PeriodSeconds(InpTimeframe);
|
|
if(tfSec > 0)
|
|
{
|
|
int barsSince = (int)((iTime(InpSymbol, InpTimeframe, 0) - g_lastFEEntryTime) / tfSec);
|
|
if(barsSince < FE_MinBarsBetweenTrades)
|
|
return;
|
|
}
|
|
}
|
|
|
|
int idxLow = -1, idxHigh = -1;
|
|
double swingLow = 0.0, swingHigh = 0.0;
|
|
if(!GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow))
|
|
return;
|
|
if(!GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
|
return;
|
|
|
|
double rangePts = (swingHigh - swingLow) / _Point;
|
|
if(rangePts < InpMinSwingPoints)
|
|
return;
|
|
|
|
MqlTick tick;
|
|
if(!SymbolInfoTick(InpSymbol, tick))
|
|
return;
|
|
|
|
// Continuation breakout model:
|
|
// - If up swing (low before high), buy above swing high and target extension.
|
|
// - If down swing (high before low), sell below swing low and target extension.
|
|
bool upSwing = (idxLow > idxHigh);
|
|
|
|
if(upSwing && tick.ask > swingHigh)
|
|
{
|
|
double sl = 0.0, tp = 0.0;
|
|
if(FE_UseHardSLTP)
|
|
{
|
|
sl = swingHigh - FE_SL_BufferPoints * _Point;
|
|
double extTP = swingLow + (swingHigh - swingLow) * FE_ExtensionLevel;
|
|
double atr = GetAtrPrice(InpSymbol, InpTimeframe, FE_AtrPeriod);
|
|
double minRisk = MathMax(FE_MinStopPoints * _Point, atr * FE_MinStopAtrMult);
|
|
double risk = tick.ask - sl;
|
|
if(risk < minRisk)
|
|
return; // Skip fragile entries with overly tight stop.
|
|
double rrTP = tick.ask + risk * FE_MinRR;
|
|
tp = MathMax(extTP, rrTP);
|
|
}
|
|
OpenBuy(FE_Magic, FE_Lots, "FiboExtension Buy", sl, tp);
|
|
}
|
|
else if(!upSwing && tick.bid < swingLow)
|
|
{
|
|
double sl = 0.0, tp = 0.0;
|
|
if(FE_UseHardSLTP)
|
|
{
|
|
sl = swingLow + FE_SL_BufferPoints * _Point;
|
|
double extTP = swingHigh - (swingHigh - swingLow) * FE_ExtensionLevel;
|
|
double atr = GetAtrPrice(InpSymbol, InpTimeframe, FE_AtrPeriod);
|
|
double minRisk = MathMax(FE_MinStopPoints * _Point, atr * FE_MinStopAtrMult);
|
|
double risk = sl - tick.bid;
|
|
if(risk < minRisk)
|
|
return; // Skip fragile entries with overly tight stop.
|
|
double rrTP = tick.bid - risk * FE_MinRR;
|
|
tp = MathMin(extTP, rrTP);
|
|
}
|
|
OpenSell(FE_Magic, FE_Lots, "FiboExtension Sell", sl, tp);
|
|
}
|
|
}
|
|
|
|
int OnInit()
|
|
{
|
|
if(!SymbolSelect(InpSymbol, true))
|
|
return(INIT_FAILED);
|
|
trade.SetDeviationInPoints(InpSlippagePoints);
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
void OnTick()
|
|
{
|
|
if(_Symbol != InpSymbol)
|
|
return;
|
|
if(!IsNewBar(InpSymbol, InpTimeframe))
|
|
return;
|
|
|
|
ManageFibonacciRetracementExit();
|
|
RunFibonacciRetracement();
|
|
RunFibonacciExtension();
|
|
}
|