Issues saving screenshots

 
//+------------------------------------------------------------------+
//|                                      Expert Advisor Template.mq5 |
//|                                                                  |
//+------------------------------------------------------------------+

// Include necessary files

#include <Files/File.mqh>
#include <Files/FileBin.mqh>
#include <Arrays/ArrayString.mqh>
#include <Arrays/ArrayObj.mqh>

input string FileName = "test.csv";      // CSV file name
input string SymbolName = "GBPUSD";      // Symbol name




// Declare your custom structure for the data
struct Data
{
    datetime sample_time;
    datetime ht_time;
    double ht_open;
    double ht_high;
    double ht_low;
    double ht_close;
    double ht_bollinger_mid;
    double ht_OHLC_max;
    double ht_OHLC_min;
    int    ht_liq_type;
    datetime lt_time;
    double lt_open;
    double lt_high;
    double lt_low;
    double lt_close;
    double lt_bollinger_mid;
    double lt_OHLC_max;
    double lt_OHLC_min;
    double lt_valid_range;
    double lt_tap_range;
    string dist_prox;
    datetime tapped_by;
    double sl;
    double entry;
    double tp;
    string dir;

    bool FromCSVLine(string line)
{
    StringReplace(line, "\"", ""); // Remove double quotes from the line
    string parts[];
    StringSplit(line, ',', parts); // Split the line using comma as a delimiter

    if(ArraySize(parts) != 27) // Check that there are 27 fields in the line
        return false;

    // Parse and populate the data structure
    sample_time = StringToTime(parts[1]);
    ht_time = StringToTime(parts[2]);
    ht_open = StringToDouble(parts[3]);
    ht_high = StringToDouble(parts[4]);
    ht_low = StringToDouble(parts[5]);
    ht_close = StringToDouble(parts[6]);
    ht_bollinger_mid = StringToDouble(parts[7]);
    ht_OHLC_max = StringToDouble(parts[8]);
    ht_OHLC_min = StringToDouble(parts[9]);
    ht_liq_type = int(StringToInteger(parts[10]));
    lt_time = StringToTime(parts[11]);
    lt_open = StringToDouble(parts[12]);
    lt_high = StringToDouble(parts[13]);
    lt_low = StringToDouble(parts[14]);
    lt_close = StringToDouble(parts[15]);
    lt_bollinger_mid = StringToDouble(parts[16]);
    lt_OHLC_max = StringToDouble(parts[17]);
    lt_OHLC_min = StringToDouble(parts[18]);
    lt_valid_range = StringToDouble(parts[19]);
    lt_tap_range = StringToDouble(parts[20]);
    dist_prox = parts[21];
    tapped_by = StringToTime(parts[22]);
    sl = StringToDouble(parts[23]);
    entry = StringToDouble(parts[24]);
    tp = StringToDouble(parts[25]);
    dir = parts[26];

    return true;
}
};


int getCsvLines(const string fName, string &l[], int flag = 0, ushort sepLine = '\n')
{
    uchar Bytes[];
    int numLines = FileLoad(fName, Bytes, flag) ? StringSplit(CharArrayToString(Bytes), sepLine, l) : 0;
    Print("getCsvLines: Read ", numLines, " lines from file: ", fName);
    return numLines;
}

bool ReadCSVFile(const string filename, Data &data[], uint &count)
{
    string lines[];
    int numLines = getCsvLines(filename, lines);

    if (numLines <= 0) {
        Print("Error reading file: ", filename);
        return false;
    }

    Print("Header: ", lines[0]);

    count = 0;
    for (int i = 1; i < numLines; i++) {
        string line = lines[i];
        if (StringLen(line) == 0) {
            Print("Skipping empty line ", i);
            continue;
        }

        Data newData;
        if (newData.FromCSVLine(line)) {
            ArrayResize(data, count + 1);
            data[count] = newData;
            count++;
        } else {
            Print("Failed to parse line ", i, ": ", line);
            break;
        }
    }

    Print("ReadCSVFile: Parsed ", count, " lines successfully.");
    return count > 0;
}


