Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 3): Upgrade auf eine scrollbare, auf den Einzelchat ausgerichtete Nutzeroberfläche
Einführung
In unserem vorigen Artikel (Teil 2) haben wir ein interaktives ChatGPT-gesteuertes Programm mit einer Nutzeroberfläche (UI) in MetaQuotes Language 5 (MQL5) erstellt. Das Tool ermöglichte es uns, Aufforderungen an die API OpenAI zu senden und die Antworten sofort direkt im Chart anzuzeigen. Jetzt, in Teil 3, gehen wir noch einen Schritt weiter: Machen Sie sich bereit für ein scrollbares Dashboard im Chat-Stil, komplett mit Zeitstempeln, reibungslosem dynamischem Scrollen und einem umfangreichen Gesprächsverlauf für ansprechende Chats mit mehreren Runden. Wir werden uns mit folgenden Themen befassen:
- Verständnis des aktualisierten ChatGPT-Programmrahmens
- Implementation in MQL5
- Testen des ChatGPT-Programms
- Schlussfolgerung
Am Ende werden Sie ein erweitertes MQL5-Programm für interaktive KI-Abfragen haben, das zur Anpassung bereit ist – legen wir los!
Verständnis des aktualisierten ChatGPT-Programmrahmens
Der aktualisierte ChatGPT-Programmrahmen verbessert unsere KI-gesteuerte Handelsschnittstelle durch eine scrollbare, chat-orientierte Nutzeroberfläche, die Multi-Turn-Konversationen, Zeitstempel und dynamische Nachrichtenverarbeitung unterstützt und es uns ermöglicht, den Kontext für Abfragen über Sitzungen hinweg beizubehalten. Seine Aufgabe ist es, eine nahtlose Konversationserfahrung zu bieten und die Nutzerfreundlichkeit zu verbessern, indem es uns ermöglicht, den Verlauf zu überprüfen und auf früheren KI-Antworten aufzubauen, was für die Verfeinerung von Handelsstrategien von entscheidender Bedeutung ist, ohne dass Erkenntnisse aus früheren Interaktionen verloren gehen. Wir dachten uns, wenn wir uns auf ein Gespräch beschränken, auf das sich die KI beziehen kann, können wir jederzeit zurückgehen und die Aufforderungen verfeinern und bei Bedarf Korrekturen vornehmen.
Unser Ansatz besteht darin, ein Dashboard zu entwickeln, das sich auf einen einzigen Chat konzentriert, mit verschiebbaren Text, Hover-Effekten und der Erstellung von Nachrichten für Anfragen via API, wobei sichergestellt wird, dass sich die Oberfläche an die Länge der Konversation und die Nutzerpräferenzen für die Sichtbarkeit des Schiebereglers anpasst. Wir werden eine Logik implementieren, um die Historie für Multi-Turn-Abfragen zu analysieren, Zeitstempel für mehr Klarheit hinzuzufügen und Funktionen wie das Löschen von Unterhaltungen oder das Starten neuer Chats zu aktivieren, um ein Tool zu schaffen, das die laufende KI-Unterstützung für Handelsentscheidungen mit einer neuen, verbesserten Schnittstelle erleichtert. Schauen Sie sich an, was wir erreichen werden.

