840 lines
35 KiB
MQL5
840 lines
35 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 50 Pips Push or Pull to EMA 25.mq5 |
|
|
//| Copyright 2025,Setup Radar |
|
|
//| https://www.Setup-Radar.com |
|
|
//| Author: X.com/Hectorand |
|
|
//| Production Ready - Cleaned & Commented |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025,Setup Radar"
|
|
#property link "https://www.Setup-Radar.com"
|
|
#property version "1.00"
|
|
|
|
// Include sector mapping functionality
|
|
#include <SymbolToSectorLink.mqh>
|
|
#include <PipDivider.mqh>
|
|
|
|
//--- Input parameters
|
|
input int EMA_Period = 25; // EMA Period
|
|
input double Min_Pips_Distance = 50.0; // Minimum Pips Distance
|
|
input int Min_Bars_Between_Touches = 3; // Min bars between touches
|
|
input bool Enable_Alerts = false; // Enable alerts
|
|
input bool Enable_Print = true; // Enable Print to Journal
|
|
input bool Show_Visual_Markers = true; // Show visual markers on chart
|
|
input bool Enable_Debug_Prints = true; // Enable debug prints for API communication
|
|
input bool Enable_Data_Send = true; // Enable sending data to API
|
|
input bool Enable_Screenshot_Send = true; // Enable sending screenshots to API
|
|
|
|
//--- Server Configuration
|
|
string alertServerURL = "https://api.setupradar.app/api/v1/strategy/webhook";
|
|
string screenshotServerURL = "https://api.setupradar.app/api/v1/strategy/webhook";
|
|
string strategyName = "50 Pips Push or Pull to EMA 25";
|
|
string strategyId = "0000003";
|
|
string allowedTimeframe[]; // Empty array, no elements
|
|
|
|
//--- Session Configuration
|
|
string SydneySessionStart = "22:00"; // Sydney session start time (crosses midnight)
|
|
string SydneySessionEnd = "00:00"; // Sydney session end time
|
|
string AsiaSessionStart = "00:00"; // Asian session start time
|
|
string AsiaSessionEnd = "07:00"; // Asian session end time
|
|
string LondonSessionStart = "07:00"; // London session start time
|
|
string LondonSessionEnd = "12:00"; // London session end time
|
|
string NewYorkSessionStart = "12:00"; // New York session start time
|
|
string NewYorkSessionEnd = "22:00"; // New York session end time
|
|
string currentSession = "OutsideSession";
|
|
|
|
|
|
// ---- GLOBALS ----
|
|
string Symbols[];
|
|
int SymbolsCount;
|
|
|
|
int EmaHandles[];
|
|
double ema_buffer[];
|
|
MqlRates price_data[];
|
|
datetime last_bar_time[];
|
|
datetime last_bar_time_15[];
|
|
int min_bars;
|
|
datetime last_alert_time[];
|
|
bool touch_alerted_current_bar[];
|
|
bool alert_sent[];
|
|
// ---- INIT ----
|
|
int OnInit()
|
|
{
|
|
min_bars = EMA_Period + 100;
|
|
SymbolsCount = SymbolsTotal(true);
|
|
ArrayResize(Symbols, SymbolsCount);
|
|
ArrayResize(EmaHandles, SymbolsCount);
|
|
ArrayResize(last_bar_time, SymbolsCount);
|
|
ArrayResize(last_bar_time_15, SymbolsCount);
|
|
ArrayResize(alert_sent, SymbolsCount);
|
|
ArrayResize(touch_alerted_current_bar, SymbolsCount);
|
|
for(int i = 0; i < SymbolsCount; i++)
|
|
{
|
|
Symbols[i] = SymbolName(i, true);
|
|
EmaHandles[i] = iMA(
|
|
Symbols[i],
|
|
_Period,
|
|
EMA_Period,
|
|
0,
|
|
MODE_EMA,
|
|
PRICE_CLOSE
|
|
);
|
|
alert_sent[i] = false;
|
|
if(EmaHandles[i] == INVALID_HANDLE)
|
|
Print("EMA handle failed for ", Symbols[i]);
|
|
last_bar_time[i] = 0;
|
|
last_bar_time_15[i] = 0;
|
|
}
|
|
ArraySetAsSeries(ema_buffer, true);
|
|
ArraySetAsSeries(price_data, true);
|
|
EventSetTimer(2);
|
|
Print("Minimal multisymbol base initialized");
|
|
currentSession = GetCurrentSession();
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CheckForEMATouch(string symb, int index)
|
|
{
|
|
if(!IsCandleTouchingEMA(0))
|
|
return;
|
|
if(IsPriceAboveEMA(1))
|
|
CheckBullishSetup(0, symb, index);
|
|
else
|
|
if(IsPriceBelowEMA(1))
|
|
{
|
|
CheckBearishSetup(0, symb, index);
|
|
}
|
|
}
|
|
// ---- TIMER LOOP ----
|
|
void OnTimer()
|
|
{
|
|
for(int i = 0; i < SymbolsCount; i++)
|
|
{
|
|
string symbol_name = Symbols[i];
|
|
if(!RefreshData(i))
|
|
continue;
|
|
if(IsNewBar(i))
|
|
{
|
|
alert_sent[i] = false;
|
|
}
|
|
CheckForEMATouch(Symbols[i], i);
|
|
}
|
|
// Update session every 15 minutes
|
|
if(IsNew15MinBar(0))
|
|
currentSession = GetCurrentSession();
|
|
}
|
|
|
|
// ---- DATA REFRESH ----
|
|
bool RefreshData(int index)
|
|
{
|
|
string sym = Symbols[index];
|
|
int bars_needed = Bars(sym, _Period);
|
|
if(bars_needed < min_bars)
|
|
return false;
|
|
if(Bars(sym, _Period) < EMA_Period + 5)
|
|
return false;
|
|
if(CopyRates(sym, _Period, 0, min_bars, price_data) < min_bars)
|
|
return false;
|
|
if(CopyBuffer(EmaHandles[index], 0, 0, min_bars, ema_buffer) < min_bars)
|
|
return false;
|
|
ArraySetAsSeries(ema_buffer, true);
|
|
return true;
|
|
}
|
|
|
|
// ---- NEW BAR CHECK ----
|
|
bool IsNewBar(int index)
|
|
{
|
|
datetime t = iTime(Symbols[index], _Period, 0);
|
|
if(t != last_bar_time[index])
|
|
{
|
|
last_bar_time[index] = t;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool IsNew15MinBar(int index)
|
|
{
|
|
datetime t = iTime(Symbols[index], PERIOD_M15, 0);
|
|
if(t != last_bar_time_15[index])
|
|
{
|
|
last_bar_time_15[index] = t;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// ---- DEINIT ----
|
|
void OnDeinit(const int reason)
|
|
{
|
|
for(int i = 0; i < SymbolsCount; i++)
|
|
{
|
|
if(EmaHandles[i] != INVALID_HANDLE)
|
|
IndicatorRelease(EmaHandles[i]);
|
|
}
|
|
EventKillTimer();
|
|
Print("Minimal multisymbol base stopped");
|
|
Comment("");
|
|
ObjectDelete(0, WM_NAME);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if EMA is between candle high and low (touch detection) |
|
|
//+------------------------------------------------------------------+
|
|
bool IsCandleTouchingEMA(int candle_idx)
|
|
{
|
|
return (ema_buffer[candle_idx + 1] >= price_data[candle_idx].low &&
|
|
ema_buffer[candle_idx + 1] <= price_data[candle_idx].high);
|
|
}
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool IsPriceAboveEMA(int candle_idx)
|
|
{
|
|
return price_data[candle_idx].open > ema_buffer[candle_idx];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool IsPriceBelowEMA(int candle_idx)
|
|
{
|
|
return price_data[candle_idx].open < ema_buffer[candle_idx];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CheckBullishSetup(int current_candle_idx, string symbol, int i)
|
|
{
|
|
int ema_size = ArraySize(ema_buffer);
|
|
if(current_candle_idx + 1 >= ema_size)
|
|
return;
|
|
int prev_touch_idx = FindPreviousTouch(current_candle_idx);
|
|
if(prev_touch_idx == -1)
|
|
return;
|
|
if(MathAbs(current_candle_idx - prev_touch_idx) <= Min_Bars_Between_Touches)
|
|
return;
|
|
double highest_high = FindHighestHigh(prev_touch_idx, current_candle_idx, symbol);
|
|
// Print(current_candle_idx, " " , prev_touch_idx, " ", highest_high);
|
|
if(highest_high == 0)
|
|
return;
|
|
double ema_val = ema_buffer[current_candle_idx + 1];
|
|
double distance_pips = PriceToPips(highest_high - ema_val, symbol);
|
|
if(distance_pips >= Min_Pips_Distance && bearish(symbol, current_candle_idx))
|
|
{
|
|
string alert_msg = StringFormat(
|
|
"BULLISH Setup | %s %s | Distance: %.1f pips | HH: %.5f | EMA: %.5f",
|
|
symbol, GetTimeframeString(), distance_pips,
|
|
highest_high, ema_val
|
|
);
|
|
if(!alert_sent[i])
|
|
{
|
|
if(Enable_Print)
|
|
Print(alert_msg);
|
|
if(Enable_Alerts)
|
|
{
|
|
Alert(alert_msg);
|
|
}
|
|
string screenshotFilename =
|
|
TakeScreenshot("Bullish", highest_high, ema_val, distance_pips, symbol, current_candle_idx, i);
|
|
SendAlert("Bullish", highest_high, ema_val, distance_pips, screenshotFilename, symbol);
|
|
alert_sent[i] = true;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Validate bearish EMA bounce setup |
|
|
//+------------------------------------------------------------------+
|
|
void CheckBearishSetup(int current_candle_idx, string symbol, int i)
|
|
{
|
|
// Find previous EMA touch
|
|
int prev_touch_idx = FindPreviousTouch(current_candle_idx);
|
|
if(prev_touch_idx == -1)
|
|
return;
|
|
// Ensure minimum separation between touches
|
|
if(MathAbs(current_candle_idx - prev_touch_idx) <= Min_Bars_Between_Touches)
|
|
return;
|
|
// Find lowest low between the two touches
|
|
double lowest_low = FindLowestLow(prev_touch_idx, current_candle_idx, symbol);
|
|
// Print(current_candle_idx, prev_touch_idx, lowest_low);
|
|
if(lowest_low == 0)
|
|
return;
|
|
// Calculate distance from EMA to lowest low in pips
|
|
double distance_pips = PriceToPips(ema_buffer[current_candle_idx] - lowest_low, symbol);
|
|
// Validate minimum distance requirement (50 pips)
|
|
if(distance_pips >= Min_Pips_Distance && bullish(symbol, current_candle_idx))
|
|
{
|
|
// Format alert message with setup details
|
|
string alert_msg = StringFormat("BEARISH Setup | %s %s | Distance: %.1f pips | LL: %.5f | EMA: %.5f",
|
|
symbol, GetTimeframeString(), distance_pips,
|
|
lowest_low, ema_buffer[current_candle_idx]);
|
|
// Print to journal if enabled
|
|
if(!alert_sent[i])
|
|
{
|
|
if(Enable_Print)
|
|
Print(alert_msg);
|
|
// Show pop-up alert only on that candle
|
|
if(Enable_Alerts)
|
|
{
|
|
Alert(alert_msg);
|
|
}
|
|
// Take screenshot and send to API
|
|
string screenshotFilename = TakeScreenshot("Bearish", lowest_low, ema_buffer[current_candle_idx], distance_pips, symbol, current_candle_idx, i);
|
|
SendAlert("Bearish", lowest_low, ema_buffer[current_candle_idx], distance_pips, screenshotFilename, symbol);
|
|
alert_sent[i] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find previous EMA touch candle (search forward in time) |
|
|
//+------------------------------------------------------------------+
|
|
int FindPreviousTouch(int start_candle_idx)
|
|
{
|
|
for(int i = start_candle_idx + 1; i < MathMin(min_bars, ArraySize(price_data)); i++)
|
|
{
|
|
if(i >= ArraySize(price_data) || i >= ArraySize(ema_buffer))
|
|
break;
|
|
if(IsCandleTouchingEMA(i))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Find highest high between two touch candles |
|
|
//+------------------------------------------------------------------+
|
|
double FindHighestHigh(int start_idx, int end_idx, string symb)
|
|
{
|
|
double highest = 0;
|
|
int highint = iHighest(symb, _Period, MODE_HIGH, MathAbs(start_idx - end_idx), end_idx);
|
|
return iHigh(symb, _Period, highint);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find lowest low between two touch candles |
|
|
//+------------------------------------------------------------------+
|
|
double FindLowestLow(int start_idx, int end_idx, string symb)
|
|
{
|
|
double lowest = 0;
|
|
int lowint = iLowest(symb, _Period, MODE_LOW, MathAbs(start_idx - end_idx), end_idx);
|
|
return iLow(symb, _Period, lowint);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Convert price difference to pip units |
|
|
//+------------------------------------------------------------------+
|
|
double PriceToPips(double price_diff, string symbol)
|
|
{
|
|
return MathAbs(price_diff / GetPipDivider(symbol));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Convert period to readable timeframe string |
|
|
//+------------------------------------------------------------------+
|
|
string GetTimeframeString()
|
|
{
|
|
switch(_Period)
|
|
{
|
|
case PERIOD_M1:
|
|
return "M1";
|
|
case PERIOD_M2:
|
|
return "M2";
|
|
case PERIOD_M3:
|
|
return "M3";
|
|
case PERIOD_M4:
|
|
return "M4";
|
|
case PERIOD_M5:
|
|
return "M5";
|
|
case PERIOD_M6:
|
|
return "M6";
|
|
case PERIOD_M10:
|
|
return "M10";
|
|
case PERIOD_M15:
|
|
return "M15";
|
|
case PERIOD_M20:
|
|
return "M20";
|
|
case PERIOD_M30:
|
|
return "M30";
|
|
case PERIOD_H1:
|
|
return "H1";
|
|
case PERIOD_H2:
|
|
return "H2";
|
|
case PERIOD_H3:
|
|
return "H3";
|
|
case PERIOD_H4:
|
|
return "H4";
|
|
case PERIOD_H6:
|
|
return "H6";
|
|
case PERIOD_H8:
|
|
return "H8";
|
|
case PERIOD_H12:
|
|
return "H12";
|
|
case PERIOD_D1:
|
|
return "D1";
|
|
case PERIOD_W1:
|
|
return "W1";
|
|
case PERIOD_MN1:
|
|
return "MN1";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Determine current trading session based on time |
|
|
//+------------------------------------------------------------------+
|
|
string GetCurrentSession()
|
|
{
|
|
datetime currentTime = TimeCurrent();
|
|
MqlDateTime timeStruct;
|
|
TimeToStruct(currentTime, timeStruct);
|
|
// Check Sydney session
|
|
string startTimeStr = TimeToString(currentTime, TIME_DATE) + " " + SydneySessionStart;
|
|
string endTimeStr = TimeToString(currentTime, TIME_DATE) + " " + SydneySessionEnd;
|
|
datetime sessionStartTime = StringToTime(startTimeStr);
|
|
datetime sessionEndTime = StringToTime(endTimeStr);
|
|
if(sessionEndTime <= sessionStartTime)
|
|
sessionEndTime += 24 * 60 * 60;
|
|
if(currentTime >= sessionStartTime && currentTime < sessionEndTime)
|
|
return "Sydney";
|
|
// Check Asian session
|
|
startTimeStr = TimeToString(currentTime, TIME_DATE) + " " + AsiaSessionStart;
|
|
endTimeStr = TimeToString(currentTime, TIME_DATE) + " " + AsiaSessionEnd;
|
|
sessionStartTime = StringToTime(startTimeStr);
|
|
sessionEndTime = StringToTime(endTimeStr);
|
|
if(sessionEndTime <= sessionStartTime)
|
|
sessionEndTime += 24 * 60 * 60;
|
|
if(currentTime >= sessionStartTime && currentTime < sessionEndTime)
|
|
return "Asian";
|
|
// Check London session
|
|
startTimeStr = TimeToString(currentTime, TIME_DATE) + " " + LondonSessionStart;
|
|
endTimeStr = TimeToString(currentTime, TIME_DATE) + " " + LondonSessionEnd;
|
|
sessionStartTime = StringToTime(startTimeStr);
|
|
sessionEndTime = StringToTime(endTimeStr);
|
|
if(sessionEndTime <= sessionStartTime)
|
|
sessionEndTime += 24 * 60 * 60;
|
|
if(currentTime >= sessionStartTime && currentTime < sessionEndTime)
|
|
return "London";
|
|
// Check New York session
|
|
startTimeStr = TimeToString(currentTime, TIME_DATE) + " " + NewYorkSessionStart;
|
|
endTimeStr = TimeToString(currentTime, TIME_DATE) + " " + NewYorkSessionEnd;
|
|
sessionStartTime = StringToTime(startTimeStr);
|
|
sessionEndTime = StringToTime(endTimeStr);
|
|
if(sessionEndTime <= sessionStartTime)
|
|
sessionEndTime += 24 * 60 * 60;
|
|
if(currentTime >= sessionStartTime && currentTime < sessionEndTime)
|
|
return "NewYork";
|
|
return "OutsideSession";
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool WaitForChart(long chart_id, int timeout_ms = 5000)
|
|
{
|
|
int waited = 0;
|
|
while(waited < timeout_ms)
|
|
{
|
|
if(ChartSymbol(chart_id) != "")
|
|
return true;
|
|
Sleep(100);
|
|
waited += 100;
|
|
}
|
|
return false;
|
|
}
|
|
string WM_NAME = "TV_STYLE_WATERMARK";
|
|
//+------------------------------------------------------------------+
|
|
//| Capture chart screenshot when setup is detected |
|
|
//+------------------------------------------------------------------+
|
|
string TakeScreenshot(string setupType, double extremePrice, double emaPrice,
|
|
double distancePips, string symbol, int current_candle_idx, int index)
|
|
{
|
|
string filename = setupType + "_EMA_Bounce_" + symbol + "_" +
|
|
IntegerToString(TimeCurrent()) + ".png";
|
|
long chart_id = ChartOpen(symbol, _Period);
|
|
if(chart_id <= 0)
|
|
return "failed to open chart";
|
|
tradeviewtpl(chart_id, index);
|
|
Sleep(200);
|
|
DrawWatermark(chart_id);
|
|
if(Show_Visual_Markers)
|
|
AddVisualMarker(current_candle_idx, setupType, distancePips,
|
|
extremePrice, symbol, chart_id);
|
|
Sleep(200);
|
|
if(ChartScreenShot(chart_id, filename, 1350, 1080, ALIGN_RIGHT))
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Screenshot taken: ", filename);
|
|
Sleep(50);
|
|
}
|
|
else
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Screenshot failed");
|
|
filename = "";
|
|
}
|
|
ChartClose(chart_id);
|
|
ObjectDelete(0, WM_NAME);
|
|
return filename;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void tradeviewtpl(long chartid, int index)
|
|
{
|
|
ChartSetInteger(chartid, CHART_COLOR_BACKGROUND, C'242,242,242');
|
|
ChartSetInteger(chartid, CHART_COLOR_FOREGROUND, C'19,23,34');
|
|
ChartSetInteger(chartid, CHART_COLOR_GRID, clrNONE);
|
|
ChartSetInteger(chartid, CHART_COLOR_CHART_UP, C'38,166,154');
|
|
ChartSetInteger(chartid, CHART_COLOR_CHART_DOWN, C'239,83,80');
|
|
ChartSetInteger(chartid, CHART_COLOR_CANDLE_BULL, C'38,166,154');
|
|
ChartSetInteger(chartid, CHART_COLOR_CANDLE_BEAR, C'239,83,80');
|
|
ChartSetInteger(chartid, CHART_COLOR_CHART_LINE, C'38,166,154');
|
|
ChartSetInteger(chartid, CHART_SHOW_VOLUMES, false);
|
|
ChartSetInteger(chartid, CHART_SHIFT, false);
|
|
ChartSetInteger(chartid, CHART_SCALE, 5);
|
|
Print("Indiciator added for screenshot : ", ChartIndicatorAdd(chartid, 0, EmaHandles[index]));
|
|
}
|
|
//trying to commit
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Generate a unique identifier using timestamp and random number |
|
|
//+------------------------------------------------------------------+
|
|
string GenerateUniqueId()
|
|
{
|
|
// Create a unique ID using timestamp and random number
|
|
int randomNum = (int)MathRand();
|
|
string uniqueId = IntegerToString(TimeCurrent()) + "_" + IntegerToString(randomNum) + "_" + IntegerToString(GetTickCount());
|
|
return uniqueId;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Send alert data and screenshot to API |
|
|
//+------------------------------------------------------------------+
|
|
void SendAlert(string setupType, double extremePrice, double emaPrice, double distancePips, string screenshotFilename, string symbol)
|
|
{
|
|
// Generate unique identifier for this alert
|
|
string uniqueId = GenerateUniqueId();
|
|
// Send setup data to API if enabled
|
|
if(Enable_Data_Send)
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Sending alert data to API... | Unique ID: ", uniqueId);
|
|
SendalertData(setupType, extremePrice, emaPrice, distancePips, screenshotFilename, symbol, uniqueId);
|
|
}
|
|
else
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Data send disabled - skipping alert data");
|
|
}
|
|
// Send screenshot to API if enabled and file exists
|
|
if(Enable_Screenshot_Send && screenshotFilename != "")
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Sending screenshot to API... | Unique ID: ", uniqueId);
|
|
SendScreenshotBinary(screenshotFilename, setupType, extremePrice, emaPrice, distancePips, symbol, uniqueId);
|
|
}
|
|
else
|
|
if(screenshotFilename == "")
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] No screenshot to send (filename empty)");
|
|
}
|
|
else
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Screenshot send disabled - skipping");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Send setup data as JSON to API server |
|
|
//+------------------------------------------------------------------+
|
|
void SendalertData(string setupType, double extremePrice, double emaPrice, double distancePips, string screenshotFilename, string symbol, string uniqueId)
|
|
{
|
|
// Get symbol sector and current market prices
|
|
string symbolSector = GetCustomSector(symbol);
|
|
double currentBid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
double currentAsk = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
// Debug output if enabled
|
|
if(Enable_Debug_Prints)
|
|
{
|
|
Print("[DEBUG] Preparing JSON payload...");
|
|
Print("[DEBUG] Symbol: ", symbol, " | Sector: ", symbolSector);
|
|
Print("[DEBUG] Session: ", currentSession, " | Strategy: ", strategyName);
|
|
Print("[DEBUG] Setup Type: ", setupType, " | Distance: ", distancePips, " pips");
|
|
Print("[DEBUG] Screenshot Filename: ", screenshotFilename);
|
|
Print("[DEBUG] Unique ID: ", uniqueId);
|
|
}
|
|
// Build JSON payload with all setup information
|
|
string jsonPayload = "{";
|
|
jsonPayload = jsonPayload + "\"symbol\":\"" + symbol + "\",";
|
|
jsonPayload = jsonPayload + "\"sector\":\"" + symbolSector + "\",";
|
|
jsonPayload = jsonPayload + "\"timeframe\":\"" + GetTimeframeString() + "\",";
|
|
jsonPayload = jsonPayload + "\"strategy_name\":\"" + strategyName + "\",";
|
|
jsonPayload = jsonPayload + "\"strategy_id\":\"" + strategyId + "\",";
|
|
// Add unique identifier and screenshot filename
|
|
jsonPayload = jsonPayload + "\"unique_id\":\"" + uniqueId + "\",";
|
|
jsonPayload = jsonPayload + "\"screenshot_filename\":\"" + screenshotFilename + "\",";
|
|
// Handle allowed_timeframes (empty or populated)
|
|
jsonPayload += "\"allowed_timeframes\":[";
|
|
int tfCount = ArraySize(allowedTimeframe);
|
|
for(int i = 0; i < tfCount; i++)
|
|
{
|
|
jsonPayload = jsonPayload + "\"" + allowedTimeframe[i] + "\"";
|
|
if(i < tfCount - 1)
|
|
jsonPayload = jsonPayload + ",";
|
|
}
|
|
jsonPayload = jsonPayload + "],";
|
|
jsonPayload = jsonPayload + "\"session\":\"" + currentSession + "\",";
|
|
jsonPayload = jsonPayload + "\"setup_type\":\"" + setupType + "\",";
|
|
jsonPayload = jsonPayload + "\"formation_time\":" + IntegerToString(TimeCurrent()) + ",";
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
// Add setup-specific price data
|
|
if(setupType == "Bullish")
|
|
{
|
|
jsonPayload = jsonPayload + "\"highest_high\":" + DoubleToString(extremePrice, digits) + ",";
|
|
jsonPayload = jsonPayload + "\"ema_price\":" + DoubleToString(emaPrice, digits) + ",";
|
|
}
|
|
else
|
|
{
|
|
jsonPayload = jsonPayload + "\"lowest_low\":" + DoubleToString(extremePrice, digits) + ",";
|
|
jsonPayload = jsonPayload + "\"ema_price\":" + DoubleToString(emaPrice, digits) + ",";
|
|
}
|
|
// Add distance and current market data
|
|
jsonPayload = jsonPayload + "\"distance_pips\":" + DoubleToString(distancePips, 1) + ",";
|
|
jsonPayload = jsonPayload + "\"current_bid\":" + DoubleToString(currentBid, digits) + ",";
|
|
jsonPayload = jsonPayload + "\"current_ask\":" + DoubleToString(currentAsk, digits);
|
|
jsonPayload = jsonPayload + "}";
|
|
// Debug output of full JSON payload
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] JSON Payload: ", jsonPayload);
|
|
// Send HTTP POST request to API server
|
|
string headers = "Content-Type: application/json\r\n";
|
|
uchar dataArray[];
|
|
StringToCharArray(jsonPayload, dataArray, 0, StringLen(jsonPayload));
|
|
uchar result[];
|
|
string resultHeaders;
|
|
int response = WebRequest("POST", alertServerURL, headers, 10000, dataArray, result, resultHeaders);
|
|
// Debug response status
|
|
if(Enable_Debug_Prints)
|
|
{
|
|
if(response == 200)
|
|
Print("[DEBUG] Alert data sent successfully. Response: 200 OK | Unique ID: ", uniqueId);
|
|
else
|
|
Print("[DEBUG] Failed to send alert data. Response: ", response, " | Error: ", GetLastError(), " | Unique ID: ", uniqueId);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Send screenshot as binary multipart form data |
|
|
//+------------------------------------------------------------------+
|
|
void SendScreenshotBinary(string filename, string setupType, double extremePrice, double emaPrice, double distancePips, string symbol, string uniqueId)
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Opening screenshot file: ", filename, " | Unique ID: ", uniqueId);
|
|
// Open screenshot file for reading
|
|
int filehandle = FileOpen(filename, FILE_READ | FILE_BIN);
|
|
if(filehandle == INVALID_HANDLE)
|
|
{
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Failed to open screenshot file");
|
|
return;
|
|
}
|
|
// Get file size and read contents
|
|
int fileSize = (int)FileSize(filehandle);
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Screenshot file size: ", fileSize, " bytes | Unique ID: ", uniqueId);
|
|
uchar byteArray[];
|
|
ArrayResize(byteArray, fileSize);
|
|
FileReadArray(filehandle, byteArray, 0, fileSize);
|
|
FileClose(filehandle);
|
|
// Create unique boundary for multipart form data
|
|
string boundary = "----WebKitFormBoundary" + IntegerToString(TimeCurrent());
|
|
string headers = "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";
|
|
// Build form data with ALL REQUIRED setup information including unique identifier and filename
|
|
string formData = "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"alert_type\"\r\n\r\n";
|
|
formData = formData + setupType + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"symbol\"\r\n\r\n";
|
|
formData = formData + symbol + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"timestamp\"\r\n\r\n";
|
|
formData = formData + IntegerToString(TimeCurrent()) + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"strategy_name\"\r\n\r\n";
|
|
formData = formData + strategyName + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"strategy_id\"\r\n\r\n";
|
|
formData = formData + strategyId + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"sector\"\r\n\r\n";
|
|
formData = formData + GetCustomSector(symbol) + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"session\"\r\n\r\n";
|
|
formData = formData + currentSession + "\r\n";
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"distance_pips\"\r\n\r\n";
|
|
formData = formData + DoubleToString(distancePips, 1) + "\r\n";
|
|
//this is another one
|
|
// Add unique identifier field (same as in JSON payload)
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"unique_id\"\r\n\r\n";
|
|
formData = formData + uniqueId + "\r\n";
|
|
// Add screenshot filename field (same as in JSON payload)
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"screenshot_filename\"\r\n\r\n";
|
|
formData = formData + filename + "\r\n";
|
|
// Add allowed_timeframes field (can be empty)
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"allowed_timeframes\"\r\n\r\n";
|
|
string allowedTFsStr = "";
|
|
int tfCount = ArraySize(allowedTimeframe);
|
|
for(int i = 0; i < tfCount; i++)
|
|
{
|
|
allowedTFsStr += allowedTimeframe[i];
|
|
if(i < tfCount - 1)
|
|
allowedTFsStr += ",";
|
|
}
|
|
formData = formData + allowedTFsStr + "\r\n";
|
|
// Add current timeframe
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"timeframe\"\r\n\r\n";
|
|
formData = formData + GetTimeframeString() + "\r\n";
|
|
// Add screenshot file section with original filename
|
|
formData = formData + "--" + boundary + "\r\n";
|
|
formData = formData + "Content-Disposition: form-data; name=\"screenshot\"; filename=\"" + filename + "\"\r\n";
|
|
formData = formData + "Content-Type: image/png\r\n\r\n";
|
|
// Convert form data to byte array
|
|
uchar headerArray[];
|
|
StringToCharArray(formData, headerArray, 0, StringLen(formData));
|
|
// Create form data footer
|
|
string footer = "\r\n--" + boundary + "--\r\n";
|
|
uchar footerArray[];
|
|
StringToCharArray(footer, footerArray, 0, StringLen(footer));
|
|
// Combine all parts: headers + image data + footer
|
|
uchar postData[];
|
|
int totalSize = ArraySize(headerArray) + ArraySize(byteArray) + ArraySize(footerArray);
|
|
ArrayResize(postData, totalSize);
|
|
// Copy header bytes
|
|
for(int i = 0; i < ArraySize(headerArray); i++)
|
|
postData[i] = headerArray[i];
|
|
// Copy image bytes
|
|
int offset = ArraySize(headerArray);
|
|
for(int i = 0; i < ArraySize(byteArray); i++)
|
|
postData[offset + i] = byteArray[i];
|
|
// Copy footer bytes
|
|
offset += ArraySize(byteArray);
|
|
for(int i = 0; i < ArraySize(footerArray); i++)
|
|
postData[offset + i] = footerArray[i];
|
|
// Debug output before sending
|
|
if(Enable_Debug_Prints)
|
|
Print("[DEBUG] Sending multipart form data, total size: ", totalSize, " bytes | Unique ID: ", uniqueId);
|
|
// Send HTTP POST request with multipart data
|
|
uchar result[];
|
|
string resultHeaders;
|
|
int response = WebRequest("POST", screenshotServerURL, headers, 15000, postData, result, resultHeaders);
|
|
// Debug response status
|
|
if(Enable_Debug_Prints)
|
|
{
|
|
if(response == 200)
|
|
Print("[DEBUG] Screenshot sent successfully. Response: 200 OK | Unique ID: ", uniqueId);
|
|
else
|
|
if(response == 400)
|
|
Print("[DEBUG] Bad Request (400). Server rejected the form data. Check required fields. | Unique ID: ", uniqueId);
|
|
else
|
|
Print("[DEBUG] Failed to send screenshot. Response: ", response, " | Error: ", GetLastError(), " | Unique ID: ", uniqueId);
|
|
}
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Add visual marker (arrow) to chart for setup visualization |
|
|
//+------------------------------------------------------------------+
|
|
void AddVisualMarker(int candle_idx, string type, double distance_pips, double extreme_price, string symbol, long chartid)
|
|
{
|
|
ObjectsDeleteAll(0, "EMA_Bounce_");
|
|
string obj_name = "EMA_Bounce_" + IntegerToString(TimeCurrent());
|
|
// Calculate arrow position: below candle for bullish, above for bearish
|
|
double arrow_price = type == "Bullish" ?
|
|
price_data[candle_idx].low - GetPipDivider(symbol) :
|
|
price_data[candle_idx].high + GetPipDivider(symbol);
|
|
// Create bullish arrow (green up arrow)
|
|
if(type == "Bullish")
|
|
{
|
|
if(ObjectCreate(chartid, obj_name, OBJ_ARROW_BUY, 0, price_data[candle_idx].time, arrow_price))
|
|
{
|
|
ObjectSetInteger(chartid, obj_name, OBJPROP_ARROWCODE, 241);
|
|
ObjectSetInteger(chartid, obj_name, OBJPROP_COLOR, clrLime);
|
|
}
|
|
}
|
|
// Create bearish arrow (red down arrow)
|
|
else
|
|
{
|
|
if(ObjectCreate(chartid, obj_name, OBJ_ARROW_SELL, 0, price_data[candle_idx].time, arrow_price))
|
|
{
|
|
ObjectSetInteger(chartid, obj_name, OBJPROP_ARROWCODE, 242);
|
|
ObjectSetInteger(chartid, obj_name, OBJPROP_COLOR, clrRed);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
bool DrawWatermark(long chartid)
|
|
{
|
|
string tf = GetTimeframeString();
|
|
string symbol = ChartSymbol(chartid);
|
|
string text = symbol + " " + tf;
|
|
|
|
if(ObjectFind(chartid, WM_NAME) == -1)
|
|
{
|
|
ObjectCreate(chartid, WM_NAME, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetString(chartid, WM_NAME, OBJPROP_TEXT, text);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_COLOR, clrSilver);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_FONTSIZE, 50);
|
|
ObjectSetString(chartid, WM_NAME, OBJPROP_FONT, "Arial Black");
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_BACK, true);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_HIDDEN, true);
|
|
|
|
// Get chart dimensions
|
|
int chart_width = (int)ChartGetInteger(chartid, CHART_WIDTH_IN_PIXELS);
|
|
int chart_height = (int)ChartGetInteger(chartid, CHART_HEIGHT_IN_PIXELS);
|
|
|
|
// Calculate center position
|
|
int center_x = 1350 / 2; //chart_width / 2;
|
|
int center_y = 1080 / 2; //chart_height / 2;
|
|
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_XDISTANCE, center_x);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_YDISTANCE, center_y);
|
|
ObjectSetInteger(chartid, WM_NAME, OBJPROP_ANCHOR, ANCHOR_CENTER);
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|
|
int EstimateTextWidth(string text, int fontSize)
|
|
{
|
|
return (int)(StringLen(text) * fontSize * 0.65);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool bullish(string symbol, int shift)
|
|
{
|
|
return iClose(symbol, PERIOD_CURRENT, shift) >= iOpen(symbol, PERIOD_CURRENT, shift);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
bool bearish(string symbol, int shift)
|
|
{
|
|
return iClose(symbol, PERIOD_CURRENT, shift) < iOpen(symbol, PERIOD_CURRENT, shift);
|
|
}
|
|
//+------------------------------------------------------------------+
|