Viewing the resource: Building a ChatGPT-Powered EA with an Interactive Dashboard in MQL5 for AI-Assisted Forex Trading

Building a ChatGPT-Powered EA with an Interactive Dashboard in MQL5 for AI-Assisted Forex Trading

Allan Munene Mutiiria 2025-10-16 15:32:19 760 Views
This article details the construction of an MQL5 Expert Advisor that integrates ChatGPT via API for ...

Introduction


In the fast-paced world of forex trading, having an edge means blending human intuition with cutting-edge tech—like tapping into AI for real-time insights right inside your MetaTrader 5 terminal. Manually querying ChatGPT for market analysis or strategies can be a hassle, pulling you away from the charts. This article dives into an MQL5 Expert Advisor (EA) that embeds a full ChatGPT interface as an interactive dashboard, letting you chat with AI, append chart data, manage chat histories (stored with Advanced Encryption Standard (AES256) encryption and ZIP compression), and get responses without leaving your platform. It handles API calls, encrypts chats for privacy, scales UI for crisp visuals, and adds smooth scrolling/hovers for that pro feel, especially for multiline inputs. We'll break down the blueprint, step-by-step code, testing vibes, and takeaways to help traders of all stripes build their own AI sidekick. It is typically a major overhaul of the previous starter lesson that we had. If hands-on demos are your thing, swing by our YouTube video for a guide!


Implementation Blueprint


In AI-driven trading platforms, the ability to manage multiline inputs is vital, as it lets users submit elaborate instructions or information—like extended market analyses or code fragments—allowing the system to handle intricate requests fully without cutting them off. This is key for delivering precise outputs in fast-paced financial environments, where one-line entries often fail to capture sufficient background. Maintaining chat history further enhances usability by preserving dialogue across interactions, so users can leverage earlier AI suggestions without redundant explanations. Meanwhile, generating trade signals involves the AI evaluating market trends to offer practical buy or sell advice, cutting down on hands-on review and enabling quicker reactions to shifts like pattern changes. Collectively, these elements form a stronger framework, boosting efficiency through sustained context and blending AI with live trading choices to cut down on mistakes and boost returns, as we intend to. Our strategy involves enhancing the AI program that we had already created in our YouTube lesson earlier on with sophisticated text management to support multiline entries, given that the existing setup restricts inputs to a smooth 63 characters at most, confining us to basic queries. We'll broaden this capacity to accommodate extensive lines as required, since certain scenarios demand more in-depth descriptions when directing the AI for trade alerts. Additionally, we'll integrate reliable storage solutions for chat continuity, facilitating simple access and review of prior exchanges to avoid unnecessary repetition when citing past details. For safeguarding, we'll apply the Advanced Encryption Standard (AES) approach to secure the conversations. We selected this for its straightforward implementation, though alternatives are fine too. We won't delve into the security mechanics extensively, but we've put together a diagram illustrating its operation, shown below.

The concept is that users might occasionally engage in a dialogue centered on examining a specific chart, such as XAUUSD, before launching a separate one for something like GBPUSD. Eventually, they could need to revisit that prior exchange—for example, to review earlier replies, fix inaccuracies, or add a fresh input. Instead of rehashing the full discussion, they can easily point to the preserved record.

To bring this to life and track improvements, we'll introduce capabilities for pulling in and combining chart details to create starting trade alerts through AI evaluation. This will call for overhauling the user interface to infuse more branding via icons and a sidebar for navigation. We'll build a UI featuring easy-to-use controls for overseeing conversations and presenting signals, making the setup approachable and streamlined for those looking to harness AI in their trading tactics. Check out the illustration below to preview our end goal.


Implementation of the ChatGPT AI Expert Advisor


To roll out this enhanced AI trading beast in MQL5, we'll kick things off with some smart code modularization—think of it as decluttering your digital closet. We'll split off the files we don't need front and center from the ones that are the stars of the show. Remember how we chatted about isolating that JSON file way back? Well, grab the popcorn because now's the big reveal—it's go time! We'll also whip up a dedicated function for wrangling bitmap files, yank it out into its own file, and then pull it back in. Why all this fuss? Because life's too short for tangled spaghetti code, this setup makes debugging a breeze, updates a walk in the park, and keeps your sanity intact when the market's throwing curveballs. Plus, who doesn't love a tidy codebase that doesn't make you want to pull your hair out? It is really important to see that lesson that we did on our YouTube channel to get what we are specifically talking about. Here is the YouTube link for that. We can't insist enough.

//+------------------------------------------------------------------+
//|                                         AI ChatGPT EA Part 4.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict
#property icon "1. Forex Algo-Trader.ico"

#include "AI JSON FILE.mqh"                        //--- Include JSON parsing library
#include "AI CREATE OBJECTS FNS.mqh"               //--- Include object creation functions
We craft these files as handy includes—basically, reusable chunks of code that save you from reinventing the wheel every time. As you'll spot in the example, we weave them into our main program with the trusty #include directive, like inviting your favorite sidekicks to the party. For the sake of keeping things hilariously simple (and avoiding unnecessary folder-hopping adventures), we plopped them right in the base directory alongside the core script—that's why we're rocking the double quotes style in the include statement. It's like saying, "Hey, compiler, these buddies are right here in the room with us—no need to go searching the house." But hey, if your files are chilling in a different folder (maybe they're introverts who need their space), swap those quotes for angle brackets (< >) and spell out the full path correctly. Why? Because otherwise, the compiler throws a tantrum like a toddler who can't find their toy, and nobody wants build errors crashing the fun. You can read the documentation, but see what we mean here.

#include <file_name>
#include "file_name"
This is what this means:

The preprocessor replaces the line #include <file_name> with the content of the file_name. Angle brackets indicate that the file_name file will be taken from the standard directory (usually it is terminal_installation_directory\MQL5\Include). The current directory is not included in the search.

If the file name is enclosed in quotation marks, the search is made in the current directory (which contains the main source file). The standard directory is not included in the search.

Check out the visual below for a laughably clear demo—it's basically the "before and after" of code organization that won't put you to sleep! 😂

We just shifted the code segments. We will need to have a function for handling the bitmap labels, so we need a function for that.

//+------------------------------------------------------------------+
//| Creates a bitmap label object                                    |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                          //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) { //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      return false;                                            //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);         //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);         //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);       //--- Set corner
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);   //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);          //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);         //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);    //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);      //--- Disable selection
   return true;                                                //--- Return success
}
We put together a handy function for whipping up bitmap labels, which will let us splash scaled icons and images all over the UI—just like what you spotted in that earlier rundown. Why bother? Well, because who wants a boring, text-only interface when you can jazz it up with visuals that make trading feel less like staring at a spreadsheet and more like a video game? In the "createBitmapLabel" function, we fire up the ObjectCreate function to birth a bitmap label (that's OBJ_BITMAP_LABEL for the tech-savvy) using given positions ("xDistance" and "yDistance" to pinpoint where it lands), dimensions ("xSize" and "ySize" so it doesn't awkwardly overflow like an overfilled coffee cup), the path to the bitmap file, a splash of color, and a default corner setup (CORNER_LEFT_UPPER, because starting from the top-left keeps things tidy and predictable—nobody likes surprises in code alignment).

We then tweak its traits with stuff like "OBJPROP_BMPFILE" to slap on the actual image, make sure it's not selectable (to avoid accidental clicks turning into chaos), and push it to the front layer using the ObjectSetInteger function. If anything goes sideways during creation, we log the flop with a Print statement—think of it as the code's way of yelling "Help!" so we can debug without pulling our hair out. Overall, here's the complete setup for that object creation script, because modular code is like a well-organized toolbox: it saves you from digging through a mess every time you need a screwdriver.

//+------------------------------------------------------------------+
//|                                        AI CREATE OBJECTS FNS.mqh |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"

//+------------------------------------------------------------------+
//| Creates a rectangle label object                                 |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                    color bgColor, int borderWidth, color borderColor = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {   //--- Create rectangle label
   ResetLastError();                                                 //--- Reset previous errors
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) {    //--- Attempt creation
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Print error
      return (false);                                                //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);         //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);         //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);       //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);     //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, borderWidth); //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not background
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not pressed
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw chart
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates a button object                                          |
//+------------------------------------------------------------------+
bool createButton(string objName, int xDistance, int yDistance, int xSize, int ySize,
                  string text = "", color textColor = clrBlack, int fontSize = 12,
                  color bgColor = clrNONE, color borderColor = clrNONE,
                  string font = "Arial Rounded MT Bold",
                  ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, bool isBack = false) { //--- Create button
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {     //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the button! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);       //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);       //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);   //--- Set background
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);       //--- Set back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not pressed
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates an edit field object                                     |
//+------------------------------------------------------------------+
bool createEdit(string objName, int xDistance, int yDistance, int xSize, int ySize,
                string text = "", color textColor = clrBlack, int fontSize = 12,
                color bgColor = clrNONE, color borderColor = clrNONE,
                string font = "Arial Rounded MT Bold",
                ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER,
                int align = ALIGN_LEFT, bool readOnly = false) {  //--- Create edit
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_EDIT, 0, 0, 0)) {      //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the edit! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);       //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);       //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor);   //--- Set background
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_ALIGN, align);       //--- Set alignment
   ObjectSetInteger(0, objName, OBJPROP_READONLY, readOnly); //--- Set read-only
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not active
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}
//+------------------------------------------------------------------+
//| Creates a text label object                                      |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xDistance, int yDistance,
                 string text, color textColor = clrBlack, int fontSize = 12,
                 string font = "Arial Rounded MT Bold",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER,
                 ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER) {   //--- Create label
   ResetLastError();                                         //--- Reset errors
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {     //--- Attempt creation
      Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Print error
      return (false);                                        //--- Failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);     //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text);          //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);   //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);          //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);        //--- Not back
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);       //--- Not active
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);  //--- Not selectable
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);    //--- Not selected
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor);     //--- Set anchor
   ChartRedraw(0);                                           //--- Redraw
   return (true);                                            //--- Success
}

//+------------------------------------------------------------------+
//| Creates a bitmap label object                                    |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xDistance, int yDistance, int xSize, int ySize,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                          //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) { //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      return false;                                            //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance); //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance); //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xSize);         //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, ySize);         //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);       //--- Set corner
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);   //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);          //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);         //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);    //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);      //--- Disable selection
   return true;                                                //--- Return success
}
We're sticking with a similar setup for the JSON handling, but with a nifty little tweak to better manage turning those integer and double strings into something usable—think of it as giving the code a caffeine boost so it doesn't choke on numbers disguised as text, keeping everything running smoothly without those pesky conversion errors that could turn a trading day into a debugging nightmare. Next up on our to-do list: sprucing up how we define and load those image icons as bitmap files, because visuals are the spice of life in a UI, and nobody wants a bland interface that puts traders to sleep faster than a monotonous market lull.

No sweat over their original sizes—we'll handle the resizing ourselves to keep things straightforward and avoid any awkward stretching or squishing that might make your icons look like they survived a funhouse mirror. To cut down on headaches, we're parking these images right in the main directory, so path hunting doesn't become a wild goose chase that wastes precious coding time. Just a heads-up: pay attention to the file formats here, since we're locked into bitmap files only—MQL5's picky that way, ensuring compatibility without format roulette that could crash your setup. Take a peek at the example setup below for how we're rolling with it in this scenario.

When you are ready with the files, we will need to include them so we can use them. We will create them as resources so that they are available in the final program, so they will not require the user to always have the files after compilation. Here is the approach we take to achieve that.

#resource "AI MQL5.bmp"
#define resourceImg "::AI MQL5.bmp"                //--- Define main image resource
#resource "AI LOGO.bmp"
#define resourceImgLogo "::AI LOGO.bmp"            //--- Define logo image resource
#resource "AI NEW CHAT.bmp"
#define resourceNewChat "::AI NEW CHAT.bmp"        //--- Define new chat icon resource
#resource "AI CLEAR.bmp"
#define resourceClear "::AI CLEAR.bmp"             //--- Define clear icon resource
#resource "AI HISTORY.bmp"
#define resourceHistory "::AI HISTORY.bmp"         //--- Define history icon resource
With the #resource directive, we incorporate five bitmap files—"AI MQL5.bmp", "AI LOGO.bmp", "AI NEW CHAT.bmp", "AI CLEAR.bmp", and "AI HISTORY.bmp"—and link them to constants like "resourceImg", "resourceImgLogo", "resourceNewChat", "resourceClear", and "resourceHistory" via the #define directive, ensuring uniform access across the entire program. Why go through this ritual? Because hardcoding file paths everywhere is a recipe for typos and maintenance migraines—think of it as giving your code a cheat sheet so it doesn't forget where the pretty pictures are hiding, keeping things tidy and error-free like a well-organized sock drawer.

This setup paves the way for weaving in our bespoke icons on the primary dashboard emblem, the sidebar badge, and those handy action triggers, jazzing up the look and feel to make the UI less of a snooze-fest and more intuitive for users who don't want to hunt for buttons like buried treasure. We'll also have to toss in extra inputs and global variables to wrangle the fresh dashboard components, because without them, it's like building a car without wheels—looks great but goes nowhere fast.