// GetCandleInformation function definition
void GetCandleInformation(string symbol, ENUM_TIMEFRAMES sample_time_tf, ENUM_TIMEFRAMES ht_time_tf, ENUM_TIMEFRAMES lt_time_tf, int index, Data& data)
{
    data.sample_time = iTime(symbol, sample_time_tf, index);
    data.ht_time = iTime(symbol, ht_time_tf, index);
    data.ht_open = iOpen(symbol, ht_time_tf, index);
    data.ht_high = iHigh(symbol, ht_time_tf, index);
    data.ht_low = iLow(symbol, ht_time_tf, index);
    data.ht_close = iClose(symbol, ht_time_tf, index);

    double ht_bollinger_mid[];
    int ht_bands_handle = iBands(symbol, ht_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(ht_bands_handle, 1, 0, 1, ht_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for HT at index ", index);
        return;
    }
    data.ht_bollinger_mid = ht_bollinger_mid[0];

    data.ht_OHLC_max = MathMax(MathMax(data.ht_open, data.ht_high), MathMax(data.ht_low, data.ht_close));
    data.ht_OHLC_min = MathMin(MathMin(data.ht_open, data.ht_high), MathMin(data.ht_low, data.ht_close));
    data.ht_liq_type = (data.ht_close > data.ht_bollinger_mid) ? 1 : 2;

    data.lt_time = iTime(symbol, lt_time_tf, index);
    data.lt_open = iOpen(symbol, lt_time_tf, index);
    data.lt_high = iHigh(symbol, lt_time_tf, index);
    data.lt_low = iLow(symbol, lt_time_tf, index);
    data.lt_close = iClose(symbol, lt_time_tf, index);

    double lt_bollinger_mid[];
    int lt_bands_handle = iBands(symbol, lt_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(lt_bands_handle, 1, 0, 1, lt_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for LT at index ", index);
        return;
    }
    data.lt_bollinger_mid = lt_bollinger_mid[0];

    data.lt_OHLC_max = MathMax(MathMax(data.lt_open, data.lt_high), MathMax(data.lt_low, data.lt_close));
    data.lt_OHLC_min = MathMin(MathMin(data.lt_open, data.lt_high), MathMin(data.lt_low, data.lt_close));
    data.lt_valid_range = data.lt_close - data.lt_open;
    data.lt_tap_range = MathAbs(data.ht_close - data.lt_close) / (data.ht_OHLC_max - data.ht_OHLC_min);
    data.dist_prox = (data.ht_liq_type == 1) ? "far" : "close";
    data.tapped_by = (data.ht_close > data.lt_close) ? data.ht_time : data.lt_time;
    data.sl = (data.ht_liq_type == 1) ? data.lt_low : data.lt_high;
    data.entry = (data.ht_liq_type == 1) ? data.lt_close - data.lt_valid_range * 2 : data.ht_close + data.lt_valid_range * 2;
  data.tp = (data.ht_liq_type == 1) ? data.lt_close - data.lt_tap_range * data.lt_valid_range : data.ht_close + data.lt_tap_range * data.lt_valid_range;
  data.dir = (data.ht_close > data.lt_close) ? "long" : "short";
}


void DrawMarkersAndLines(Data &data) {
    datetime chartTime = iTime(SymbolName, PERIOD_M15, iBarShift(SymbolName, PERIOD_M15, data.sample_time));

    if (chartTime != WRONG_VALUE) {
        // Create BuyArrow
        if (data.dir == "long") {
            ObjectCreate(ChartID(), "BuyArrow", OBJ_ARROW, 0, chartTime, data.entry);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_ARROWCODE, 233); // Up arrow
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_WIDTH, 2);
            Print("Buy Arrow created at ", TimeToString(chartTime));
        }

        // Create SellArrow
        if (data.dir == "short") {
            ObjectCreate(ChartID(), "SellArrow", OBJ_ARROW, 0, chartTime, data.entry);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_ARROWCODE, 234); // Down arrow
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_WIDTH, 2);
            Print("Sell Arrow created at ", TimeToString(chartTime));
        }

        // Create StopLoss
        ObjectCreate(ChartID(), "StopLoss", OBJ_HLINE, 0, chartTime, data.sl);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_COLOR, clrOrange);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "StopLoss", OBJPROP_TEXT, "SL");
        Print("Stop Loss created at ", TimeToString(chartTime));

        // Create TakeProfit
        ObjectCreate(ChartID(), "TakeProfit", OBJ_HLINE, 0, chartTime, data.tp);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_COLOR, clrGreen);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "TakeProfit", OBJPROP_TEXT, "TP");
        Print("Take Profit created at ", TimeToString(chartTime));
    } else {
        Print("Invalid chart index for sample_time=", TimeToString(data.sample_time));
    }
}

// SaveScreenshot function definition
bool save_screenshot(string screenshot_path)
{
    Print("Attempting to save screenshot: ", screenshot_path);

    ChartNavigate(ChartID(), CHART_END, 0); // Align the chart to the right edge
    Sleep(1000); // Add a delay of 1 second before taking the screenshot

    if (ChartScreenShot(ChartID(), screenshot_path, 800, 600, ALIGN_RIGHT))
    {
        Print("Failed to save screenshot: ", screenshot_path);
        Print("Error: ", GetLastError());
        return false;
    }
    else
    {
        Print("Screenshot saved: ", screenshot_path);
        return true;
    }
}


