581 lines
23 KiB
Plaintext
581 lines
23 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| GoldScalperPro.mq5 |
|
|
//| |
|
|
//| A dedicated XAUUSD (gold) scalping Expert Advisor. |
|
|
//| |
|
|
//| Strategy (trend-filtered momentum pullback) |
|
|
//| ------------------------------------------ |
|
|
//| 1. A higher/slower EMA defines the prevailing trend, so the |
|
|
//| EA only ever trades WITH the dominant direction. |
|
|
//| 2. Inside that trend it waits for a short pullback: price |
|
|
//| dips back to the fast EMA and RSI leaves an oversold |
|
|
//| (long) / overbought (short) extreme - i.e. it buys dips in |
|
|
//| an uptrend and sells rallies in a downtrend. |
|
|
//| 3. An ATR filter makes sure there is enough volatility to pay |
|
|
//| for the spread, and an ATR-based stop/target adapts the |
|
|
//| trade size to current gold volatility. |
|
|
//| 4. Position size is derived from a fixed % risk of equity, so |
|
|
//| a small account never over-leverages on a single trade. |
|
|
//| 5. Hard daily-loss and daily-profit circuit breakers, a max |
|
|
//| trades-per-day cap, a spread guard and a trading-session |
|
|
//| window keep the scalper out of bad conditions. |
|
|
//| |
|
|
//| This EA is completely independent of any other strategy and |
|
|
//| manages only its own orders (identified by the magic number). |
|
|
//| |
|
|
//| All times are broker/server time. |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Sam Watts"
|
|
#property version "1.00"
|
|
#property strict
|
|
#property description "Trend-filtered momentum pullback scalper for XAUUSD (gold)."
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
|
|
//--- Position sizing mode
|
|
enum ENUM_SIZING_MODE
|
|
{
|
|
SIZE_FIXED_LOT, // Fixed lot size
|
|
SIZE_RISK_PERCENT // Risk a % of equity per trade
|
|
};
|
|
|
|
//--- Stop loss / take profit calculation mode
|
|
enum ENUM_STOP_MODE
|
|
{
|
|
STOP_ATR, // ATR multiple (adapts to volatility)
|
|
STOP_POINTS // Fixed distance in points
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Inputs |
|
|
//+------------------------------------------------------------------+
|
|
input group "=== 策略 / 信号 ==="
|
|
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M5; // 工作时间框架
|
|
input int InpFastEmaPeriod = 21; // 快速EMA (回调价位)
|
|
input int InpSlowEmaPeriod = 100; // 慢速EMA (趋势过滤器)
|
|
input int InpRsiPeriod = 14; // RSI周期
|
|
input double InpRsiBuyLevel = 45.0; // 当RSI回升至该值上方时买入
|
|
input double InpRsiSellLevel = 55.0; // 当RSI跌破该值下方时卖出
|
|
input double InpPullbackAtrMult = 2.0; // 价格与快速EMA的最大允许距离 (x ATR)
|
|
|
|
input group "=== 波动性 / 过滤器 ==="
|
|
input int InpAtrPeriod = 14; // ATR周期
|
|
input int InpMinAtrPoints = 0; // ATR低于此值时跳过 (点数, 0 = 忽略)
|
|
input double InpMaxSpreadAtrPct = 25.0; // 最大价差占ATR百分比 (0 = 忽略)
|
|
|
|
input group "=== 仓位计算 ==="
|
|
input ENUM_SIZING_MODE InpSizingMode = SIZE_RISK_PERCENT; // 仓位计算方式
|
|
input double InpFixedLots = 0.01; // 固定手数 (固定手数模式)
|
|
input double InpRiskPercent = 1.0; // 每笔交易风险百分比 (账户权益)
|
|
|
|
input group "=== 止损 / 止盈 ==="
|
|
input ENUM_STOP_MODE InpStopMode = STOP_ATR; // 止损/止盈计算模式
|
|
input double InpAtrSLMult = 1.5; // 止损 = ATR x 此值
|
|
input double InpAtrTPMult = 2.0; // 止盈 = ATR x 此值
|
|
input int InpStopLossPoints = 200; // 止损 (点数, 固定模式)
|
|
input int InpTakeProfitPoints = 300; // 止盈 (点数, 固定模式)
|
|
input bool InpUseBreakEven = true; // 移动止损到盈亏平衡点
|
|
input int InpBreakEvenPoints = 150; // 触发盈亏平衡的利润点数
|
|
input int InpBreakEvenLock = 20; // 盈亏平衡时锁定的点数
|
|
input bool InpUseTrailing = true; // 使用移动止损
|
|
input int InpTrailStartPoints = 200; // 开始移动止损的利润点数
|
|
input int InpTrailStepPoints = 120; // 移动止损距离 (点数)
|
|
|
|
input group "=== 交易控制 / 风险限制 ==="
|
|
input int InpMaxPositions = 1; // 最大持仓数 (本EA)
|
|
input int InpMaxTradesPerDay = 6; // 每日最大交易次数 (0 = 不限制)
|
|
input double InpDailyLossLimit = 5.0; // 亏损达到此 equity百分比时停止交易 (0 = 关闭)
|
|
input double InpDailyProfitTarget = 0.0; // 盈利达到此equity百分比时停止交易 (0 = 关闭)
|
|
input int InpMinSecondsBetween = 60; // 最小交易间隔秒数
|
|
|
|
input group "=== 交易时段 (服务器时间) ==="
|
|
input bool InpUseSession = true; // 限制交易时段
|
|
input int InpSessionStartHour = 7; // 时段开始小时 (0-23)
|
|
input int InpSessionEndHour = 20; // 时段结束小时 (0-23)
|
|
|
|
input group "=== 常规 ==="
|
|
input long InpMagicNumber = 20240530; // 魔术号码
|
|
input string InpComment = "GoldScalperPro"; // 订单注释
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Globals |
|
|
//+------------------------------------------------------------------+
|
|
CTrade trade;
|
|
CPositionInfo posInfo;
|
|
|
|
int g_fastEmaHandle = INVALID_HANDLE;
|
|
int g_slowEmaHandle = INVALID_HANDLE;
|
|
int g_rsiHandle = INVALID_HANDLE;
|
|
int g_atrHandle = INVALID_HANDLE;
|
|
|
|
datetime g_lastBarTime = 0; // last processed bar of the working timeframe
|
|
datetime g_currentDay = 0; // day (00:00) the daily counters belong to
|
|
datetime g_lastTradeTime = 0; // time of the last entry
|
|
int g_tradesToday = 0; // entries opened today
|
|
double g_dayStartEquity = 0.0; // equity at the start of the trading day
|
|
bool g_dayBlocked = false; // daily circuit breaker tripped
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
trade.SetExpertMagicNumber(InpMagicNumber);
|
|
trade.SetTypeFillingBySymbol(_Symbol);
|
|
trade.SetDeviationInPoints(20);
|
|
|
|
if(InpFastEmaPeriod <= 0 || InpSlowEmaPeriod <= 0 ||
|
|
InpFastEmaPeriod >= InpSlowEmaPeriod)
|
|
{
|
|
Print("Fast EMA period must be > 0 and smaller than the slow EMA period.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(InpRsiPeriod <= 0 || InpAtrPeriod <= 0)
|
|
{
|
|
Print("RSI and ATR periods must be greater than zero.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(InpSizingMode == SIZE_FIXED_LOT && InpFixedLots <= 0.0)
|
|
{
|
|
Print("Fixed lot size must be greater than zero.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(InpSizingMode == SIZE_RISK_PERCENT && InpRiskPercent <= 0.0)
|
|
{
|
|
Print("Risk percent must be greater than zero.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
|
|
g_fastEmaHandle = iMA(_Symbol, InpTimeframe, InpFastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
g_slowEmaHandle = iMA(_Symbol, InpTimeframe, InpSlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
|
g_rsiHandle = iRSI(_Symbol, InpTimeframe, InpRsiPeriod, PRICE_CLOSE);
|
|
g_atrHandle = iATR(_Symbol, InpTimeframe, InpAtrPeriod);
|
|
|
|
if(g_fastEmaHandle == INVALID_HANDLE || g_slowEmaHandle == INVALID_HANDLE ||
|
|
g_rsiHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
|
|
{
|
|
Print("Failed to create one or more indicator handles.");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
ResetDailyCounters(DayStart(TimeCurrent()));
|
|
|
|
PrintFormat("GoldScalperPro initialised on %s (%s) | magic %I64d",
|
|
_Symbol, EnumToString(InpTimeframe), InpMagicNumber);
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(g_fastEmaHandle != INVALID_HANDLE) IndicatorRelease(g_fastEmaHandle);
|
|
if(g_slowEmaHandle != INVALID_HANDLE) IndicatorRelease(g_slowEmaHandle);
|
|
if(g_rsiHandle != INVALID_HANDLE) IndicatorRelease(g_rsiHandle);
|
|
if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
|
|
Comment("");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
datetime now = TimeCurrent();
|
|
|
|
//--- New trading day: reset the daily counters / circuit breaker.
|
|
datetime today = DayStart(now);
|
|
if(today != g_currentDay)
|
|
ResetDailyCounters(today);
|
|
|
|
//--- Manage what is already open on every tick (responsive exits).
|
|
ManageOpenPositions();
|
|
|
|
//--- Trip / hold the daily circuit breaker.
|
|
CheckDailyLimits();
|
|
|
|
//--- Only evaluate fresh signals once per closed bar.
|
|
datetime barTime = (datetime)SeriesInfoInteger(_Symbol, InpTimeframe, SERIES_LASTBAR_DATE);
|
|
if(barTime == g_lastBarTime)
|
|
{
|
|
UpdateDashboard();
|
|
return;
|
|
}
|
|
g_lastBarTime = barTime;
|
|
|
|
EvaluateEntry();
|
|
UpdateDashboard();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset the per-day counters and snapshot starting equity |
|
|
//+------------------------------------------------------------------+
|
|
void ResetDailyCounters(const datetime today)
|
|
{
|
|
g_currentDay = today;
|
|
g_tradesToday = 0;
|
|
g_dayBlocked = false;
|
|
g_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Daily loss / profit circuit breaker |
|
|
//+------------------------------------------------------------------+
|
|
void CheckDailyLimits()
|
|
{
|
|
if(g_dayBlocked)
|
|
return;
|
|
if(g_dayStartEquity <= 0.0)
|
|
return;
|
|
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double pct = (equity - g_dayStartEquity) / g_dayStartEquity * 100.0;
|
|
|
|
if(InpDailyLossLimit > 0.0 && pct <= -InpDailyLossLimit)
|
|
{
|
|
g_dayBlocked = true;
|
|
PrintFormat("Daily loss limit hit (%.2f%%). Trading halted for the day.", pct);
|
|
}
|
|
else if(InpDailyProfitTarget > 0.0 && pct >= InpDailyProfitTarget)
|
|
{
|
|
g_dayBlocked = true;
|
|
PrintFormat("Daily profit target hit (%.2f%%). Trading halted for the day.", pct);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Evaluate the entry signal on the latest closed bar |
|
|
//+------------------------------------------------------------------+
|
|
void EvaluateEntry()
|
|
{
|
|
//--- Respect all the gates before doing any work.
|
|
if(g_dayBlocked)
|
|
return;
|
|
if(InpUseSession && !InSession())
|
|
return;
|
|
if(InpMaxTradesPerDay > 0 && g_tradesToday >= InpMaxTradesPerDay)
|
|
return;
|
|
if(CountOpenPositions() >= InpMaxPositions)
|
|
return;
|
|
if(g_lastTradeTime > 0 && (TimeCurrent() - g_lastTradeTime) < InpMinSecondsBetween)
|
|
return;
|
|
|
|
//--- Pull indicator values for the just-closed bar and the previous one
|
|
//--- so we can detect an RSI cross. Arrays are set as time-series, so
|
|
//--- index 0 = most recent (shift 1) and index 1 = the bar before it.
|
|
double fastEma[], slowEma[], rsi[], atr[];
|
|
ArraySetAsSeries(fastEma, true);
|
|
ArraySetAsSeries(slowEma, true);
|
|
ArraySetAsSeries(rsi, true);
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
if(CopyBuffer(g_fastEmaHandle, 0, 1, 2, fastEma) < 2) return;
|
|
if(CopyBuffer(g_slowEmaHandle, 0, 1, 2, slowEma) < 2) return;
|
|
if(CopyBuffer(g_rsiHandle, 0, 1, 2, rsi) < 2) return;
|
|
if(CopyBuffer(g_atrHandle, 0, 1, 1, atr) < 1) return;
|
|
|
|
double atrNow = atr[0];
|
|
double fastNow = fastEma[0];
|
|
double slowNow = slowEma[0];
|
|
double rsiNow = rsi[0]; // last closed bar
|
|
double rsiPrev = rsi[1]; // the bar before it
|
|
|
|
if(atrNow <= 0.0)
|
|
return;
|
|
if(InpMinAtrPoints > 0 && (atrNow / _Point) < InpMinAtrPoints)
|
|
return;
|
|
if(!SpreadOK(atrNow))
|
|
return;
|
|
|
|
double closePrice = iClose(_Symbol, InpTimeframe, 1);
|
|
if(closePrice <= 0.0)
|
|
return;
|
|
|
|
bool trendUp = (fastNow > slowNow) && (closePrice > slowNow);
|
|
bool trendDown = (fastNow < slowNow) && (closePrice < slowNow);
|
|
|
|
//--- How far price has pulled back from the fast EMA, measured in ATR so
|
|
//--- it is independent of the symbol's digits / point size.
|
|
double distToFast = MathAbs(closePrice - fastNow);
|
|
bool nearFast = (distToFast <= InpPullbackAtrMult * atrNow);
|
|
|
|
//--- Long: uptrend, price near the fast EMA, RSI turning back UP through
|
|
//--- the buy level (momentum returning after a dip).
|
|
bool buySignal = trendUp && nearFast &&
|
|
rsiPrev < InpRsiBuyLevel && rsiNow >= InpRsiBuyLevel;
|
|
|
|
//--- Short: downtrend, price near the fast EMA, RSI turning back DOWN
|
|
//--- through the sell level.
|
|
bool sellSignal = trendDown && nearFast &&
|
|
rsiPrev > InpRsiSellLevel && rsiNow <= InpRsiSellLevel;
|
|
|
|
if(buySignal)
|
|
OpenTrade(ORDER_TYPE_BUY, atrNow);
|
|
else if(sellSignal)
|
|
OpenTrade(ORDER_TYPE_SELL, atrNow);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open a market order with ATR/points based SL & TP |
|
|
//+------------------------------------------------------------------+
|
|
void OpenTrade(const ENUM_ORDER_TYPE type, const double atrValue)
|
|
{
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
double slDist = StopDistance(InpStopMode == STOP_ATR ? InpAtrSLMult : InpStopLossPoints, atrValue);
|
|
double tpDist = StopDistance(InpStopMode == STOP_ATR ? InpAtrTPMult : InpTakeProfitPoints, atrValue);
|
|
|
|
//--- Respect the broker's minimum stop distance.
|
|
double minStop = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
|
|
if(slDist < minStop) slDist = minStop;
|
|
if(tpDist < minStop) tpDist = minStop;
|
|
if(slDist <= 0.0)
|
|
{
|
|
Print("Computed stop distance is zero - aborting entry.");
|
|
return;
|
|
}
|
|
|
|
double price = (type == ORDER_TYPE_BUY) ? ask : bid;
|
|
double sl, tp;
|
|
if(type == ORDER_TYPE_BUY)
|
|
{
|
|
sl = NormalizeDouble(price - slDist, _Digits);
|
|
tp = NormalizeDouble(price + tpDist, _Digits);
|
|
}
|
|
else
|
|
{
|
|
sl = NormalizeDouble(price + slDist, _Digits);
|
|
tp = NormalizeDouble(price - tpDist, _Digits);
|
|
}
|
|
|
|
double lots = CalcLots(slDist);
|
|
if(lots <= 0.0)
|
|
{
|
|
Print("Computed lot size is zero - aborting entry.");
|
|
return;
|
|
}
|
|
|
|
bool ok = (type == ORDER_TYPE_BUY)
|
|
? trade.Buy(lots, _Symbol, price, sl, tp, InpComment)
|
|
: trade.Sell(lots, _Symbol, price, sl, tp, InpComment);
|
|
|
|
if(ok)
|
|
{
|
|
g_tradesToday++;
|
|
g_lastTradeTime = TimeCurrent();
|
|
PrintFormat("%s %.2f lots @ %.*f SL %.*f TP %.*f (trade %d/%d today)",
|
|
(type == ORDER_TYPE_BUY ? "BUY" : "SELL"), lots,
|
|
_Digits, price, _Digits, sl, _Digits, tp,
|
|
g_tradesToday, InpMaxTradesPerDay);
|
|
}
|
|
else
|
|
PrintFormat("Order failed: %d - %s",
|
|
trade.ResultRetcode(), trade.ResultRetcodeDescription());
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Convert an SL/TP setting to a price distance |
|
|
//+------------------------------------------------------------------+
|
|
double StopDistance(const double value, const double atrValue)
|
|
{
|
|
if(value <= 0.0)
|
|
return(0.0);
|
|
if(InpStopMode == STOP_ATR)
|
|
return(value * atrValue);
|
|
return(value * _Point); // STOP_POINTS
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Position size from fixed lot or % risk of equity |
|
|
//+------------------------------------------------------------------+
|
|
double CalcLots(const double slDistance)
|
|
{
|
|
if(InpSizingMode == SIZE_FIXED_LOT)
|
|
return(NormalizeLots(InpFixedLots));
|
|
|
|
//--- Risk-percent sizing: lots = riskMoney / (slDistance valued per lot).
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double riskMoney = equity * InpRiskPercent / 100.0;
|
|
|
|
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
if(tickValue <= 0.0 || tickSize <= 0.0)
|
|
return(NormalizeLots(InpFixedLots));
|
|
|
|
double lossPerLot = slDistance / tickSize * tickValue;
|
|
if(lossPerLot <= 0.0)
|
|
return(NormalizeLots(InpFixedLots));
|
|
|
|
double lots = riskMoney / lossPerLot;
|
|
return(NormalizeLots(lots));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Break-even and trailing-stop management for our positions |
|
|
//+------------------------------------------------------------------+
|
|
void ManageOpenPositions()
|
|
{
|
|
if(!InpUseBreakEven && !InpUseTrailing)
|
|
return;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0)
|
|
continue;
|
|
if(!posInfo.SelectByTicket(ticket))
|
|
continue;
|
|
if(posInfo.Symbol() != _Symbol || posInfo.Magic() != InpMagicNumber)
|
|
continue;
|
|
|
|
long type = posInfo.PositionType();
|
|
double openPrice = posInfo.PriceOpen();
|
|
double curSL = posInfo.StopLoss();
|
|
double curTP = posInfo.TakeProfit();
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double newSL = curSL;
|
|
|
|
if(type == POSITION_TYPE_BUY)
|
|
{
|
|
double profitPts = (bid - openPrice) / _Point;
|
|
|
|
if(InpUseBreakEven && profitPts >= InpBreakEvenPoints)
|
|
{
|
|
double be = NormalizeDouble(openPrice + InpBreakEvenLock * _Point, _Digits);
|
|
if(be > newSL)
|
|
newSL = be;
|
|
}
|
|
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
|
|
{
|
|
double trail = NormalizeDouble(bid - InpTrailStepPoints * _Point, _Digits);
|
|
if(trail > newSL)
|
|
newSL = trail;
|
|
}
|
|
if(newSL > curSL && newSL < bid)
|
|
trade.PositionModify(ticket, newSL, curTP);
|
|
}
|
|
else if(type == POSITION_TYPE_SELL)
|
|
{
|
|
double profitPts = (openPrice - ask) / _Point;
|
|
|
|
if(InpUseBreakEven && profitPts >= InpBreakEvenPoints)
|
|
{
|
|
double be = NormalizeDouble(openPrice - InpBreakEvenLock * _Point, _Digits);
|
|
if(curSL == 0.0 || be < newSL)
|
|
newSL = be;
|
|
}
|
|
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
|
|
{
|
|
double trail = NormalizeDouble(ask + InpTrailStepPoints * _Point, _Digits);
|
|
if(curSL == 0.0 || trail < newSL)
|
|
newSL = trail;
|
|
}
|
|
if(newSL != curSL && (curSL == 0.0 || newSL < curSL) && newSL > ask)
|
|
trade.PositionModify(ticket, newSL, curTP);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Count this EA's open positions on this symbol |
|
|
//+------------------------------------------------------------------+
|
|
int CountOpenPositions()
|
|
{
|
|
int count = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0)
|
|
continue;
|
|
if(!posInfo.SelectByTicket(ticket))
|
|
continue;
|
|
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == InpMagicNumber)
|
|
count++;
|
|
}
|
|
return(count);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| True while the clock is inside the trading session window |
|
|
//+------------------------------------------------------------------+
|
|
bool InSession()
|
|
{
|
|
MqlDateTime st;
|
|
TimeToStruct(TimeCurrent(), st);
|
|
int hour = st.hour;
|
|
|
|
if(InpSessionStartHour == InpSessionEndHour)
|
|
return(true); // 24h
|
|
if(InpSessionStartHour < InpSessionEndHour)
|
|
return(hour >= InpSessionStartHour && hour < InpSessionEndHour);
|
|
//--- window that wraps past midnight
|
|
return(hour >= InpSessionStartHour || hour < InpSessionEndHour);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Spread check (relative to ATR, so it works on any gold symbol) |
|
|
//+------------------------------------------------------------------+
|
|
bool SpreadOK(const double atrValue)
|
|
{
|
|
if(InpMaxSpreadAtrPct <= 0.0 || atrValue <= 0.0)
|
|
return(true);
|
|
double spreadPrice = (double)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
|
|
return(spreadPrice <= atrValue * InpMaxSpreadAtrPct / 100.0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize lots to the symbol's volume constraints |
|
|
//+------------------------------------------------------------------+
|
|
double NormalizeLots(double lots)
|
|
{
|
|
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
if(lotStep > 0.0)
|
|
lots = MathFloor(lots / lotStep) * lotStep;
|
|
if(lots < minLot) lots = minLot;
|
|
if(lots > maxLot) lots = maxLot;
|
|
return(lots);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Midnight (00:00) of the day a timestamp belongs to |
|
|
//+------------------------------------------------------------------+
|
|
datetime DayStart(const datetime t)
|
|
{
|
|
return(t - (t % 86400));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| On-chart status read-out |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateDashboard()
|
|
{
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double dayPct = (g_dayStartEquity > 0.0)
|
|
? (equity - g_dayStartEquity) / g_dayStartEquity * 100.0 : 0.0;
|
|
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
|
|
|
string state = g_dayBlocked ? "HALTED (daily limit)"
|
|
: (InpUseSession && !InSession()) ? "outside session" : "active";
|
|
|
|
string txt = StringFormat(
|
|
"GoldScalperPro [%s %s]\n"
|
|
"State: %s\n"
|
|
"Open positions: %d / %d\n"
|
|
"Trades today: %d / %d\n"
|
|
"Day P/L: %.2f%%\n"
|
|
"Spread: %d pts",
|
|
_Symbol, EnumToString(InpTimeframe),
|
|
state, CountOpenPositions(), InpMaxPositions,
|
|
g_tradesToday, InpMaxTradesPerDay,
|
|
dayPct, (int)spread);
|
|
Comment(txt);
|
|
}
|
|
//+------------------------------------------------------------------+
|