#define P_SCROLL_LEADER "ChatGPT_P_Scroll_Leader"  //--- Define prompt scrollbar leader name
#define P_SCROLL_UP_REC "ChatGPT_P_Scroll_Up_Rec"  //--- Define prompt scroll up rectangle name
#define P_SCROLL_UP_LABEL "ChatGPT_P_Scroll_Up_Label" //--- Define prompt scroll up label name
#define P_SCROLL_DOWN_REC "ChatGPT_P_Scroll_Down_Rec" //--- Define prompt scroll down rectangle name
#define P_SCROLL_DOWN_LABEL "ChatGPT_P_Scroll_Down_Label" //--- Define prompt scroll down label name
#define P_SCROLL_SLIDER "ChatGPT_P_Scroll_Slider"  //--- Define prompt scrollbar slider name

input string OpenAI_Model = "gpt-4o";              // OpenAI model for API requests

input int MaxChartBars = 10;                       // Maximum recent bars to fetch details

string conversationHistory = "";                    //--- Store conversation history
string currentPrompt = "";                         //--- Store current user prompt
int logFileHandle = INVALID_HANDLE;                //--- Store log file handle
bool button_hover = false;                         //--- Track submit button hover state
color button_original_bg = clrRoyalBlue;           //--- Set submit button background color
color button_darker_bg;                            //--- Store submit button darker background
bool clear_hover = false;                          //--- Track clear button hover state
bool new_chat_hover = false;                       //--- Track new chat button hover state
color clear_original_bg = clrLightCoral;           //--- Set clear button background color
color clear_darker_bg;                             //--- Store clear button darker background
color new_chat_original_bg = clrLightBlue;         //--- Set new chat button background color
color new_chat_darker_bg;                          //--- Store new chat button darker background
color chart_button_bg = clrLightGreen;             //--- Set chart button background color
color chart_button_darker_bg;                      //--- Store chart button darker background
bool chart_hover = false;                          //--- Track chart button hover state
bool close_hover = false;                          //--- Track close button hover state
color close_original_bg = clrLightGray;            //--- Set close button background color
color close_darker_bg;                             //--- Store close button darker background
int g_sidebarWidth = 150;                         //--- Set sidebar width
int g_dashboardX = 10;                            //--- Set dashboard x position
int g_mainContentX = g_dashboardX + g_sidebarWidth; //--- Calculate main content x position
int g_mainY = 30;                                 //--- Set main content y position
int g_mainWidth = 550;                            //--- Set main content width
int g_dashboardWidth = g_sidebarWidth + g_mainWidth; //--- Calculate total dashboard width
int g_mainHeight = 0;                             //--- Store calculated main height
int g_padding = 10;                               //--- Set general padding
int g_sidePadding = 6;                            //--- Set side padding
int g_textPadding = 10;                           //--- Set text padding
int g_headerHeight = 40;                          //--- Set header height
int g_displayHeight = 280;                        //--- Set display height
int g_footerHeight = 180;                         //--- Set footer height
int g_promptHeight = 130;                         //--- Set prompt area height
int g_margin = 5;                                 //--- Set margin
int g_buttonHeight = 36;                          //--- Set button height
int g_editHeight = 25;                            //--- Set edit field height
int g_lineSpacing = 2;                            //--- Set line spacing
int g_editW = 0;                                  //--- Store edit field width
bool scroll_visible = false;                       //--- Track main scrollbar visibility
bool mouse_in_display = false;                    //--- Track mouse in main display area
int scroll_pos = 0;                               //--- Store main scroll position
int prev_scroll_pos = -1;                         //--- Store previous main scroll position
int slider_height = 20;                           //--- Set main slider height
bool movingStateSlider = false;                   //--- Track main slider drag state
int mlbDownX_Slider = 0;                          //--- Store main slider mouse x position
int mlbDownY_Slider = 0;                          //--- Store main slider mouse y position
int mlbDown_YD_Slider = 0;                        //--- Store main slider y distance
int g_total_height = 0;                           //--- Store total main display height
int g_visible_height = 0;                         //--- Store visible main display height
bool p_scroll_visible = false;                    //--- Track prompt scrollbar visibility
bool mouse_in_prompt = false;                     //--- Track mouse in prompt area
int p_scroll_pos = 0;                             //--- Store prompt scroll position
int p_slider_height = 20;                         //--- Set prompt slider height
bool p_movingStateSlider = false;                 //--- Track prompt slider drag state
int p_mlbDownX_Slider = 0;                        //--- Store prompt slider mouse x position
int p_mlbDownY_Slider = 0;                        //--- Store prompt slider mouse y position
int p_mlbDown_YD_Slider = 0;                      //--- Store prompt slider y distance
int p_total_height = 0;                           //--- Store total prompt height
int p_visible_height = 0;                         //--- Store visible prompt height
color g_promptBg = clrOldLace;                    //--- Set prompt background color
string g_scaled_image_resource = "";               //--- Store scaled main image resource
string g_scaled_sidebar_resource = "";            //--- Store scaled sidebar image resource
string g_scaled_newchat_resource = "";            //--- Store scaled new chat icon resource
string g_scaled_clear_resource = "";              //--- Store scaled clear icon resource
string g_scaled_history_resource = "";            //--- Store scaled history icon resource
bool dashboard_visible = true;                     //--- Track dashboard visibility
string dashboardObjects[20];                      //--- Store dashboard object names
int objCount = 0;                                 //--- Track number of dashboard objects
In this section, we kick things off by tossing in the fresh scrollbar setups, then swap out the AI model for a beefier one (gpt-4o), because we're gearing up to juggle trickier datasets and snag sharper replies—especially when sensitive stuff like trading signals is on the line, where mediocre answers could mean missing out on that golden opportunity or worse, a facepalm-worthy trade flop. Feel free to pick whatever model floats your boat, though. We toss in a few extra global variables too, to wrangle the upcoming logic additions without everything turning into a tangled spaghetti code mess—think of them as the unsung heroes keeping our script from imploding under its own weight. We've sprinkled in comments for that extra clarity boost, so even if you're half-asleep from market volatility, you won't get lost in the weeds. Joking though! 😂 I meant the normal weed, like weed. You know weed? With that groundwork laid, we dive into the actual building phase, starting by whipping up some utility functions to resize those images, ensuring they fit just right without looking like they got stretched on a medieval rack or squished by a steamroller, just like we promised you not to bother altering their sizes. Like our image as we did visualize is 561 by 214 pixels and we will need to make is smaller like say 30 by 30 pixels? I don't know man, guess we'll find out soon. Forex Algo-Trader team is here, we gat you!

Here is the code though to accomplish that. This's some long journey we got, so no pressure from above, just below 👌

//+------------------------------------------------------------------+
//| Scale Image Using Bicubic Interpolation                          |
//+------------------------------------------------------------------+
void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) {
   uint scaled_pixels[];                          //--- Declare array for scaled pixels
   ArrayResize(scaled_pixels, new_width * new_height); //--- Resize scaled pixel array
   for (int y = 0; y < new_height; y++) {        //--- Iterate through new height
      for (int x = 0; x < new_width; x++) {      //--- Iterate through new width
         double original_x = (double)x * original_width / new_width; //--- Calculate original x coordinate
         double original_y = (double)y * original_height / new_height; //--- Calculate original y coordinate
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel color
         scaled_pixels[y * new_width + x] = pixel; //--- Store interpolated pixel
      }
   }
   ArrayResize(pixels, new_width * new_height);   //--- Resize original pixel array
   ArrayCopy(pixels, scaled_pixels);              //--- Copy scaled pixels to original array
}

//+------------------------------------------------------------------+
//| Perform Bicubic Interpolation for a Pixel                        |
//+------------------------------------------------------------------+
uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) {
   int x0 = (int)x;                               //--- Get integer x coordinate
   int y0 = (int)y;                               //--- Get integer y coordinate
   double fractional_x = x - x0;                  //--- Calculate fractional x
   double fractional_y = y - y0;                  //--- Calculate fractional y
   int x_indices[4], y_indices[4];                //--- Declare arrays for neighbor indices
   for (int i = -1; i <= 2; i++) {               //--- Iterate to set indices
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Clamp x indices
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Clamp y indices
   }
   uint neighborhood_pixels[16];                  //--- Declare array for 4x4 pixel neighborhood
   for (int j = 0; j < 4; j++) {                 //--- Iterate through y indices
      for (int i = 0; i < 4; i++) {              //--- Iterate through x indices
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store neighbor pixel
      }
   }
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare arrays for color components
   for (int i = 0; i < 16; i++) {                //--- Iterate through neighborhood pixels
      GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Extract ARGB components
   }
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha component
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red component
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green component
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue component
   return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine components into pixel color
}

//+------------------------------------------------------------------+
//| Perform Bicubic Interpolation for a Color Component              |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   double weights_x[4];                           //--- Declare x interpolation weights
   double t = fractional_x;                       //--- Set x fraction
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate first x weight
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate second x weight
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate third x weight
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate fourth x weight
   double y_values[4];                            //--- Declare y interpolation values
   for (int j = 0; j < 4; j++) {                 //--- Iterate through rows
      y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] +
                    weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Calculate row value
   }
   double weights_y[4];                           //--- Declare y interpolation weights
   t = fractional_y;                              //--- Set y fraction
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate first y weight
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate second y weight
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate third y weight
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate fourth y weight
   double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] +
                   weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Calculate final interpolated value
   return MathMax(0, MathMin(255, result));      //--- Clamp result to valid range
}

//+------------------------------------------------------------------+
//| Extract ARGB Components from a Pixel                             |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF);         //--- Extract alpha component
   red = (uchar)((pixel >> 16) & 0xFF);           //--- Extract red component
   green = (uchar)((pixel >> 8) & 0xFF);          //--- Extract green component
   blue = (uchar)(pixel & 0xFF);                  //--- Extract blue component
}
Here, we roll out some slick image scaling functions to guarantee top-notch visual flair in our chat-focused interface—because let's face it, pixelated icons are about as appealing as a blurry chart during a market crash, and we want our UI to scream "professional trader" rather than "1990s throwback." The "ScaleImage" function tweaks image sizes to snugly match various UI spots by whipping up a fresh pixel grid called "scaled_pixels," figuring out the source coordinates through smart proportional scaling (so nothing gets distorted like a funhouse mirror), and calling on "BicubicInterpolate" to craft buttery-smooth pixel hues that avoid those jagged edges that could make your eyes water. Once done, it shoves the polished result back into the original array using the ArrayCopy function—efficient and clean, preventing any data loss that might turn your logos into abstract art nobody asked for. Meanwhile, the "BicubicInterpolate" function dives into a cozy 4x4 pixel clique, pulling apart ARGB bits with "GetArgb" for individual handling, and leans on "BicubicInterpolateComponent" complete with fancy cubic weighting math to blend each color channel seamlessly, delivering razor-sharp graphics for those sidebar and dashboard icons that keep users clicking happily instead of squinting in confusion. Next, we'll tackle the prompt scrollbar using a comparable approach to the one we used for the response display scrollbar setup.