// OnStart function
int OnInit()
{
    Print("OnInit Checkpoint: Initialization started");
    Print("Using file name: ", FileName);

    Data data[];
    uint count = 0;
    if (ReadCSVFile("test.csv", data, count)) {
        Print("CSV file read successfully. Number of data rows: ", count);
        // Debug: Print content of the data array
        for (uint i = 0; i < count; i++) {
            Print("Data[", i, "]: sample_time=", TimeToString(data[i].sample_time));
        }
    } else {
        Print("Error reading CSV file.");
    }
    
    for (uint i = 0; i < count; i++) {
        Print("Current index: ", i);
        Print("Sample time: ", TimeToString(data[i].sample_time));

        int chartIndex = iBarShift(_Symbol, PERIOD_M15, data[i].sample_time);

        Print("Chart index: ", chartIndex);
        GetCandleInformation(SymbolName, PERIOD_M15, PERIOD_H4, PERIOD_M15, chartIndex, data[i]);

        DrawMarkersAndLines(data[i]);

        ChartRedraw();
        string file_name = StringFormat("s_t_%s_%d.jpg", TimeToString(data[i].sample_time, TIME_DATE | TIME_SECONDS), i);
        StringReplace(file_name, ":", "_"); // Replace colons with underscores
        StringReplace(file_name, " ", "_"); // Replace spaces with underscores

        string screenshot_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\Screenshots\\" + file_name;

        if (!save_screenshot(screenshot_path)) {
            Print("Failed to save screenshot: ", screenshot_path);
            Print("Failed to save screenshot for index: ", i);
        }
    }
    return(INIT_SUCCEEDED);
}



In the following Expert Advisor the screenshots aren't being saved in the given path. I've tried changing the path and it keeps not creating the screenshot.

In the terminal upon adding the script to the chart I get:

2023.04.14 15:20:20.328 mt5_chart_marker (GBPUSD,M15) Attempting to save screenshot: C:\Users\manu_\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\Screenshots\s_t_2022.05.24_11_30_00_24.jpg

2023.04.14 15:20:21.335 mt5_chart_marker (GBPUSD,M15) Screenshot saved: C:\Users\manu_\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Files\Screenshots\s_t_2022.05.24_11_30_00_24.jpg

But when I go to the folder there is no screenshot. What might be the issue?
 

You don't need to add the path yourself.

string screenshot_path = "Screenshots\\" + file_name;
 
Screenshots are now being sucesfully saved and created. I have a new problem. The screenshot is always taken in the real time chart, and doesn't go back to the sample data where all the markers are created.
Here is the updated script:
//+------------------------------------------------------------------+
//|                                      Expert Advisor Template.mq5 |
//|                                                                  |
//+------------------------------------------------------------------+

// Include necessary files

#include <Files/File.mqh>
#include <Files/FileBin.mqh>
#include <Arrays/ArrayString.mqh>
#include <Arrays/ArrayObj.mqh>

input string FileName = "test.csv";      // CSV file name
input string SymbolName = "GBPUSD";      // Symbol name
input string StartTime = "1999.01.15 20:45:00"; // Start time for the visualizer
input string EndTime = "2022.06.02 05:00:00";   // End time for the visualizer
input string ScreenshotFolder = "Screenshots"; // Folder name for storing screenshots


// Declare your custom structure for the data
struct Data
{
    datetime sample_time;
    datetime ht_time;
    double ht_open;
    double ht_high;
    double ht_low;
    double ht_close;
    double ht_bollinger_mid;
    double ht_OHLC_max;
    double ht_OHLC_min;
    int    ht_liq_type;
    datetime lt_time;
    double lt_open;
    double lt_high;
    double lt_low;
    double lt_close;
    double lt_bollinger_mid;
    double lt_OHLC_max;
    double lt_OHLC_min;
    double lt_valid_range;
    double lt_tap_range;
    string dist_prox;
    datetime tapped_by;
    double sl;
    double entry;
    double tp;
    string dir;

    bool FromCSVLine(string line)
{
    StringReplace(line, "\"", ""); // Remove double quotes from the line
    string parts[];
    StringSplit(line, ',', parts); // Split the line using comma as a delimiter

    if(ArraySize(parts) != 27) // Check that there are 27 fields in the line
        return false;

    // Parse and populate the data structure
    sample_time = StringToTime(parts[1]);
    ht_time = StringToTime(parts[2]);
    ht_open = StringToDouble(parts[3]);
    ht_high = StringToDouble(parts[4]);
    ht_low = StringToDouble(parts[5]);
    ht_close = StringToDouble(parts[6]);
    ht_bollinger_mid = StringToDouble(parts[7]);
    ht_OHLC_max = StringToDouble(parts[8]);
    ht_OHLC_min = StringToDouble(parts[9]);
    ht_liq_type = int(StringToInteger(parts[10]));
    lt_time = StringToTime(parts[11]);
    lt_open = StringToDouble(parts[12]);
    lt_high = StringToDouble(parts[13]);
    lt_low = StringToDouble(parts[14]);
    lt_close = StringToDouble(parts[15]);
    lt_bollinger_mid = StringToDouble(parts[16]);
    lt_OHLC_max = StringToDouble(parts[17]);
    lt_OHLC_min = StringToDouble(parts[18]);
    lt_valid_range = StringToDouble(parts[19]);
    lt_tap_range = StringToDouble(parts[20]);
    dist_prox = parts[21];
    tapped_by = StringToTime(parts[22]);
    sl = StringToDouble(parts[23]);
    entry = StringToDouble(parts[24]);
    tp = StringToDouble(parts[25]);
    dir = parts[26];

    return true;
}
};


