Code: Select all
//+------------------------------------------------------------------+
//| ProNewsPullbackScalper.mq5 |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
input string Grp1 = "--- Time & Reaction ---";
input int NewsStartHour = 14;
input int NewsStartMinute = 0;
input int NewsEndHour = 14;
input int NewsEndMinute = 15;
input string Grp2 = "--- Trigger & Filters ---";
input int EmaLength = 9;
input int MaxWaitBars = 30;
input double MaxSlPips = 30.0;
input string Grp3 = "--- Risk Management ---";
input double RiskPercent = 1.0;
input double RrRatio = 2.0;
CTrade trade;
int emaHandle;
double initOpen = 0, initHigh = 0, initLow = 0;
int trendDir = 0, barsSinceNews = 0;
bool pullbackValid = false, tradeTaken = false, wasInNews = false;
double pivotExtreme = 0;
datetime lastBarTime = 0, sessionStartTime = 0;
string currentBox = "";
double pipSize = 0;
int OnInit() {
pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
emaHandle = iMA(_Symbol, PERIOD_CURRENT, EmaLength, 0, MODE_EMA, PRICE_CLOSE);
if(emaHandle == INVALID_HANDLE) return INIT_FAILED;
wasInNews = IsInNewsWindow(TimeCurrent());
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) { IndicatorRelease(emaHandle); }
bool IsInNewsWindow(datetime time) {
MqlDateTime dt; TimeToStruct(time, dt);
int currentMins = dt.hour * 60 + dt.min;
int startMins = NewsStartHour * 60 + NewsStartMinute;
int endMins = NewsEndHour * 60 + NewsEndMinute;
if(startMins < endMins) return (currentMins >= startMins && currentMins < endMins);
return (currentMins >= startMins || currentMins < endMins);
}
double CalculateLotSize(double slPrice, ENUM_ORDER_TYPE type) {
double entryPrice = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
double riskUsd = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double ticksAtRisk = MathAbs(entryPrice - slPrice) / tickSize;
if(ticksAtRisk <= 0) return 0;
double lots = riskUsd / (ticksAtRisk * tickValue);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lots = MathFloor(lots / lotStep) * lotStep;
if(lots < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) return 0;
if(lots > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) lots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
return lots;
}
void OnTick() {
datetime timeArray[]; ArraySetAsSeries(timeArray, true);
CopyTime(_Symbol, PERIOD_CURRENT, 0, 1, timeArray);
bool isNewBar = false;
if(timeArray[0] != lastBarTime) {
isNewBar = true; lastBarTime = timeArray[0];
}
bool inNews = IsInNewsWindow(TimeCurrent());
double openPrices[], closePrices[], lowPrices[], highPrices[];
ArraySetAsSeries(openPrices, true); ArraySetAsSeries(closePrices, true);
ArraySetAsSeries(lowPrices, true); ArraySetAsSeries(highPrices, true);
CopyOpen(_Symbol, PERIOD_CURRENT, 0, 2, openPrices);
CopyClose(_Symbol, PERIOD_CURRENT, 0, 2, closePrices);
CopyLow(_Symbol, PERIOD_CURRENT, 0, 2, lowPrices);
CopyHigh(_Symbol, PERIOD_CURRENT, 0, 2, highPrices);
// 1. Session Start
if(inNews && !wasInNews) {
initOpen = openPrices[0]; initHigh = highPrices[0]; initLow = lowPrices[0];
trendDir = 0; pullbackValid = false; tradeTaken = false;
pivotExtreme = 0; barsSinceNews = 0; sessionStartTime = timeArray[0];
currentBox = "NewsBox_" + TimeToString(TimeCurrent());
ObjectCreate(0, currentBox, OBJ_RECTANGLE, 0, sessionStartTime, initHigh, timeArray[0], initLow);
ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrDarkGray);
ObjectSetInteger(0, currentBox, OBJPROP_BACK, true);
}
// 2. Update Box
if(inNews) {
initHigh = MathMax(initHigh, highPrices[0]);
initLow = MathMin(initLow, lowPrices[0]);
ObjectMove(0, currentBox, 0, sessionStartTime, initHigh);
ObjectMove(0, currentBox, 1, timeArray[0], initLow);
}
// 3. Session End
if(!inNews && wasInNews) {
double initClose = closePrices[1];
if(initClose > initOpen) { trendDir = 1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrTeal); }
else if(initClose < initOpen) { trendDir = -1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrMaroon); }
else trendDir = 0;
pivotExtreme = (trendDir == 1) ? lowPrices[1] : highPrices[1];
}
wasInNews = inNews;
// 4. Pullback & Filters
if(!inNews && trendDir != 0 && !tradeTaken) {
if(isNewBar) barsSinceNews++;
if(barsSinceNews > MaxWaitBars) {
trendDir = 0; pullbackValid = false; return;
}
if(trendDir == 1 && lowPrices[0] < pivotExtreme) pivotExtreme = lowPrices[0];
if(trendDir == -1 && highPrices[0] > pivotExtreme) pivotExtreme = highPrices[0];
if(isNewBar) {
double emaBuffer[]; ArraySetAsSeries(emaBuffer, true);
if(CopyBuffer(emaHandle, 0, 1, 1, emaBuffer) <= 0) return;
double ema1 = emaBuffer[0];
double close1 = closePrices[1];
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// LONG SETUP
if(trendDir == 1) {
if(close1 < ema1) pullbackValid = true;
if(pullbackValid && close1 > ema1) {
double sl = NormalizeDouble(pivotExtreme, _Digits);
double slDistance = MathAbs(ask - sl) / pipSize;
if(slDistance > 0 && slDistance <= MaxSlPips) {
double lots = CalculateLotSize(sl, ORDER_TYPE_BUY);
if(lots > 0) {
double tp = NormalizeDouble(ask + ((ask - sl) * RrRatio), _Digits);
if(trade.Buy(lots, _Symbol, ask, sl, tp, "Pro Scalp")) {
tradeTaken = true; pullbackValid = false;
}
}
}
}
}
// SHORT SETUP
if(trend dispersal == -1) { // Typo fix: (trendDir == -1)
if(trendDir == -1) {
if(close1 > ema1) pullbackValid = true;
if(pullbackValid && close1 < ema1) {
double sl = NormalizeDouble(pivotExtreme, _Digits);
double slDistance = MathAbs(sl - bid) / pipSize;
if(slDistance > 0 && slDistance <= MaxSlPips) {
double lots = CalculateLotSize(sl, ORDER_TYPE_SELL);
if(lots > 0) {
double tp = NormalizeDouble(bid - ((sl - bid) * RrRatio), _Digits);
if(trade.Sell(lots, _Symbol, bid, sl, tp, "Pro Scalp")) {
tradeTaken = true; pullbackValid = false;
}
}
}
}
}
}
}
}