//+------------------------------------------------------------------+
//| Create Prompt Scrollbar Elements                                 |
//+------------------------------------------------------------------+
void CreatePromptScrollbar() {
   int promptX = g_mainContentX + g_sidePadding;  //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;              //--- Calculate prompt y position
   int promptW = g_mainWidth - 2 * g_sidePadding; //--- Calculate prompt width
   int scrollbar_x = promptX + promptW - 16;      //--- Calculate prompt scrollbar x position
   int scrollbar_y = promptY + 16;                //--- Set prompt scrollbar y position
   int scrollbar_width = 16;                      //--- Set prompt scrollbar width
   int scrollbar_height = g_promptHeight - 2 * 16; //--- Calculate prompt scrollbar height
   int button_size = 16;                          //--- Set prompt button size
   createRecLabel(P_SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'220,220,220', 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scrollbar leader rectangle
   createRecLabel(P_SCROLL_UP_REC, scrollbar_x, promptY, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scroll up button rectangle
   createLabel(P_SCROLL_UP_LABEL, scrollbar_x + 2, promptY + -2, CharToString(0x35), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); //--- Create prompt scroll up arrow label
   createRecLabel(P_SCROLL_DOWN_REC, scrollbar_x, promptY + g_promptHeight - button_size, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scroll down button rectangle
   createLabel(P_SCROLL_DOWN_LABEL, scrollbar_x + 2, promptY + g_promptHeight - button_size + -2, CharToString(0x36), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); //--- Create prompt scroll down arrow label
   p_slider_height = CalculatePromptSliderHeight(); //--- Calculate prompt slider height
   createRecLabel(P_SCROLL_SLIDER, scrollbar_x, promptY + g_promptHeight - button_size - p_slider_height, scrollbar_width, p_slider_height, clrSilver, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create prompt scrollbar slider rectangle
}

//+------------------------------------------------------------------+
//| Delete Prompt Scrollbar Elements                                 |
//+------------------------------------------------------------------+
void DeletePromptScrollbar() {
   ObjectDelete(0, P_SCROLL_LEADER);              //--- Delete prompt scrollbar leader
   ObjectDelete(0, P_SCROLL_UP_REC);              //--- Delete prompt scroll up rectangle
   ObjectDelete(0, P_SCROLL_UP_LABEL);            //--- Delete prompt scroll up label
   ObjectDelete(0, P_SCROLL_DOWN_REC);            //--- Delete prompt scroll down rectangle
   ObjectDelete(0, P_SCROLL_DOWN_LABEL);          //--- Delete prompt scroll down label
   ObjectDelete(0, P_SCROLL_SLIDER);              //--- Delete prompt scrollbar slider
}

//+------------------------------------------------------------------+
//| Calculate Prompt Scrollbar Slider Height                         |
//+------------------------------------------------------------------+
int CalculatePromptSliderHeight() {
   int scroll_area_height = g_promptHeight - 2 * 16; //--- Calculate prompt scroll area height
   int slider_min_height = 20;                    //--- Set minimum prompt slider height
   if (p_total_height <= p_visible_height) return scroll_area_height; //--- Return full height if no scroll needed
   double visible_ratio = (double)p_visible_height / p_total_height; //--- Calculate visible prompt height ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate proportional slider height
   return MathMax(slider_min_height, height);     //--- Return minimum or calculated height
}

//+------------------------------------------------------------------+
//| Update Prompt Scrollbar Slider Position                          |
//+------------------------------------------------------------------+
void UpdatePromptSliderPosition() {
   int promptX = g_mainContentX + g_sidePadding;  //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;              //--- Calculate prompt y position
   int scrollbar_x = promptX + (g_mainWidth - 2 * g_sidePadding) - 16; //--- Calculate prompt scrollbar x position
   int scrollbar_y = promptY + 16;                //--- Set prompt scrollbar y position
   int scroll_area_height = g_promptHeight - 2 * 16; //--- Calculate prompt scroll area height
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (max_scroll <= 0) return;                   //--- Exit if no scrolling needed
   double scroll_ratio = (double)p_scroll_pos / max_scroll; //--- Calculate prompt scroll position ratio
   int scroll_area_y_max = scrollbar_y + scroll_area_height - p_slider_height; //--- Calculate maximum prompt slider y position
   int scroll_area_y_min = scrollbar_y;           //--- Set minimum prompt slider y position
   int new_y = scroll_area_y_min + (int)(scroll_ratio * (scroll_area_y_max - scroll_area_y_min)); //--- Calculate new prompt slider y position
   new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); //--- Clamp y position to valid range
   ObjectSetInteger(0, P_SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Update prompt slider y position
}

//+------------------------------------------------------------------+
//| Update Prompt Scrollbar Button Colors                            |
//+------------------------------------------------------------------+
void UpdatePromptButtonColors() {
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (p_scroll_pos == 0) {                       //--- Check if at top of prompt display
      ObjectSetInteger(0, P_SCROLL_UP_LABEL, OBJPROP_COLOR, clrSilver); //--- Set prompt scroll up label to disabled color
   } else {                                       //--- Not at top
      ObjectSetInteger(0, P_SCROLL_UP_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set prompt scroll up label to active color
   }
   if (p_scroll_pos == max_scroll) {              //--- Check if at bottom of prompt display
      ObjectSetInteger(0, P_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrSilver); //--- Set prompt scroll down label to disabled color
   } else {                                       //--- Not at bottom
      ObjectSetInteger(0, P_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set prompt scroll down label to active color
   }
}

//+------------------------------------------------------------------+
//| Scroll Up Prompt Display                                         |
//+------------------------------------------------------------------+
void PromptScrollUp() {
   if (p_scroll_pos > 0) {                        //--- Check if prompt scroll position allows scrolling up
      p_scroll_pos = MathMax(0, p_scroll_pos - 30); //--- Decrease prompt scroll position by 30
      UpdatePromptDisplay();                      //--- Update prompt display
      if (p_scroll_visible) {                     //--- Check if prompt scrollbar is visible
         UpdatePromptSliderPosition();            //--- Update prompt slider position
         UpdatePromptButtonColors();              //--- Update prompt scrollbar button colors
      }
   }
}

//+------------------------------------------------------------------+
//| Scroll Down Prompt Display                                       |
//+------------------------------------------------------------------+
void PromptScrollDown() {
   int max_scroll = MathMax(0, p_total_height - p_visible_height); //--- Calculate maximum prompt scroll distance
   if (p_scroll_pos < max_scroll) {               //--- Check if prompt scroll position allows scrolling down
      p_scroll_pos = MathMin(max_scroll, p_scroll_pos + 30); //--- Increase prompt scroll position by 30
      UpdatePromptDisplay();                      //--- Update prompt display
      if (p_scroll_visible) {                     //--- Check if prompt scrollbar is visible
         UpdatePromptSliderPosition();            //--- Update prompt slider position
         UpdatePromptButtonColors();              //--- Update prompt scrollbar button colors
      }
   }
}

We proceed to roll out a scrollable zone for prompts to manage those multiline user entries like a boss, fixing the old headaches of chopping off elaborate instructions mid-sentence—because nothing screams "frustration" like a system that treats your detailed market musings like a tweet with a character limit. The "CreatePromptScrollbar" function assembles a nifty scrollbar for this prompt space, leaning on "createRecLabel" to sketch out the "P_SCROLL_LEADER", "P_SCROLL_UP_REC", "P_SCROLL_DOWN_REC", and "P_SCROLL_SLIDER" rectangles, plus "createLabel" for the "P_SCROLL_UP_LABEL" and "P_SCROLL_DOWN_LABEL" sporting those quirky Webdings arrows, all positioned smartly using "g_mainContentX", "g_sidePadding", and "g_promptHeight" to keep everything aligned without turning your UI into a crooked Picasso.

The "DeletePromptScrollbar" function wipes these elements clean with ObjectDelete for that essential tidying up—think of it as the digital equivalent of sweeping crumbs off the table so your next meal (or code run) isn't a messy affair. Meanwhile, "CalculatePromptSliderHeight" crunches the numbers for "p_slider_height" in a way that's spot-on proportional to the viewable prompt zone, drawing from "p_visible_height" and "p_total_height" to avoid sliders that are comically oversized or uselessly tiny, ensuring the scrollbar feels intuitive rather than infuriating. The "UpdatePromptSliderPosition" function fine-tunes the spot of "P_SCROLL_SLIDER" via ObjectSetInteger, guided by the "p_scroll_pos" proportion, while "UpdatePromptButtonColors" flips the hues of "P_SCROLL_UP_LABEL" and "P_SCROLL_DOWN_LABEL" between "clrSilver" and clrDimGray to signal when scrolling's on the menu—subtle visual cues that prevent users from blindly poking around like they're in a dark room. Wrapping it up, "PromptScrollUp" and "PromptScrollDown" nudge "p_scroll_pos" by 30 pixels each way, firing off "UpdatePromptDisplay" and refreshing the scrollbar looks only if "p_scroll_visible" is greenlit, paving the path for seamless cruising through multiline goodies in the UI without users feeling like they're wrestling a stubborn elevator.

Now that we've got the scrollbar shenanigans sorted, we'll whip up the prompt container next, complete with an edit field nestled inside for that hands-on feel. On the edit field front, we're still capped at 63 characters max—yeah, it's a relic, but we're dodging that straitjacket by stitching together chunks of input, which is precisely why a roomier holder is our hero here, expanding our horizons beyond those puny one-liners. The snag pops up again when editing wraps up, as fresh inputs get tacked on as separate lines by default, potentially turning your eloquent prompt into a choppy poem nobody wants. To craft a truly user-friendly beast that flows like a natural paragraph continuation, we'll glue new bits onto the tail end of the last one—smooth as butter, keeping the vibe cohesive so traders don't feel like they're assembling a jigsaw puzzle mid-analysis. But wait, plot twist: sometimes you crave actual new paragraphs for clarity, like separating market observations from strategy tweaks. To sidestep that drama without forcing users to type clunky codes like "\n" or "\newLine" (which could cramp their style faster than a bad trade), we brainstormed a simple hack: spotting double periods ".." in the input and treating them as a sneaky signal for a line break. It's a random pick we landed on for its rarity in normal text—avoids accidental triggers during rants about volatility—but hey, swap it for whatever floats your boat, like triple commas or a secret emoji, because customization keeps things fun and flexible. You get the drift. So, let's cook up some code to make that magic happen without putting anyone to sleep mid-scroll. 😂

//+------------------------------------------------------------------+
//| Split String on Delimiter                                        |
//+------------------------------------------------------------------+
int SplitOnString(string inputText, string delim, string &result[]) {
   ArrayResize(result, 0);                        //--- Clear result array
   int pos = 0;                                   //--- Initialize starting position
   int delim_len = StringLen(delim);              //--- Get delimiter length
   while (true) {                                 //--- Loop until string is fully processed
      int found = StringFind(inputText, delim, pos); //--- Find delimiter position
      if (found == -1) {                          //--- Check if no more delimiters
         string part = StringSubstr(inputText, pos); //--- Extract remaining string
         if (StringLen(part) > 0 || ArraySize(result) > 0) { //--- Check if part is non-empty or array not empty
            int size = ArraySize(result);         //--- Get current array size
            ArrayResize(result, size + 1);        //--- Resize result array
            result[size] = part;                  //--- Add remaining part
         }
         break;                                  //--- Exit loop
      }
      string part = StringSubstr(inputText, pos, found - pos); //--- Extract part before delimiter
      int size = ArraySize(result);              //--- Get current array size
      ArrayResize(result, size + 1);             //--- Resize result array
      result[size] = part;                       //--- Add part to array
      pos = found + delim_len;                   //--- Update position past delimiter
   }
   return ArraySize(result);                     //--- Return number of parts
}

//+------------------------------------------------------------------+
//| Replace Exact Double Periods with Newline                        |
//+------------------------------------------------------------------+
string ReplaceExactDoublePeriods(string text) {
   string result = "";                            //--- Initialize result string
   int len = StringLen(text);                     //--- Get text length
   for (int i = 0; i < len; i++) {               //--- Iterate through characters
      if (i + 1 < len && StringGetCharacter(text, i) == '.' && StringGetCharacter(text, i + 1) == '.') { //--- Check for double period
         bool preceded = (i > 0 && StringGetCharacter(text, i - 1) == '.'); //--- Check if preceded by period
         bool followed = (i + 2 < len && StringGetCharacter(text, i + 2) == '.'); //--- Check if followed by period
         if (!preceded && !followed) {            //--- Confirm exact double period
            result += "\n";                       //--- Append newline
            i++;                                  //--- Skip next period
         } else {                                 //--- Not exact double period
            result += ".";                        //--- Append period
         }
      } else {                                    //--- Non-double period character
         result += StringSubstr(text, i, 1);      //--- Append character
      }
   }
   return result;                                //--- Return processed string
}

//+------------------------------------------------------------------+
//| Create Prompt Placeholder Label                                  |
//+------------------------------------------------------------------+
void CreatePlaceholder() {
   if (ObjectFind(0, "ChatGPT_PromptPlaceholder") < 0 && StringLen(currentPrompt) == 0) { //--- Check if placeholder is needed
      int placeholderFontSize = 10;               //--- Set placeholder font size
      string placeholderFont = "Arial";            //--- Set placeholder font
      int lineHeight = TextGetHeight("A", placeholderFont, placeholderFontSize); //--- Calculate line height
      int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
      int promptY = footerY + g_margin;           //--- Calculate prompt y position
      int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
      int editX = g_mainContentX + g_sidePadding + g_textPadding; //--- Calculate edit field x position
      int labelY = editY + (g_editHeight - lineHeight) / 2; //--- Calculate label y position
      createLabel("ChatGPT_PromptPlaceholder", editX + 2, labelY, "Type your prompt here...", clrGray, placeholderFontSize, placeholderFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create placeholder label
      ChartRedraw();                              //--- Redraw chart to reflect changes
   }
}

//+------------------------------------------------------------------+
//| Delete Prompt Placeholder Label                                  |
//+------------------------------------------------------------------+
void DeletePlaceholder() {
   if (ObjectFind(0, "ChatGPT_PromptPlaceholder") >= 0) { //--- Check if placeholder exists
      ObjectDelete(0, "ChatGPT_PromptPlaceholder"); //--- Delete placeholder label
      ChartRedraw();                              //--- Redraw chart to reflect changes
   }
}
Alright, buckle up, because we're about to slice and dice user input with the finesse of a master chef. Enter the "SplitOnString" function—our digital text-scalpel that chops input into a neat array using whatever delimiter we throw at it. It leans on StringFind and StringSubstr to hunt down and carve out segments, while ArrayResize dutifully makes room in the pantry to store them all. This is the secret sauce for parsing the conversation history with surgical precision, so we can actually understand the flow of the user's rambling market genius.

Next, we deploy the "ReplaceExactDoublePeriods" function, our clandestine agent for turning those sneaky ".." combos into legitimate newlines. Using StringGetCharacter for a pixel-perfect operation, it ensures only the exact double periods get the promotion to a line break, leaving any stray single periods or dramatic ellipses in their place. This is how we avoid turning a thoughtful sentence... into an accidental haiku. We specifically chose this quirky sequence so your average period or an ellipsis... doesn't accidentally trigger a paragraph apocalypse.

But what's a fancy input field without a little visual charm? The "CreatePlaceholder" function swoops in to add a "ChatGPT_PromptPlaceholder" label using our trusty createLabel, which politely loiters in the prompt area whenever "currentPrompt" is emptier than a ghost town. It uses "TextGetHeight" to sit perfectly aligned, like a painting in a gallery, just begging for you to type something. The moment you start writing, "DeletePlaceholder" evicts this textual squatter with a swift ObjectDelete, ensuring a clean slate so your brilliant ideas don't have to compete with placeholder text for attention.
Now, a word from our sponsors: Good Programming Practices™. It's a fantastic habit to compulsively compile and test your code every milestone, lest you unleash a cascade of bugs so horrifying they'd make a grown developer weep. So, with that in mind, we're rolling up our sleeves to build the dashboard itself. We'll call our gaggle of functions to wake up the main display and bolt on the prompt section. And we're not stopping there—we're giving the main background holder a serious expansion, stretching it out so it can comfortably house the left sidebar without everything feeling like a cramped subway car at rush hour.
//+------------------------------------------------------------------+
//| Create Dashboard Elements                                        |
//+------------------------------------------------------------------+
void CreateDashboard() {
   objCount = 0;                             //--- Reset object count
   g_mainHeight = g_headerHeight + 2 * g_padding + g_displayHeight + g_footerHeight; //--- Calculate main dashboard height
   int displayX = g_mainContentX + g_sidePadding; //--- Calculate display x position
   int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate display y position
   int displayW = g_mainWidth - 2 * g_sidePadding; //--- Calculate display width
   int footerY = displayY + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;         //--- Calculate prompt y position
   int buttonsY = promptY + g_promptHeight + g_margin; //--- Calculate buttons y position
   int buttonW = 140;                        //--- Set button width
   int chartX = g_mainContentX + g_sidePadding; //--- Calculate chart button x position
   int sendX = g_mainContentX + g_mainWidth - g_sidePadding - buttonW; //--- Calculate send button x position
   dashboardObjects[objCount++] = "ChatGPT_MainContainer"; //--- Store main container object name
   createRecLabel("ChatGPT_MainContainer", g_mainContentX, g_mainY, g_mainWidth, g_mainHeight, clrWhite, 1, clrLightGray); //--- Create main container rectangle
   dashboardObjects[objCount++] = "ChatGPT_HeaderBg"; //--- Store header background object name
   createRecLabel("ChatGPT_HeaderBg", g_mainContentX, g_mainY, g_mainWidth, g_headerHeight, clrWhiteSmoke, 0, clrNONE); //--- Create header background rectangle
   string logo_resource = (StringLen(g_scaled_image_resource) > 0) ? g_scaled_image_resource : resourceImg; //--- Select header logo resource
   dashboardObjects[objCount++] = "ChatGPT_HeaderLogo"; //--- Store header logo object name
   createBitmapLabel("ChatGPT_HeaderLogo", g_mainContentX + g_sidePadding, g_mainY + (g_headerHeight - 40)/2, 104, 40, logo_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create header logo
   string title = "ChatGPT AI EA";           //--- Set dashboard title
   string titleFont = "Arial Rounded MT Bold"; //--- Set title font
   int titleSize = 14;                       //--- Set title font size
   TextSetFont(titleFont, titleSize);        //--- Set title font
   uint titleWid, titleHei;                  //--- Declare title dimensions
   TextGetSize(title, titleWid, titleHei);   //--- Get title dimensions
   int titleY = g_mainY + (g_headerHeight - (int)titleHei) / 2 - 4; //--- Calculate title y position
   int titleX = g_mainContentX + g_sidePadding + 104 + 5; //--- Calculate title x position
   dashboardObjects[objCount++] = "ChatGPT_TitleLabel"; //--- Store title label object name
   createLabel("ChatGPT_TitleLabel", titleX, titleY, title, clrDarkSlateGray, titleSize, titleFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label
   string dateStr = TimeToString(TimeTradeServer(), TIME_MINUTES); //--- Get current server time
   string dateFont = "Arial";                //--- Set date font
   int dateSize = 12;                        //--- Set date font size
   TextSetFont(dateFont, dateSize);          //--- Set date font
   uint dateWid, dateHei;                    //--- Declare date dimensions
   TextGetSize(dateStr, dateWid, dateHei);   //--- Get date dimensions
   int dateX = g_mainContentX + g_mainWidth / 2 - (int)(dateWid / 2) + 20; //--- Calculate date x position
   int dateY = g_mainY + (g_headerHeight - (int)dateHei) / 2 - 4; //--- Calculate date y position
   dashboardObjects[objCount++] = "ChatGPT_DateLabel"; //--- Store date label object name
   createLabel("ChatGPT_DateLabel", dateX, dateY, dateStr, clrSlateGray, dateSize, dateFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create date label
   int closeWidth = 100;                     //--- Set close button width
   int closeX = g_mainContentX + g_mainWidth - closeWidth - g_sidePadding; //--- Calculate close button x position
   int closeY = g_mainY + 4;                 //--- Calculate close button y position
   dashboardObjects[objCount++] = "ChatGPT_CloseButton"; //--- Store close button object name
   createButton("ChatGPT_CloseButton", closeX, closeY, closeWidth, g_headerHeight - 8, "Close", clrWhite, 11, close_original_bg, clrGray); //--- Create close button
   dashboardObjects[objCount++] = "ChatGPT_ResponseBg"; //--- Store response background object name
   createRecLabel("ChatGPT_ResponseBg", displayX, displayY, displayW, g_displayHeight, clrWhite, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID); //--- Create response background rectangle
   dashboardObjects[objCount++] = "ChatGPT_FooterBg"; //--- Store footer background object name
   createRecLabel("ChatGPT_FooterBg", g_mainContentX, footerY, g_mainWidth, g_footerHeight, clrGainsboro, 0, clrNONE); //--- Create footer background rectangle
   dashboardObjects[objCount++] = "ChatGPT_PromptBg"; //--- Store prompt background object name
   createRecLabel("ChatGPT_PromptBg", displayX, promptY, displayW, g_promptHeight, g_promptBg, 1, g_promptBg, BORDER_FLAT, STYLE_SOLID); //--- Create prompt background rectangle
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int editX = displayX + g_textPadding;     //--- Calculate edit field x position
   g_editW = displayW - 2 * g_textPadding;   //--- Calculate edit field width
   dashboardObjects[objCount++] = "ChatGPT_PromptEdit"; //--- Store prompt edit object name
   createEdit("ChatGPT_PromptEdit", editX, editY, g_editW, g_editHeight, "", clrBlack, 13, DarkenColor(g_promptBg,0.93), DarkenColor(g_promptBg,0.87),"Calibri"); //--- Create prompt edit field
   ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set edit field border type
   dashboardObjects[objCount++] = "ChatGPT_GetChartButton"; //--- Store chart button object name
   createButton("ChatGPT_GetChartButton", chartX, buttonsY, buttonW, g_buttonHeight, "Get Chart Data", clrWhite, 11, chart_button_bg, clrDarkGreen); //--- Create chart data button
   dashboardObjects[objCount++] = "ChatGPT_SendPromptButton"; //--- Store send button object name
   createButton("ChatGPT_SendPromptButton", sendX, buttonsY, buttonW, g_buttonHeight, "Send Prompt", clrWhite, 11, button_original_bg, clrDarkBlue); //--- Create send prompt button
   ChartRedraw();                            //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Expert Initialization Function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   button_darker_bg = DarkenColor(button_original_bg); //--- Set darker background for submit button
   clear_darker_bg = DarkenColor(clear_original_bg);  //--- Set darker background for clear button
   new_chat_darker_bg = DarkenColor(new_chat_original_bg); //--- Set darker background for new chat button
   chart_button_darker_bg = DarkenColor(chart_button_bg); //--- Set darker background for chart button
   close_darker_bg = DarkenColor(close_original_bg);  //--- Set darker background for close button
   logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); //--- Open log file for reading and writing
   if (logFileHandle == INVALID_HANDLE) {             //--- Check if file opening failed
      Print("Failed to open log file: ", GetLastError()); //--- Log error
      return(INIT_FAILED);                            //--- Return initialization failure
   }
   FileSeek(logFileHandle, 0, SEEK_END);             //--- Move file pointer to end
   uint img_pixels[];                                //--- Declare array for main image pixels
   uint orig_width = 0, orig_height = 0;             //--- Initialize main image dimensions
   bool image_loaded = ResourceReadImage(resourceImg, img_pixels, orig_width, orig_height); //--- Load main image resource
   if (image_loaded && orig_width > 0 && orig_height > 0) { //--- Check if main image loaded successfully
      ScaleImage(img_pixels, (int)orig_width, (int)orig_height, 104, 40); //--- Scale main image to 104x40
      g_scaled_image_resource = "::ChatGPT_HeaderImageScaled"; //--- Set scaled main image resource name
      if (ResourceCreate(g_scaled_image_resource, img_pixels, 104, 40, 0, 0, 104, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled main image resource
         Print("Scaled image resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled image resource"); //--- Log error
      }
   } else {                                          //--- Handle main image load failure
      Print("Failed to load original image resource"); //--- Log error
   }
   uint img_pixels_logo[];                           //--- Declare array for logo image pixels
   uint orig_width_logo = 0, orig_height_logo = 0;   //--- Initialize logo image dimensions
   bool image_loaded_logo = ResourceReadImage(resourceImgLogo, img_pixels_logo, orig_width_logo, orig_height_logo); //--- Load logo image resource
   if (image_loaded_logo && orig_width_logo > 0 && orig_height_logo > 0) { //--- Check if logo image loaded successfully
      ScaleImage(img_pixels_logo, (int)orig_width_logo, (int)orig_height_logo, 81, 81); //--- Scale logo image to 81x81
      g_scaled_sidebar_resource = "::ChatGPT_SidebarImageScaled"; //--- Set scaled logo image resource name
      if (ResourceCreate(g_scaled_sidebar_resource, img_pixels_logo, 81, 81, 0, 0, 81, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled logo image resource
         Print("Scaled sidebar image resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled sidebar image resource"); //--- Log error
      }
   } else {                                          //--- Handle logo image load failure
      Print("Failed to load sidebar image resource"); //--- Log error
   }
   uint img_pixels_newchat[];                        //--- Declare array for new chat icon pixels
   uint orig_width_newchat = 0, orig_height_newchat = 0; //--- Initialize new chat icon dimensions
   bool image_loaded_newchat = ResourceReadImage(resourceNewChat, img_pixels_newchat, orig_width_newchat, orig_height_newchat); //--- Load new chat icon resource
   if (image_loaded_newchat && orig_width_newchat > 0 && orig_height_newchat > 0) { //--- Check if new chat icon loaded successfully
      ScaleImage(img_pixels_newchat, (int)orig_width_newchat, (int)orig_height_newchat, 30, 30); //--- Scale new chat icon to 30x30
      g_scaled_newchat_resource = "::ChatGPT_NewChatIconScaled"; //--- Set scaled new chat icon resource name
      if (ResourceCreate(g_scaled_newchat_resource, img_pixels_newchat, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled new chat icon resource
         Print("Scaled new chat icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled new chat icon resource"); //--- Log error
      }
   } else {                                          //--- Handle new chat icon load failure
      Print("Failed to load new chat icon resource"); //--- Log error
   }
   uint img_pixels_clear[];                          //--- Declare array for clear icon pixels
   uint orig_width_clear = 0, orig_height_clear = 0; //--- Initialize clear icon dimensions
   bool image_loaded_clear = ResourceReadImage(resourceClear, img_pixels_clear, orig_width_clear, orig_height_clear); //--- Load clear icon resource
   if (image_loaded_clear && orig_width_clear > 0 && orig_height_clear > 0) { //--- Check if clear icon loaded successfully
      ScaleImage(img_pixels_clear, (int)orig_width_clear, (int)orig_height_clear, 30, 30); //--- Scale clear icon to 30x30
      g_scaled_clear_resource = "::ChatGPT_ClearIconScaled"; //--- Set scaled clear icon resource name
      if (ResourceCreate(g_scaled_clear_resource, img_pixels_clear, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled clear icon resource
         Print("Scaled clear icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled clear icon resource"); //--- Log error
      }
   } else {                                          //--- Handle clear icon load failure
      Print("Failed to load clear icon resource"); //--- Log error
   }
   uint img_pixels_history[];                        //--- Declare array for history icon pixels
   uint orig_width_history = 0, orig_height_history = 0; //--- Initialize history icon dimensions
   bool image_loaded_history = ResourceReadImage(resourceHistory, img_pixels_history, orig_width_history, orig_height_history); //--- Load history icon resource
   if (image_loaded_history && orig_width_history > 0 && orig_height_history > 0) { //--- Check if history icon loaded successfully
      ScaleImage(img_pixels_history, (int)orig_width_history, (int)orig_height_history, 30, 30); //--- Scale history icon to 30x30
      g_scaled_history_resource = "::ChatGPT_HistoryIconScaled"; //--- Set scaled history icon resource name
      if (ResourceCreate(g_scaled_history_resource, img_pixels_history, 30, 30, 0, 0, 30, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled history icon resource
         Print("Scaled history icon resource created successfully"); //--- Log success
      } else {                                       //--- Handle resource creation failure
         Print("Failed to create scaled history icon resource"); //--- Log error
      }
   } else {                                          //--- Handle history icon load failure
      Print("Failed to load history icon resource"); //--- Log error
   }
   g_mainHeight = g_headerHeight + 2 * g_padding + g_displayHeight + g_footerHeight; //--- Calculate main dashboard height
   createRecLabel("ChatGPT_DashboardBg", g_dashboardX, g_mainY, g_dashboardWidth, g_mainHeight, clrWhite, 1, clrLightGray); //--- Create dashboard background rectangle
   ObjectSetInteger(0, "ChatGPT_DashboardBg", OBJPROP_ZORDER, 0); //--- Set dashboard background z-order
   createRecLabel("ChatGPT_SidebarBg", g_dashboardX+2, g_mainY+2, g_sidebarWidth - 2 - 1, g_mainHeight - 2 - 2, clrGainsboro, 1, clrNONE); //--- Create sidebar background rectangle
   ObjectSetInteger(0, "ChatGPT_SidebarBg", OBJPROP_ZORDER, 0); //--- Set sidebar background z-order
   CreateDashboard();                                //--- Create dashboard elements
   UpdateResponseDisplay();                          //--- Update response display
   CreatePlaceholder();                              //--- Create prompt placeholder
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);     //--- Enable chart mouse scrolling
   return(INIT_SUCCEEDED);                           //--- Return initialization success
}
We're now beefing up the "CreateDashboard" function, the grand architect that constructs the entire interface from the ground up. It begins by performing the layout math, juggling variables such as "g_mainContentX", "g_sidePadding", "g_headerHeight", "g_displayHeight", and "g_footerHeight" to ensure all measurements are just right. Then, it gets to work hammering together the visual elements: it creates the "ChatGPT_MainContainer" (which we're deliberately stretching wider), the "ChatGPT_HeaderBg", and the "ChatGPT_FooterBg" using our reliable "createRecLabel" function.

For a touch of brand-name flair, it adds a scaled header logo dubbed "ChatGPT_HeaderLogo" via "createBitmapLabel", feeding it either the "g_scaled_image_resource" or a "resourceImg" so it looks crisp and not like a stretched JPEG nightmare. It also slaps on a title "ChatGPT_TitleLabel" and a timestamp "ChatGPT_DateLabel" using "createLabel", because even a sophisticated AI needs a name tag and a calendar. But what's a dashboard without controls? So, it wires in a "ChatGPT_PromptEdit" field using "createEdit" for user input, a "ChatGPT_GetChartButton" to pull in live market data, a "ChatGPT_SendPromptButton" as the big "let's go!" button for firing off prompts, and a "ChatGPT_CloseButton" for making the whole thing disappear. Every single one of these object names gets neatly filed away into the "dashboardObjects" array for safe keeping and management. Now, how does this whole digital contraption wake up? That's the job of the OnInit event handler, the program's ignition sequence. It starts by setting a moody, dark theme for the buttons using a function called "DarkenColor". Then, it cracks open a log file named "ChatGPT_EA_Log.txt" using FileOpen, because you always need a notepad for the system's secret thoughts. Next, it takes all the bitmap resources—"AI MQL5.bmp", "AI LOGO.bmp", "AI NEW CHAT.bmp", "AI CLEAR.bmp", and "AI HISTORY.bmp"—and runs them through the "ScaleImage" and ResourceCreate wringer to ensure they look consistently sharp and not pixelated on any screen.

With the visuals prepped, it calls in the construction crew: "CreateDashboard" to build the stage, "UpdateResponseDisplay" to light up the main screen, and "CreatePlaceholder" to add those helpful hints in the input field. Finally, it flips the switch for interactivity by enabling mouse events with ChartSetInteger, making sure the dashboard is ready for all your future clicks and scrolls. You hit compile, and the outcome is a fully-formed, interactive interface, ready for action, as below.

Now that we have the updated display, we will need to work on getting the chart data, showing on the prompt display, and sending it for analysis. That being said, we will need to better handle UTF-8 since we will be handling critical data, and also, enhance logging, which can be removed later, just so we can see what we are doing exactly, so in case of issues, we can resolve them. Let us start with the function to update our prompt display, which will use a similar approach to that of the response display.

//+------------------------------------------------------------------+
//| Update Prompt Display                                            |
//+------------------------------------------------------------------+
void UpdatePromptDisplay() {
   int total = ObjectsTotal(0, 0, -1);       //--- Get total number of chart objects
   for (int j = total - 1; j >= 0; j--) {   //--- Iterate through objects in reverse
      string name = ObjectName(0, j, 0, -1); //--- Get object name
      if (StringFind(name, "ChatGPT_PromptLine_") == 0) { //--- Check if object is prompt line
         ObjectDelete(0, name);              //--- Delete prompt line object
      }
   }
   int promptX = g_mainContentX + g_sidePadding; //--- Calculate prompt x position
   int footerY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;         //--- Calculate prompt y position
   int textX = promptX + g_textPadding;      //--- Calculate text x position
   int textY = promptY + g_textPadding;      //--- Calculate text y position
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int fullMaxWidth = g_mainWidth - 2 * g_sidePadding - 2 * g_textPadding; //--- Calculate maximum text width
   int visibleHeight = editY - textY - g_textPadding - g_margin; //--- Calculate visible height
   if (currentPrompt == "") {                //--- Check if prompt is empty
      p_total_height = 0;                    //--- Set total prompt height to zero
      p_visible_height = visibleHeight;       //--- Set visible prompt height
      if (p_scroll_visible) {                //--- Check if prompt scrollbar is visible
         DeletePromptScrollbar();            //--- Delete prompt scrollbar
         p_scroll_visible = false;           //--- Set prompt scrollbar visibility to false
      }
      ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_XSIZE, g_editW); //--- Set edit field width
      ChartRedraw();                         //--- Redraw chart
      return;                                //--- Exit function
   }
   string font = "Arial";                    //--- Set font for prompt
   int fontSize = 10;                        //--- Set font size for prompt
   int lineHeight = TextGetHeight("A", font, fontSize); //--- Calculate line height
   int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Adjust line height with spacing
   p_visible_height = visibleHeight;         //--- Set global visible prompt height
   string wrappedLines[];                    //--- Declare array for wrapped lines
   WrapText(currentPrompt, font, fontSize, fullMaxWidth, wrappedLines); //--- Wrap prompt text
   int totalLines = ArraySize(wrappedLines); //--- Get number of wrapped lines
   int totalHeight = totalLines * adjustedLineHeight; //--- Calculate total height
   bool need_scroll = totalHeight > visibleHeight; //--- Check if scrollbar is needed
   bool should_show_scrollbar = false;       //--- Initialize scrollbar visibility
   int reserved_width = 0;                   //--- Initialize reserved width for scrollbar
   if (ScrollbarMode != SCROLL_WHEEL_ONLY) { //--- Check if scrollbar mode allows display
      should_show_scrollbar = need_scroll && (ScrollbarMode == SCROLL_DYNAMIC_ALWAYS || (ScrollbarMode == SCROLL_DYNAMIC_HOVER && mouse_in_prompt)); //--- Determine if scrollbar should show
      if (should_show_scrollbar) {           //--- Check if scrollbar is visible
         reserved_width = 16;                //--- Reserve width for scrollbar
      }
   }
   if (reserved_width > 0) {                 //--- Check if scrollbar space reserved
      WrapText(currentPrompt, font, fontSize, fullMaxWidth - reserved_width, wrappedLines); //--- Re-wrap text with adjusted width
      totalLines = ArraySize(wrappedLines);  //--- Update number of wrapped lines
      totalHeight = totalLines * adjustedLineHeight; //--- Update total height
   }
   p_total_height = totalHeight;             //--- Set global total prompt height
   bool prev_p_scroll_visible = p_scroll_visible; //--- Store previous prompt scrollbar visibility
   p_scroll_visible = should_show_scrollbar; //--- Update prompt scrollbar visibility
   if (p_scroll_visible != prev_p_scroll_visible) { //--- Check if visibility changed
      if (p_scroll_visible) {                //--- Check if scrollbar should be shown
         CreatePromptScrollbar();            //--- Create prompt scrollbar
      } else {                               //--- Scrollbar not needed
         DeletePromptScrollbar();            //--- Delete prompt scrollbar
      }
   }
   ObjectSetInteger(0, "ChatGPT_PromptEdit", OBJPROP_XSIZE, g_editW - reserved_width); //--- Adjust edit field width
   int max_scroll = MathMax(0, totalHeight - visibleHeight); //--- Calculate maximum scroll distance
   if (p_scroll_pos > max_scroll) p_scroll_pos = max_scroll; //--- Clamp prompt scroll position
   if (p_scroll_pos < 0) p_scroll_pos = 0;   //--- Ensure prompt scroll position is non-negative
   if (p_scroll_visible) {                   //--- Check if prompt scrollbar is visible
      p_slider_height = CalculatePromptSliderHeight(); //--- Calculate prompt slider height
      ObjectSetInteger(0, P_SCROLL_SLIDER, OBJPROP_YSIZE, p_slider_height); //--- Update prompt slider size
      UpdatePromptSliderPosition();          //--- Update prompt slider position
      UpdatePromptButtonColors();            //--- Update prompt scrollbar button colors
   }
   int currentY = textY - p_scroll_pos;      //--- Calculate current y position
   int endY = textY + visibleHeight;         //--- Calculate end y position
   int startLineIndex = 0;                   //--- Initialize start line index
   int currentHeight = 0;                    //--- Initialize current height
   for (int line = 0; line < totalLines; line++) { //--- Iterate through lines
      if (currentHeight >= p_scroll_pos) {   //--- Check if line is in view
         startLineIndex = line;              //--- Set start line index
         currentY = textY + (currentHeight - p_scroll_pos); //--- Update current y position
         break;                              //--- Exit loop
      }
      currentHeight += adjustedLineHeight;   //--- Add line height
   }
   int numVisibleLines = 0;                  //--- Initialize visible lines count
   int visibleHeightUsed = 0;                //--- Initialize used visible height
   for (int line = startLineIndex; line < totalLines; line++) { //--- Iterate from start line
      if (visibleHeightUsed + adjustedLineHeight > visibleHeight) break; //--- Check if exceeds visible height
      visibleHeightUsed += adjustedLineHeight; //--- Add to used height
      numVisibleLines++;                     //--- Increment visible lines
   }
   int textX_pos = textX;                    //--- Set text x position
   int maxTextX = g_mainContentX + g_mainWidth - g_sidePadding - g_textPadding - reserved_width; //--- Calculate maximum text x position
   color textCol = clrBlack;                 //--- Set text color
   for (int li = 0; li < numVisibleLines; li++) { //--- Iterate through visible lines
      int lineIndex = startLineIndex + li;   //--- Calculate line index
      if (lineIndex >= totalLines) break;    //--- Check if index exceeds total lines
      string line = wrappedLines[lineIndex]; //--- Get line text
      string display_line = line;            //--- Initialize display line
      if (line == " ") {                     //--- Check if line is empty
         display_line = " ";                 //--- Set display line to space
         textCol = clrWhite;                 //--- Set text color to white
      }
      string lineName = "ChatGPT_PromptLine_" + IntegerToString(lineIndex); //--- Generate line object name
      if (currentY >= textY && currentY < endY) { //--- Check if line is visible
         createLabel(lineName, textX_pos, currentY, display_line, textCol, fontSize, font, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create line label
      }
      currentY += adjustedLineHeight;        //--- Update current y position
   }
   ChartRedraw();                            //--- Redraw chart
}
Here, we implement the "UpdatePromptDisplay" function to manage the display of multiline user prompts, ensuring smooth rendering and scrolling. We start by clearing existing "ChatGPT_PromptLine_" objects using ObjectsTotal and ObjectDelete. We then calculate the prompt area’s layout with "g_mainContentX", "g_sidePadding", "g_promptHeight", and "g_textPadding".

If "currentPrompt" is empty, we reset "p_total_height", set "p_visible_height", remove the scrollbar with "DeletePromptScrollbar", and adjust the "ChatGPT_PromptEdit" width using ObjectSetInteger.

For non-empty prompts, we wrap text into lines using the "WrapText" function we defined earlier, compute "p_total_height" from "adjustedLineHeight", and dynamically show or hide the scrollbar based on "ScrollbarMode" and "mouse_in_prompt", reserving space with "reserved_width" if needed. We then render visible lines as "ChatGPT_PromptLine_" labels with "createLabel", updating positions with "p_scroll_pos" and refreshing the chart with ChartRedraw for seamless interaction. To append the chart data to the prompt, we implement the following function.

//+------------------------------------------------------------------+
//| Convert Timeframe to String                                      |
//+------------------------------------------------------------------+
string PeriodToString(ENUM_TIMEFRAMES period) {
   switch(period) {                          //--- Switch on timeframe
      case PERIOD_M1: return "M1";           //--- Return M1 for 1-minute
      case PERIOD_M5: return "M5";           //--- Return M5 for 5-minute
      case PERIOD_M15: return "M15";         //--- Return M15 for 15-minute
      case PERIOD_M30: return "M30";         //--- Return M30 for 30-minute
      case PERIOD_H1: return "H1";           //--- Return H1 for 1-hour
      case PERIOD_H4: return "H4";           //--- Return H4 for 4-hour
      case PERIOD_D1: return "D1";           //--- Return D1 for daily
      case PERIOD_W1: return "W1";           //--- Return W1 for weekly
      case PERIOD_MN1: return "MN1";         //--- Return MN1 for monthly
      default: return IntegerToString(period); //--- Return period as string for others
   }
}

//+------------------------------------------------------------------+
//| Append Chart Data to Prompt                                      |
//+------------------------------------------------------------------+
void GetAndAppendChartData() {
   string symbol = Symbol();                  //--- Get current chart symbol
   ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)_Period; //--- Get current timeframe
   string timeframe = PeriodToString(tf);     //--- Convert timeframe to string
   long visibleBarsLong = ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get number of visible bars
   int visibleBars = (int)visibleBarsLong;   //--- Convert visible bars to integer
   MqlRates rates[];                         //--- Declare array for rate data
   int copied = CopyRates(symbol, tf, 0, MaxChartBars, rates); //--- Copy recent bar data
   if (copied != MaxChartBars) {             //--- Check if copy failed
      Print("Failed to copy rates: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   ArraySetAsSeries(rates, true);            //--- Set rates as time series
   string data = "Chart Details: Symbol=" + symbol + ", Timeframe=" + timeframe + ", Visible Bars=" + IntegerToString(visibleBars) + "\n"; //--- Build chart details string
   data += "Recent Bars Data (Bar 1 is latest):\n"; //--- Add header for bar data
   for (int i = 0; i < copied; i++) {       //--- Iterate through copied bars
      data += "Bar " + IntegerToString(i + 1) + ": Date=" + TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES) + ", Open=" + DoubleToString(rates[i].open, _Digits) + ", High=" + DoubleToString(rates[i].high, _Digits) + ", Low=" + DoubleToString(rates[i].low, _Digits) + ", Close=" + DoubleToString(rates[i].close, _Digits) + ", Volume=" + IntegerToString((int)rates[i].tick_volume) + "\n"; //--- Add bar data
   }
   Print("Chart data appended to prompt: \n" + data); //--- Log chart data
   FileWrite(logFileHandle, "Chart data appended to prompt: \n" + data); //--- Write chart data to log
   string fileName = "candlesticksdata.txt"; //--- Set file name for chart data
   int handle = FileOpen(fileName, FILE_WRITE | FILE_TXT | FILE_ANSI); //--- Open file for writing
   if (handle == INVALID_HANDLE) {           //--- Check if file opening failed
      Print("Failed to open file for writing: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   FileWriteString(handle, data);            //--- Write chart data to file
   FileClose(handle);                        //--- Close file
   handle = FileOpen(fileName, FILE_READ | FILE_TXT | FILE_ANSI); //--- Open file for reading
   if (handle == INVALID_HANDLE) {           //--- Check if file opening failed
      Print("Failed to open file for reading: ", GetLastError()); //--- Log error
      return;                                //--- Exit function
   }
   string fileContent = "";                  //--- Initialize file content string
   while (!FileIsEnding(handle)) {           //--- Loop until end of file
      fileContent += FileReadString(handle) + "\n"; //--- Read and append line
   }
   FileClose(handle);                        //--- Close file
   if (StringLen(currentPrompt) > 0) {       //--- Check if prompt is non-empty
      currentPrompt += "\n";                 //--- Append newline to prompt
   }
   currentPrompt += fileContent;             //--- Append chart data to prompt
   DeletePlaceholder();                      //--- Delete prompt placeholder
   UpdatePromptDisplay();                    //--- Update prompt display
   p_scroll_pos = MathMax(0, p_total_height - p_visible_height); //--- Set prompt scroll to bottom
   if (p_scroll_visible) {                   //--- Check if prompt scrollbar is visible
      UpdatePromptSliderPosition();          //--- Update prompt slider position
      UpdatePromptButtonColors();            //--- Update prompt scrollbar button colors
   }
   ChartRedraw();                            //--- Redraw chart
}
To make our AI a true market savant, we teach it to read the charts. First, we define the "PeriodToString" function, a handy little translator that converts cryptic timeframe enums like PERIOD_M1 or "PERIOD_H1" into plain English strings like "M1" or "H1" using a switch statement—because nobody wants the AI pondering over what a "PERIOD_W1" is.

Then, we roll out the "GetAndAppendChartData" function, our data-fetching bloodhound. It sniffs out the current chart's symbol with "Symbol", grabs the timeframe with _Period, and counts the visible bars with ChartGetInteger. Then, it uses CopyRates to fetch a fresh batch of recent market data, stuffing it all into an MqlRates array. We take this raw data and pretty it up, formatting the open, high, low, close, and volume into a neat string using TimeToString and DoubleToString—turning a chaotic mess of numbers into a presentable report.

We then log this financial gossip, save it to "candlesticksdata.txt" using "FileWriteString", and immediately read it back with FileReadString (we like to be thorough). This juicy data is then appended to the "currentPrompt" for the AI to chew on. Finally, we refresh the whole display by calling "DeletePlaceholder" and "UpdatePromptDisplay", and give the scrollbar a fresh coat of paint with "UpdatePromptSliderPosition" and "UpdatePromptButtonColors". This ensures that when we click 'send chart data,' we first download and store the market's life story, as shown below.

Since we will be building the messages from history that we will use to track the conversation, we need to advance our function to consider the new chart data that we send to the AI, since it has a new format, so we consider all content between roles.

//+------------------------------------------------------------------+
//| Build JSON Messages from History                                 |
//+------------------------------------------------------------------+
string BuildMessagesFromHistory(string newPrompt) {
   string lines[];                           //--- Declare array for history lines
   int numLines = StringSplit(conversationHistory, '\n', lines); //--- Split history into lines
   string messages = "[";                    //--- Initialize JSON messages array
   string currentRole = "";                  //--- Initialize current role
   string currentContent = "";               //--- Initialize current content
   for (int i = 0; i < numLines; i++) {     //--- Iterate through history lines
      string line = lines[i];                //--- Get current line
      string trimmed = line;                 //--- Copy line for trimming
      StringTrimLeft(trimmed);               //--- Remove leading whitespace
      StringTrimRight(trimmed);              //--- Remove trailing whitespace
      if (StringLen(trimmed) == 0 || IsTimestamp(trimmed)) continue; //--- Skip empty or timestamp lines
      if (StringFind(trimmed, "You: ") == 0) { //--- Check if user message
         if (currentRole != "") {            //--- Check if previous message exists
            string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
            messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add message to JSON
         }
         currentRole = "User";              //--- Set role to user
         currentContent = StringSubstr(line, StringFind(line, "You: ") + 5); //--- Extract user message
      } else if (StringFind(trimmed, "AI: ") == 0) { //--- Check if AI message
         if (currentRole != "") {            //--- Check if previous message exists
            string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
            messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add message to JSON
         }
         currentRole = "AI";                //--- Set role to AI
         currentContent = StringSubstr(line, StringFind(line, "AI: ") + 4); //--- Extract AI message
      } else if (currentRole != "") {       //--- Handle continuation line
         currentContent += "\n" + line;     //--- Append line to content
      }
   }
   if (currentRole != "") {                  //--- Check if final message exists
      string roleJson = (currentRole == "User") ? "user" : "assistant"; //--- Set JSON role
      messages += "{\"role\":\"" + roleJson + "\",\"content\":\"" + JsonEscape(currentContent) + "\"},"; //--- Add final message to JSON
   }
   messages += "{\"role\":\"user\",\"content\":\"" + JsonEscape(newPrompt) + "\"}]"; //--- Add new prompt to JSON
   return messages;                          //--- Return JSON messages
}
We supercharge the "BuildMessagesFromHistory" function to package our conversation for the OpenAI API expertly. We start by splitting the "conversationHistory" string into lines using StringSplit with a newline delimiter. We then process each line, giving it a tidy haircut with StringTrimLeft and StringTrimRight to remove any unsightly whitespace, and we ruthlessly skip any empty lines or those pesky timestamp lines identified by "IsTimestamp".

Next, we play detective: we identify user messages that start with "You: " and AI messages that start with "AI: " using StringFind, then extract the juicy content with StringSubstr. We then build a pristine JSON array named "messages" by appending each message as a JSON object with its designated role ("user" or "assistant"), escaping the content for good behavior using "JsonEscape". This ensures our new prompt gets the royal treatment as the final user message.

Now, let's tackle the sidebar. We're going to update it with all the necessary elements and introduce persistent chats, because who doesn't love a good conversation that doesn't just vanish? But first, we need to define the chat's core logic, which will become the engine for rendering our complete navigation bar.

//+------------------------------------------------------------------+
//| Chat Structure Definition                                        |
//+------------------------------------------------------------------+
struct Chat {
   int id;                                        //--- Store chat ID
   string title;                                  //--- Store chat title
   string history;                                //--- Store chat history
};
Chat chats[];                                     //--- Declare array for chat storage
int current_chat_id = -1;                         //--- Store current chat ID
string current_title = "";                        //--- Store current chat title
string chatsFileName = "ChatGPT_Chats.txt";       //--- Set file name for chat storage

//+------------------------------------------------------------------+
//| Encode Chat ID to Base62                                         |
//+------------------------------------------------------------------+
string EncodeID(int id) {
   string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //--- Define base62 character set
   string res = "";                                //--- Initialize result string
   if (id == 0) return "0";                        //--- Return "0" for zero ID
   while (id > 0) {                                //--- Loop while ID is positive
      res = StringSubstr(chars, id % 62, 1) + res; //--- Prepend base62 character
      id /= 62;                                    //--- Divide ID by 62
   }
   return res;                                     //--- Return encoded ID
}

//+------------------------------------------------------------------+
//| Decode Base62 Chat ID                                            |
//+------------------------------------------------------------------+
int DecodeID(string enc) {
   string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; //--- Define base62 character set
   int id = 0;                                     //--- Initialize decoded ID
   for (int i = 0; i < StringLen(enc); i++) {      //--- Iterate through encoded string
      id = id * 62 + StringFind(chars, StringSubstr(enc, i, 1)); //--- Calculate ID value
   }
   return id;                                      //--- Return decoded ID
}

//+------------------------------------------------------------------+
//| Load Chats from File                                             |
//+------------------------------------------------------------------+
void LoadChats() {
   if (!FileIsExist(chatsFileName)) {              //--- Check if chats file exists
      CreateNewChat();                             //--- Create new chat if file not found
      return;                                      //--- Exit function
   }
   int handle = FileOpen(chatsFileName, FILE_READ | FILE_BIN); //--- Open chats file for reading
   if (handle == INVALID_HANDLE) {                 //--- Check if file opening failed
      Print("Failed to load chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   int file_size = (int)FileSize(handle);          //--- Get file size
   uchar encoded_file[];                           //--- Declare array for encoded file data
   ArrayResize(encoded_file, file_size);           //--- Resize array to file size
   FileReadArray(handle, encoded_file, 0, file_size); //--- Read file data into array
   FileClose(handle);                              //--- Close file
   uchar empty_key[];                              //--- Declare empty key array
   uchar key[32];                                  //--- Declare key array for decryption
   uchar api_bytes[];                              //--- Declare array for API key bytes
   StringToCharArray(OpenAI_API_Key, api_bytes);   //--- Convert API key to byte array
   uchar hash[];                                   //--- Declare array for hash
   CryptEncode(CRYPT_HASH_SHA256, api_bytes, empty_key, hash); //--- Generate SHA256 hash of API key
   ArrayCopy(key, hash, 0, 0, 32);                 //--- Copy first 32 bytes of hash to key
   uchar decoded_aes[];                            //--- Declare array for AES-decrypted data
   int res_dec = CryptDecode(CRYPT_AES256, encoded_file, key, decoded_aes); //--- Decrypt file data with AES256
   if (res_dec <= 0) {                             //--- Check if decryption failed
      Print("Failed to decrypt chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   uchar decoded_zip[];                            //--- Declare array for unzipped data
   int res_zip = CryptDecode(CRYPT_ARCH_ZIP, decoded_aes, empty_key, decoded_zip); //--- Decompress decrypted data
   if (res_zip <= 0) {                             //--- Check if decompression failed
      Print("Failed to decompress chats: ", GetLastError()); //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   string jsonStr = CharArrayToString(decoded_zip); //--- Convert decompressed data to string
   char charArray[];                               //--- Declare array for JSON characters
   int len = StringToCharArray(jsonStr, charArray, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert JSON string to char array
   JsonValue json;                                 //--- Declare JSON object
   int index = 0;                                  //--- Initialize JSON parsing index
   if (!json.DeserializeFromArray(charArray, len, index)) { //--- Parse JSON data
      Print("Failed to parse chats JSON");         //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   if (json.m_type != JsonArray) {                 //--- Check if JSON is an array
      Print("Chats JSON not an array");            //--- Log error
      CreateNewChat();                             //--- Create new chat
      return;                                      //--- Exit function
   }
   int size = ArraySize(json.m_children);          //--- Get number of chat objects
   ArrayResize(chats, size);                       //--- Resize chats array
   int max_id = 0;                                 //--- Initialize maximum chat ID
   for (int i = 0; i < size; i++) {                //--- Iterate through chat objects
      JsonValue obj = json.m_children[i];          //--- Get current chat object
      chats[i].id = (int)obj["id"].ToInteger();    //--- Set chat ID
      chats[i].title = obj["title"].ToString();    //--- Set chat title
      chats[i].history = obj["history"].ToString(); //--- Set chat history
      max_id = MathMax(max_id, chats[i].id);       //--- Update maximum chat ID
   }
   if (size > 0) {                                 //--- Check if chats exist
      current_chat_id = chats[size - 1].id;        //--- Set current chat ID to last chat
      current_title = chats[size - 1].title;       //--- Set current chat title
      conversationHistory = chats[size - 1].history; //--- Set current conversation history
   } else {                                        //--- No chats found
      CreateNewChat();                             //--- Create new chat
   }
}

//+------------------------------------------------------------------+
//| Save Chats to File                                               |
//+------------------------------------------------------------------+
void SaveChats() {
   JsonValue jsonArr;                            //--- Declare JSON array
   jsonArr.m_type = JsonArray;                   //--- Set JSON type to array
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      JsonValue obj;                             //--- Declare JSON object
      obj.m_type = JsonObject;                   //--- Set JSON type to object
      obj["id"] = chats[i].id;                   //--- Set chat ID in JSON
      obj["title"] = chats[i].title;             //--- Set chat title in JSON
      obj["history"] = chats[i].history;         //--- Set chat history in JSON
      jsonArr.AddChild(obj);                     //--- Add object to JSON array
   }
   string jsonStr = jsonArr.SerializeToString(); //--- Serialize JSON array to string
   uchar data[];                                 //--- Declare array for JSON data
   StringToCharArray(jsonStr, data);             //--- Convert JSON string to byte array
   uchar empty_key[];                            //--- Declare empty key array
   uchar zipped[];                               //--- Declare array for zipped data
   int res_zip = CryptEncode(CRYPT_ARCH_ZIP, data, empty_key, zipped); //--- Compress JSON data
   if (res_zip <= 0) {                           //--- Check if compression failed
      Print("Failed to compress chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   uchar key[32];                                //--- Declare key array for encryption
   uchar api_bytes[];                            //--- Declare array for API key bytes
   StringToCharArray(OpenAI_API_Key, api_bytes); //--- Convert API key to byte array
   uchar hash[];                                 //--- Declare array for hash
   CryptEncode(CRYPT_HASH_SHA256, api_bytes, empty_key, hash); //--- Generate SHA256 hash of API key
   ArrayCopy(key, hash, 0, 0, 32);               //--- Copy first 32 bytes of hash to key
   uchar encoded[];                              //--- Declare array for encrypted data
   int res_enc = CryptEncode(CRYPT_AES256, zipped, key, encoded); //--- Encrypt compressed data with AES256
   if (res_enc <= 0) {                           //--- Check if encryption failed
      Print("Failed to encrypt chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   int handle = FileOpen(chatsFileName, FILE_WRITE | FILE_BIN); //--- Open chats file for writing
   if (handle == INVALID_HANDLE) {               //--- Check if file opening failed
      Print("Failed to save chats: ", GetLastError()); //--- Log error
      return;                                    //--- Exit function
   }
   FileWriteArray(handle, encoded, 0, res_enc);  //--- Write encrypted data to file
   FileClose(handle);                            //--- Close file
}

//+------------------------------------------------------------------+
//| Get Chat Index by ID                                             |
//+------------------------------------------------------------------+
int GetChatIndex(int id) {
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      if (chats[i].id == id) return i;           //--- Return index if ID matches
   }
   return -1;                                    //--- Return -1 if ID not found
}

//+------------------------------------------------------------------+
//| Create New Chat                                                  |
//+------------------------------------------------------------------+
void CreateNewChat() {
   int max_id = 0;                               //--- Initialize maximum chat ID
   for (int i = 0; i < ArraySize(chats); i++) {  //--- Iterate through chats
      max_id = MathMax(max_id, chats[i].id);     //--- Update maximum chat ID
   }
   int new_id = max_id + 1;                      //--- Calculate new chat ID
   int size = ArraySize(chats);                  //--- Get current chats array size
   ArrayResize(chats, size + 1);                 //--- Resize chats array
   chats[size].id = new_id;                      //--- Set new chat ID
   chats[size].title = "Chat " + IntegerToString(new_id); //--- Set new chat title
   chats[size].history = "";                     //--- Initialize empty chat history
   current_chat_id = new_id;                     //--- Set current chat ID
   current_title = chats[size].title;            //--- Set current chat title
   conversationHistory = "";                     //--- Clear current conversation history
   SaveChats();                                  //--- Save chats to file
   UpdateSidebarDynamic();                       //--- Update sidebar display
   UpdateResponseDisplay();                      //--- Update response display
   UpdatePromptDisplay();                        //--- Update prompt display
   CreatePlaceholder();                          //--- Create prompt placeholder
   ChartRedraw();                                //--- Redraw chart to reflect changes
}

//+------------------------------------------------------------------+
//| Update Current Chat History                                      |
//+------------------------------------------------------------------+
void UpdateCurrentHistory() {
   int idx = GetChatIndex(current_chat_id);      //--- Get index of current chat
   if (idx >= 0) {                               //--- Check if valid index
      chats[idx].history = conversationHistory;  //--- Update chat history
      chats[idx].title = current_title;          //--- Update chat title
      SaveChats();                               //--- Save chats to file
   }
}
Here, we implement persistent chat storage and management functions to make our conversations stick around between sessions, enabling that sweet, seamless navigation via the sidebar we're about to update. We define a "Chat" structure to store the "id", "title", and "history" for each conversation, backed by a "chats" array, a "current_chat_id", and a "current_title" to keep track of the active session. We stash all this gold in a file named "ChatGPT_Chats.txt".

To keep things tidy, we create the "EncodeID" and "DecodeID" functions, which convert chat IDs to and from a compact "base62" format using a custom character set and StringSubstr, perfect for squeezing into our sidebar.

The "LoadChats" function is our digital archaeologist. It reads the "ChatGPT_Chats.txt" file with FileOpen, then goes on a decryption adventure using CryptDecode with "CRYPT_AES256" and a key derived from your "OpenAI_API_Key". After decompressing the data with "CRYPT_ARCH_ZIP", it parses the JSON with "DeserializeFromArray" to resurrect our "chats" array. If it finds only digital dust (or just errors), it wisely defaults to "CreateNewChat".

Conversely, "SaveChats" is the pack-rat. It serializes the "chats" array to JSON with "SerializeToString", squeezes it down with CryptEncode using "CRYPT_ARCH_ZIP", encrypts it with "CRYPT_AES256", and finally locks it all away in "ChatGPT_Chats.txt" using FileWriteArray.

We also whip up "GetChatIndex" to find a chat by its ID and "CreateNewChat" to spin up new conversations with fresh IDs, updating all the relevant trackers, saving the changes, and refreshing the UI. The "UpdateCurrentHistory" function ensures the current chat's "history" and "title" are always saved, making our chats gloriously persistent.

The choice of this decoding and encoding approach is entirely based on you. We just chose the easiest path to keep things simple. Now equipped with these functions, we can define the logic to update the sidebar.

//+------------------------------------------------------------------+
//| Update Sidebar Dynamically                                       |
//+------------------------------------------------------------------+
void UpdateSidebarDynamic() {
   int total = ObjectsTotal(0, 0, -1);           //--- Get total number of chart objects
   for (int j = total - 1; j >= 0; j--) {       //--- Iterate through objects in reverse
      string name = ObjectName(0, j, 0, -1);     //--- Get object name
      if (StringFind(name, "ChatGPT_NewChatButton") == 0 || StringFind(name, "ChatGPT_ClearButton") == 0 || StringFind(name, "ChatGPT_HistoryButton") == 0 || StringFind(name, "ChatGPT_ChatLabel_") == 0 || StringFind(name, "ChatGPT_ChatBg_") == 0 || StringFind(name, "ChatGPT_SidebarLogo") == 0 || StringFind(name, "ChatGPT_NewChatIcon") == 0 || StringFind(name, "ChatGPT_NewChatLabel") == 0 || StringFind(name, "ChatGPT_ClearIcon") == 0 || StringFind(name, "ChatGPT_ClearLabel") == 0 || StringFind(name, "ChatGPT_HistoryIcon") == 0 || StringFind(name, "ChatGPT_HistoryLabel") == 0) { //--- Check if object is part of sidebar
         ObjectDelete(0, name);                  //--- Delete sidebar object
      }
   }
   int sidebarX = g_dashboardX;                   //--- Set sidebar x position
   int itemY = g_mainY + 10;                     //--- Set initial item y position
   string sidebar_logo_resource = (StringLen(g_scaled_sidebar_resource) > 0) ? g_scaled_sidebar_resource : resourceImgLogo; //--- Select sidebar logo resource
   createBitmapLabel("ChatGPT_SidebarLogo", sidebarX + (g_sidebarWidth - 81)/2, itemY, 81, 81, sidebar_logo_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create sidebar logo
   ObjectSetInteger(0, "ChatGPT_SidebarLogo", OBJPROP_ZORDER, 1); //--- Set logo z-order
   itemY += 81 + 10;                             //--- Update item y position
   createButton("ChatGPT_NewChatButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, new_chat_original_bg, clrRoyalBlue); //--- Create new chat button
   ObjectSetInteger(0, "ChatGPT_NewChatButton", OBJPROP_ZORDER, 1); //--- Set new chat button z-order
   string newchat_icon_resource = (StringLen(g_scaled_newchat_resource) > 0) ? g_scaled_newchat_resource : resourceNewChat; //--- Select new chat icon resource
   createBitmapLabel("ChatGPT_NewChatIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, newchat_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create new chat icon
   ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_ZORDER, 2); //--- Set new chat icon z-order
   ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_SELECTABLE, false); //--- Disable new chat icon selectability
   createLabel("ChatGPT_NewChatLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "New Chat", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); //--- Create new chat label
   ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_ZORDER, 2); //--- Set new chat label z-order
   ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_SELECTABLE, false); //--- Disable new chat label selectability
   itemY += g_buttonHeight + 5;                  //--- Update item y position
   createButton("ChatGPT_ClearButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, clear_original_bg, clrIndianRed); //--- Create clear button
   ObjectSetInteger(0, "ChatGPT_ClearButton", OBJPROP_ZORDER, 1); //--- Set clear button z-order
   string clear_icon_resource = (StringLen(g_scaled_clear_resource) > 0) ? g_scaled_clear_resource : resourceClear; //--- Select clear icon resource
   createBitmapLabel("ChatGPT_ClearIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, clear_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create clear icon
   ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_ZORDER, 2); //--- Set clear icon z-order
   ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_SELECTABLE, false); //--- Disable clear icon selectability
   createLabel("ChatGPT_ClearLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "Clear", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); //--- Create clear label
   ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_ZORDER, 2); //--- Set clear label z-order
   ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_SELECTABLE, false); //--- Disable clear label selectability
   itemY += g_buttonHeight + 10;                 //--- Update item y position
   createButton("ChatGPT_HistoryButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrBlack, 12, clrWhite, clrGray); //--- Create history button
   ObjectSetInteger(0, "ChatGPT_HistoryButton", OBJPROP_ZORDER, 1); //--- Set history button z-order
   string history_icon_resource = (StringLen(g_scaled_history_resource) > 0) ? g_scaled_history_resource : resourceHistory; //--- Select history icon resource
   createBitmapLabel("ChatGPT_HistoryIcon", sidebarX + 5 + 10, itemY + (g_buttonHeight - 30)/2, 30, 30, history_icon_resource, clrNONE, CORNER_LEFT_UPPER); //--- Create history icon
   ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_ZORDER, 2); //--- Set history icon z-order
   ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_SELECTABLE, false); //--- Disable history icon selectability
   createLabel("ChatGPT_HistoryLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "History", clrBlack, 12, "Arial", CORNER_LEFT_UPPER); //--- Create history label
   ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_ZORDER, 2); //--- Set history label z-order
   ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_SELECTABLE, false); //--- Disable history label selectability
   itemY += g_buttonHeight + 5;                  //--- Update item y position
   int numChats = MathMin(ArraySize(chats), 7);  //--- Limit number of chats to display
   int chatIndices[7];                           //--- Declare array for chat indices
   for (int i = 0; i < numChats; i++) {          //--- Iterate to set chat indices
      chatIndices[i] = ArraySize(chats) - 1 - i; //--- Set index for latest chats first
   }
   for (int i = 0; i < numChats; i++) {          //--- Iterate through chats to display
      int chatIdx = chatIndices[i];              //--- Get chat index
      string hashed_id = EncodeID(chats[chatIdx].id); //--- Encode chat ID to base62
      string fullText = chats[chatIdx].title + " > " + hashed_id; //--- Create full chat title text
      string labelText = fullText;               //--- Initialize label text
      if (StringLen(fullText) > 19) {            //--- Check if text exceeds length limit
         labelText = StringSubstr(fullText, 0, 16) + "..."; //--- Truncate text with ellipsis
      }
      string bgName = "ChatGPT_ChatBg_" + hashed_id; //--- Generate background object name
      string labelName = "ChatGPT_ChatLabel_" + hashed_id; //--- Generate label object name
      color bgColor = clrWhite;                  //--- Set background color
      color borderColor = clrGray;               //--- Set border color
      createRecLabel(bgName, sidebarX + 5 + 10, itemY, g_sidebarWidth - 10 - 10, 25, clrBeige, 1, DarkenColor(clrBeige, 9), BORDER_FLAT, STYLE_SOLID); //--- Create chat background rectangle
      ObjectSetInteger(0, bgName, OBJPROP_ZORDER, 1); //--- Set background z-order
      color textColor = (chats[chatIdx].id == current_chat_id) ? clrBlue : clrBlack; //--- Set text color based on selection
      createLabel(labelName, sidebarX + 10 + 10, itemY + 3, labelText, textColor, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create chat label
      ObjectSetInteger(0, labelName, OBJPROP_ZORDER, 2); //--- Set label z-order
      itemY += 25 + 5;                           //--- Update item y position
   }
   ChartRedraw();                                //--- Redraw chart to reflect changes
}
We implement the "UpdateSidebarDynamic" function to build a dynamic sidebar, our very own navigation panel for all the persistent chat histories we create. First, we play digital janitor, clearing out old sidebar objects like "ChatGPT_NewChatButton", "ChatGPT_ClearButton", and the various "ChatGPT_ChatLabel_" items. We do this by looping through ObjectsTotal, checking names with "ObjectName" and StringFind, and giving them the boot with "ObjectDelete".

With a clean slate, we rebuild the sidebar from the ground up at position "g_dashboardX". We start by mounting the "ChatGPT_SidebarLogo" using "createBitmapLabel" and our "g_scaled_sidebar_resource", because every good cockpit needs a brand insignia.

Next, we wire up the main controls: the "ChatGPT_NewChatButton", "ChatGPT_ClearButton", and "ChatGPT_HistoryButton" using "createButton". We give them some visual flair with their accompanying icons ("ChatGPT_NewChatIcon", etc.) via "createBitmapLabel" and text labels ("ChatGPT_NewChatLabel", etc.) with "createLabel". We set their "OBJPROP_ZORDER" and disable their selectability with OBJPROP_SELECTABLE so they don't get accidentally highlighted like a misplaced museum piece.

Finally, we populate the sidebar with up to seven of our most recent chats from the "chats" array. We encode their IDs with "EncodeID" for a compact look, then create a background "ChatGPT_ChatBg_" and a text "ChatGPT_ChatLabel_" for each using "createRecLabel" and "createLabel". If a chat title gets too long-winded, we politely cut it short with "StringSubstr". And to show which chat is currently holding the microphone, we highlight the active one in "clrBlue" by comparing it to the "current_chat_id". A quick ChartRedraw later, and we have a seamless sidebar experience. When we call this function during initialization, we get the following outcome.

With the sidebar fully updated, we are now all good. We just need to take care of the elements that we did create when needed to in the "OnDeinit" event handler.

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   UpdateCurrentHistory();                           //--- Update current chat history
   ObjectsDeleteAll(0, "ChatGPT_");                  //--- Delete all ChatGPT objects
   DeleteScrollbar();                                //--- Delete main scrollbar elements
   DeletePromptScrollbar();                          //--- Delete prompt scrollbar elements
   if (StringLen(g_scaled_image_resource) > 0) {     //--- Check if main image resource exists
      ResourceFree(g_scaled_image_resource);         //--- Free main image resource
   }
   if (StringLen(g_scaled_sidebar_resource) > 0) {   //--- Check if sidebar image resource exists
      ResourceFree(g_scaled_sidebar_resource);       //--- Free sidebar image resource
   }
   if (StringLen(g_scaled_newchat_resource) > 0) {   //--- Check if new chat icon resource exists
      ResourceFree(g_scaled_newchat_resource);       //--- Free new chat icon resource
   }
   if (StringLen(g_scaled_clear_resource) > 0) {     //--- Check if clear icon resource exists
      ResourceFree(g_scaled_clear_resource);         //--- Free clear icon resource
   }
   if (StringLen(g_scaled_history_resource) > 0) {   //--- Check if history icon resource exists
      ResourceFree(g_scaled_history_resource);       //--- Free history icon resource
   }
   if (logFileHandle != INVALID_HANDLE) {            //--- Check if log file is open
      FileClose(logFileHandle);                      //--- Close log file
   }
}

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick() {
}

//+------------------------------------------------------------------+
//| Hide Dashboard                                                   |
//+------------------------------------------------------------------+
void HideDashboard() {
   dashboard_visible = false;                        //--- Set dashboard visibility to false
   for (int i = 0; i < objCount; i++) {              //--- Iterate through dashboard objects
      ObjectDelete(0, dashboardObjects[i]);          //--- Delete dashboard object
   }
   DeleteScrollbar();                                //--- Delete main scrollbar elements
   DeletePromptScrollbar();                          //--- Delete prompt scrollbar elements
   ObjectDelete(0, "ChatGPT_DashboardBg");           //--- Delete dashboard background
   ObjectDelete(0, "ChatGPT_SidebarBg");             //--- Delete sidebar background
   ChartRedraw();                                    //--- Redraw chart to reflect changes
}
In the OnDeinit function, we perform a graceful shutdown. We call "UpdateCurrentHistory" to save the current chat's state like a diligent scribe. Then, we wipe the slate clean, removing all "ChatGPT_" prefixed objects with ObjectsDeleteAll and specifically targeting scrollbars with "DeleteScrollbar" and "DeletePromptScrollbar". We also free our scaled image resources—"g_scaled_image_resource", "g_scaled_sidebar_resource", and others—using ResourceFree if they exist, and finally close the "logFileHandle" with FileClose to prevent any resource leaks.

The OnTick function remains empty for now, as our program is a creature of event-driven whims. Meanwhile, the "HideDashboard" function plays hide-and-seek masterfully: it sets "dashboard_visible" to false, deletes all objects in the "dashboardObjects" array using ObjectDelete, removes "ChatGPT_DashboardBg" and "ChatGPT_SidebarBg", and banishes the scrollbars with "DeleteScrollbar" and "DeletePromptScrollbar". A final "ChartRedraw" function refreshes the chart, making the UI vanish seamlessly. We call this masterful disappearing act when we click the close chat button.

Also, now that we append chart data to our prompt, we need to upgrade the function that sends the prompt message. Here is the logic we used to achieve that.

//+------------------------------------------------------------------+
//| Submit User Prompt and Handle Response                           |
//+------------------------------------------------------------------+
void SubmitMessage(string prompt) {
   if (StringLen(prompt) == 0) return;       //--- Exit if prompt is empty
   string timestamp = TimeToString(TimeCurrent(), TIME_MINUTES); //--- Get current time as string
   string response = "";                     //--- Initialize response string
   bool send_to_api = true;                  //--- Set flag to send to API
   if (StringFind(prompt, "set title ") == 0) { //--- Check if prompt is a title change
      string new_title = StringSubstr(prompt, 10); //--- Extract new title
      current_title = new_title;             //--- Set current chat title
      response = "Title set to " + new_title; //--- Set response message
      send_to_api = false;                   //--- Disable API call
      UpdateCurrentHistory();                //--- Update current chat history
      UpdateSidebarDynamic();                //--- Update sidebar display
   }
   if (send_to_api) {                        //--- Check if API call is needed
      Print("Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); //--- Log chat ID and title
      FileWrite(logFileHandle, "Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); //--- Write chat ID and title to log
      Print("User: " + prompt);              //--- Log user prompt
      FileWrite(logFileHandle, "User: " + prompt); //--- Write user prompt to log
      response = GetChatGPTResponse(prompt); //--- Get response from ChatGPT API
      Print("AI: " + response);              //--- Log AI response
      FileWrite(logFileHandle, "AI: " + response); //--- Write AI response to log
      if (StringFind(current_title, "Chat ") == 0) { //--- Check if title is default
         current_title = StringSubstr(prompt, 0, 30); //--- Set title to first 30 characters of prompt
         if (StringLen(prompt) > 30) current_title += "..."; //--- Append ellipsis if truncated
         UpdateCurrentHistory();             //--- Update current chat history
         UpdateSidebarDynamic();             //--- Update sidebar display
      }
   }
   conversationHistory += "You: " + prompt + "\n" + timestamp + "\nAI: " + response + "\n" + timestamp + "\n\n"; //--- Append to conversation history
   UpdateCurrentHistory();                   //--- Update current chat history
   UpdateResponseDisplay();                  //--- Update response display
   scroll_pos = MathMax(0, g_total_height - g_visible_height); //--- Set scroll position to bottom
   UpdateResponseDisplay();                  //--- Update response display again
   if (scroll_visible) {                     //--- Check if main scrollbar is visible
      UpdateSliderPosition();                //--- Update main slider position
      UpdateButtonColors();                  //--- Update main scrollbar button colors
   }
   ChartRedraw();                            //--- Redraw chart
}
In the "SubmitMessage" function, we upgrade it to handle the full user prompt—now supercharged with chart data—and seamlessly integrate the AI's response, all while managing custom chat titles and rock-solid conversation persistence.

First, we check if the "prompt" is empty using StringLen and make a quick exit if it is. If we have a real prompt, we capture the current timestamp with TimeToString. Here's where the magic splits: if the prompt starts with "set title " (found via StringFind), we're in configuration mode. We extract the new title with StringSubstr, update the "current_title", set a local "response" to confirm the change, and call "UpdateCurrentHistory" and "UpdateSidebarDynamic" without bothering the AI.

Otherwise, we're in a standard chat. We log the "current_chat_id" and "current_title", then fetch the real AI wisdom using "GetChatGPTResponse". If we're still using a default title, we cleverly generate a new one from the first 30 characters of the prompt. We then append both the prompt and the brilliant response to the "conversationHistory" with their timestamps, making it all official. Finally, we refresh the entire UI with "UpdateResponseDisplay", "UpdateSliderPosition", and "UpdateButtonColors", ensuring we automatically scroll to the bottom using "scroll_pos" and redraw the chart.

We can now update the final part on chart interaction, which follows the same structure we've mastered. We'll just explain the most critical part of the new chat histories; the rest is familiar territory to us now.

//+------------------------------------------------------------------+
//| Handle Chart Events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   int displayX = g_mainContentX + g_sidePadding;   //--- Calculate main display x position
   int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate main display y position
   int displayW = g_mainWidth - 2 * g_sidePadding;  //--- Calculate main display width
   int displayH = g_displayHeight;                  //--- Set main display height
   int footerY = displayY + g_displayHeight + g_padding; //--- Calculate footer y position
   int promptY = footerY + g_margin;                //--- Calculate prompt y position
   int promptH = g_promptHeight;                    //--- Set prompt height
   int closeX = g_mainContentX + g_mainWidth - 100 - g_sidePadding; //--- Calculate close button x position
   int closeY = g_mainY + 4;                        //--- Calculate close button y position
   int closeW = 100;                                //--- Set close button width
   int closeH = g_headerHeight - 8;                 //--- Calculate close button height
   int buttonsY = promptY + g_promptHeight + g_margin; //--- Calculate buttons y position
   int buttonW = 140;                               //--- Set button width
   int chartX = g_mainContentX + g_sidePadding;     //--- Calculate chart button x position
   int sendX = g_mainContentX + g_mainWidth - g_sidePadding - buttonW; //--- Calculate send button x position
   int editY = promptY + g_promptHeight - g_editHeight - 5; //--- Calculate edit field y position
   int editX = displayX + g_textPadding;            //--- Calculate edit field x position
   bool need_scroll = g_total_height > g_visible_height; //--- Check if main scrollbar is needed
   bool p_need_scroll = p_total_height > p_visible_height; //--- Check if prompt scrollbar is needed
   if (id == CHARTEVENT_OBJECT_CLICK) {             //--- Handle object click event
      if (StringFind(sparam, "ChatGPT_ChatLabel_") == 0) { //--- Check if chat label clicked
         string hashed_id = StringSubstr(sparam, StringLen("ChatGPT_ChatLabel_")); //--- Extract hashed ID
         int new_id = DecodeID(hashed_id);          //--- Decode chat ID
         int idx = GetChatIndex(new_id);            //--- Get chat index
         if (idx >= 0 && new_id != current_chat_id) { //--- Check if valid and different chat
            UpdateCurrentHistory();                 //--- Update current chat history
            current_chat_id = new_id;               //--- Set new chat ID
            current_title = chats[idx].title;       //--- Set new chat title
            conversationHistory = chats[idx].history; //--- Set new conversation history
            UpdateResponseDisplay();                //--- Update response display
            UpdateSidebarDynamic();                 //--- Update sidebar display
            ChartRedraw();                          //--- Redraw chart
         }
         return;                                    //--- Exit function
      }
   }
}
In the OnChartEvent event handler, we calculate layout positions for the main display, prompt area, and buttons using variables like "g_mainContentX", "g_sidePadding", "g_headerHeight", "g_displayHeight", "g_promptHeight", and "g_textPadding", and determine scrollbar needs with "g_total_height", "g_visible_height", "p_total_height", and "p_visible_height" like we did in the previous version.

For "CHARTEVENT_OBJECT_CLICK" events, we check if a "ChatGPT_ChatLabel_" is clicked using StringFind, extract the hashed ID with StringSubstr, decode it with "DecodeID", and switch to the selected chat by updating "current_chat_id", "current_title", and "conversationHistory" via "GetChatIndex", followed by refreshing the UI with "UpdateCurrentHistory", "UpdateResponseDisplay", "UpdateSidebarDynamic", and ChartRedraw, ensuring seamless chat navigation in the sidebar. When we compile, we get the following outcome.

From the visualization, we can see the chart events are working beautifully. For the chats, we can confirm they are perfectly persistent between sessions, allowing us to retrieve old conversations and continue them seamlessly. They are securely encrypted, so if you try to peek at the log file, you should be greeted by something completely unreadable to humans, like the following sample from our own tests:

From the visualization, we can see we've successfully upgraded the program by adding new elements, displaying a fully functional scrollable prompt section, and crafting an interface that's genuinely interactable with persistent chats. We have, therefore, successfully achieved our core objectives. The final thing that remains is backtesting the program, and that adventure is handled in the next section.


Backtesting the Enhancements


We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.


Conclusion


In conclusion, we've supercharged our MQL5 program, conquering the ancient beast of multiline input with a robust text-rendering system. We've built a dynamic sidebar for navigating persistent chats, locked down with secure "CRYPT_AES256" encryption and "CRYPT_ARCH_ZIP" compression, and we've even taught it to generate initial trade signals by integrating live chart data.

This powerful system now lets us interact seamlessly with AI-driven market insights, maintaining perfect conversation context across sessions through intuitive controls, all wrapped in a visually branded UI complete with dual scrollbars. For the next versions, we'll further refine AI-driven signal generation and explore the thrilling frontier of automated trade execution to elevate our trading assistant from a savvy advisor to a full-blown digital co-pilot. Stay tuned. Meanwhile, you can watch the whole lesson about the creation that we have made in our YouTube video lesson.


Attached Files


 S/NName Type Description 
 1 AI_JSON_FILE.mqh JSON Class Library Class for handling JSON serialization and deserialization
 2 AI_CREATE_OBJECTS_FNS.mqh Object Functions Library Functions for creating visualization objects like labels and buttons
 3 AI_ChatGPT_EA_Part_4.mq5 Expert Advisor source code Main Expert Advisor source code for handling AI integration

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.