int getCsvLines(const string fName, string &l[], int flag = 0, ushort sepLine = '\n')
{
    uchar Bytes[];
    int numLines = FileLoad(fName, Bytes, flag) ? StringSplit(CharArrayToString(Bytes), sepLine, l) : 0;
    Print("getCsvLines: Read ", numLines, " lines from file: ", fName);
    return numLines;
}

bool ReadCSVFile(const string filename, Data &data[], uint &count)
{
    string lines[];
    int numLines = getCsvLines(filename, lines);

    if (numLines <= 0) {
        Print("Error reading file: ", filename);
        return false;
    }

    Print("Header: ", lines[0]);

    count = 0;
    for (int i = 1; i < numLines; i++) {
        string line = lines[i];
        if (StringLen(line) == 0) {
            Print("Skipping empty line ", i);
            continue;
        }

        Data newData;
        if (newData.FromCSVLine(line)) {
            ArrayResize(data, count + 1);
            data[count] = newData;
            count++;
        } else {
            Print("Failed to parse line ", i, ": ", line);
            break;
        }
    }

    Print("ReadCSVFile: Parsed ", count, " lines successfully.");
    return count > 0;
}


// GetCandleInformation function definition
void GetCandleInformation(string symbol, ENUM_TIMEFRAMES sample_time_tf, ENUM_TIMEFRAMES ht_time_tf, ENUM_TIMEFRAMES lt_time_tf, int index, Data& data)
{
    data.sample_time = iTime(symbol, sample_time_tf, index);
    data.ht_time = iTime(symbol, ht_time_tf, index);
    data.ht_open = iOpen(symbol, ht_time_tf, index);
    data.ht_high = iHigh(symbol, ht_time_tf, index);
    data.ht_low = iLow(symbol, ht_time_tf, index);
    data.ht_close = iClose(symbol, ht_time_tf, index);

    double ht_bollinger_mid[];
    int ht_bands_handle = iBands(symbol, ht_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(ht_bands_handle, 1, 0, 1, ht_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for HT at index ", index);
        return;
    }
    data.ht_bollinger_mid = ht_bollinger_mid[0];

    data.ht_OHLC_max = MathMax(MathMax(data.ht_open, data.ht_high), MathMax(data.ht_low, data.ht_close));
    data.ht_OHLC_min = MathMin(MathMin(data.ht_open, data.ht_high), MathMin(data.ht_low, data.ht_close));
    data.ht_liq_type = (data.ht_close > data.ht_bollinger_mid) ? 1 : 2;

    data.lt_time = iTime(symbol, lt_time_tf, index);
    data.lt_open = iOpen(symbol, lt_time_tf, index);
    data.lt_high = iHigh(symbol, lt_time_tf, index);
    data.lt_low = iLow(symbol, lt_time_tf, index);
    data.lt_close = iClose(symbol, lt_time_tf, index);

    double lt_bollinger_mid[];
    int lt_bands_handle = iBands(symbol, lt_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(lt_bands_handle, 1, 0, 1, lt_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for LT at index ", index);
        return;
    }
    data.lt_bollinger_mid = lt_bollinger_mid[0];

    data.lt_OHLC_max = MathMax(MathMax(data.lt_open, data.lt_high), MathMax(data.lt_low, data.lt_close));
    data.lt_OHLC_min = MathMin(MathMin(data.lt_open, data.lt_high), MathMin(data.lt_low, data.lt_close));
    data.lt_valid_range = data.lt_close - data.lt_open;
    data.lt_tap_range = MathAbs(data.ht_close - data.lt_close) / (data.ht_OHLC_max - data.ht_OHLC_min);
    data.dist_prox = (data.ht_liq_type == 1) ? "far" : "close";
    data.tapped_by = (data.ht_close > data.lt_close) ? data.ht_time : data.lt_time;
    data.sl = (data.ht_liq_type == 1) ? data.lt_low : data.lt_high;
    data.entry = (data.ht_liq_type == 1) ? data.lt_close - data.lt_valid_range * 2 : data.ht_close + data.lt_valid_range * 2;
  data.tp = (data.ht_liq_type == 1) ? data.lt_close - data.lt_tap_range * data.lt_valid_range : data.ht_close + data.lt_tap_range * data.lt_valid_range;
  data.dir = (data.ht_close > data.lt_close) ? "long" : "short";
}


void DrawMarkersAndLines(Data &data) {
    datetime chartTime = iTime(SymbolName, PERIOD_M15, iBarShift(SymbolName, PERIOD_M15, data.sample_time));

    if (chartTime != WRONG_VALUE) {
        // Create BuyArrow
        if (data.dir == "long") {
            ObjectCreate(ChartID(), "BuyArrow", OBJ_ARROW, 0, chartTime, data.entry);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_ARROWCODE, 233); // Up arrow
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_WIDTH, 2);
            Print("Buy Arrow created at ", TimeToString(chartTime));
        }

        // Create SellArrow
        if (data.dir == "short") {
            ObjectCreate(ChartID(), "SellArrow", OBJ_ARROW, 0, chartTime, data.entry);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_ARROWCODE, 234); // Down arrow
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_WIDTH, 2);
            Print("Sell Arrow created at ", TimeToString(chartTime));
        }

        // Create StopLoss
        ObjectCreate(ChartID(), "StopLoss", OBJ_HLINE, 0, chartTime, data.sl);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_COLOR, clrOrange);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "StopLoss", OBJPROP_TEXT, "SL");
        Print("Stop Loss created at ", TimeToString(chartTime));

        // Create TakeProfit
        ObjectCreate(ChartID(), "TakeProfit", OBJ_HLINE, 0, chartTime, data.tp);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_COLOR, clrGreen);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "TakeProfit", OBJPROP_TEXT, "TP");
        Print("Take Profit created at ", TimeToString(chartTime));
        
         // Add marker for sample_time candle
        ObjectCreate(ChartID(), "SampleTime", OBJ_VLINE, 0, data.sample_time,0);
        ObjectSetInteger(ChartID(), "SampleTime", OBJPROP_COLOR, clrYellow);
        ObjectSetInteger(ChartID(), "SampleTime", OBJPROP_WIDTH, 1);
        Print("Sample Time marker created at ", TimeToString(data.sample_time));

        // Add marker for ht_time candle
        ObjectCreate(ChartID(), "HtTime", OBJ_VLINE, 0, data.ht_time,0);
        ObjectSetInteger(ChartID(), "HtTime", OBJPROP_COLOR, clrPurple);
        ObjectSetInteger(ChartID(), "HtTime", OBJPROP_WIDTH, 1);
        Print("HT Time marker created at ", TimeToString(data.ht_time));
    } else {
        Print("Invalid chart index for sample_time=", TimeToString(data.sample_time));
    }
}

