ictCore/Experts/preReleasedBots/mvp.mq5

447 lines
17 KiB
MQL5

//+------------------------------------------------------------------+
//| mvp.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MAGNUM TECH"
#property link "https://www.mql5.com"
#property version "2.00"
//add input param to use a fixed sl points OR
//build a logic to adjust for as per Instrument
input bool useFixedSL; // whethe to add a buffer of symbol's mini stop level
//input double SL_POINTS;
input double lot;
#include <Trade\Trade.mqh>
CTrade trade;
double fixedSL;
struct SwingPoint {
double price;
datetime time;
};
SwingPoint lastHigh;
SwingPoint lastLow;
datetime lastBarTime = 0;
// Temporary Counters
int countD1 = 0;
int countH1 = 0;
int countM5 = 0;
int countM1 = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize swing points
lastHigh.price = 0;
lastHigh.time = 0;
lastLow.price = 0;
lastLow.time = 0;
//getFixedSL(fixedSL);
Print("TickSize: ",SYMBOL_TRADE_TICK_SIZE/_Point,"\nSpread: ",SYMBOL_SPREAD/_Point,"\nstopLevels: ",SYMBOL_TRADE_STOPS_LEVEL/_Point);
/*if(useFixedSL){*/Print("SLFixed choosen is ",fixedSL);//}
Alert("");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("Maturity Counts -- D1: ", countD1, " | H1: ", countH1, " | M5: ", countM5, " | M1: ", countM1);
}
//+------------------------------------------------------------------+
//| Candlestick Functions |
//+------------------------------------------------------------------+
int IsEngulfing(MqlRates &r[]) {
// Check r[1] (latest closed) vs r[2] (previous)
bool bull = r[1].close > r[1].open;
bool bear = r[1].close < r[1].open;
bool prevBear = r[2].close < r[2].open;
bool prevBull = r[2].close > r[2].open;
// Bullish Engulfing: Current Bull, Prev Bear, Body engulfs Prev Body
if (bull && r[1].high > r[2].high && r[1].low < r[2].low) return 1;
// Bearish Engulfing: Current Bear, Prev Bull, Body engulfs Prev Body
if (bear && r[1].low < r[2].low && r[1].high > r[2].high) return -1;
return 0;
}
int IsGreenlight(MqlRates &r[]) {
// Check r[1]
double upperWick = r[1].high - MathMax(r[1].open, r[1].close);
double lowerWick = MathMin(r[1].open, r[1].close) - r[1].low;
if (r[1].high < r[2].high) { // catching bullish move early
if (upperWick < lowerWick) return 1;
}
else if (r[1].low > r[2].low) { // catching bearish move early
if (lowerWick < upperWick) return -1;
}
return 0;
}
//+------------------------------------------------------------------+
//| Swing Detection |
//+------------------------------------------------------------------+
void DetectSwings(MqlRates &r[]) {
// r[2] is the candidate swing candle
// r[3] is left, r[1] is right
bool isHigh = r[2].high > r[3].high && r[2].high > r[1].high;
bool isLow = r[2].low < r[3].low && r[2].low < r[1].low;
// Exception: Outside bar engulfing both sides
if (isHigh && isLow) {
lastHigh.price = r[2].high;
lastHigh.time = r[2].time;
lastLow.price = r[2].low;
lastLow.time = r[2].time;
Print("Dual Swing Detected at ", TimeToString(r[2].time));
} else {
if (isHigh) {
lastHigh.price = r[2].high;
lastHigh.time = r[2].time;
//Print("Swing High Detected at ", TimeToString(r[2].time));
}
if (isLow) {
lastLow.price = r[2].low;
lastLow.time = r[2].time;
//Print("Swing Low Detected at ", TimeToString(r[2].time));
}
}
}
//+------------------------------------------------------------------+
//| Model 1 Signal Logic |
//+------------------------------------------------------------------+
int model1(MqlRates &r[]) {
int eng = IsEngulfing(r);
int gl = IsGreenlight(r);
int signal = 0;
if (eng == 1 || gl == 1) signal = 1;
if (eng == -1 || gl == -1) signal = -1;
if (signal == 1) {
// Bullish signal: Check if backed by swing low
// Swing low should exist and preferably be below current price (support)
if (lastLow.time != 0 && lastLow.price < r[1].low) {
return 1;
}
}
if (signal == -1) {
// Bearish signal: Check if backed by swing high
// Swing high should exist and preferably be above current price (resistance)
if (lastHigh.time != 0 && lastHigh.price > r[1].high) {
return -1;
}
}
return 0;
}
//+------------------------------------------------------------------+
//| Trade Signal Structure & Enums |
//+------------------------------------------------------------------+
enum ENUM_SIGNAL_MATURITY {
MATURITY_NONE,
MATURITY_D1,
MATURITY_H1,
MATURITY_M5,
MATURITY_M1_EXECUTION,
MATURITY_INVALID
};
struct LevelParams {
double sl;
double tp;
datetime time;
bool active;
};
struct TradeSignal {
string id; // Composite: Symbol_TimeD1
string symbol;
int type; // 1: Buy, -1: Sell
double entryPrice; // Signal Price
LevelParams matchD1;
LevelParams matchH1;
LevelParams matchM5;
LevelParams matchM1;
// Current operational pointers
double currentSL;
double currentTP;
ENUM_SIGNAL_MATURITY maturity;
bool isActive;
string comment;
};
TradeSignal activeSignals[];
//+------------------------------------------------------------------+
//| Helper Functions |
//+------------------------------------------------------------------+
/*might as well be problem */string GetSignalID(string symbol, datetime time) {
return symbol + "_" + TimeToString(time, TIME_DATE|TIME_MINUTES);
}
int FindSignal(string id) {
for(int i=0; i<ArraySize(activeSignals); i++) {
if(activeSignals[i].id == id && activeSignals[i].isActive) return i;
}
return -1;
}
bool PositionExists(string comment) {
for(int i=PositionsTotal()-1; i>=0; i--) {
//if(PositionSelectByTicket(PositionGetTicket(i))) {
if(PositionGetString(POSITION_COMMENT) == comment) return true;
//}
}
return false;
}
// Check for New Bar per timeframe
bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &last_time) {
datetime current_time = iTime(symbol, tf, 0);
if(last_time == 0) {
last_time = current_time;
return false;
}
if(current_time != last_time) {
last_time = current_time;
return true;
}
return false;
}
// Same logic as DetectSwings but returns value for arbitrary array
void FindRecentSwings(MqlRates &rates[], double &outHigh, double &outLow) {
outHigh = 0;
outLow = 0;
/*is this the right logic??*/ int limit = MathMin(ArraySize(rates)-2, 50);
for(int i=2; i<limit; i++) { // Start at 2 to allow i-1, i+1 checks
// High Logic: r[i] > r[i+1] && r[i] > r[i-1] as per DetectSwings
if (outHigh == 0) {
if (rates[i].high > rates[i+1].high && rates[i].high > rates[i-1].high) {
outHigh = rates[i].high;
}
}
// Low Logic: r[i] < r[i+1] && r[i] < r[i-1]
if (outLow == 0) {
if (rates[i].low < rates[i+1].low && rates[i].low < rates[i-1].low) {
outLow = rates[i].low;
}
}
if (outHigh != 0 && outLow != 0) break;
}
}
void DemoteSignal(int index) {
if (index < 0 || index >= ArraySize(activeSignals)) return;
ENUM_SIGNAL_MATURITY oldLevel = activeSignals[index].maturity;
Print("Signal Demoted: ", activeSignals[index].id, " from ", EnumToString(oldLevel));
if (oldLevel == MATURITY_H1) {
activeSignals[index].maturity = MATURITY_D1;
activeSignals[index].currentSL = activeSignals[index].matchD1.sl;
activeSignals[index].currentTP = activeSignals[index].matchD1.tp;
} else if (oldLevel == MATURITY_M5) {
activeSignals[index].maturity = MATURITY_H1;
activeSignals[index].currentSL = activeSignals[index].matchH1.sl;
activeSignals[index].currentTP = activeSignals[index].matchH1.tp;
} else if (oldLevel == MATURITY_M1_EXECUTION) {
activeSignals[index].maturity = MATURITY_M5;
activeSignals[index].currentSL = activeSignals[index].matchM5.sl;
activeSignals[index].currentTP = activeSignals[index].matchM5.tp;
} else {
activeSignals[index].isActive = false; // D1 or lower invalid
}
}
void PromoteSignal(int index, ENUM_SIGNAL_MATURITY newLevel, MqlRates &rates[]) {
if (index < 0 || index >= ArraySize(activeSignals)) return;
double swingHigh = 0, swingLow = 0;
FindRecentSwings(rates, swingHigh, swingLow);
// rates[1] is the signal candle
double signalLow = rates[1].low;
double signalHigh = rates[1].high;
LevelParams params;
params.time = rates[1].time;
params.active = true;
if (activeSignals[index].type == 1) { // BUY
params.sl = signalLow;
params.tp = swingHigh;
} else { // SELL
params.sl = signalHigh;
params.tp = swingLow;
}
// Fallback if no swing found
if (params.tp == 0) params.tp = (activeSignals[index].type == 1) ? params.sl + (params.sl*0.01) : params.sl - (params.sl*0.01);
if (newLevel == MATURITY_H1) activeSignals[index].matchH1 = params;
else if (newLevel == MATURITY_M5) activeSignals[index].matchM5 = params;
else if (newLevel == MATURITY_M1_EXECUTION) activeSignals[index].matchM1 = params;
activeSignals[index].currentSL = params.sl;
activeSignals[index].currentTP = params.tp;
activeSignals[index].maturity = newLevel;
if (newLevel == MATURITY_H1) countH1++;
else if (newLevel == MATURITY_M5) countM5++;
else if (newLevel == MATURITY_M1_EXECUTION){
if(activeSignals[index].type==1){
///*buy*/
if(!PositionExists(activeSignals[index].id)){}
//trade.Buy(lot,_Symbol,0,(activeSignals[index].currentSL-fixedSL),activeSignals[index].matchD1.tp,activeSignals[index].id);
//instead try not inluding sl in position opening
//rather have a function killing trades right when the price touch it
}
else if(activeSignals[index].type==-1){
//sell
if(!PositionExists(activeSignals[index].id)){}
//trade.Sell(lot,_Symbol,0,(activeSignals[index].currentSL+fixedSL),activeSignals[index].matchD1.tp,activeSignals[index].id);
}
countM1++;
}
Print("Signal Promoted: ", activeSignals[index].id, " to ", EnumToString(newLevel));
}
//double getFixedSL(double &fixedSL){
// if(useFixedSL){
// fixedSL = SL_POINTS / _Point;
// } else {
// fixedSL=SYMBOL_TRADE_TICK_SIZE*safetyFactor*_Point;
// }
// return fixedSL;
//}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
static datetime lastD1=0, lastH1=0, lastM5=0, lastM1=0;
bool isNewD1 = IsNewBar(_Symbol, PERIOD_D1, lastD1);
bool isNewH1 = IsNewBar(_Symbol, PERIOD_H1, lastH1);
bool isNewM5 = IsNewBar(_Symbol, PERIOD_M5, lastM5);
bool isNewM1 = IsNewBar(_Symbol, PERIOD_M1, lastM1);
// --- D1 SCAN ---
if (isNewD1) {
printf("new D bar formed!");
MqlRates avgD1[];
ArraySetAsSeries(avgD1, true);
if(CopyRates(_Symbol, PERIOD_D1, 0, 5, avgD1) == 5) {
DetectSwings(avgD1); // Update Globals for model1
int sigD1 = model1(avgD1);
if (sigD1 != 0) { //
string sigID = GetSignalID(_Symbol, avgD1[1].time); //give ID
if (FindSignal(sigID) == -1) { //new ID found
int newIdx = ArrayResize(activeSignals, (ArraySize(activeSignals) + 1)); //make space for new signal
activeSignals[newIdx-1].id = sigID;
activeSignals[newIdx-1].symbol = _Symbol;
activeSignals[newIdx-1].type = sigD1;
activeSignals[newIdx-1].maturity = MATURITY_D1;
activeSignals[newIdx-1].isActive = true;
activeSignals[newIdx-1].matchD1.time = avgD1[1].time;
activeSignals[newIdx-1].matchD1.active = true;
// Assign Initial D1 Targets
if(sigD1 == 1) { // Buy
activeSignals[newIdx-1].matchD1.sl = avgD1[1].low;
activeSignals[newIdx-1].matchD1.tp = lastHigh.price;
// Fallback if TP is not logical (e.g. Price > lastHigh)
//if (activeSignals[newIdx-1].matchD1.tp <= avgD1[1].close)
// activeSignals[newIdx-1].matchD1.tp = avgD1[1].close + (avgD1[1].close - lastLow.price)*2;
} else { // Sell
activeSignals[newIdx-1].matchD1.sl = avgD1[1].high;
activeSignals[newIdx-1].matchD1.tp = lastLow.price;
// Fallback
//if (activeSignals[newIdx-1].matchD1.tp >= avgD1[1].close)
// activeSignals[newIdx-1].matchD1.tp = avgD1[1].close - (lastHigh.price - avgD1[1].close)*2;
}
activeSignals[newIdx-1].currentSL = activeSignals[newIdx-1].matchD1.sl;
activeSignals[newIdx-1].currentTP = activeSignals[newIdx-1].matchD1.tp;
countD1++;
Print("New D1 Signal Found: ", sigID, " SL: ", activeSignals[newIdx-1].currentSL, " TP: ", activeSignals[newIdx-1].currentTP);
}
}
}
}
// --- MATURITY LOOP ---
for(int i=0; i<ArraySize(activeSignals); i++) {
if(!activeSignals[i].isActive) continue;
double currentBid = SymbolInfoDouble(activeSignals[i].symbol, SYMBOL_BID);
double currentAsk = SymbolInfoDouble(activeSignals[i].symbol, SYMBOL_ASK);
// VALIDATION (Every Tick)
if (activeSignals[i].currentSL > 0 && activeSignals[i].currentTP > 0) {
bool hitSL = false, hitTP = false;
if (activeSignals[i].type == 1) { // Buy
if (currentBid <= activeSignals[i].currentSL) hitSL = true;
if (currentBid >= activeSignals[i].currentTP) hitTP = true;
} else { // Sell
if (currentAsk >= activeSignals[i].currentSL) hitSL = true;
if (currentAsk <= activeSignals[i].currentTP) hitTP = true;
}
if (hitSL || hitTP) {
DemoteSignal(i);
continue;
}
}
// PROMOTION (Check on target TF New Bar)
MqlRates tfRates[];
ArraySetAsSeries(tfRates, true);
if (activeSignals[i].maturity == MATURITY_D1 /*lookback if prev candle was a signal*/&& isNewH1) {
if(CopyRates(_Symbol, PERIOD_H1, 0, 50, tfRates) >= 5) {
if (IsGreenlight(tfRates) == activeSignals[i].type) PromoteSignal(i, MATURITY_H1, tfRates);
}
}
else if (activeSignals[i].maturity == MATURITY_H1 /*lookback if prev candle was a signal*/&& isNewM5) {
if(CopyRates(_Symbol, PERIOD_M5, 0, 50, tfRates) >= 5) {
if (IsGreenlight(tfRates) == activeSignals[i].type) PromoteSignal(i, MATURITY_M5, tfRates);
}
}
else if (activeSignals[i].maturity == MATURITY_M5 /*lookback if prev candle was a signal*/&& isNewM1) {
if(CopyRates(_Symbol, PERIOD_M1, 0, 50, tfRates) >= 5) {
if (IsGreenlight(tfRates) == activeSignals[i].type) {
PromoteSignal(i, MATURITY_M1_EXECUTION, tfRates);
Print(" >>> EXECUTION SIGNAL <<< ", activeSignals[i].id);
}
}
}
}
}