Implementation in MQL5
Um das aktualisierte Programm in MQL5 zu implementieren, werden wir zunächst den Abschnitt Eingaben ändern, um eine neue Eingabe für den Schiebereglermodus zu integrieren, sodass wir den Schieberegler entweder einblenden können, wenn wir den Mauszeiger bewegen müssen, oder ihn immer sichtbar haben. Wir werden eine Enumeration dafür hinzufügen und auch die Antwort-Token-Grenze auf 3000 erhöhen, da wir jetzt eine reichhaltige Konversation haben können, die sich gerade ausreichend anfühlt, aber Sie können sie bei Bedarf erhöhen.
//+------------------------------------------------------------------+ //| ChatGPT AI EA Part 3.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 //--- Input parameters enum ENUM_SCROLLBAR_MODE { SCROLL_DYNAMIC_ALWAYS, // Show when needed SCROLL_DYNAMIC_HOVER, // Show on hover when needed SCROLL_WHEEL_ONLY // No scrollbar, wheel scroll only }; input ENUM_SCROLLBAR_MODE ScrollbarMode = SCROLL_DYNAMIC_HOVER; // Scrollbar Behavior //--- Scrollbar object names #define SCROLL_LEADER "ChatGPT_Scroll_Leader" #define SCROLL_UP_REC "ChatGPT_Scroll_Up_Rec" #define SCROLL_UP_LABEL "ChatGPT_Scroll_Up_Label" #define SCROLL_DOWN_REC "ChatGPT_Scroll_Down_Rec" #define SCROLL_DOWN_LABEL "ChatGPT_Scroll_Down_Label" #define SCROLL_SLIDER "ChatGPT_Scroll_Slider" //--- Input parameters input string OpenAI_Model = "gpt-3.5-turbo"; // OpenAI Model input string OpenAI_Endpoint = "https://api.openai.com/v1/chat/completions"; // OpenAI API Endpoint input int MaxResponseLength = 3000; // Max length of ChatGPT response to display input string LogFileName = "ChatGPT_EA_Log.txt"; // Log file name
Wir beginnen die Upgrade-Implementierung mit der Definition der Konfigurationsparameter und Konstanten zur Steuerung des Bildlaufverhaltens und der API-Einstellungen. Zunächst erstellen wir die Enumeration „ENUM_SCROLLBAR_MODE“ mit den Optionen „SCROLL_DYNAMIC_ALWAYS“ (Schieberegler bei Bedarf anzeigen), „SCROLL_DYNAMIC_HOVER“ (zeigt bei Bedarf den Schieberegler an) und „SCROLL_WHEEL_ONLY“ (kein Schieberegler, nur Bildlauf mit dem Rad), wobei der Eingang „ScrollbarMode“ auf „SCROLL_DYNAMIC_HOVER“ gesetzt Schieberegler^wird, damit der Nutzer dies bevorzugt.
Dann definieren wir die Konstanten für Schieberegler-Objektnamen wie „SCROLL_LEADER“, „SCROLL_UP_REC“, „SCROLL_UP_LABEL“, „SCROLL_DOWN_REC“, „SCROLL_DOWN_LABEL“ und „SCROLL_SLIDER“ für eine einheitliche Referenzierung in der Nutzeroberfläche. Als Nächstes setzen wir den Eingabeparameter „MaxResponseLength“ auf 3000, um den angezeigten Antworttext zu begrenzen und sicherzustellen, dass wir längere Gespräche führen können. Als Nächstes müssen wir die Klasse JSON so ändern, dass sie auch mit doppelten Werten umgehen kann – nur eine kleine Erweiterung.
bool DeserializeFromArray(char &jsonCharacterArray[], int arrayLength, int ¤tIndex) { //--- Deserialize from array string validNumericCharacters = "0123456789+-.eE"; //--- Valid number chars int startPosition = currentIndex; //--- Start position for(; currentIndex < arrayLength; currentIndex++) { //--- Loop array char currentCharacter = jsonCharacterArray[currentIndex]; //--- Current char if(currentCharacter == 0) break; //--- Break on null switch(currentCharacter) { //--- Switch on char case '\t': case '\r': case '\n': case ' ': startPosition = currentIndex + 1; break; //--- Skip whitespace case '[': { //--- Array start startPosition = currentIndex + 1; //--- Update start if(m_type != JsonUndefined) return false; //--- Type check m_type = JsonArray; //--- Set array currentIndex++; //--- Increment JsonValue childValue(GetPointer(this), JsonUndefined); //--- Child value while(childValue.DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex)) { //--- Loop children if(childValue.m_type != JsonUndefined) AddChild(childValue); //--- Add if defined if(childValue.m_type == JsonInteger || childValue.m_type == JsonDouble || childValue.m_type == JsonArray) currentIndex++; //--- Adjust index childValue.Reset(); //--- Reset child childValue.m_parent = GetPointer(this); //--- Set parent if(jsonCharacterArray[currentIndex] == ']') break; //--- End array currentIndex++; //--- Increment if(currentIndex >= arrayLength) return false; //--- Bounds check } return (jsonCharacterArray[currentIndex] == ']' || jsonCharacterArray[currentIndex] == 0); //--- Valid end } //--- End array case case ']': return (m_parent && m_parent.m_type == JsonArray); //--- Array end case ':': { //--- Key separator if(m_temporaryKey == "") return false; //--- Key check JsonValue childValue(GetPointer(this), JsonUndefined); //--- New child JsonValue *addedChild = AddChild(childValue); //--- Add addedChild.m_key = m_temporaryKey; //--- Set key m_temporaryKey = ""; //--- Clear temp currentIndex++; //--- Increment if(!addedChild.DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex)) return false; //--- Recurse } break; //--- End key case case ',': { //--- Value separator startPosition = currentIndex + 1; //--- Update start if(!m_parent && m_type != JsonObject) return false; //--- Check context if(m_parent && m_parent.m_type != JsonArray && m_parent.m_type != JsonObject) return false; //--- Parent type if(m_parent && m_parent.m_type == JsonArray && m_type == JsonUndefined) return true; //--- Undefined in array } break; //--- End separator case '{': { //--- Object start startPosition = currentIndex + 1; //--- Update start if(m_type != JsonUndefined) return false; //--- Type check m_type = JsonObject; //--- Set object currentIndex++; //--- Increment if(!DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex)) return false; //--- Recurse return (jsonCharacterArray[currentIndex] == '}' || jsonCharacterArray[currentIndex] == 0); //--- Valid end } break; //--- End object case case '}': return (m_type == JsonObject); //--- Object end case 't': case 'T': case 'f': case 'F': { //--- Boolean start if(m_type != JsonUndefined) return false; //--- Type check m_type = JsonBoolean; //--- Set boolean if(currentIndex + 3 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "true", false) == 0) { //--- True check m_booleanValue = true; currentIndex += 3; return true; //--- Set true } if(currentIndex + 4 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 5), "false", false) == 0) { //--- False check m_booleanValue = false; currentIndex += 4; return true; //--- Set false } return false; //--- Invalid boolean } break; //--- End boolean case 'n': case 'N': { //--- Null start if(m_type != JsonUndefined) return false; //--- Type check m_type = JsonNull; //--- Set null if(currentIndex + 3 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "null", false) == 0) { //--- Null check currentIndex += 3; return true; //--- Valid null } return false; //--- Invalid null } break; //--- End null case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '+': case '.': { //--- Number start if(m_type != JsonUndefined) return false; //--- Type check bool isDouble = false; //--- Double flag int startOfNumber = currentIndex; //--- Number start while(jsonCharacterArray[currentIndex] != 0 && currentIndex < arrayLength) { //--- Parse number currentIndex++; //--- Increment if(StringFind(validNumericCharacters, GetSubstringFromArray(jsonCharacterArray, currentIndex, 1)) < 0) break; //--- Invalid char if(!isDouble) isDouble = (jsonCharacterArray[currentIndex] == '.' || jsonCharacterArray[currentIndex] == 'e' || jsonCharacterArray[currentIndex] == 'E'); //--- Set double } m_stringValue = GetSubstringFromArray(jsonCharacterArray, startOfNumber, currentIndex - startOfNumber); //--- Get string if(isDouble) { //--- Double handling m_type = JsonDouble; //--- Set type m_doubleValue = StringToDouble(m_stringValue); //--- Convert double m_integerValue = (long)m_doubleValue; //--- Set integer m_booleanValue = m_integerValue != 0; //--- Set boolean } else { //--- Integer handling m_type = JsonInteger; //--- Set type m_integerValue = StringToInteger(m_stringValue); //--- Convert integer m_doubleValue = (double)m_integerValue; //--- Set double m_booleanValue = m_integerValue != 0; //--- Set boolean } currentIndex--; //--- Adjust index return true; //--- Success } break; //--- End number case '\"': { //--- String or key start if(m_type == JsonObject) { //--- Key in object currentIndex++; //--- Increment int startOfString = currentIndex; //--- String start if(!ExtractStringFromArray(jsonCharacterArray, arrayLength, currentIndex)) return false; //--- Extract m_temporaryKey = GetSubstringFromArray(jsonCharacterArray, startOfString, currentIndex - startOfString); //--- Set temp key } else { //--- Value string if(m_type != JsonUndefined) return false; //--- Type check m_type = JsonString; //--- Set string currentIndex++; //--- Increment int startOfString = currentIndex; //--- String start if(!ExtractStringFromArray(jsonCharacterArray, arrayLength, currentIndex)) return false; //--- Extract SetFromString(JsonString, GetSubstringFromArray(jsonCharacterArray, startOfString, currentIndex - startOfString)); //--- Set value return true; //--- Success } } break; //--- End string } } return true; //--- Default success }
In der De-Serialisierungsfunktion behandeln wir nur die „double“-Typen, die wir in den vorherigen Versionen nicht berücksichtigt hatten. Der Klarheit halber haben wir den entsprechenden Abschnitt hervorgehoben. Wir müssen nun neue globale Variablen für das UI-Layout, den Bildlauf, die Hover-Zustände und die Farben hinzufügen, um ein komplexeres Dashboard mit einer Kopf- und Fußzeile, Schaltflächen und einen Schieberegler zu unterstützen.
bool clear_hover = false; bool new_chat_hover = false; color clear_original_bg = clrLightCoral; color clear_darker_bg; color new_chat_original_bg = clrLightBlue; color new_chat_darker_bg; int g_mainX = 10; int g_mainY = 30; int g_mainWidth = 550; int g_mainHeight = 0; int g_padding = 10; int g_sidePadding = 6; int g_textPadding = 10; int g_headerHeight = 40; int g_displayHeight = 280; int g_footerHeight = 50; int g_lineSpacing = 2; bool scroll_visible = false; bool mouse_in_display = false; int scroll_pos = 0; int prev_scroll_pos = -1; int slider_height = 20; bool movingStateSlider = false; int mlbDownX_Slider = 0; int mlbDownY_Slider = 0; int mlbDown_YD_Slider = 0; int g_total_height = 0; int g_visible_height = 0;
Hier initialisieren wir einige weitere globale Variablen und Farbschemata für das aktualisierte Programm, um dynamische Hover-Effekte und Layout-Verwaltung zu unterstützen. Wir setzen die Hover-Flags „clear_hover“ und „new_chat_hover“ für die Schaltflächen „clear“ und „new_chat“ auf „false“ und definieren die Originalhintergründe „clear_original_bg“ als „clrLightCoral“ und „new_chat_original_bg“ als „clrLightBlue“, mit dunkleren Versionen „clear_darker_bg“ und „new_chat_darker_bg“ für den Schwebezustand. Dann konfigurieren wir die Abmessungen des Dashboards: „g_mainX“ auf 10, „g_mainY“ auf 30, „g_mainWidth“ auf 550, „g_mainHeight“ auf 0 (zu berechnen), Padding-Werte wie „g_padding“ auf 10, „g_sidePadding“ auf 6, „g_textPadding“ auf 10, Höhen für Kopfzeile („g_headerHeight“ auf 40), Anzeige („g_displayHeight“ auf 280), Fußzeile („g_footerHeight“ auf 50) und Zeilenabstand („g_lineSpacing“ auf 2).
Schließlich initialisieren wir die Bildlaufvariablen: „scroll_visible“ und „mouse_in_display“ auf false, „scroll_pos“ und „prev_scroll_pos“ auf 0 und -1, „slider_height“ auf 20, Drag-States „movingStateSlider“ auf false und die Positionen „mlbDownX_Slider“, „mlbDownY_Slider“, „mlbDown_YD_Slider“ auf 0, und die Höhen-Tracker „g_total_height“ und „g_visible_height“ auf 0. Dann müssen wir den Schieberegler definieren, bevor wir die Anzeige aktualisieren, da sie dynamisch sein wird. Definieren wir also die Funktionen für den Schieberegler.
//+------------------------------------------------------------------+ //| Calculate font size based on screen DPI | //+------------------------------------------------------------------+ int getFontSizeByDPI(int baseFontSize, int baseDPI = 96) { int currentDPI = (int)TerminalInfoInteger(TERMINAL_SCREEN_DPI); //--- Retrieve current screen DPI int scaledFontSize = (int)(baseFontSize * (double)baseDPI / currentDPI); //--- Calculate scaled font size return MathMax(scaledFontSize, 8); //--- Ensure minimum font size of 8 } //+------------------------------------------------------------------+ //| Create scrollbar elements | //+------------------------------------------------------------------+ void CreateScrollbar() { int displayX = g_mainX + 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 scrollbar_x = displayX + displayW - 16; //--- Set scrollbar x position int scrollbar_y = displayY + 16; //--- Set scrollbar y position int scrollbar_width = 16; //--- Set scrollbar width int scrollbar_height = g_displayHeight - 2 * 16; //--- Calculate scrollbar height int button_size = 16; //--- Set button size if (!createRecLabel(SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'220,220,220', 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER)) { //--- Create scrollbar leader FileWriteString(logFileHandle, "Failed to create scrollbar leader\n"); //--- Log failure } if (!createRecLabel(SCROLL_UP_REC, scrollbar_x, displayY, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER)) { //--- Create scroll up button FileWriteString(logFileHandle, "Failed to create scrollbar up button\n"); //--- Log failure } if (!createLabel(SCROLL_UP_LABEL, scrollbar_x + 2, displayY + -2, CharToString(0x35), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER)) { //--- Create scroll up label FileWriteString(logFileHandle, "Failed to create scrollbar up label\n"); //--- Log failure } if (!createRecLabel(SCROLL_DOWN_REC, scrollbar_x, displayY + g_displayHeight - button_size, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER)) { //--- Create scroll down button FileWriteString(logFileHandle, "Failed to create scrollbar down button\n"); //--- Log failure } if (!createLabel(SCROLL_DOWN_LABEL, scrollbar_x + 2, displayY + g_displayHeight - button_size + -2, CharToString(0x36), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER)) { //--- Create scroll down label FileWriteString(logFileHandle, "Failed to create scrollbar down label\n"); //--- Log failure } slider_height = CalculateSliderHeight(); //--- Calculate slider height if (!createRecLabel(SCROLL_SLIDER, scrollbar_x, displayY + g_displayHeight - button_size - slider_height, scrollbar_width, slider_height, clrSilver, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER)) { //--- Create scrollbar slider FileWriteString(logFileHandle, "Failed to create scrollbar slider\n"); //--- Log failure } FileWriteString(logFileHandle, "Scrollbar created: x=" + IntegerToString(scrollbar_x) + ", y=" + IntegerToString(scrollbar_y) + ", height=" + IntegerToString(scrollbar_height) + ", slider_height=" + IntegerToString(slider_height) + "\n"); //--- Log scrollbar creation } //+------------------------------------------------------------------+ //| Delete scrollbar elements | //+------------------------------------------------------------------+ void DeleteScrollbar() { ObjectDelete(0, SCROLL_LEADER); //--- Remove scrollbar leader ObjectDelete(0, SCROLL_UP_REC); //--- Remove scroll up rectangle ObjectDelete(0, SCROLL_UP_LABEL); //--- Remove scroll up label ObjectDelete(0, SCROLL_DOWN_REC); //--- Remove scroll down rectangle ObjectDelete(0, SCROLL_DOWN_LABEL); //--- Remove scroll down label ObjectDelete(0, SCROLL_SLIDER); //--- Remove scrollbar slider } //+------------------------------------------------------------------+ //| Calculate scrollbar slider height | //+------------------------------------------------------------------+ int CalculateSliderHeight() { int scroll_area_height = g_displayHeight - 2 * 16; //--- Calculate scroll area height int slider_min_height = 20; //--- Set minimum slider height if (g_total_height <= g_visible_height) return scroll_area_height; //--- Return full height if no scroll double visible_ratio = (double)g_visible_height / g_total_height; //--- Calculate visible ratio int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate slider height return MathMax(slider_min_height, height); //--- Return minimum or calculated height } //+------------------------------------------------------------------+ //| Update scrollbar slider position | //+------------------------------------------------------------------+ void UpdateSliderPosition() { int displayX = g_mainX + g_sidePadding; //--- Calculate display x position int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate display y position int scrollbar_x = displayX + (g_mainWidth - 2 * g_sidePadding) - 16; //--- Set scrollbar x position int scrollbar_y = displayY + 16; //--- Set scrollbar y position int scroll_area_height = g_displayHeight - 2 * 16; //--- Calculate scroll area height int max_scroll = MathMax(0, g_total_height - g_visible_height); //--- Calculate maximum scroll if (max_scroll <= 0) return; //--- Exit if no scroll needed double scroll_ratio = (double)scroll_pos / max_scroll; //--- Calculate scroll ratio int scroll_area_y_max = scrollbar_y + scroll_area_height - slider_height; //--- Calculate max slider y int scroll_area_y_min = scrollbar_y; //--- Set min slider y int new_y = scroll_area_y_min + (int)(scroll_ratio * (scroll_area_y_max - scroll_area_y_min)); //--- Calculate new y position new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); //--- Clamp y position ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Update slider y position FileWriteString(logFileHandle, "Slider position updated: scroll_pos=" + IntegerToString(scroll_pos) + ", max_scroll=" + IntegerToString(max_scroll) + ", new_y=" + IntegerToString(new_y) + "\n"); //--- Log slider update } //+------------------------------------------------------------------+ //| Update scrollbar button colors | //+------------------------------------------------------------------+ void UpdateButtonColors() { int max_scroll = MathMax(0, g_total_height - g_visible_height); //--- Calculate maximum scroll if (scroll_pos == 0) { //--- Check if at top ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, clrSilver); //--- Set scroll up label to disabled color } else { //--- Not at top ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set scroll up label to active color } if (scroll_pos == max_scroll) { //--- Check if at bottom ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrSilver); //--- Set scroll down label to disabled color } else { //--- Not at bottom ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrDimGray); //--- Set scroll down label to active color } FileWriteString(logFileHandle, "Button colors updated: scroll_pos=" + IntegerToString(scroll_pos) + ", max_scroll=" + IntegerToString(max_scroll) + "\n"); //--- Log button color update } //+------------------------------------------------------------------+ //| Scroll up (show earlier messages) | //+------------------------------------------------------------------+ void ScrollUp() { if (scroll_pos > 0) { //--- Check if scroll possible scroll_pos = MathMax(0, scroll_pos - 30); //--- Decrease scroll position UpdateResponseDisplay(); //--- Update response display if (scroll_visible) { //--- Check if scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } FileWriteString(logFileHandle, "Scrolled up: scroll_pos=" + IntegerToString(scroll_pos) + "\n"); //--- Log scroll up } } //+------------------------------------------------------------------+ //| Scroll down (show later messages) | //+------------------------------------------------------------------+ void ScrollDown() { int max_scroll = MathMax(0, g_total_height - g_visible_height); //--- Calculate maximum scroll if (scroll_pos < max_scroll) { //--- Check if scroll possible scroll_pos = MathMin(max_scroll, scroll_pos + 30); //--- Increase scroll position UpdateResponseDisplay(); //--- Update response display if (scroll_visible) { //--- Check if scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } FileWriteString(logFileHandle, "Scrolled down: scroll_pos=" + IntegerToString(scroll_pos) + "\n"); //--- Log scroll down } }
Um eine reaktionsschnelle und anpassungsfähige chatorientierte Schnittstelle zu gewährleisten, wird in der Funktion „getFontSizeByDPI“ die DPI (Dots Per Inch) des Bildschirms mit TerminalInfoInteger unter Verwendung von TERMINAL_SCREEN_DPI abgerufen, eine Basisschriftgröße relativ zu einem Standard-DPI (96) skaliert und eine Mindestgröße von 8 mit MathMax erzwungen, um eine einheitliche Textlesbarkeit auf allen Bildschirmen zu gewährleisten. In der Funktion „CreateScrollbar“ berechnen wir die Positionen („displayX“, „displayY“) und Abmessungen für den Schieberegler und erstellen ein Führungsrechteck („SCROLL_LEADER“) mit hellgrauem Hintergrund (C'220,220,220'), Auf-/Ab-Schaltflächen („SCROLL_UP_REC“, „SCROLL_DOWN_REC“) mit clrGainsboro und ihre Beschriftungen („SCROLL_UP_LABEL“, „SCROLL_DOWN_LABEL“) mit Webdings-Pfeilen (0x35, 0x36) und DPI-angepassten Schriftgrößen über „createLabel“ und „createRecLabel“, Protokollierung von Fehlern mit FileWriteString in unsere Datei.
Die Funktion „DeleteScrollbar“ löscht alle Schieberegler-Objekte mit ObjectDelete zur Bereinigung. In „CalculateSliderHeight“ wird die Höhe des Schiebereglers auf der Grundlage des Verhältnisses des sichtbaren Textes berechnet, wobei ein Minimum von 20 Pixeln gewährleistet wird. Dadurch wird sichergestellt, dass der Schieberegler nicht zu klein ist, um ihn zu benutzen, wenn das Gespräch länger wird. Die Funktion „UpdateSliderPosition“ passt die y-Position des Schiebereglers unter Verwendung eines aus „scroll_pos“ und „max_scroll“ abgeleiteten Bildlaufverhältnisses an, hält sie in Grenzen und protokolliert Aktualisierungen. In „UpdateButtonColors“ setzen wir die Farben der Tasten des Schieberegler auf „clrSilver“, wenn sie deaktiviert sind (oben/unten), oder auf „clrDimGray“, wenn sie aktiv sind und protokollieren die Änderungen. Die Funktionen „ScrollUp“ und „ScrollDown“ passen „scroll_pos“ um 30 Pixel an, rufen „UpdateResponseDisplay“ auf, aktualisieren den Schieberegler, wenn sie sichtbar ist, und protokollieren Aktionen, wodurch ein System für eine dynamische, verschiebbare Nutzeroberfläche mit adaptiver Textgröße und Protokollierung für die Chat-Schnittstelle geschaffen wird.
Da wir nun komplexe Unterhaltungen mit längeren Absätzen verpacken werden, müssen wir mit leeren Zeilen umgehen, um Klarheit zu schaffen. Hier ist die Logik, die wir verwenden, um die Funktion „WrapText“ zu verbessern, um dies zu erreichen.
//+------------------------------------------------------------------+ //| Wrap text respecting newlines and max width | //+------------------------------------------------------------------+ void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], int offset = 0) { const int maxChars = 60; //--- Set maximum characters per line ArrayResize(wrappedLines, 0); //--- Clear wrapped lines array TextSetFont(font, fontSize); //--- Set font string paragraphs[]; //--- Declare paragraphs array int numParagraphs = StringSplit(inputText, '\n', paragraphs); //--- Split text into paragraphs for (int p = 0; p < numParagraphs; p++) { //--- Iterate through paragraphs string para = paragraphs[p]; //--- Get current paragraph if (StringLen(para) == 0) { //--- Check empty paragraph int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = " "; //--- Add empty line continue; //--- Skip to next } string words[]; //--- Declare words array int numWords = StringSplit(para, ' ', words); //--- Split paragraph into words string currentLine = ""; //--- Initialize current line for (int w = 0; w < numWords; w++) { //--- Iterate through words string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line uint wid, hei; //--- Declare width and height TextGetSize(testLine, wid, hei); //--- Get test line size int textWidth = (int)wid; //--- Get text width if (textWidth + offset <= maxWidth && StringLen(testLine) <= maxChars) { //--- Check line fits currentLine = testLine; //--- Update current line } else { //--- Line exceeds limits if (StringLen(currentLine) > 0) { //--- Check non-empty line int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = currentLine; //--- Add line } currentLine = words[w]; //--- Start new line TextGetSize(currentLine, wid, hei); //--- Get new line size textWidth = (int)wid; //--- Update text width if (textWidth + offset > maxWidth || StringLen(currentLine) > maxChars) { //--- Check word too long string wrappedWord = ""; //--- Initialize wrapped word for (int c = 0; c < StringLen(words[w]); c++) { //--- Iterate through characters string testWord = wrappedWord + StringSubstr(words[w], c, 1); //--- Build test word TextGetSize(testWord, wid, hei); //--- Get test word size int wordWidth = (int)wid; //--- Get word width if (wordWidth + offset > maxWidth || StringLen(testWord) > maxChars) { //--- Check word fits if (StringLen(wrappedWord) > 0) { //--- Check non-empty word int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = wrappedWord; //--- Add wrapped word } wrappedWord = StringSubstr(words[w], c, 1); //--- Start new word } else { //--- Word fits wrappedWord = testWord; //--- Update wrapped word } } currentLine = wrappedWord; //--- Set current line to wrapped word } } } if (StringLen(currentLine) > 0) { //--- Check remaining line int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = currentLine; //--- Add line } } }
Es ist nicht das erste Mal, dass wir auf diese Funktion stoßen, daher werden wir sie überprüfen und auf die wichtigste Upgrade-Logik hinweisen. In der Funktion „WrapText“ setzen wir ein Maximum von 60 Zeichen pro Zeile („maxChars“) und leeren das Ausgabe-Array „wrappedLines“ mit ArrayResize genau wie bei der vorherigen Funktion. Wir konfigurieren die Schriftart und -größe mit TextSetFont und teilen den Eingabetext mit StringSplit an den Zeilenende-Zeichen in Absätze auf. Für jeden Absatz werden leere Absätze behandelt, indem ein Leerzeichen zu „wrappedLines“ hinzugefügt und zum nächsten übersprungen wird.
Nicht leere Absätze werden mit „StringSplit“ in Wörter aufgeteilt, und es werden Zeilen gebildet, indem Wörter hinzugefügt werden, wenn sie innerhalb von „maxWidth“ (angepasst durch „offset“) und der Zeichengrenze liegen, wobei TextGetSize zur Überprüfung der Breite verwendet wird. Wenn eine Zeile die Grenzen überschreitet, fügen wir die aktuelle Zeile zu „wrappedLines“ hinzu und beginnen eine neue Zeile mit dem aktuellen Wort; bei übergroßen Wörtern teilen wir sie zeichenweise auf und fügen Segmente zu neuen Zeilen hinzu, wenn die Breiten- oder Zeichengrenzen überschritten werden, wobei jedes Segment in „wrappedLines“ gespeichert wird. Jede verbleibende Zeile wird der Ausgabe hinzugefügt. Während der Initialisierung müssen wir die Farben der Elemente festlegen und die neuen Elemente beim Entfernen des Programms löschen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { button_darker_bg = DarkenColor(button_original_bg); //--- Set darker button background clear_darker_bg = DarkenColor(clear_original_bg); //--- Set darker clear button background new_chat_darker_bg = DarkenColor(new_chat_original_bg); //--- Set darker new chat button background logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); //--- Open log file if (logFileHandle == INVALID_HANDLE) { //--- Check file open failure Print("Failed to open log file: ", GetLastError()); //--- Log failure return(INIT_FAILED); //--- Return initialization failure } FileSeek(logFileHandle, 0, SEEK_END); //--- Move to end of log file FileWriteString(logFileHandle, "EA Initialized at " + TimeToString(TimeCurrent()) + "\n"); //--- Log initialization CreateDashboard(); //--- Create dashboard UI UpdateResponseDisplay(); //--- Update response display 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 scrolling return(INIT_SUCCEEDED); //--- Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "ChatGPT_"); //--- Remove all ChatGPT objects DeleteScrollbar(); //--- Delete scrollbar elements if (logFileHandle != INVALID_HANDLE) { //--- Check if log file open FileClose(logFileHandle); //--- Close log file } }
Nachdem die Initialisierung abgeschlossen ist, definieren wir die neuen Elemente und fügen sie der Anzeige hinzu, damit wir sehen können, welchen Meilenstein wir erreicht haben.
//+------------------------------------------------------------------+ //| Create dashboard UI | //+------------------------------------------------------------------+ void CreateDashboard() { g_mainHeight = g_headerHeight + 2 * g_padding + g_displayHeight + g_footerHeight; //--- Calculate main height int displayX = g_mainX + g_sidePadding; //--- Calculate display x int displayY = g_mainY + g_headerHeight + g_padding; //--- Calculate display y int displayW = g_mainWidth - 2 * g_sidePadding; //--- Calculate display width int footerY = displayY + g_displayHeight + g_padding; //--- Calculate footer y int inputWidth = 448; //--- Set input field width int sendWidth = 80; //--- Set send button width int gap = 10; //--- Set gap between elements int totalW = inputWidth + gap + sendWidth; //--- Calculate total width int centerX = g_mainX + (g_mainWidth - totalW) / 2; //--- Calculate center x int inputX = centerX; //--- Set input field x int sendX = inputX + inputWidth + gap; //--- Calculate send button x int elemHeight = 36; //--- Set element height int elemY = footerY + 8; //--- Calculate element y createRecLabel("ChatGPT_MainContainer", g_mainX, g_mainY, g_mainWidth, g_mainHeight, clrWhite, 1, clrLightGray); //--- Create main container createRecLabel("ChatGPT_HeaderBg", g_mainX, g_mainY, g_mainWidth, g_headerHeight, clrWhiteSmoke, 0, clrNONE); //--- Create header background string title = "ChatGPT AI EA"; //--- Set 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 size int titleY = g_mainY + (g_headerHeight - (int)titleHei) / 2 - 4; //--- Calculate title y int titleX = g_mainX + g_sidePadding; //--- Set title x createLabel("ChatGPT_TitleLabel", titleX, titleY, title, clrDarkSlateGray, titleSize, titleFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label string dateStr = TimeToString(TimeTradeServer(), TIME_DATE|TIME_MINUTES); //--- Get current date 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 size int dateX = g_mainX + g_mainWidth / 2 - (int)(dateWid / 2) - 50; //--- Calculate date x int dateY = g_mainY + (g_headerHeight - (int)dateHei) / 2 - 4; //--- Calculate date y createLabel("ChatGPT_DateLabel", dateX, dateY, dateStr, clrSlateGray, dateSize, dateFont, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create date label int clearWidth = 100; //--- Set clear button width int clearX = g_mainX + g_mainWidth - clearWidth - g_sidePadding; //--- Calculate clear button x int clearY = g_mainY + 4; //--- Set clear button y createButton("ChatGPT_ClearButton", clearX, clearY, clearWidth, g_headerHeight - 8, "Clear", clrWhite, 11, clear_original_bg, clrIndianRed); //--- Create clear button int new_chat_width = 100; //--- Set new chat button width int new_chat_x = clearX - new_chat_width - g_sidePadding; //--- Calculate new chat button x createButton("ChatGPT_NewChatButton", new_chat_x, clearY, new_chat_width, g_headerHeight - 8, "New Chat", clrWhite, 11, new_chat_original_bg, clrRoyalBlue); //--- Create new chat button createRecLabel("ChatGPT_ResponseBg", displayX, displayY, displayW, g_displayHeight, clrWhite, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID); //--- Create response background createRecLabel("ChatGPT_FooterBg", g_mainX, footerY, g_mainWidth, g_footerHeight, clrGainsboro, 0, clrNONE); //--- Create footer background createEdit("ChatGPT_InputEdit", inputX, elemY, inputWidth, elemHeight, "", clrBlack, 11, clrWhite, clrSilver); //--- Create input field createButton("ChatGPT_SubmitButton", sendX, elemY, sendWidth, elemHeight, "Send", clrWhite, 11, button_original_bg, clrDarkBlue); //--- Create send button ChartRedraw(); //--- Redraw chart }
In der zentralen Dashboard-Layoutfunktion berechnen wir die Höhe des Hauptcontainers („g_mainHeight“) als Summe von „g_headerHeight“, „g_displayHeight“, „g_footerHeight“ und zweimal „g_padding“ und bestimmen die Positionen für den Anzeigebereich („displayX“, „displayY“) und die Fußzeile („footerY“) mit Hilfe von Padding-Werten, da unser Dashboard dynamisch und nicht wie in der vorherigen Version statisch sein soll. Wir erstellen den Hauptcontainer („ChatGPT_MainContainer“) und den Kopfzeilenhintergrund („ChatGPT_HeaderBg“) mit „createRecLabel“ unter Verwendung der Farben Weiß und Hellgrau, und fügen ein Titel („ChatGPT_TitleLabel“) mit „ChatGPT AI EA“ in „Arial Rounded MT Bold“ in der Größe 14 hinzu, das mit TextGetSize zur Ausrichtung positioniert wird. Ein Datum („ChatGPT_DateLabel“) wird mit der aktuellen Serverzeit von TimeTradeServer in „Arial“ in Größe 12, horizontal zentriert, erstellt.
Wir fügen die Schaltflächen „Löschen“ („ChatGPT_ClearButton“) und „Neuer Chat“ („ChatGPT_NewChatButton“) in der Kopfzeile mit „createButton“ hinzu, wobei wir unterschiedliche Farben („clrLightCoral“, „clrLightBlue“) und eine kleinere Schriftgröße von 11 verwenden. Der Antwortbereich („ChatGPT_ResponseBg“) und die Fußzeile („ChatGPT_FooterBg“) werden mit „createRecLabel“ für die Chatanzeige und den Eingabebereich erstellt. Ein Eingabefeld („ChatGPT_InputEdit“) mit einer Breite von 448 und eine Schaltfläche „Send“ („ChatGPT_SubmitButton“) mit einer Breite von 80 werden mit „createEdit“ und „createButton“ in der Fußzeile zentriert, mit einem Abstand von 10 Pixeln. Schließlich wird das Chart mit der Funktion ChartRedraw neu gezeichnet. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Da wir nun die Schnittstelle mit allen Elementen haben, können wir mit der Aktualisierung der Anzeige mit dem neuen Gesprächsverlauf fortfahren. Wir benötigen jedoch einige Hilfsfunktionen, um die Nachrichtenzeilen und ihre Höhe zu ermitteln, damit die Konversation in den Anzeigebereich passt, ohne überzulaufen, und um die Zeitstempelzeilen zu berücksichtigen, die wir einfügen werden.
//+------------------------------------------------------------------+ //| Check if string is a timestamp in HH:MM format | //+------------------------------------------------------------------+ bool IsTimestamp(string line) { StringTrimLeft(line); //--- Trim left whitespace StringTrimRight(line); //--- Trim right whitespace if (StringLen(line) != 5) return false; //--- Check length if (StringGetCharacter(line, 2) != ':') return false; //--- Check colon string hh = StringSubstr(line, 0, 2); //--- Extract hours string mm = StringSubstr(line, 3, 2); //--- Extract minutes int h = (int)StringToInteger(hh); //--- Convert hours to integer int m = (int)StringToInteger(mm); //--- Convert minutes to integer if (h < 0 || h > 23 || m < 0 || m > 59) return false; //--- Validate time return true; //--- Confirm valid timestamp } //+------------------------------------------------------------------+ //| Compute lines and height for messages | //+------------------------------------------------------------------+ void ComputeLinesAndHeight(const string &font, const int fontSize, const int timestampFontSize, const int adjustedLineHeight, const int adjustedTimestampHeight, const int messageMargin, const int maxTextWidth, const string &msgRoles[], const string &msgContents[], const string &msgTimestamps[], const int numMessages, int &totalHeight_out, int &totalLines_out, string &allLines_out[], string &lineRoles_out[], int &lineHeights_out[]) { ArrayResize(allLines_out, 0); //--- Clear lines array ArrayResize(lineRoles_out, 0); //--- Clear roles array ArrayResize(lineHeights_out, 0); //--- Clear heights array totalLines_out = 0; //--- Initialize total lines totalHeight_out = 0; //--- Initialize total height for (int m = 0; m < numMessages; m++) { //--- Iterate through messages string wrappedLines[]; //--- Declare wrapped lines WrapText(msgContents[m], font, fontSize, maxTextWidth, wrappedLines); //--- Wrap message content int numLines = ArraySize(wrappedLines); //--- Get number of lines int currSize = ArraySize(allLines_out); //--- Get current size ArrayResize(allLines_out, currSize + numLines + 1); //--- Resize lines array ArrayResize(lineRoles_out, currSize + numLines + 1); //--- Resize roles array ArrayResize(lineHeights_out, currSize + numLines + 1); //--- Resize heights array for (int l = 0; l < numLines; l++) { //--- Iterate through wrapped lines allLines_out[currSize + l] = wrappedLines[l]; //--- Add line lineRoles_out[currSize + l] = msgRoles[m]; //--- Add role lineHeights_out[currSize + l] = adjustedLineHeight; //--- Set line height totalHeight_out += adjustedLineHeight; //--- Update total height } allLines_out[currSize + numLines] = msgTimestamps[m]; //--- Add timestamp lineRoles_out[currSize + numLines] = msgRoles[m] + "_timestamp"; //--- Add timestamp role lineHeights_out[currSize + numLines] = adjustedTimestampHeight; //--- Set timestamp height totalHeight_out += adjustedTimestampHeight; //--- Update total height totalLines_out += numLines + 1; //--- Update total lines if (m < numMessages - 1) { //--- Check for margin totalHeight_out += messageMargin; //--- Add message margin } } }
Hier implementieren wir Hilfsfunktionen zur Validierung von Zeitstempeln und zur Berechnung der Anzeigeeigenschaften von Nachrichten. In der Funktion „IsTimestamp“ werden mit StringTrimLeft und StringTrimRight Leerzeichen aus dem Eingabestring entfernt, es wird geprüft, ob die Länge genau 5 Zeichen beträgt, mit StringGetCharacter wird ein Doppelpunkt an Position 2 überprüft, mit StringSubstr werden Stunden und Minuten extrahiert, mit StringToInteger werden sie in ganze Zahlen umgewandelt, und es wird true zurückgegeben, wenn die Stunden (0-23) und Minuten (0-59) eine gültige Zeit bilden, wodurch eine genaue Zeitstempelerkennung im Gesprächsverlauf gewährleistet wird. Wenn Sie einen anderen Ansatz wählen, müssen Sie Ihre eigenen Regeln festlegen.
In der Funktion „ComputeLinesAndHeight“ leeren wir die Ausgabe-Arrays („allLines_out“, „lineRoles_out“, „lineHeights_out“) mit ArrayResize und initialisieren „totalLines_out“ und „totalHeight_out“ auf Null. Für jede Nachricht wird der Inhalt mithilfe von „WrapText“ mit der angegebenen Schriftart, Schriftgröße und maximalen Breite umbrochen und die Größe der Ausgabe-Arrays so angepasst, dass sie die umbrochenen Zeilen und einen Zeitstempel aufnehmen können, wobei der Text, die Rolle („User“ oder „AI“) und Höhe („adjustedLineHeight“ für Inhalt, „adjustedTimestampHeight“ für Zeitstempel) zu „allLines_out“, „lineRoles_out“ bzw. „lineHeights_out“ und aktualisieren „totalHeight_out“ und „totalLines_out“. Wir fügen einen Nachrichtenrand („messageMargin“) zwischen den Nachrichten (außer der letzten) ein, um eine visuelle Trennung zu gewährleisten und sicherzustellen, dass wir Zeitstempel validieren und den Nachrichtentext für die Anzeige in unserer scrollbaren Chat-Oberfläche organisieren.
Mit diesen Funktionen können wir nun die Anzeigefunktion aktualisieren, um die Historie in Rollen, Inhalte und Zeitstempel aufzuteilen, Rollennachrichten auszurichten, Ränder hinzuzufügen, Scrollen und Beschneiden zu handhaben und den Schieberegler dynamisch anzuzeigen.
//+------------------------------------------------------------------+ //| Update response display with scrolling | //+------------------------------------------------------------------+ void UpdateResponseDisplay() { int total = ObjectsTotal(0, 0, -1); //--- Get total objects for (int j = total - 1; j >= 0; j--) { //--- Iterate through objects string name = ObjectName(0, j, 0, -1); //--- Get object name if (StringFind(name, "ChatGPT_ResponseLine_") == 0 || StringFind(name, "ChatGPT_MessageBg_") == 0 || StringFind(name, "ChatGPT_MessageText_") == 0 || StringFind(name, "ChatGPT_Timestamp_") == 0) { //--- Check for message objects ObjectDelete(0, name); //--- Delete object } } string displayText = conversationHistory; //--- Get conversation history int textX = g_mainX + g_sidePadding + g_textPadding; //--- Calculate text x position int textY = g_mainY + g_headerHeight + g_padding + g_textPadding; //--- Calculate text y position int fullMaxWidth = g_mainWidth - 2 * g_sidePadding - 2 * g_textPadding; //--- Calculate max text width if (displayText == "") { //--- Check empty history string objName = "ChatGPT_ResponseLine_0"; //--- Set default label name createLabel(objName, textX, textY, "Type your message below and click Send to chat with the AI.", clrGray, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create default label g_total_height = 0; //--- Reset total height g_visible_height = g_displayHeight - 2 * g_textPadding; //--- Set visible height if (scroll_visible) { //--- Check scrollbar visible DeleteScrollbar(); //--- Delete scrollbar scroll_visible = false; //--- Reset scrollbar visibility } ChartRedraw(); //--- Redraw chart return; //--- Exit function } string parts[]; //--- Declare parts array int numParts = StringSplit(displayText, '\n', parts); //--- Split history into parts string msgRoles[]; //--- Declare roles array string msgContents[]; //--- Declare contents array string msgTimestamps[]; //--- Declare timestamps array string currentRole = ""; //--- Initialize current role string currentContent = ""; //--- Initialize current content string currentTimestamp = ""; //--- Initialize current timestamp for (int p = 0; p < numParts; p++) { //--- Iterate through parts string line = parts[p]; //--- Get current line StringTrimLeft(line); //--- Trim left whitespace StringTrimRight(line); //--- Trim right whitespace if (StringLen(line) == 0) { //--- Check empty line if (currentRole != "") currentContent += "\n"; //--- Append newline continue; //--- Skip to next } if (StringFind(line, "You: ") == 0) { //--- Check user message if (currentRole != "") { //--- Check existing message int size = ArraySize(msgRoles); //--- Get current size ArrayResize(msgRoles, size + 1); //--- Resize roles array ArrayResize(msgContents, size + 1); //--- Resize contents array ArrayResize(msgTimestamps, size + 1); //--- Resize timestamps array msgRoles[size] = currentRole; //--- Add role msgContents[size] = currentContent; //--- Add content msgTimestamps[size] = currentTimestamp; //--- Add timestamp } currentRole = "User"; //--- Set role to User currentContent = StringSubstr(line, 5); //--- Extract user content currentTimestamp = ""; //--- Reset timestamp continue; //--- Skip to next } else if (StringFind(line, "AI: ") == 0) { //--- Check AI message if (currentRole != "") { //--- Check existing message int size = ArraySize(msgRoles); //--- Get current size ArrayResize(msgRoles, size + 1); //--- Resize roles array ArrayResize(msgContents, size + 1); //--- Resize contents array ArrayResize(msgTimestamps, size + 1); //--- Resize timestamps array msgRoles[size] = currentRole; //--- Add role msgContents[size] = currentContent; //--- Add content msgTimestamps[size] = currentTimestamp; //--- Add timestamp } currentRole = "AI"; //--- Set role to AI currentContent = StringSubstr(line, 4); //--- Extract AI content currentTimestamp = ""; //--- Reset timestamp continue; //--- Skip to next } else if (IsTimestamp(line)) { //--- Check timestamp if (currentRole != "") { //--- Check existing message currentTimestamp = line; //--- Set timestamp int size = ArraySize(msgRoles); //--- Get current size ArrayResize(msgRoles, size + 1); //--- Resize roles array ArrayResize(msgContents, size + 1); //--- Resize contents array ArrayResize(msgTimestamps, size + 1); //--- Resize timestamps array msgRoles[size] = currentRole; //--- Add role msgContents[size] = currentContent; //--- Add content msgTimestamps[size] = currentTimestamp; //--- Add timestamp currentRole = ""; //--- Reset role } } else { //--- Append to content if (currentRole != "") { //--- Check active message currentContent += "\n" + line; //--- Append line } } } if (currentRole != "") { //--- Check final message int size = ArraySize(msgRoles); //--- Get current size ArrayResize(msgRoles, size + 1); //--- Resize roles array ArrayResize(msgContents, size + 1); //--- Resize contents array ArrayResize(msgTimestamps, size + 1); //--- Resize timestamps array msgRoles[size] = currentRole; //--- Add role msgContents[size] = currentContent; //--- Add content msgTimestamps[size] = currentTimestamp; //--- Add timestamp } int numMessages = ArraySize(msgRoles); //--- Get number of messages if (numMessages == 0) { //--- Check no messages string objName = "ChatGPT_ResponseLine_0"; //--- Set default label name createLabel(objName, textX, textY, "Type your message below and click Send to chat with the AI.", clrGray, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create default label g_total_height = 0; //--- Reset total height g_visible_height = g_displayHeight - 2 * g_textPadding; //--- Set visible height if (scroll_visible) { //--- Check scrollbar visible DeleteScrollbar(); //--- Delete scrollbar scroll_visible = false; //--- Reset scrollbar visibility } ChartRedraw(); //--- Redraw chart return; //--- Exit function } string font = "Arial"; //--- Set font int fontSize = 10; //--- Set font size int timestampFontSize = 8; //--- Set timestamp font size int lineHeight = TextGetHeight("A", font, fontSize); //--- Get line height int timestampHeight = TextGetHeight("A", font, timestampFontSize); //--- Get timestamp height int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Calculate adjusted line height int adjustedTimestampHeight = timestampHeight + g_lineSpacing; //--- Calculate adjusted timestamp height int messageMargin = 12; //--- Set message margin int visibleHeight = g_displayHeight - 2 * g_textPadding; //--- Calculate visible height g_visible_height = visibleHeight; //--- Set visible height string tentativeAllLines[]; //--- Declare tentative lines string tentativeLineRoles[]; //--- Declare tentative roles int tentativeLineHeights[]; //--- Declare tentative heights int tentativeTotalHeight, tentativeTotalLines; //--- Declare tentative totals ComputeLinesAndHeight(font, fontSize, timestampFontSize, adjustedLineHeight, adjustedTimestampHeight, messageMargin, fullMaxWidth, msgRoles, msgContents, msgTimestamps, numMessages, tentativeTotalHeight, tentativeTotalLines, tentativeAllLines, tentativeLineRoles, tentativeLineHeights); //--- Compute tentative lines bool need_scroll = tentativeTotalHeight > visibleHeight; //--- Check if scrolling needed bool should_show_scrollbar = false; //--- Initialize scrollbar visibility int reserved_width = 0; //--- Initialize reserved width if (ScrollbarMode != SCROLL_WHEEL_ONLY) { //--- Check scrollbar mode should_show_scrollbar = need_scroll && (ScrollbarMode == SCROLL_DYNAMIC_ALWAYS || (ScrollbarMode == SCROLL_DYNAMIC_HOVER && mouse_in_display)); //--- Determine scrollbar visibility if (should_show_scrollbar) { //--- Check if scrollbar needed reserved_width = 16; //--- Reserve scrollbar width } } string allLines[]; //--- Declare final lines string lineRoles[]; //--- Declare final roles int lineHeights[]; //--- Declare final heights int totalHeight, totalLines; //--- Declare final totals int maxTextWidth = fullMaxWidth - reserved_width; //--- Calculate max text width if (reserved_width > 0) { //--- Check if scrollbar reserved ComputeLinesAndHeight(font, fontSize, timestampFontSize, adjustedLineHeight, adjustedTimestampHeight, messageMargin, maxTextWidth, msgRoles, msgContents, msgTimestamps, numMessages, totalHeight, totalLines, allLines, lineRoles, lineHeights); //--- Compute lines with reduced width } else { //--- Use tentative values totalHeight = tentativeTotalHeight; //--- Set total height totalLines = tentativeTotalLines; //--- Set total lines ArrayCopy(allLines, tentativeAllLines); //--- Copy lines ArrayCopy(lineRoles, tentativeLineRoles); //--- Copy roles ArrayCopy(lineHeights, tentativeLineHeights); //--- Copy heights } FileWriteString(logFileHandle, "UpdateResponseDisplay: totalHeight=" + IntegerToString(totalHeight) + ", visibleHeight=" + IntegerToString(visibleHeight) + ", totalLines=" + IntegerToString(totalLines) + ", reserved_width=" + IntegerToString(reserved_width) + "\n"); //--- Log display update g_total_height = totalHeight; //--- Set total height bool prev_scroll_visible = scroll_visible; //--- Store previous scrollbar state scroll_visible = should_show_scrollbar; //--- Update scrollbar visibility if (scroll_visible != prev_scroll_visible) { //--- Check scrollbar state change if (scroll_visible) { //--- Show scrollbar CreateScrollbar(); //--- Create scrollbar } else { //--- Hide scrollbar DeleteScrollbar(); //--- Delete scrollbar } } int max_scroll = MathMax(0, totalHeight - visibleHeight); //--- Calculate max scroll if (scroll_pos > max_scroll) scroll_pos = max_scroll; //--- Clamp scroll position if (scroll_pos < 0) scroll_pos = 0; //--- Ensure non-negative scroll if (totalHeight > visibleHeight && scroll_pos == prev_scroll_pos && prev_scroll_pos == -1) { //--- Check initial scroll scroll_pos = max_scroll; //--- Set to bottom } if (scroll_visible) { //--- Update scrollbar slider_height = CalculateSliderHeight(); //--- Calculate slider height ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); //--- Set slider height UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } int currentY = textY - 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++) { //--- Find start line if (currentHeight >= scroll_pos) { //--- Check if at scroll position startLineIndex = line; //--- Set start line currentY = textY + (currentHeight - scroll_pos); //--- Set current y break; //--- Exit loop } currentHeight += lineHeights[line]; //--- Add line height if (line < totalLines - 1 && StringFind(lineRoles[line], "_timestamp") >= 0 && StringFind(lineRoles[line + 1], "_timestamp") < 0) { //--- Check message gap currentHeight += messageMargin; //--- Add message margin } } int numVisibleLines = 0; //--- Initialize visible lines int visibleHeightUsed = 0; //--- Initialize used height for (int line = startLineIndex; line < totalLines; line++) { //--- Count visible lines int lineHeight = lineHeights[line]; //--- Get line height if (visibleHeightUsed + lineHeight > visibleHeight) break; //--- Check height limit visibleHeightUsed += lineHeight; //--- Add line height numVisibleLines++; //--- Increment visible lines if (line < totalLines - 1 && StringFind(lineRoles[line], "_timestamp") >= 0 && StringFind(lineRoles[line + 1], "_timestamp") < 0) { //--- Check message gap if (visibleHeightUsed + messageMargin > visibleHeight) break; //--- Check margin limit visibleHeightUsed += messageMargin; //--- Add message margin } } FileWriteString(logFileHandle, "Visible lines: startLineIndex=" + IntegerToString(startLineIndex) + ", numVisibleLines=" + IntegerToString(numVisibleLines) + ", scroll_pos=" + IntegerToString(scroll_pos) + ", currentY=" + IntegerToString(currentY) + "\n"); //--- Log visible lines int leftX = g_mainX + g_sidePadding + g_textPadding; //--- Set left text x int rightX = g_mainX + g_mainWidth - g_sidePadding - g_textPadding - reserved_width; //--- Set right text x color userColor = clrGray; //--- Set user text color color aiColor = clrBlue; //--- Set AI text color color timestampColor = clrDarkGray; //--- Set timestamp color for (int li = 0; li < numVisibleLines; li++) { //--- Display visible lines int lineIndex = startLineIndex + li; //--- Calculate line index if (lineIndex >= totalLines) break; //--- Check valid index string line = allLines[lineIndex]; //--- Get line text string role = lineRoles[lineIndex]; //--- Get line role bool isTimestamp = StringFind(role, "_timestamp") >= 0; //--- Check if timestamp int currFontSize = isTimestamp ? timestampFontSize : fontSize; //--- Set font size color textCol = isTimestamp ? timestampColor : (StringFind(role, "User") >= 0 ? userColor : aiColor); //--- Set text color string display_line = line; //--- Set display line if (line == " ") { //--- Check empty line display_line = " "; //--- Set to space textCol = clrWhite; //--- Set to white } int textX_pos = (StringFind(role, "User") >= 0) ? rightX : leftX; //--- Set text x position ENUM_ANCHOR_POINT textAnchor = (StringFind(role, "User") >= 0) ? ANCHOR_RIGHT_UPPER : ANCHOR_LEFT_UPPER; //--- Set text anchor string lineName = "ChatGPT_MessageText_" + IntegerToString(lineIndex); //--- Generate line name if (currentY >= textY && currentY < endY) { //--- Check if visible createLabel(lineName, textX_pos, currentY, display_line, textCol, currFontSize, font, CORNER_LEFT_UPPER, textAnchor); //--- Create label } currentY += lineHeights[lineIndex]; //--- Increment y position if (lineIndex < totalLines - 1 && StringFind(lineRoles[lineIndex], "_timestamp") >= 0 && StringFind(lineRoles[lineIndex + 1], "_timestamp") < 0) { //--- Check message gap currentY += messageMargin; //--- Add message margin } } ChartRedraw(); //--- Redraw chart }
In der Funktion „UpdateResponseDisplay“ werden zunächst die vorhandenen nachrichtenbezogenen Objekte („ChatGPT_ResponseLine_“, „ChatGPT_MessageBg_“, „ChatGPT_MessageText_“, „ChatGPT_Timestamp_“) mit ObjectsTotal und ObjectDelete gelöscht, um die Anzeige zu aktualisieren. Wenn der Gesprächsverlauf („conversationHistory“) leer ist, erstellen wir mit „createLabel“ eine Standardtext, der den Nutzer auffordert, eine Nachricht einzugeben, setzen „g_total_height“ auf 0 zurück, setzen „g_visible_height“ auf die Anzeigehöhe abzüglich Padding, entfernen den Schieberegler mit „DeleteScrollbar“, falls er sichtbar ist, und zeichnen alles neu mit der Funktion ChartRedraw. Andernfalls wird der Verlauf mit StringSplit bei Zeilenumbrüchen in Teile zerlegt, die Zeilen werden in „msgRoles“, „msgContents“ und „msgTimestamps“ geparst, indem „You: „, „AI: „ und Zeitstempel mit „IsTimestamp“ identifiziert werden, der Inhalt wird zeilenübergreifend akkumuliert und abgeschlossene Nachrichten gespeichert.
Wir berechnen die Textpositionierung („textX“, „textY“) und die maximale Breite („fullMaxWidth“), legen die Schriftgröße fest (10 für Nachrichten, 8 für Zeitstempel) und berechnen die Zeilenhöhe mit „TextGetHeight“ plus „g_lineSpacing“. Mit „ComputeLinesAndHeight“ erzeugen wir vorläufige Zeilenanordnungen und -höhen, prüfen, ob ein Bildlauf erforderlich ist, und bestimmen die Sichtbarkeit des Schiebereglers anhand von „ScrollbarMode“ und „mouse_in_display“, wobei 16 Pixel für den Schieberegler reserviert werden, falls sie angezeigt wird. Wir berechnen die Zeilen gegebenenfalls mit angepasster Breite neu, aktualisieren „g_total_height“, verwalten die Sichtbarkeit des Schiebereglers mit „CreateScrollbar“ oder „DeleteScrollbar“, beschränken „scroll_pos“ in „max_scroll“ und setzen sie für neue Nachrichten an den unteren Rand.
Wir berechnen die Startlinie und die y-Position auf der Grundlage von „scroll_pos“, bestimmen die sichtbaren Zeilen innerhalb von „g_visible_height“ und rendern jede Zeile mit „createLabel“, wobei wir linksbündige KI-Nachrichten („clrBlue“) und rechtsbündige Nutzernachrichten („clrGray“) mit Zeitstempeln („clrDarkGray“) verwenden und einen Nachrichtenrand von 12 Pixeln anwenden. Zum Schluss werden die Anzeigedetails mit FileWriteString protokolliert und neu gezeichnet. Dadurch wird sichergestellt, dass der Anzeigebereich mit dem verfügbaren Gesprächsverlauf aufgefüllt wird. Jetzt müssen wir sicherstellen, dass die Aufforderung gesendet wird, wenn wir auf „Send" klicken. In zukünftigen Versionen werden wir die bestehende Funktion in mehrere Funktionen erweitern, um die Verwaltung zu erleichtern.
//+------------------------------------------------------------------+ //| Build messages array from history | //+------------------------------------------------------------------+ string BuildMessagesFromHistory(string newPrompt) { string messages = "["; //--- Start JSON array string temp = conversationHistory; //--- Copy conversation history while (StringLen(temp) > 0) { //--- Process history int you_pos = StringFind(temp, "You: "); //--- Find user message if (you_pos != 0) break; //--- Exit if no user message temp = StringSubstr(temp, 5); //--- Extract after "You: " int end_user = StringFind(temp, "\n"); //--- Find end of user message string user_content = StringSubstr(temp, 0, end_user); //--- Get user content temp = StringSubstr(temp, end_user + 1); //--- Move past user message int end_ts1 = StringFind(temp, "\n"); //--- Find end of timestamp temp = StringSubstr(temp, end_ts1 + 1); //--- Move past timestamp int ai_pos = StringFind(temp, "AI: "); //--- Find AI message if (ai_pos != 0) break; //--- Exit if no AI message temp = StringSubstr(temp, 4); //--- Extract after "AI: " int end_ai = StringFind(temp, "\n"); //--- Find end of AI message string ai_content = StringSubstr(temp, 0, end_ai); //--- Get AI content temp = StringSubstr(temp, end_ai + 1); //--- Move past AI message int end_ts2 = StringFind(temp, "\n\n"); //--- Find end of conversation block temp = StringSubstr(temp, end_ts2 + 2); //--- Move past block messages += "{\"role\":\"user\",\"content\":\"" + JsonEscape(user_content) + "\"},"; //--- Add user message messages += "{\"role\":\"assistant\",\"content\":\"" + JsonEscape(ai_content) + "\"},"; //--- Add AI message } messages += "{\"role\":\"user\",\"content\":\"" + JsonEscape(newPrompt) + "\"}]"; //--- Add new prompt return messages; //--- Return JSON messages } //+------------------------------------------------------------------+ //| Get ChatGPT response via API | //+------------------------------------------------------------------+ string GetChatGPTResponse(string prompt) { string messages = BuildMessagesFromHistory(prompt); //--- Build JSON messages string requestData = "{\"model\":\"" + OpenAI_Model + "\",\"messages\":" + messages + ",\"max_tokens\":" + IntegerToString(MaxResponseLength) + "}"; //--- Create request JSON FileWriteString(logFileHandle, "Request Data: " + requestData + "\n"); //--- Log request data char postData[]; //--- Declare post data array int dataLen = StringToCharArray(requestData, postData, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert request to char array ArrayResize(postData, dataLen - 1); //--- Remove null terminator FileWriteString(logFileHandle, "Raw Post Data (Hex): " + LogCharArray(postData) + "\n"); //--- Log raw data string headers = "Authorization: Bearer " + OpenAI_API_Key + "\r\n" + "Content-Type: application/json; charset=UTF-8\r\n" + "Content-Length: " + IntegerToString(dataLen - 1) + "\r\n\r\n"; //--- Set request headers FileWriteString(logFileHandle, "Request Headers: " + headers + "\n"); //--- Log headers char result[]; //--- Declare result array string resultHeaders; //--- Declare result headers int res = WebRequest("POST", OpenAI_Endpoint, headers, 10000, postData, result, resultHeaders); //--- Send API request if (res != 200) { //--- Check request failure string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert result to string string errMsg = "API request failed: HTTP Code " + IntegerToString(res) + ", Error: " + IntegerToString(GetLastError()) + ", Response: " + response; //--- Create error message Print(errMsg); //--- Print error FileWriteString(logFileHandle, errMsg + "\n"); //--- Log error FileWriteString(logFileHandle, "Raw Response Data (Hex): " + LogCharArray(result) + "\n"); //--- Log raw response return errMsg; //--- Return error message } string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert response to string FileWriteString(logFileHandle, "API Response: " + response + "\n"); //--- Log response JsonValue jsonObject; //--- Declare JSON object int index = 0; //--- Initialize parse index char charArray[]; //--- Declare char array int arrayLength = StringToCharArray(response, charArray, 0, WHOLE_ARRAY, CP_UTF8); //--- Convert response to char array if (!jsonObject.DeserializeFromArray(charArray, arrayLength, index)) { //--- Parse JSON string errMsg = "Error: Failed to parse API response JSON: " + response; //--- Create error message Print(errMsg); //--- Print error FileWriteString(logFileHandle, errMsg + "\n"); //--- Log error return errMsg; //--- Return error message } JsonValue *error = jsonObject.FindChildByKey("error"); //--- Check for error if (error != NULL) { //--- Check error exists string errMsg = "API Error: " + error["message"].ToString(); //--- Get error message Print(errMsg); //--- Print error FileWriteString(logFileHandle, errMsg + "\n"); //--- Log error return errMsg; //--- Return error message } string content = jsonObject["choices"][0]["message"]["content"].ToString(); //--- Extract response content if (StringLen(content) > 0) { //--- Check non-empty content StringReplace(content, "\\n", "\n"); //--- Replace escaped newlines StringTrimLeft(content); //--- Trim left whitespace StringTrimRight(content); //--- Trim right whitespace return content; //--- Return content } string errMsg = "Error: No content in API response: " + response; //--- Create error message Print(errMsg); //--- Print error FileWriteString(logFileHandle, errMsg + "\n"); //--- Log error return errMsg; //--- Return error message } //+------------------------------------------------------------------+ //| Submit user message to ChatGPT | //+------------------------------------------------------------------+ void SubmitMessage() { string prompt = (string)ObjectGetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT); //--- Get user input if (StringLen(prompt) > 0) { //--- Check non-empty input string response = GetChatGPTResponse(prompt); //--- Get AI response Print("User: " + prompt); //--- Log user prompt Print("AI: " + response); //--- Log AI response string timestamp = TimeToString(TimeCurrent(), TIME_MINUTES); //--- Get current timestamp conversationHistory += "You: " + prompt + "\n" + timestamp + "\nAI: " + response + "\n" + timestamp + "\n\n"; //--- Append to history ObjectSetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT, ""); //--- Clear input field UpdateResponseDisplay(); //--- Update display with new content scroll_pos = MathMax(0, g_total_height - g_visible_height); //--- Scroll to bottom UpdateResponseDisplay(); //--- Redraw display if (scroll_visible) { //--- Check scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } FileWriteString(logFileHandle, "Prompt: " + prompt + " | Response: " + response + " | Time: " + timestamp + "\n"); //--- Log interaction ChartRedraw(); //--- Redraw chart } }
Hier implementieren wir die wichtigsten API-Interaktions- und Nachrichtenbearbeitungsfunktionen. Zunächst definieren wir die Funktion „BuildMessagesFromHistory“, mit der wir ein JSON-Array für API-Anfragen erstellen, indem wir „ConversationHistory“ analysieren, durch das Array iterieren, um Nutzer- („You: “) und KI- („AI: “) mit StringFind und StringSubstr extrahieren, wobei Zeitstempel und Leerzeilen übersprungen werden, und mit „JsonEscape“ den Inhalt in JSON-Objekte mit Rollen („Nutzer“ oder „Assistent“) formatieren, wobei die neue Nutzeraufforderung als letzte Nachricht angehängt wird, was zu einem ordnungsgemäß formatierten Array für Unterhaltungen mit mehreren Runden führt.
In der Funktion „GetChatGPTResponse“ erstellen wir eine JSON-Anfrage mit „BuildMessagesFromHistory“, dem „OpenAI_Model“ und „MaxResponseLength“, konvertieren sie in ein Char-Array mit StringToCharArray, setzen Header mit dem „OpenAI_API_Key“ und senden eine POST-Anfrage an „OpenAI_Endpoint“ unter Verwendung der Funktion WebRequest. Wir behandeln Antworten, indem wir auf HTTP-Fehler (Status ist nicht 200), Protokollierung von Rohdaten und Fehlern mit FileWriteString in „logFileHandle“, Parsing der JSON-Antwort mit „JsonValue::DeserializeFromArray“, Überprüfung auf API-Fehler und Extrahieren des Inhalts aus „choices[0][message][content]“, wobei Zeilenumbrüche entfernt und Leerzeichen abgeschnitten werden, bevor der Inhalt zurückgegeben wird, genau wie bei der vorherigen Version.
In der Funktion „SubmitMessage“ holen wir die Nutzereingabe aus „ChatGPT_InputEdit“ mit ObjectGetString ab, rufen „GetChatGPTResponse“ auf, wenn sie nicht leer ist, protokollieren die Eingabeaufforderung und die Antwort mit Print, fügen an „conversationHistory“ mit Zeitstempeln aus TimeCurrent anhängen, das Eingabefeld mit ObjectSetString löschen, die Anzeige mit „UpdateResponseDisplay“ aktualisieren, durch Setzen von „scroll_pos“ zum unteren Rand scrollen und bei Bedarf die Bildschirmanzeigen des Schiebereglers aktualisieren und die Interaktion protokollieren. So entsteht ein System zur Verwaltung von KI-Unterhaltungen, zur API-Kommunikation und zur dynamischen Aktualisierung der Chat-Nutzeroberfläche. Wir können diese Funktion wie folgt aufrufen, wenn wir auf die Schaltfläche „Nachricht senden" klicken.
//+------------------------------------------------------------------+ //| Chart event handler for ChatGPT UI | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SubmitButton") { //--- Handle submit button click SubmitMessage(); //--- Submit user message } }
Für die Bearbeitung von Chartinteraktionen verwenden wir die Ereignishandlung durch OnChartEvent. Wenn das Ereignis ein Klick auf unsere Schaltfläche ist, rufen wir unsere Funktion auf, um die Aufforderung zu senden. Hier ist eine Visualisierung dessen, was wir erhalten.

Aus dem Bild ist ersichtlich, dass die Konversation länger und intuitiv ist, wobei die Konversation des Nutzers rechts und die KI links hinzugefügt wird, alle mit Zeitstempel. Nun muss noch eine interaktive Anzeige gewährleistet werden, um das Ziehen des Schiebereglers und den Hover-Status der hinzugefügten Schaltflächen zu ermöglichen. Hier ist die vollständige Logik, die wir dafür verwendet haben.
//+------------------------------------------------------------------+ //| Chart event handler for ChatGPT UI | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int displayX = g_mainX + 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 displayH = g_displayHeight; //--- Set display height int clearX = g_mainX + g_mainWidth - 100 - g_sidePadding; //--- Calculate clear button x int clearY = g_mainY + 4; //--- Set clear button y int clearW = 100; //--- Set clear button width int clearH = g_headerHeight - 8; //--- Calculate clear button height int new_chat_x = clearX - 100 - g_sidePadding; //--- Calculate new chat button x int new_chat_w = 100; //--- Set new chat button width int new_chat_h = clearH; //--- Set new chat button height int sendX = g_mainX + (g_mainWidth - 448 - 10 - 80) / 2 + 448 + 10; //--- Calculate send button x int sendY = g_mainY + g_headerHeight + g_padding + g_displayHeight + g_padding; //--- Calculate send button y int sendW = 80; //--- Set send button width int sendH = g_footerHeight; //--- Set send button height bool need_scroll = g_total_height > g_visible_height; //--- Check if scrolling needed if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SubmitButton") { //--- Handle submit button click SubmitMessage(); //--- Submit user message } else if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_ClearButton") { //--- Handle clear button click conversationHistory = ""; //--- Clear conversation history scroll_pos = 0; //--- Reset scroll position prev_scroll_pos = -1; //--- Reset previous scroll position UpdateResponseDisplay(); //--- Update response display ObjectSetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT, ""); //--- Clear input field ChartRedraw(); //--- Redraw chart } else if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_NewChatButton") { //--- Handle new chat button click conversationHistory = ""; //--- Clear conversation history scroll_pos = 0; //--- Reset scroll position prev_scroll_pos = -1; //--- Reset previous scroll position UpdateResponseDisplay(); //--- Update response display ObjectSetString(0, "ChatGPT_InputEdit", OBJPROP_TEXT, ""); //--- Clear input field ChartRedraw(); //--- Redraw chart } else if (id == CHARTEVENT_OBJECT_CLICK && (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL)) { //--- Handle scroll up click ScrollUp(); //--- Scroll up } else if (id == CHARTEVENT_OBJECT_CLICK && (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL)) { //--- Handle scroll down click ScrollDown(); //--- Scroll down } else if (id == CHARTEVENT_MOUSE_MOVE) { //--- Handle mouse move events int mouseX = (int)lparam; //--- Get mouse x coordinate int mouseY = (int)dparam; //--- Get mouse y coordinate bool isOverSend = (mouseX >= sendX && mouseX <= sendX + sendW && mouseY >= sendY && mouseY <= sendY + sendH); //--- Check send button hover if (isOverSend && !button_hover) { //--- Check send button hover start ObjectSetInteger(0, "ChatGPT_SubmitButton", OBJPROP_BGCOLOR, button_darker_bg); //--- Set hover background button_hover = true; //--- Set hover flag ChartRedraw(); //--- Redraw chart } else if (!isOverSend && button_hover) { //--- Check send button hover end ObjectSetInteger(0, "ChatGPT_SubmitButton", OBJPROP_BGCOLOR, button_original_bg); //--- Reset background button_hover = false; //--- Reset hover flag ChartRedraw(); //--- Redraw chart } bool isOverClear = (mouseX >= clearX && mouseX <= clearX + clearW && mouseY >= clearY && mouseY <= clearY + clearH); //--- Check clear button hover if (isOverClear && !clear_hover) { //--- Check clear button hover start ObjectSetInteger(0, "ChatGPT_ClearButton", OBJPROP_BGCOLOR, clear_darker_bg); //--- Set hover background clear_hover = true; //--- Set hover flag ChartRedraw(); //--- Redraw chart } else if (!isOverClear && clear_hover) { //--- Check clear button hover end ObjectSetInteger(0, "ChatGPT_ClearButton", OBJPROP_BGCOLOR, clear_original_bg); //--- Reset background clear_hover = false; //--- Reset hover flag ChartRedraw(); //--- Redraw chart } bool isOverNewChat = (mouseX >= new_chat_x && mouseX <= new_chat_x + new_chat_w && mouseY >= clearY && mouseY <= clearY + new_chat_h); //--- Check new chat button hover if (isOverNewChat && !new_chat_hover) { //--- Check new chat button hover start ObjectSetInteger(0, "ChatGPT_NewChatButton", OBJPROP_BGCOLOR, new_chat_darker_bg); //--- Set hover background new_chat_hover = true; //--- Set hover flag ChartRedraw(); //--- Redraw chart } else if (!isOverNewChat && new_chat_hover) { //--- Check new chat button hover end ObjectSetInteger(0, "ChatGPT_NewChatButton", OBJPROP_BGCOLOR, new_chat_original_bg); //--- Reset background new_chat_hover = false; //--- Reset hover flag ChartRedraw(); //--- Redraw chart } bool is_in = (mouseX >= displayX && mouseX <= displayX + displayW && mouseY >= displayY && mouseY <= displayY + displayH); //--- Check if mouse in display if (is_in != mouse_in_display) { //--- Check display hover change mouse_in_display = is_in; //--- Update display hover status ChartSetInteger(0, CHART_MOUSE_SCROLL, !(mouse_in_display && need_scroll)); //--- Update chart scroll if (ScrollbarMode == SCROLL_DYNAMIC_HOVER) { //--- Check dynamic hover mode UpdateResponseDisplay(); //--- Update response display } } static int prevMouseState = 0; //--- Store previous mouse state int MouseState = (int)sparam; //--- Get current mouse state if (prevMouseState == 0 && MouseState == 1 && scroll_visible) { //--- Check slider drag start int scrollbar_x = displayX + displayW - 16; //--- Calculate scrollbar x int xd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get slider x int yd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get slider y int xs_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); //--- Get slider width int ys_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get slider height if (mouseX >= xd_slider && mouseX <= xd_slider + xs_slider && mouseY >= yd_slider && mouseY <= yd_slider + ys_slider) { //--- Check slider click movingStateSlider = true; //--- Set drag state mlbDownX_Slider = mouseX; //--- Store mouse x mlbDownY_Slider = mouseY; //--- Store mouse y mlbDown_YD_Slider = yd_slider; //--- Store slider y ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrDimGray); //--- Set drag color ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); //--- Set slider height ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scroll FileWriteString(logFileHandle, "Slider drag started: x=" + IntegerToString(mouseX) + ", y=" + IntegerToString(mouseY) + "\n"); //--- Log drag start } } if (movingStateSlider) { //--- Handle slider drag int delta_y = mouseY - mlbDownY_Slider; //--- Calculate y displacement int new_y = mlbDown_YD_Slider + delta_y; //--- Calculate new y position int scroll_area_y_min = (g_mainY + g_headerHeight + g_padding) + 16; //--- Set min slider y int scroll_area_y_max = (g_mainY + g_headerHeight + g_padding + g_displayHeight - 16 - slider_height); //--- Set max slider y new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); //--- Clamp y position ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Update slider y int max_scroll = MathMax(0, g_total_height - g_visible_height); //--- Calculate max scroll double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); //--- Calculate scroll ratio int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll); //--- Calculate new scroll position if (new_scroll_pos != scroll_pos) { //--- Check if scroll changed scroll_pos = new_scroll_pos; //--- Update scroll position UpdateResponseDisplay(); //--- Update response display if (scroll_visible) { //--- Check scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } FileWriteString(logFileHandle, "Slider dragged: new_scroll_pos=" + IntegerToString(new_scroll_pos) + "\n"); //--- Log drag } ChartRedraw(); //--- Redraw chart } if (MouseState == 0) { //--- Handle mouse release if (movingStateSlider) { //--- Check if dragging movingStateSlider = false; //--- Reset drag state if (scroll_visible) { //--- Check scrollbar visible ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, clrGray); //--- Reset slider color } ChartSetInteger(0, CHART_MOUSE_SCROLL, !(mouse_in_display && need_scroll)); //--- Restore chart scroll FileWriteString(logFileHandle, "Slider drag ended\n"); //--- Log drag end } } prevMouseState = MouseState; //--- Update previous mouse state static bool prevMouseInsideScrollUp = false; //--- Track previous scroll up hover static bool prevMouseInsideScrollDown = false; //--- Track previous scroll down hover static bool prevMouseInsideSlider = false; //--- Track previous slider hover if (scroll_visible) { //--- Check scrollbar visible int scrollbar_x = displayX + displayW - 16; //--- Calculate scrollbar x int button_size = 16; //--- Set button size int xd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get slider x int yd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get slider y int xs_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); //--- Get slider width int ys_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get slider height bool isMouseInsideUp = (mouseX >= scrollbar_x && mouseX <= scrollbar_x + 16 && mouseY >= displayY && mouseY <= displayY + button_size); //--- Check scroll up hover bool isMouseInsideDown = (mouseX >= scrollbar_x && mouseX <= scrollbar_x + 16 && mouseY >= displayY + g_displayHeight - button_size && mouseY <= displayY + g_displayHeight); //--- Check scroll down hover bool isMouseInsideSlider = (mouseX >= xd_slider && mouseX <= xd_slider + xs_slider && mouseY >= yd_slider && mouseY <= yd_slider + ys_slider); //--- Check slider hover if (isMouseInsideUp != prevMouseInsideScrollUp) { //--- Check scroll up hover change ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, isMouseInsideUp ? clrSilver : clrGainsboro); //--- Update scroll up color prevMouseInsideScrollUp = isMouseInsideUp; //--- Update hover state ChartRedraw(); //--- Redraw chart } if (isMouseInsideDown != prevMouseInsideScrollDown) { //--- Check scroll down hover change ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, isMouseInsideDown ? clrSilver : clrGainsboro); //--- Update scroll down color prevMouseInsideScrollDown = isMouseInsideDown; //--- Update hover state ChartRedraw(); //--- Redraw chart } if (isMouseInsideSlider != prevMouseInsideSlider && !movingStateSlider) { //--- Check slider hover change ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, isMouseInsideSlider ? clrDarkGray : clrSilver); //--- Update slider color prevMouseInsideSlider = isMouseInsideSlider; //--- Update hover state ChartRedraw(); //--- Redraw chart } } } else if (id == CHARTEVENT_MOUSE_WHEEL) { //--- Handle mouse wheel events int mouseX = (int)lparam; //--- Get mouse x coordinate int mouseY = (int)dparam; //--- Get mouse y coordinate int delta = (int)sparam; //--- Get wheel delta bool in_display = (mouseX >= displayX && mouseX <= displayX + displayW && mouseY >= displayY && mouseY <= displayY + displayH); //--- Check if mouse in display if (in_display != mouse_in_display) { //--- Check display hover change mouse_in_display = in_display; //--- Update display hover ChartSetInteger(0, CHART_MOUSE_SCROLL, !(mouse_in_display && need_scroll)); //--- Update chart scroll if (ScrollbarMode == SCROLL_DYNAMIC_HOVER) { //--- Check dynamic hover mode UpdateResponseDisplay(); //--- Update response display } } if (in_display && need_scroll) { //--- Check scroll conditions int scroll_amount = 30 * (delta > 0 ? -1 : 1); //--- Calculate scroll amount scroll_pos = MathMax(0, MathMin(MathMax(0, g_total_height - g_visible_height), scroll_pos + scroll_amount)); //--- Update scroll position UpdateResponseDisplay(); //--- Update response display if (scroll_visible) { //--- Check scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } ChartRedraw(); //--- Redraw chart } } }
Um volle Interaktivität zu erreichen, berechnen wir in der Funktion OnChartEvent die Positionen für den Anzeigebereich („displayX“, „displayY“, „displayW“, „displayH“), die Schaltfläche „clear“ („clearX“, „clearY“, „clearW“, „clearH“), neue Chat-Schaltfläche („new_chat_x“, „new_chat_w“, „new_chat_h“) und Sendeschaltfläche („sendX“, „sendY“, „sendW“, „sendH“) mithilfe globaler Layoutvariablen. Bei Klick-Ereignissen (CHARTEVENT_OBJECT_CLICK) behandeln wir „ChatGPT_ClearButton“ und „ChatGPT_NewChatButton“, indem wir „conversationHistory“ löschen, „scroll_pos“ und „prev_scroll_pos“ zurücksetzen, das Eingabefeld mit ObjectSetString leeren und die Anzeige mit „UpdateResponseDisplay“, und Scroll-Buttons („SCROLL_UP_REC“, „SCROLL_UP_LABEL“, „SCROLL_DOWN_REC“, „SCROLL_DOWN_LABEL“) durch Aufruf der Funktionen „ScrollUp“ oder „ScrollDown“.
Bei Mausbewegungsereignissen (CHARTEVENT_MOUSE_MOVE) erkennen wir, wenn der Mauszeiger über den Schaltflächen „Senden“, „Löschen“ und „Neuer Chat“ schwebt, und aktualisieren deren Hintergründe („button_darker_bg“, „clear_darker_bg“, „new_chat_darker_bg“) mit „ObjectSetInteger“, wenn der Mauszeiger in der Schwebe gehalten wird, und prüfen, ob sich die Maus im Anzeigebereich befindet, um „mouse_in_display“ zu aktivieren und den Chartbildlauf mit „ChartSetInteger“ zu aktualisieren und die Anzeige im Modus „SCROLL_DYNAMIC_HOVER“ zu aktualisieren.
Wir behandeln das Ziehen des Schiebereglers, indem wir Klicks auf „SCROLL_SLIDER“ erkennen, „movingStateSlider“ setzen, die y-Position des Schiebereglers mit ObjectSetInteger auf der Grundlage der Mausbewegung aktualisieren, „scroll_pos“ über das Scrollverhältnis berechnen und mit der Funktion FileWriteString protokollieren. Wenn die Maus losgelassen wird, werden der Ziehzustand und die Farbe des Schiebereglers zurückgesetzt. Bei Mausrad-Ereignissen (CHARTEVENT_MOUSE_WHEEL) wird „scroll_pos“ je nach Richtung des Mausrads um 30 Pixel angepasst, die Anzeige aktualisiert und der Schieberegler aktualisiert, falls sichtbar. Wir verwalten auch die Hover-Effekte des Schiebereglers und aktualisieren die Farben für die Auf- und Abwärtsschaltflächen und den Schieberegler. Jede Aktion löst ChartRedraw für visuelle Aktualisierungen aus. Dadurch wird sichergestellt, dass unser Programm Klicks, Hover, Drags und Scrolling unterstützt. Hier ist das Endergebnis.

Aus dem Bild können wir ersehen, dass wir das Programm durch Hinzufügen neuer Elemente, die Anzeige eines blätterbaren Gesprächsverlaufs und die Interaktivität der Nutzeroberfläche verbessern und somit unsere Ziele erreichen konnten. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.
Testen des ChatGPT-Programms
Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Schlussfolgerung
Abschließend haben wir unser in ChatGPT integriertes Programm in MQL5 verbessert, indem wir es zu einer scrollbaren, auf einen einzigen Chat ausgerichteten Nutzeroberfläche mit dynamischem JSON Parsing, einem mit Zeitstempel versehenen Gesprächsverlauf und interaktiven Steuerelementen wie Schaltflächen zum Absenden, Löschen und einem neuen Chat aufgerüstet haben. Dieses System ermöglicht es uns, nahtlos mit KI-gesteuerten Erkenntnissen zur Marktanalyse zu interagieren, den Kontext über mehrere Gesprächsrunden hinweg beizubehalten und gleichzeitig die Nutzerfreundlichkeit mit adaptivem Scrollen und Hover-Effekten zu optimieren. In den vorangehenden Versionen werden wir die Anzeige aktualisieren, um Vor-und-Rück-Konversationen zu handhaben und Live-Daten zu teilen, um Einblicke in den Handel zu erhalten. Bleiben Sie dran.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19741
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Entwicklung des Price Action Analysis Toolkit (Teil 46): Entwicklung eines interaktiven Fibonacci Retracement EA mit intelligenter Visualisierung in MQL5
Wiederverwendung von ungültig gemachten Orderblöcken als Mitigation Blocks (SMC)
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 84): Verwendung von Mustern des Stochastik-Oszillators und des FrAMA – Schlussfolgerung
Entwicklung des Price Action Analysis Toolkit (Teil 47): Verfolgen von Forex-Sitzungen und Ausbrüchen in MetaTrader 5
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.