// SaveScreenshot function definition
bool save_screenshot(string screenshot_path, datetime sample_time)
{
    Print("Attempting to save screenshot: ", screenshot_path);
    
    int chartIndex = iBarShift(_Symbol, PERIOD_M15, sample_time);
    if(chartIndex == -1) {
        Print("Failed to find the chart index for sample_time=", TimeToString(sample_time));
        return false;
    }

    ChartNavigate(ChartID(), CHART_BEGIN, chartIndex); // Navigate to the specific chart index
    Sleep(5000); // Add a delay of 3 seconds before taking the screenshot
    
    if (ChartScreenShot(ChartID(), screenshot_path, 800, 600, ALIGN_RIGHT))
    {
        Print("Screenshot saved: ", screenshot_path);
        return true;
    }
    else
    {
        Print("Failed to save screenshot: ", screenshot_path);
        Print("Error: ", GetLastError());
        return false;
    }
}



// OnStart function
int OnInit()
{
    Print("OnInit Checkpoint: Initialization started");
    Print("Using file name: ", FileName);

    Data data[];
    uint count = 0;
    if (ReadCSVFile(FileName, data, count)) {
        Print("CSV file read successfully. Number of data rows: ", count);
    } else {
        Print("Error reading CSV file.");
    }

    datetime start_time = StringToTime(StartTime);
datetime end_time = StringToTime(EndTime);

for (uint i = 0; i < count; i++) {
    if (data[i].sample_time < start_time || data[i].sample_time > end_time) {
        continue;
    }
        Print("Current index: ", i);
        Print("Sample time: ", TimeToString(data[i].sample_time));

        int chartIndex = iBarShift(_Symbol, PERIOD_M15, data[i].sample_time);

        Print("Chart index: ", chartIndex);
        GetCandleInformation(SymbolName, PERIOD_M15, PERIOD_H4, PERIOD_M15, chartIndex, data[i]);
        Sleep(3000);
        ChartNavigate(ChartID(), CHART_BEGIN, chartIndex - 10);
        DrawMarkersAndLines(data[i]);

        ChartRedraw();
        string file_name = StringFormat("s_t_%s_%s_%d.jpg", FileName, TimeToString(data[i].sample_time, TIME_DATE | TIME_SECONDS), i);
        StringReplace(file_name, ":", "_"); // Replace colons with underscores
        StringReplace(file_name, " ", "_"); // Replace spaces with underscores

        string screenshot_path = ScreenshotFolder + "\\" + file_name;

        if (!save_screenshot(screenshot_path, data[i].sample_time)) {
            Print("Failed to save screenshot: ", screenshot_path);
            Print("Failed to save screenshot for index: ", i);
        }
    }
    return(INIT_SUCCEEDED);
}
 
bump
 
Manu Duarte #: bump

Please be patient! It's Friday night or Saturday morning and the forum is usually less visited during weekends.

I've never used the screenshot functionality, so I can't offer you any advice, but be patient as someone will eventually offer some guidance.

 
Manu Duarte #:
Screenshots are now being sucesfully saved and created. I have a new problem. The screenshot is always taken in the real time chart, and doesn't go back to the sample data where all the markers are created.
Here is the updated script:

Did you try to search the forum before asking ?

ChartScreenShot() always saves the latest position of the chart
ChartScreenShot() always saves the latest position of the chart
  • 2020.07.25
  • www.mql5.com
Hey guys, I have a problem with ChartScreenShot(). I want to save screenshots from a specific time...
 
