Test/1.mq5
2026-02-27 12:05:32 +01:00

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);
}
//+------------------------------------------------------------------+