859 lines
31 KiB
Text
859 lines
31 KiB
Text
//+------------------------------------------------------------------+
|
|
//| GoldXAU_Scalper_Full.mq5 |
|
|
//| Gold-only scalper EA with multi-timeframe bias and trailing TP |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Assistant"
|
|
#property version "1.12"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
CTrade trade;
|
|
|
|
//---------------------- User inputs --------------------------------
|
|
input string TradeSymbol = "XAUUSD"; // symbol (change if your broker uses a different name)
|
|
input double FixedLotSize = 0.01; // fixed lot size used when UseFixedLot=true
|
|
input bool UseFixedLot = true; // always trade fixed lot (true) or dynamic sizing (false)
|
|
input double RiskPercent = 0.5; // if UseFixedLot=false -> use this % of balance as risk per trade
|
|
|
|
// timeframe and indicator params
|
|
input ENUM_TIMEFRAMES TF = PERIOD_M5; // entry TF
|
|
input int SMA_Fast = 5;
|
|
input int SMA_Slow = 10;
|
|
input int SMA_Trend = 50;
|
|
input int ATR_Period = 14;
|
|
input double ATR_Multiplier = 0.9; // SL = ATR * multiplier
|
|
input double RewardRatio = 1.2; // TP = SL * RewardRatio
|
|
|
|
// scalper vs normal
|
|
input bool ScalperMode = false; // if true use smaller TP/SL multipliers for quick scalps
|
|
input double Scalper_RR = 0.9; // R:R for scalper (tp = rr * sl)
|
|
input int Scalper_MaxTrades = 2; // allow more small trades if scalper
|
|
|
|
// Trailing & rally behavior
|
|
input bool UseTrailingTP = true;
|
|
input double TrailingStartATRMult = 1.0; // start trailing once price is this * ATR in profit
|
|
input double TrailingPercent = 1.0; // percent behind market to place trailing TP (1.0 means TP = price*(1 - 0.01) for longs)
|
|
input double TrailingStepPoints = 0.5; // minimum price change to update TP (in price units)
|
|
|
|
// multi-timeframe bias
|
|
input bool UseMultiTFBias = true;
|
|
input ENUM_TIMEFRAMES BiasTF1 = PERIOD_H1;
|
|
input ENUM_TIMEFRAMES BiasTF2 = PERIOD_H4;
|
|
input ENUM_TIMEFRAMES BiasTF3 = PERIOD_D1;
|
|
input int BiasSMA_Period = 50; // SMA for bias check (on each bias TF)
|
|
input int BiasRequiredMajority = 2; // how many TFs must agree to consider bullish/bearish
|
|
|
|
// trade direction
|
|
enum ENUM_TRADE_DIR {DIR_BOTH=0, DIR_LONG_ONLY=1, DIR_SHORT_ONLY=2};
|
|
input ENUM_TRADE_DIR AllowedDirection = DIR_BOTH;
|
|
|
|
// safety
|
|
input int MagicNumber = 20251023;
|
|
input int MaxOpenTrades = 3;
|
|
input double MinFreeMarginBufferUSD = 5.0; // require at least this free margin (USD) to open trades
|
|
|
|
// misc
|
|
input int SlippagePoints = 200; // allowed slippage in points for order send
|
|
|
|
// NEW: Enhanced Risk Management
|
|
input double SpreadBufferMultiplier = 0.8; // add this * spread to SL for buffer
|
|
input double SlippageBufferPoints = 2.0; // add this many points to SL for slippage buffer
|
|
input int MaxDailyTrades = 5; // maximum trades per day
|
|
input int MinCooldownMinutes = 30; // minimum minutes between trades
|
|
input bool UseTimeFilters = true; // skip trading during news/gaps
|
|
input int SkipStartHour = 22; // skip trading from this hour (GMT)
|
|
input int SkipEndHour = 2; // until this hour (GMT)
|
|
input bool UseBreakEven = true; // move SL to breakeven at +0.5R profit
|
|
input double BreakEvenRMultiple = 0.5; // move SL to BE at this R profit
|
|
input bool UsePartialTP = false; // close half position at smaller TP
|
|
input double PartialTPRMultiple = 0.6; // close half at this R profit
|
|
input double TrailingATRMultiple = 0.25; // trail TP at this ATR distance
|
|
|
|
//---------------------- globals ------------------------------------
|
|
double pointSz = 0.0;
|
|
int symbolDigits = 0;
|
|
|
|
// NEW: Enhanced tracking variables
|
|
datetime lastTradeTime = 0;
|
|
int dailyTradeCount = 0;
|
|
datetime lastDailyReset = 0;
|
|
|
|
//---------------------- helper declarations -------------------------
|
|
double GetATR(const string symbol, ENUM_TIMEFRAMES tf, int period, int shift);
|
|
double GetSMA(const string symbol, ENUM_TIMEFRAMES tf, int period, int shift);
|
|
int CountMyPositions(const string symbol);
|
|
void PrintSymbolInfo(const string symbol);
|
|
bool IsMarketBiasBullish();
|
|
bool IsMarketBiasBearish();
|
|
double CalcLotByRisk_USD(const string symbol, double riskUSD, double entryPrice, double slPrice);
|
|
double NormalizeLotToStep(const string symbol, double lot);
|
|
bool CanOpenMoreTrades();
|
|
void ManageTrailing();
|
|
ulong OpenBuy(double lot, double sl, double tp);
|
|
ulong OpenSell(double lot, double sl, double tp);
|
|
double GetPriceAsk(const string symbol);
|
|
double GetPriceBid(const string symbol);
|
|
|
|
// NEW: Enhanced helper functions
|
|
bool IsTimeToTrade();
|
|
bool CanTradeNow();
|
|
double CalculateSpreadBuffer();
|
|
double CalculateSlippageBuffer();
|
|
void ManageBreakEven();
|
|
void ManagePartialTP();
|
|
void ResetDailyCounter();
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// ensure symbol exists and select it
|
|
if(!SymbolSelect(TradeSymbol,true))
|
|
{
|
|
Print("ERROR: cannot select symbol ", TradeSymbol);
|
|
return(INIT_FAILED);
|
|
}
|
|
pointSz = SymbolInfoDouble(TradeSymbol,SYMBOL_POINT);
|
|
symbolDigits = (int)SymbolInfoInteger(TradeSymbol,SYMBOL_DIGITS);
|
|
if(pointSz<=0)
|
|
pointSz = _Point;
|
|
PrintFormat("EA init symbol=%s point=%g symbolDigits=%d", TradeSymbol, pointSz, symbolDigits);
|
|
// print symbol trade properties for verification
|
|
PrintSymbolInfo(TradeSymbol);
|
|
|
|
// safety: ensure AllowedDirection valid (remove modification of input parameter)
|
|
// AllowedDirection is an input parameter and cannot be modified
|
|
if(AllowedDirection<DIR_BOTH || AllowedDirection>DIR_SHORT_ONLY)
|
|
{
|
|
Print("WARNING: Invalid AllowedDirection value. Using DIR_BOTH as default.");
|
|
}
|
|
|
|
Print("XAU Scalp EA initialized. Magic=", MagicNumber);
|
|
|
|
// Initialize enhanced tracking
|
|
lastTradeTime = 0;
|
|
dailyTradeCount = 0;
|
|
lastDailyReset = TimeCurrent();
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinit |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
Print("EA deinitialized, reason=", reason);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| OnTick |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Reset daily counter if new day
|
|
ResetDailyCounter();
|
|
|
|
// Check if we can trade now (time filters, cooldown, daily limits)
|
|
if(!CanTradeNow())
|
|
return;
|
|
|
|
// Ensure we have enough market data
|
|
MqlRates rates[];
|
|
int copied = CopyRates(TradeSymbol,TF,0,3,rates); // need only last 2-3 bars
|
|
if(copied<3)
|
|
return;
|
|
|
|
// Calculate indicators on closed bar (shift=1)
|
|
double smaFastPrev = GetSMA(TradeSymbol,TF,SMA_Fast,1);
|
|
double smaSlowPrev = GetSMA(TradeSymbol,TF,SMA_Slow,1);
|
|
double smaTrendPrev = GetSMA(TradeSymbol,TF,SMA_Trend,1);
|
|
double atrPrev = GetATR(TradeSymbol,TF,ATR_Period,1);
|
|
|
|
// Validate indicators
|
|
if(smaFastPrev <= 0 || smaSlowPrev <= 0 || smaTrendPrev <= 0 || atrPrev <= 0)
|
|
{
|
|
Print("Invalid indicator values - skipping tick");
|
|
return;
|
|
}
|
|
|
|
// entry conditions
|
|
bool trendUp = (iClose(TradeSymbol,TF,1) > smaTrendPrev);
|
|
bool trendDown = (iClose(TradeSymbol,TF,1) < smaTrendPrev);
|
|
|
|
// detect cross on closed bar
|
|
double smaFastPrev2 = GetSMA(TradeSymbol,TF,SMA_Fast,2);
|
|
double smaSlowPrev2 = GetSMA(TradeSymbol,TF,SMA_Slow,2);
|
|
bool crossUp = (smaFastPrev > smaSlowPrev) && (smaFastPrev2 <= smaSlowPrev2);
|
|
bool crossDown = (smaFastPrev < smaSlowPrev) && (smaFastPrev2 >= smaSlowPrev2);
|
|
|
|
// multi TF bias check
|
|
bool biasBullish = true, biasBearish = true;
|
|
if(UseMultiTFBias)
|
|
{
|
|
biasBullish = IsMarketBiasBullish(); // majority H1/H4/D1 bullish?
|
|
biasBearish = IsMarketBiasBearish();
|
|
}
|
|
|
|
// Count my EA positions on symbol
|
|
int myPositions = CountMyPositions(TradeSymbol);
|
|
|
|
// trailing modify for existing positions
|
|
if(myPositions>0 && UseTrailingTP)
|
|
ManageTrailing();
|
|
|
|
// NEW: Manage break-even and partial TP
|
|
if(myPositions>0)
|
|
{
|
|
if(UseBreakEven)
|
|
ManageBreakEven();
|
|
if(UsePartialTP)
|
|
ManagePartialTP();
|
|
}
|
|
|
|
// STOP: limit trades
|
|
if(!CanOpenMoreTrades())
|
|
return;
|
|
|
|
// Decide to open long?
|
|
bool wantLong = false, wantShort = false;
|
|
|
|
if(AllowedDirection!=DIR_SHORT_ONLY)
|
|
{
|
|
// only consider long side if allowed
|
|
if(trendUp && crossUp)
|
|
{
|
|
if(!UseMultiTFBias || biasBullish)
|
|
wantLong = true;
|
|
}
|
|
}
|
|
|
|
if(AllowedDirection!=DIR_LONG_ONLY)
|
|
{
|
|
// consider short side if allowed
|
|
if(trendDown && crossDown)
|
|
{
|
|
if(!UseMultiTFBias || biasBearish)
|
|
wantShort = true;
|
|
}
|
|
}
|
|
|
|
// Prepare order params if needed
|
|
if(wantLong || wantShort)
|
|
{
|
|
double ask = GetPriceAsk(TradeSymbol);
|
|
double bid = GetPriceBid(TradeSymbol);
|
|
if(ask<=0 || bid<=0)
|
|
return;
|
|
|
|
double slPrice=0, tpPrice=0;
|
|
double lot = FixedLotSize;
|
|
if(!UseFixedLot)
|
|
{
|
|
// calculate lot by risk (risk = RiskPercent% of balance)
|
|
double balanceNow = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskUSD = balanceNow * (RiskPercent/100.0);
|
|
// estimate SL using ATR
|
|
double slAmount = atrPrev * ATR_Multiplier;
|
|
if(ScalperMode)
|
|
slAmount = atrPrev * (ATR_Multiplier*0.7);
|
|
if(wantLong)
|
|
slPrice = ask - slAmount;
|
|
else
|
|
slPrice = bid + slAmount;
|
|
lot = CalcLotByRisk_USD(TradeSymbol, riskUSD, (wantLong?ask:bid), slPrice);
|
|
lot = NormalizeLotToStep(TradeSymbol, lot);
|
|
}
|
|
else
|
|
{
|
|
// Use fixed lot, but ensure normalized to broker step
|
|
lot = NormalizeLotToStep(TradeSymbol, FixedLotSize);
|
|
}
|
|
|
|
// final safety lot clamp
|
|
double minLot = SymbolInfoDouble(TradeSymbol,SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(TradeSymbol,SYMBOL_VOLUME_MAX);
|
|
if(minLot==0)
|
|
minLot = 0.01;
|
|
if(maxLot==0)
|
|
maxLot = 100;
|
|
if(lot < minLot)
|
|
lot = minLot;
|
|
if(lot > maxLot)
|
|
lot = maxLot;
|
|
|
|
// compute TP & SL if not already set
|
|
if(!UseFixedLot) { /* slPrice already computed */ }
|
|
else
|
|
{
|
|
// compute using ATR for fixed-lot mode
|
|
double slAmount = atrPrev * ATR_Multiplier;
|
|
if(ScalperMode)
|
|
slAmount = atrPrev * (ATR_Multiplier*0.7);
|
|
|
|
// NEW: Add spread and slippage buffers
|
|
double spreadBuffer = CalculateSpreadBuffer();
|
|
double slippageBuffer = CalculateSlippageBuffer();
|
|
slAmount += spreadBuffer + slippageBuffer;
|
|
|
|
if(wantLong)
|
|
slPrice = ask - slAmount;
|
|
else
|
|
slPrice = bid + slAmount;
|
|
}
|
|
|
|
double rr = RewardRatio;
|
|
if(ScalperMode)
|
|
rr = Scalper_RR;
|
|
if(wantLong)
|
|
{
|
|
tpPrice = ask + (ask - slPrice) * rr;
|
|
}
|
|
else
|
|
{
|
|
tpPrice = bid - (slPrice - bid) * rr;
|
|
}
|
|
|
|
// Validate SL/TP distances
|
|
if(wantLong && (slPrice >= ask || tpPrice <= ask))
|
|
{
|
|
Print("Invalid SL/TP for long trade");
|
|
return;
|
|
}
|
|
if(wantShort && (slPrice <= bid || tpPrice >= bid))
|
|
{
|
|
Print("Invalid SL/TP for short trade");
|
|
return;
|
|
}
|
|
|
|
// final safety checks: free margin
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
if(freeMargin < MinFreeMarginBufferUSD)
|
|
{
|
|
Print("Not enough free margin to open trades. FreeMargin=", freeMargin);
|
|
return;
|
|
}
|
|
|
|
// Open the trade(s)
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
trade.SetDeviationInPoints(SlippagePoints);
|
|
|
|
if(wantLong)
|
|
{
|
|
ulong ticket = OpenBuy(lot, slPrice, tpPrice);
|
|
if(ticket>0)
|
|
{
|
|
PrintFormat("Opened BUY ticket=%I64u lot=%.2f sl=%.3f tp=%.3f", ticket, lot, slPrice, tpPrice);
|
|
lastTradeTime = TimeCurrent();
|
|
dailyTradeCount++;
|
|
}
|
|
else
|
|
PrintFormat("Buy open failed, err=%d", GetLastError());
|
|
}
|
|
if(wantShort)
|
|
{
|
|
ulong ticket = OpenSell(lot, slPrice, tpPrice);
|
|
if(ticket>0)
|
|
{
|
|
PrintFormat("Opened SELL ticket=%I64u lot=%.2f sl=%.3f tp=%.3f", ticket, lot, slPrice, tpPrice);
|
|
lastTradeTime = TimeCurrent();
|
|
dailyTradeCount++;
|
|
}
|
|
else
|
|
PrintFormat("Sell open failed, err=%d", GetLastError());
|
|
}
|
|
} // end wantLong/wantShort
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Helpers: indicator wrappers |
|
|
//+------------------------------------------------------------------+
|
|
double GetATR(const string symbol, ENUM_TIMEFRAMES tf, int period, int shift)
|
|
{
|
|
int handle = iATR(symbol, tf, period);
|
|
if(handle == INVALID_HANDLE)
|
|
return(0);
|
|
|
|
double buffer[];
|
|
if(CopyBuffer(handle, 0, shift, 1, buffer) <= 0)
|
|
return(0);
|
|
return(buffer[0]);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double GetSMA(const string symbol, ENUM_TIMEFRAMES tf, int period, int shift)
|
|
{
|
|
int handle = iMA(symbol, tf, period, 0, MODE_SMA, PRICE_CLOSE);
|
|
if(handle == INVALID_HANDLE)
|
|
return(0);
|
|
|
|
double buffer[];
|
|
if(CopyBuffer(handle, 0, shift, 1, buffer) <= 0)
|
|
return(0);
|
|
return(buffer[0]);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Count positions opened by this EA |
|
|
//+------------------------------------------------------------------+
|
|
int CountMyPositions(const string symbol)
|
|
{
|
|
int count = 0;
|
|
for(int i=0; i<PositionsTotal(); i++)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
string posSymbol = PositionGetString(POSITION_SYMBOL);
|
|
int posMagic = (int)PositionGetInteger(POSITION_MAGIC);
|
|
if(posSymbol == symbol && posMagic == MagicNumber)
|
|
count++;
|
|
}
|
|
}
|
|
return(count);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Print symbol information for debugging |
|
|
//+------------------------------------------------------------------+
|
|
void PrintSymbolInfo(const string symbol)
|
|
{
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
PrintFormat("Symbol %s: MinLot=%.2f MaxLot=%.2f Step=%.2f TickValue=%.2f TickSize=%.5f",
|
|
symbol, minLot, maxLot, stepLot, tickValue, tickSize);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if market bias is bullish across multiple timeframes |
|
|
//+------------------------------------------------------------------+
|
|
bool IsMarketBiasBullish()
|
|
{
|
|
int bullishCount = 0;
|
|
|
|
// Check H1 bias
|
|
double h1SMA = GetSMA(TradeSymbol, BiasTF1, BiasSMA_Period, 1);
|
|
double h1Close = iClose(TradeSymbol, BiasTF1, 1);
|
|
if(h1Close > h1SMA)
|
|
bullishCount++;
|
|
|
|
// Check H4 bias
|
|
double h4SMA = GetSMA(TradeSymbol, BiasTF2, BiasSMA_Period, 1);
|
|
double h4Close = iClose(TradeSymbol, BiasTF2, 1);
|
|
if(h4Close > h4SMA)
|
|
bullishCount++;
|
|
|
|
// Check D1 bias
|
|
double d1SMA = GetSMA(TradeSymbol, BiasTF3, BiasSMA_Period, 1);
|
|
double d1Close = iClose(TradeSymbol, BiasTF3, 1);
|
|
if(d1Close > d1SMA)
|
|
bullishCount++;
|
|
|
|
return(bullishCount >= BiasRequiredMajority);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if market bias is bearish across multiple timeframes |
|
|
//+------------------------------------------------------------------+
|
|
bool IsMarketBiasBearish()
|
|
{
|
|
int bearishCount = 0;
|
|
|
|
// Check H1 bias
|
|
double h1SMA = GetSMA(TradeSymbol, BiasTF1, BiasSMA_Period, 1);
|
|
double h1Close = iClose(TradeSymbol, BiasTF1, 1);
|
|
if(h1Close < h1SMA)
|
|
bearishCount++;
|
|
|
|
// Check H4 bias
|
|
double h4SMA = GetSMA(TradeSymbol, BiasTF2, BiasSMA_Period, 1);
|
|
double h4Close = iClose(TradeSymbol, BiasTF2, 1);
|
|
if(h4Close < h4SMA)
|
|
bearishCount++;
|
|
|
|
// Check D1 bias
|
|
double d1SMA = GetSMA(TradeSymbol, BiasTF3, BiasSMA_Period, 1);
|
|
double d1Close = iClose(TradeSymbol, BiasTF3, 1);
|
|
if(d1Close < d1SMA)
|
|
bearishCount++;
|
|
|
|
return(bearishCount >= BiasRequiredMajority);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate lot size based on risk in USD |
|
|
//+------------------------------------------------------------------+
|
|
double CalcLotByRisk_USD(const string symbol, double riskUSD, double entryPrice, double slPrice)
|
|
{
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
if(tickValue <= 0 || tickSize <= 0)
|
|
{
|
|
Print("Invalid tick value/size for ", symbol);
|
|
return(FixedLotSize);
|
|
}
|
|
|
|
double slDistance = MathAbs(entryPrice - slPrice);
|
|
double slInTicks = slDistance / tickSize;
|
|
double lossPerLot = slInTicks * tickValue;
|
|
|
|
if(lossPerLot <= 0)
|
|
{
|
|
Print("Invalid loss per lot calculation");
|
|
return(FixedLotSize);
|
|
}
|
|
|
|
double lot = riskUSD / lossPerLot;
|
|
return(lot);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize lot size to broker step |
|
|
//+------------------------------------------------------------------+
|
|
double NormalizeLotToStep(const string symbol, double lot)
|
|
{
|
|
double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
if(step <= 0)
|
|
step = 0.01;
|
|
|
|
double normalized = MathFloor(lot / step) * step;
|
|
return(normalized);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if we can open more trades |
|
|
//+------------------------------------------------------------------+
|
|
bool CanOpenMoreTrades()
|
|
{
|
|
int currentTrades = CountMyPositions(TradeSymbol);
|
|
int maxTrades = ScalperMode ? Scalper_MaxTrades : MaxOpenTrades;
|
|
|
|
if(currentTrades >= maxTrades)
|
|
{
|
|
PrintFormat("Max trades reached: %d/%d", currentTrades, maxTrades);
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage trailing take profit for open positions |
|
|
//+------------------------------------------------------------------+
|
|
void ManageTrailing()
|
|
{
|
|
for(int i=0; i<PositionsTotal(); i++)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
string posSymbol = PositionGetString(POSITION_SYMBOL);
|
|
int posMagic = (int)PositionGetInteger(POSITION_MAGIC);
|
|
|
|
if(posSymbol == TradeSymbol && posMagic == MagicNumber)
|
|
{
|
|
double posPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double posTP = PositionGetDouble(POSITION_TP);
|
|
double posSL = PositionGetDouble(POSITION_SL);
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double currentPrice = (posType == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
|
|
|
|
double atr = GetATR(TradeSymbol, TF, ATR_Period, 1);
|
|
double trailingStart = atr * TrailingStartATRMult;
|
|
|
|
double profit = 0;
|
|
if(posType == POSITION_TYPE_BUY)
|
|
profit = currentPrice - posPrice;
|
|
else
|
|
profit = posPrice - currentPrice;
|
|
|
|
if(profit >= trailingStart)
|
|
{
|
|
double newTP = 0;
|
|
double trailingDistance = atr * TrailingATRMultiple;
|
|
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
// NEW: ATR-based trailing instead of percentage
|
|
newTP = currentPrice - trailingDistance;
|
|
if(newTP > posTP + TrailingStepPoints)
|
|
{
|
|
trade.PositionModify(ticket, posSL, NormalizeDouble(newTP, symbolDigits));
|
|
PrintFormat("Trailing TP updated for BUY ticket %I64u: %.3f -> %.3f (ATR-based)", ticket, posTP, newTP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NEW: ATR-based trailing instead of percentage
|
|
newTP = currentPrice + trailingDistance;
|
|
if(newTP < posTP - TrailingStepPoints)
|
|
{
|
|
trade.PositionModify(ticket, posSL, NormalizeDouble(newTP, symbolDigits));
|
|
PrintFormat("Trailing TP updated for SELL ticket %I64u: %.3f -> %.3f (ATR-based)", ticket, posTP, newTP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open buy order |
|
|
//+------------------------------------------------------------------+
|
|
ulong OpenBuy(double lot, double sl, double tp)
|
|
{
|
|
double ask = SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
|
|
if(ask <= 0)
|
|
return(0);
|
|
|
|
bool result = trade.Buy(lot, TradeSymbol, ask,
|
|
NormalizeDouble(sl, symbolDigits),
|
|
NormalizeDouble(tp, symbolDigits),
|
|
"XAU Scalper Buy");
|
|
|
|
return(result ? trade.ResultOrder() : 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open sell order |
|
|
//+------------------------------------------------------------------+
|
|
ulong OpenSell(double lot, double sl, double tp)
|
|
{
|
|
double bid = SymbolInfoDouble(TradeSymbol, SYMBOL_BID);
|
|
if(bid <= 0)
|
|
return(0);
|
|
|
|
bool result = trade.Sell(lot, TradeSymbol, bid,
|
|
NormalizeDouble(sl, symbolDigits),
|
|
NormalizeDouble(tp, symbolDigits),
|
|
"XAU Scalper Sell");
|
|
|
|
return(result ? trade.ResultOrder() : 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current ask price |
|
|
//+------------------------------------------------------------------+
|
|
double GetPriceAsk(const string symbol)
|
|
{
|
|
return(SymbolInfoDouble(symbol, SYMBOL_ASK));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current bid price |
|
|
//+------------------------------------------------------------------+
|
|
double GetPriceBid(const string symbol)
|
|
{
|
|
return(SymbolInfoDouble(symbol, SYMBOL_BID));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Check if it's time to trade (time filters) |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTimeToTrade()
|
|
{
|
|
if(!UseTimeFilters)
|
|
return(true);
|
|
|
|
MqlDateTime dt;
|
|
TimeToStruct(TimeCurrent(), dt);
|
|
|
|
// Skip trading during specified hours (e.g., 22:00-02:00 GMT for gaps)
|
|
if(SkipStartHour > SkipEndHour)
|
|
{
|
|
// Cross midnight (e.g., 22:00 to 02:00)
|
|
if(dt.hour >= SkipStartHour || dt.hour < SkipEndHour)
|
|
return(false);
|
|
}
|
|
else
|
|
{
|
|
// Same day (e.g., 10:00 to 15:00)
|
|
if(dt.hour >= SkipStartHour && dt.hour < SkipEndHour)
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Check if we can trade now (all conditions) |
|
|
//+------------------------------------------------------------------+
|
|
bool CanTradeNow()
|
|
{
|
|
// Check time filters
|
|
if(!IsTimeToTrade())
|
|
{
|
|
Print("Skipping trade - outside trading hours");
|
|
return(false);
|
|
}
|
|
|
|
// Check daily trade limit
|
|
if(dailyTradeCount >= MaxDailyTrades)
|
|
{
|
|
PrintFormat("Daily trade limit reached: %d/%d", dailyTradeCount, MaxDailyTrades);
|
|
return(false);
|
|
}
|
|
|
|
// Check cooldown period
|
|
if(lastTradeTime > 0)
|
|
{
|
|
int minutesSinceLastTrade = (int)((TimeCurrent() - lastTradeTime) / 60);
|
|
if(minutesSinceLastTrade < MinCooldownMinutes)
|
|
{
|
|
PrintFormat("Cooldown period active: %d/%d minutes", minutesSinceLastTrade, MinCooldownMinutes);
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Calculate spread buffer for SL |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateSpreadBuffer()
|
|
{
|
|
double ask = SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
|
|
double bid = SymbolInfoDouble(TradeSymbol, SYMBOL_BID);
|
|
double spread = ask - bid;
|
|
|
|
return(spread * SpreadBufferMultiplier);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Calculate slippage buffer for SL |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateSlippageBuffer()
|
|
{
|
|
return(SlippageBufferPoints * pointSz);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Manage break-even functionality |
|
|
//+------------------------------------------------------------------+
|
|
void ManageBreakEven()
|
|
{
|
|
for(int i=0; i<PositionsTotal(); i++)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
string posSymbol = PositionGetString(POSITION_SYMBOL);
|
|
int posMagic = (int)PositionGetInteger(POSITION_MAGIC);
|
|
|
|
if(posSymbol == TradeSymbol && posMagic == MagicNumber)
|
|
{
|
|
double posPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double posSL = PositionGetDouble(POSITION_SL);
|
|
double posTP = PositionGetDouble(POSITION_TP);
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double currentPrice = (posType == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
|
|
|
|
double profit = 0;
|
|
if(posType == POSITION_TYPE_BUY)
|
|
profit = currentPrice - posPrice;
|
|
else
|
|
profit = posPrice - currentPrice;
|
|
|
|
// Calculate R multiple
|
|
double slDistance = MathAbs(posPrice - posSL);
|
|
if(slDistance > 0)
|
|
{
|
|
double rMultiple = profit / slDistance;
|
|
|
|
// Move SL to breakeven + small buffer
|
|
if(rMultiple >= BreakEvenRMultiple && posSL != posPrice)
|
|
{
|
|
double newSL = posPrice + (posType == POSITION_TYPE_BUY ? pointSz * 2 : -pointSz * 2);
|
|
trade.PositionModify(ticket, NormalizeDouble(newSL, symbolDigits), posTP);
|
|
PrintFormat("Moved SL to breakeven for ticket %I64u: %.3f -> %.3f", ticket, posSL, newSL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Manage partial take profit |
|
|
//+------------------------------------------------------------------+
|
|
void ManagePartialTP()
|
|
{
|
|
for(int i=0; i<PositionsTotal(); i++)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
string posSymbol = PositionGetString(POSITION_SYMBOL);
|
|
int posMagic = (int)PositionGetInteger(POSITION_MAGIC);
|
|
|
|
if(posSymbol == TradeSymbol && posMagic == MagicNumber)
|
|
{
|
|
double posPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double posSL = PositionGetDouble(POSITION_SL);
|
|
double posVolume = PositionGetDouble(POSITION_VOLUME);
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
double currentPrice = (posType == POSITION_TYPE_BUY) ?
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
|
|
|
|
double profit = 0;
|
|
if(posType == POSITION_TYPE_BUY)
|
|
profit = currentPrice - posPrice;
|
|
else
|
|
profit = posPrice - currentPrice;
|
|
|
|
// Calculate R multiple
|
|
double slDistance = MathAbs(posPrice - posSL);
|
|
if(slDistance > 0)
|
|
{
|
|
double rMultiple = profit / slDistance;
|
|
|
|
// Close half position at partial TP
|
|
if(rMultiple >= PartialTPRMultiple && posVolume > 0.02) // Only if volume > 0.02
|
|
{
|
|
double closeVolume = posVolume * 0.5; // Close half
|
|
if(trade.PositionClosePartial(ticket, closeVolume))
|
|
{
|
|
PrintFormat("Partial TP executed for ticket %I64u: closed %.2f lots at %.3f", ticket, closeVolume, currentPrice);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NEW: Reset daily trade counter |
|
|
//+------------------------------------------------------------------+
|
|
void ResetDailyCounter()
|
|
{
|
|
MqlDateTime currentDT, lastDT;
|
|
TimeToStruct(TimeCurrent(), currentDT);
|
|
TimeToStruct(lastDailyReset, lastDT);
|
|
|
|
// If new day, reset counter
|
|
if(currentDT.day != lastDT.day || currentDT.mon != lastDT.mon || currentDT.year != lastDT.year)
|
|
{
|
|
dailyTradeCount = 0;
|
|
lastDailyReset = TimeCurrent();
|
|
Print("Daily trade counter reset - new day");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|