Alain Verleyen #:

Did you try to search the forum before asking ?

Yes. 

I used ALLIGN_LEFT before but it still doesn't take screenshots at the exact sample data in my script. It takes screenshots in different places but wrong places.


I want it to go to the chart index of the sample data.

int chartIndex = iBarShift(_Symbol, PERIOD_M15, data[i].sample_time);

ChartNavigate(ChartID(), CHART_BEGIN, chartIndex); // Navigate to the specific chart index

But it doesn't go there.

 
Manu Duarte #:

Yes. 

I used ALLIGN_LEFT before but it still doesn't take screenshots at the exact sample data in my script. It takes screenshots in different places but wrong places.


I want it to go to the chart index of the sample data.


But it doesn't go there.

You said you used ALIGN_LEFT but your posted code show something else.

We don't know what you are doing, what you are expecting ?

Show the values used (index, datetime...) and the screenshot you get.

Otherwise it's just waste of time.

 
Alain Verleyen #:

You said you used ALIGN_LEFT but your posted code show something else.

We don't know what you are doing, what you are expecting ?

Show the values used (index, datetime...) and the screenshot you get.

Otherwise it's just waste of time.

You're right. I'm sorry. I figured it out and now screenshots are being taken perfectly. I have one last issue.

I need to build a period separator. So if lower timeframe is M15 and higher timeframe is H4, draw a shaded region (like in the photo attached) on the lower timeframe candles to separate every H4 candle. So 1 H4 candle is 1 shaded region on M15. Basically dividing M15 candles into H4 regions. For example: if 1999.01.19 20:00:00 is the higher timeframe candle, the left-most candle in the shaded region is 1999.01.19 20:00:00, and the right-most candle that should be marked (with the same colour) is 1999.01.19 23:45:00.


Need to add this background for all M15 candles. (image.png attached)

I want to use this indicator https://www.mql5.com/en/code/86 for the background object.

I'm not even sure where I should start and how to do it, can someone give me a suggestion?

Here's the updated code:

//+------------------------------------------------------------------+
//|                                      Expert Advisor Template.mq5 |
//|                                                                  |
//+------------------------------------------------------------------+

// Include necessary files


#include <Files/File.mqh>
#include <Files/FileBin.mqh>
#include <Arrays/ArrayString.mqh>
#include <Arrays/ArrayObj.mqh>

input string FileName = "test.csv";      // CSV file name
input string SymbolName = "GBPUSD";      // Symbol name
input string StartTime = "1999.01.15 20:45:00"; // Start time for the visualizer
input string EndTime = "2022.06.02 05:00:00";   // End time for the visualizer
input string ScreenshotFolder = "Screenshots"; // Folder name for storing screenshots


// Declare your custom structure for the data
struct Data
{
    datetime sample_time;
    datetime ht_time;
    double ht_open;
    double ht_high;
    double ht_low;
    double ht_close;
    double ht_bollinger_mid;
    double ht_OHLC_max;
    double ht_OHLC_min;
    int    ht_liq_type;
    datetime lt_time;
    double lt_open;
    double lt_high;
    double lt_low;
    double lt_close;
    double lt_bollinger_mid;
    double lt_OHLC_max;
    double lt_OHLC_min;
    double lt_valid_range;
    double lt_tap_range;
    string dist_prox;
    datetime tapped_by;
    double sl;
    double entry;
    double tp;
    string dir;

    bool FromCSVLine(string line)
{
    StringReplace(line, "\"", ""); // Remove double quotes from the line
    string parts[];
    StringSplit(line, ',', parts); // Split the line using comma as a delimiter

    if(ArraySize(parts) != 27) // Check that there are 27 fields in the line
        return false;

    // Parse and populate the data structure
    sample_time = StringToTime(parts[1]);
    ht_time = StringToTime(parts[2]);
    ht_open = StringToDouble(parts[3]);
    ht_high = StringToDouble(parts[4]);
    ht_low = StringToDouble(parts[5]);
    ht_close = StringToDouble(parts[6]);
    ht_bollinger_mid = StringToDouble(parts[7]);
    ht_OHLC_max = StringToDouble(parts[8]);
    ht_OHLC_min = StringToDouble(parts[9]);
    ht_liq_type = int(StringToInteger(parts[10]));
    lt_time = StringToTime(parts[11]);
    lt_open = StringToDouble(parts[12]);
    lt_high = StringToDouble(parts[13]);
    lt_low = StringToDouble(parts[14]);
    lt_close = StringToDouble(parts[15]);
    lt_bollinger_mid = StringToDouble(parts[16]);
    lt_OHLC_max = StringToDouble(parts[17]);
    lt_OHLC_min = StringToDouble(parts[18]);
    lt_valid_range = StringToDouble(parts[19]);
    lt_tap_range = StringToDouble(parts[20]);
    dist_prox = parts[21];
    tapped_by = StringToTime(parts[22]);
    sl = StringToDouble(parts[23]);
    entry = StringToDouble(parts[24]);
    tp = StringToDouble(parts[25]);
    dir = parts[26];

    return true;
}
};


int getCsvLines(const string fName, string &l[], int flag = 0, ushort sepLine = '\n')
{
    uchar Bytes[];
    int numLines = FileLoad(fName, Bytes, flag) ? StringSplit(CharArrayToString(Bytes), sepLine, l) : 0;
    Print("getCsvLines: Read ", numLines, " lines from file: ", fName);
    return numLines;
}

bool ReadCSVFile(const string filename, Data &data[], uint &count)
{
    string lines[];
    int numLines = getCsvLines(filename, lines);

    if (numLines <= 0) {
        Print("Error reading file: ", filename);
        return false;
    }

    Print("Header: ", lines[0]);

    count = 0;
    for (int i = 1; i < numLines; i++) {
        string line = lines[i];
        if (StringLen(line) == 0) {
            Print("Skipping empty line ", i);
            continue;
        }

        Data newData;
        if (newData.FromCSVLine(line)) {
            ArrayResize(data, count + 1);
            data[count] = newData;
            count++;
        } else {
            Print("Failed to parse line ", i, ": ", line);
            break;
        }
    }

    Print("ReadCSVFile: Parsed ", count, " lines successfully.");
    return count > 0;
}


// GetCandleInformation function definition
void GetCandleInformation(string symbol, ENUM_TIMEFRAMES sample_time_tf, ENUM_TIMEFRAMES ht_time_tf, ENUM_TIMEFRAMES lt_time_tf, int index, Data& data)
{
    data.sample_time = iTime(symbol, sample_time_tf, index);
    data.ht_open = iOpen(symbol, ht_time_tf, index);
    data.ht_high = iHigh(symbol, ht_time_tf, index);
    data.ht_low = iLow(symbol, ht_time_tf, index);
    data.ht_close = iClose(symbol, ht_time_tf, index);

    double ht_bollinger_mid[];
    int ht_bands_handle = iBands(symbol, ht_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(ht_bands_handle, 1, 0, 1, ht_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for HT at index ", index);
        return;
    }
    data.ht_bollinger_mid = ht_bollinger_mid[0];

    data.ht_OHLC_max = MathMax(MathMax(data.ht_open, data.ht_high), MathMax(data.ht_low, data.ht_close));
    data.ht_OHLC_min = MathMin(MathMin(data.ht_open, data.ht_high), MathMin(data.ht_low, data.ht_close));
    data.ht_liq_type = (data.ht_close > data.ht_bollinger_mid) ? 1 : 2;

    data.lt_time = iTime(symbol, lt_time_tf, index);
    data.lt_open = iOpen(symbol, lt_time_tf, index);
    data.lt_high = iHigh(symbol, lt_time_tf, index);
    data.lt_low = iLow(symbol, lt_time_tf, index);
    data.lt_close = iClose(symbol, lt_time_tf, index);

    double lt_bollinger_mid[];
    int lt_bands_handle = iBands(symbol, lt_time_tf, 20, 2, 0, PRICE_CLOSE);
    if(CopyBuffer(lt_bands_handle, 1, 0, 1, lt_bollinger_mid) != 1)
    {
        Print("Failed to get bollinger mid value for LT at index ", index);
        return;
    }
    data.lt_bollinger_mid = lt_bollinger_mid[0];

    data.lt_OHLC_max = MathMax(MathMax(data.lt_open, data.lt_high), MathMax(data.lt_low, data.lt_close));
    data.lt_OHLC_min = MathMin(MathMin(data.lt_open, data.lt_high), MathMin(data.lt_low, data.lt_close));
    data.lt_valid_range = data.lt_close - data.lt_open;
    data.lt_tap_range = MathAbs(data.ht_close - data.lt_close) / (data.ht_OHLC_max - data.ht_OHLC_min);
    data.dist_prox = (data.ht_liq_type == 1) ? "far" : "close";
    data.tapped_by = (data.ht_close > data.lt_close) ? data.ht_time : data.lt_time;
    data.sl = (data.ht_liq_type == 1) ? data.lt_low : data.lt_high;
    data.entry = (data.ht_liq_type == 1) ? data.lt_close - data.lt_valid_range * 2 : data.ht_close + data.lt_valid_range * 2;
  data.tp = (data.ht_liq_type == 1) ? data.lt_close - data.lt_tap_range * data.lt_valid_range : data.ht_close + data.lt_tap_range * data.lt_valid_range;
  data.dir = (data.ht_close > data.lt_close) ? "long" : "short";
}


void DrawMarkersAndLines(Data &data) {
    datetime chartTime = iTime(SymbolName, PERIOD_M15, iBarShift(SymbolName, PERIOD_M15, data.sample_time));

    if (chartTime != WRONG_VALUE) {
        // Create BuyArrow
        if (data.dir == "long") {
            ObjectCreate(ChartID(), "BuyArrow", OBJ_ARROW, 0, chartTime, data.entry);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_ARROWCODE, 233); // Up arrow
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_COLOR, clrBlue);
            ObjectSetInteger(ChartID(), "BuyArrow", OBJPROP_WIDTH, 1);
            Print("Buy Arrow created at ", TimeToString(chartTime));
        }

        // Create SellArrow
        if (data.dir == "short") {
            double sellArrowPrice = data.lt_close + data.lt_valid_range * 2; // Calculate the correct price for Sell Arrow
            ObjectCreate(ChartID(), "SellArrow", OBJ_ARROW, 0, chartTime, sellArrowPrice);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_ARROWCODE, 234); // Down arrow
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_COLOR, clrRed);
            ObjectSetInteger(ChartID(), "SellArrow", OBJPROP_WIDTH, 1);
            Print("Sell Arrow created at ", TimeToString(chartTime));

        }

        // Create StopLoss
        ObjectCreate(ChartID(), "StopLoss", OBJ_HLINE, 0, chartTime, data.sl);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_COLOR, clrOrange);
        ObjectSetInteger(ChartID(), "StopLoss", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "StopLoss", OBJPROP_TEXT, "SL");
        Print("Stop Loss created at ", TimeToString(chartTime));

        // Create TakeProfit
        ObjectCreate(ChartID(), "TakeProfit", OBJ_HLINE, 0, chartTime, data.tp);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_COLOR, clrGreen);
        ObjectSetInteger(ChartID(), "TakeProfit", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "TakeProfit", OBJPROP_TEXT, "TP");
        Print("Take Profit created at ", TimeToString(chartTime));
        
          // Create Entry Line
        ObjectCreate(ChartID(), "Entry", OBJ_HLINE, 0, chartTime, data.entry);
        ObjectSetInteger(ChartID(), "Entry", OBJPROP_COLOR, clrAqua);
        ObjectSetInteger(ChartID(), "Entry", OBJPROP_WIDTH, 1);
        ObjectSetString(ChartID(), "Entry", OBJPROP_TEXT, "Entry");
        Print("Entry line created at ", TimeToString(chartTime));
        

    } else {
        Print("Invalid chart index for sample_time=", TimeToString(data.sample_time));
    }
}

// SaveScreenshot function definition
bool save_screenshot(string screenshot_path, int chartShift)
{
    Print("Attempting to save screenshot: ", screenshot_path);
    
    int centeredChartShift = chartShift - 70; // Adjust the number '10' to shift the desired number of candles before the chartShift
    if (centeredChartShift < 0) {
        centeredChartShift = 0;
    }

    ChartNavigate(ChartID(), CHART_BEGIN, centeredChartShift); // Navigate to the specific chart shift
    Sleep(1000);
    if (ChartScreenShot(ChartID(), screenshot_path, 1024, 768, ALIGN_LEFT))
    {
        Print("Screenshot saved: ", screenshot_path);
        return true;
    }
    else
    {
        Print("Failed to save screenshot: ", screenshot_path);
        Print("Error: ", GetLastError());
        return false;
    }
}


// OnStart function
int OnInit()
{
    Print("OnInit Checkpoint: Initialization started");
    Print("Using file name: ", FileName);

    Data data[];
    uint count = 0;
    if (ReadCSVFile(FileName, data, count)) {
        Print("CSV file read successfully. Number of data rows: ", count);
    } else {
        Print("Error reading CSV file.");
    }

    datetime start_time = StringToTime(StartTime);
    datetime end_time = StringToTime(EndTime);

    int totalBars = iBars(_Symbol, PERIOD_M15);

    for (uint i = 0; i < count; i++) {
        if (data[i].sample_time < start_time || data[i].sample_time > end_time) {
            continue;
        }
        Print("Current index: ", i);
        Print("Sample time: ", TimeToString(data[i].sample_time));

        int chartIndex = iBarShift(_Symbol, PERIOD_M15, data[i].sample_time);
        
        Print("Chart index: ", chartIndex);
        GetCandleInformation(SymbolName, PERIOD_M15, PERIOD_H4, PERIOD_M15, chartIndex, data[i]);
        Sleep(1000);
        ChartNavigate(ChartID(), CHART_BEGIN, chartIndex - 10);
        
        DrawMarkersAndLines(data[i]);
        
        string file_name = StringFormat("s_t_%s_%s_%d.jpg", FileName, TimeToString(data[i].sample_time, TIME_DATE | TIME_SECONDS), i);
        StringReplace(file_name, ":", "_"); // Replace colons with underscores
        StringReplace(file_name, " ", "_"); // Replace spaces with underscores
        string screenshot_path = ScreenshotFolder + "\\" + file_name;

        // Calculate the chart shift value to navigate from the oldest to the most recent bar
        int chartShift = totalBars - chartIndex - 1;

        if (!save_screenshot(screenshot_path, chartShift)) {
            Print("Failed to save screenshot: ", screenshot_path);
            Print("Failed to save screenshot for index: ", i);
        }
        ChartRedraw();
    }
    return(INIT_SUCCEEDED);
}  
      
Trade Sessions Indicator
Trade Sessions Indicator
  • www.mql5.com
This indicator is based on DRAW_FILLING buffers. The input parameters are absent, the TimeTradeServer(), TimeGMT() functions are used.
Files:
image.png  3 kb
Reason: