Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 8): UI-Polnisch mit Animationen, zeitlichen Metriken und Tools für das Reaktionsmanagement
Einführung
In unserem vorangegangenen Artikel (Teil 7) haben wir das KI-gestützte Handelssystem in MetaQuotes Language 5 (MQL5) weiter modularisiert, die Organisation des Codes im Hinblick auf die Wartbarkeit verbessert und automatische Handelsfunktionen auf der Grundlage von KI-generierten Signalen mit anpassbaren Losgrößen und magischen Zahlen eingeführt. In Teil 8 entwickeln wir eine ausgefeilte Nutzeroberfläche (UI) mit Animationen, Zeitmetriken und Instrumenten für ein Reaktionsmanagement. Dieses Modell verbessert die Nutzerinteraktion durch die Anzeige von Ladeanimationen bei einer Anfrage einer API, die Bereitstellung von Feedback zur Reaktionszeit für die Leistung und die Bereitstellung von Regenerierungs- und Exportschaltflächen für die Verwaltung von KI-Ausgaben. Wir werden folgende Themen behandeln:
- Verstehen der Funktionen der erweiterten Nutzeroberfläche
- Implementierung in MQL5
- Backtests
- Schlussfolgerung
Am Ende haben Sie ein funktionsfähiges MQL5-Programm für ausgefeilte KI-gesteuerte Handelsinteraktionen, bereit für Anpassungen – fangen wir an!
Verstehen der Funktionen der erweiterten Nutzeroberfläche
Die verbesserten Schnittstellenfunktionen konzentrieren sich auf die Verbesserung der Interaktion innerhalb des KI-gesteuerten Handelssystems. Sie beinhalten Ladeanimationen, um visuelles Feedback während der API-Anfragen für die Vorbereitungs- und Denkphasen zu geben, und zeigen gleichzeitig Antwortzeitmetriken in Sekunden an, um die Nutzer über die Verarbeitungseffizienz zu informieren. Wir führen Tools zur Verwaltung von Antworten ein, z. B. Schaltflächen zum erneuten Senden der letzten Aufforderung für eine neue KI-Ausgabe und Exportschaltflächen zum Speichern von Antworten in Textdateien, die eine einfache Überprüfung oder Weitergabe ermöglichen.
Wir planen diese Funktionen modular aufbauen, indem wir bestehende UI-Komponenten um Animationsschleifen für Punktzykluseffekte, Zeitstempelberechnungen mithilfe von Tickcounts und Ereignisbehandlungen für Schaltflächenklicks zum Auslösen von Regenerationen oder Dateiexporten erweitern. Wir werden die Verwaltung der Seitenleistenzustände um dynamische Größenänderungen und die Neupositionierung von Objekten erweitern. Unser Plan sieht ein bedingtes Rendering auf der Grundlage von Nutzeraktionen vor, um nahtlose Aktualisierungen von Anzeigen und Bildlaufpositionen zu gewährleisten, ohne die KI-Kernfunktionen zu beeinträchtigen. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

Implementation in MQL5
Um die Upgrades zu implementieren, werden wir zunächst die neuen Objektkonstanten definieren, die wir erstellen wollen. Wir beginnen mit der Datei UI Components, da dort unsere Werkzeuge untergebracht sind. Hier ist die Logik, mit der wir das erreichen.
string REGEN_ICON_FONT = "Webdings"; string EXPORT_ICON_FONT = "Wingdings 3"; #define REGEN_ICON CharToString('q') // Circular arrow (spin/regenerate) #define EXPORT_ICON CharToString('7') // Proxy for save/export #define ICON_SIZE 16 #define ICON_SPACING 5 color REGEN_COLOR = clrGreen; color EXPORT_COLOR = clrBlack;
Im globalen Bereich der UI-Datei definieren wir „REGEN_ICON_FONT“ als Webdings und „EXPORT_ICON_FONT“ Wingdings 3, um Schriftfamilien für die Darstellung von Sonderzeichen als Icons festzulegen. Wir verwenden Präprozessoranweisungen, um „REGEN_ICON“ auf das Zeichen 'q' zu setzen, das über CharToString für einen kreisförmigen Pfeil zur Darstellung der Regeneration umgewandelt wird, und „EXPORT_ICON“ auf '7' als Stellvertreter für ein Speicher- oder Exportsymbol. Sie können aus der unten stehenden Tabelle eine beliebige Auswahl treffen und entsprechend wechseln.

Wir haben diejenigen markiert, die wir verwenden wollen. Dann setzen wir „ICON_SIZE“ auf 16 für konsistente Icon-Abmessungen, „ICON_SPACING“ auf 5 für Lücken zwischen ihnen, „REGEN_COLOR“ auf grün für das Regenerations-Icon und „EXPORT_COLOR“ auf schwarz für das Export-Icon. Sie können sie nach Belieben an Ihre Vorstellungen anpassen. Der nächste Schritt besteht darin, die Objekte in die Berechnung der Linienhöhe einzubeziehen.
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); ArrayResize(lineRoles_out, 0); ArrayResize(lineHeights_out, 0); totalLines_out = 0; totalHeight_out = 0; for (int m = 0; m < numMessages; m++) { string wrappedLines[]; WrapText(msgContents[m], font, fontSize, maxTextWidth, wrappedLines); int numLines = ArraySize(wrappedLines); int currSize = ArraySize(allLines_out); ArrayResize(allLines_out, currSize + numLines + 1); ArrayResize(lineRoles_out, currSize + numLines + 1); ArrayResize(lineHeights_out, currSize + numLines + 1); for (int l = 0; l < numLines; l++) { allLines_out[currSize + l] = wrappedLines[l]; lineRoles_out[currSize + l] = msgRoles[m]; lineHeights_out[currSize + l] = adjustedLineHeight; totalHeight_out += adjustedLineHeight; } allLines_out[currSize + numLines] = msgTimestamps[m]; lineRoles_out[currSize + numLines] = msgRoles[m] + "_timestamp"; lineHeights_out[currSize + numLines] = adjustedTimestampHeight; totalHeight_out += adjustedTimestampHeight; totalLines_out += numLines + 1; if (m < numMessages - 1) { totalHeight_out += messageMargin; } else if (m == numMessages - 1 && numMessages > 0) { if (totalHeight_out > 0) totalHeight_out -= messageMargin; // Adjust if last } } // Add buffer below loading messages (Preparing/Thinking) to ensure space for timestamp if (numMessages > 0 && StringFind(msgRoles[numMessages - 1], "AI") >= 0 && (StringFind(msgContents[numMessages - 1], "Preparing the Request") >= 0 || StringFind(msgContents[numMessages - 1], "Thinking...") >= 0)) { totalHeight_out += 30; // Extra space below thinking timestamp during wait } // Add padding if last message is AI and contains time note if (numMessages > 0 && StringFind(msgRoles[numMessages - 1], "AI") >= 0 && StringFind(msgContents[numMessages - 1], "(Response in ") >= 0) { totalHeight_out += 30; // Dedicated space for time note line + icons } }
In der Funktion „ComputeLinesAndHeight“ wird für jede Nachricht der Zeitstempel als zusätzliche Zeile mit der angehängten Rolle „_timestamp“ und der angepassten Höhe des Zeitstempels angehängt, wobei die Gesamthöhe und die Zeilenzahl erhöht werden. Anschließend wird ein Nachrichtenrand hinzugefügt, wenn es sich nicht um die letzte Nachricht handelt, oder abgezogen, wenn dies der Fall ist, um zusätzlichen Platz am Ende zu vermeiden. Wir fügen eine zusätzliche Pufferhöhe von 30 hinzu, wenn die letzte Nachricht von der AI stammt und „Preparing the Request“ oder „Thinking...“ lautet, um während des Ladens Platz darunter zu gewährleisten, und weitere 30, wenn sie „(Response in“ enthält, um unter Zeithinweise mit Symbolen aufzufüllen. Wir haben die spezifischen Änderungen zur Verdeutlichung hervorgehoben. Nun müssen wir die Funktion zum Rendern der Antwortanzeige aktualisieren, sodass wir auch die neuen Hilfssymbole einbeziehen.
void UpdateResponseDisplay() { if (showing_small_history_popup || showing_big_history_popup || showing_search_popup) return; int total = ObjectsTotal(0, 0, -1); for (int j = total - 1; j >= 0; j--) { string name = ObjectName(0, j, 0, -1); if (StringFind(name, "ChatGPT_ResponseLine_") == 0 || StringFind(name, "ChatGPT_MessageBg_") == 0 || StringFind(name, "ChatGPT_MessageText_") == 0 || StringFind(name, "ChatGPT_Timestamp_") == 0 || StringFind(name, "ChatGPT_RegenIcon") == 0 || StringFind(name, "ChatGPT_ExportIcon") == 0) { ObjectDelete(0, name); } } string displayText = conversationHistory; int textX = g_mainContentX + g_sidePadding + g_textPadding; int textY = g_mainY + g_headerHeight + g_padding + g_textPadding; int fullMaxWidth = g_mainWidth - 2 * g_sidePadding - 2 * g_textPadding; if (displayText == "") { string objName = "ChatGPT_ResponseLine_0"; createLabel(objName, textX, textY, "Type your prompt here and click Send to chat with the AI.", clrGray, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); g_total_height = 0; g_visible_height = g_displayHeight - 2 * g_textPadding; if (scroll_visible) { DeleteScrollbar(); scroll_visible = false; } ChartRedraw(); return; } string parts[]; int numParts = StringSplit(displayText, '\n', parts); string msgRoles[]; string msgContents[]; string msgTimestamps[]; string currentRole = ""; string currentContent = ""; string currentTimestamp = ""; for (int p = 0; p < numParts; p++) { string line = parts[p]; string trimmed = line; StringTrimLeft(trimmed); StringTrimRight(trimmed); if (StringLen(trimmed) == 0) { if (currentRole != "") currentContent += "\n"; continue; } if (StringFind(trimmed, "You: ") == 0) { if (currentRole != "") { int size = ArraySize(msgRoles); ArrayResize(msgRoles, size + 1); ArrayResize(msgContents, size + 1); ArrayResize(msgTimestamps, size + 1); msgRoles[size] = currentRole; msgContents[size] = currentContent; msgTimestamps[size] = currentTimestamp; } currentRole = "User"; currentContent = StringSubstr(line, StringFind(line, "You: ") + 5); currentTimestamp = ""; continue; } else if (StringFind(trimmed, "AI: ") == 0) { if (currentRole != "") { int size = ArraySize(msgRoles); ArrayResize(msgRoles, size + 1); ArrayResize(msgContents, size + 1); ArrayResize(msgTimestamps, size + 1); msgRoles[size] = currentRole; msgContents[size] = currentContent; msgTimestamps[size] = currentTimestamp; } currentRole = "AI"; currentContent = StringSubstr(line, StringFind(line, "AI: ") + 4); currentTimestamp = ""; continue; } else if (IsTimestamp(trimmed)) { currentTimestamp = trimmed; int size = ArraySize(msgRoles); ArrayResize(msgRoles, size + 1); ArrayResize(msgContents, size + 1); ArrayResize(msgTimestamps, size + 1); msgRoles[size] = currentRole; msgContents[size] = currentContent; msgTimestamps[size] = currentTimestamp; currentRole = ""; currentContent = ""; currentTimestamp = ""; } else { if (currentRole != "") { currentContent += "\n" + line; } } } if (currentRole != "") { int size = ArraySize(msgRoles); ArrayResize(msgRoles, size + 1); ArrayResize(msgContents, size + 1); ArrayResize(msgTimestamps, size + 1); msgRoles[size] = currentRole; msgContents[size] = currentContent; msgTimestamps[size] = currentTimestamp; } int numMessages = ArraySize(msgRoles); if (numMessages == 0) { string objName = "ChatGPT_ResponseLine_0"; createLabel(objName, textX, textY, "Type your prompt here and click Send to chat with the AI.", clrGray, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); g_total_height = 0; g_visible_height = g_displayHeight - 2 * g_textPadding; if (scroll_visible) { DeleteScrollbar(); scroll_visible = false; } ChartRedraw(); return; } string font = "Arial"; int fontSize = 10; int timestampFontSize = 8; int lineHeight = TextGetHeight("A", font, fontSize); int timestampHeight = TextGetHeight("A", font, timestampFontSize); int adjustedLineHeight = lineHeight + g_lineSpacing; int adjustedTimestampHeight = timestampHeight + g_lineSpacing; int messageMargin = 25; // Increased for extra space int visibleHeight = g_displayHeight - 2 * g_textPadding; g_visible_height = visibleHeight; string tentativeAllLines[]; string tentativeLineRoles[]; int tentativeLineHeights[]; int tentativeTotalHeight, tentativeTotalLines; ComputeLinesAndHeight(font, fontSize, timestampFontSize, adjustedLineHeight, adjustedTimestampHeight, messageMargin, fullMaxWidth, msgRoles, msgContents, msgTimestamps, numMessages, tentativeTotalHeight, tentativeTotalLines, tentativeAllLines, tentativeLineRoles, tentativeLineHeights); bool need_scroll = tentativeTotalHeight > visibleHeight; bool should_show_scrollbar = false; int reserved_width = 0; if (ScrollbarMode != SCROLL_WHEEL_ONLY) { should_show_scrollbar = need_scroll && (ScrollbarMode == SCROLL_DYNAMIC_ALWAYS || (ScrollbarMode == SCROLL_DYNAMIC_HOVER && mouse_in_display)); if (should_show_scrollbar) { reserved_width = 16; } } string allLines[]; string lineRoles[]; int lineHeights[]; int totalHeight, totalLines; if (reserved_width > 0) { ComputeLinesAndHeight(font, fontSize, timestampFontSize, adjustedLineHeight, adjustedTimestampHeight, messageMargin, fullMaxWidth - reserved_width, msgRoles, msgContents, msgTimestamps, numMessages, totalHeight, totalLines, allLines, lineRoles, lineHeights); } else { totalHeight = tentativeTotalHeight; totalLines = tentativeTotalLines; ArrayCopy(allLines, tentativeAllLines); ArrayCopy(lineRoles, tentativeLineRoles); ArrayCopy(lineHeights, tentativeLineHeights); } g_total_height = totalHeight; bool prev_scroll_visible = scroll_visible; scroll_visible = should_show_scrollbar; if (scroll_visible != prev_scroll_visible) { if (scroll_visible) { CreateScrollbar(); } else { DeleteScrollbar(); } } int max_scroll = MathMax(0, totalHeight - visibleHeight); if (scroll_pos > max_scroll) scroll_pos = max_scroll; if (scroll_pos < 0) scroll_pos = 0; if (totalHeight > visibleHeight && scroll_pos == prev_scroll_pos && prev_scroll_pos == -1) { scroll_pos = max_scroll; } if (scroll_visible) { slider_height = CalculateSliderHeight(); ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); UpdateSliderPosition(); UpdateButtonColors(); } int currentY = textY - scroll_pos; int endY = textY + visibleHeight; int startLineIndex = 0; int currentHeight = 0; for (int line = 0; line < totalLines; line++) { if (currentHeight >= scroll_pos) { startLineIndex = line; currentY = textY + (currentHeight - scroll_pos); break; } currentHeight += lineHeights[line]; if (line < totalLines - 1 && StringFind(lineRoles[line], "_timestamp") >= 0 && StringFind(lineRoles[line + 1], "_timestamp") < 0) { currentHeight += messageMargin; } } int numVisibleLines = 0; int visibleHeightUsed = 0; for (int line = startLineIndex; line < totalLines; line++) { int lineHeight = lineHeights[line]; if (visibleHeightUsed + lineHeight > visibleHeight) break; visibleHeightUsed += lineHeight; numVisibleLines++; if (line < totalLines - 1 && StringFind(lineRoles[line], "_timestamp") >= 0 && StringFind(lineRoles[line + 1], "_timestamp") < 0) { if (visibleHeightUsed + messageMargin > visibleHeight) break; visibleHeightUsed += messageMargin; } } int leftX = g_mainContentX + g_sidePadding + g_textPadding; int rightX = g_mainContentX + g_mainWidth - g_sidePadding - g_textPadding - reserved_width; color userColor = clrGray; color aiColor = clrBlue; color timestampColor = clrDarkGray; for (int li = 0; li < numVisibleLines; li++) { int lineIndex = startLineIndex + li; if (lineIndex >= totalLines) break; string line = allLines[lineIndex]; string role = lineRoles[lineIndex]; bool isTimestamp = StringFind(role, "_timestamp") >= 0; int currFontSize = isTimestamp ? timestampFontSize : fontSize; color textCol = isTimestamp ? timestampColor : (StringFind(role, "User") >= 0 ? userColor : aiColor); string currFont = font; if (StringFind(line, "Preparing the Request") >= 0) { textCol = clrDodgerBlue; currFont = "Arial Bold"; } if (StringFind(line, "Thinking...") >= 0) { textCol = clrRed; currFont = "Arial Bold"; } if (StringFind(line, "(Response in ") == 0) { textCol = clrGray; } string display_line = line; if (line == " ") { display_line = " "; textCol = clrWhite; } int textX_pos = (StringFind(role, "User") >= 0) ? rightX : leftX; ENUM_ANCHOR_POINT textAnchor = (StringFind(role, "User") >= 0) ? ANCHOR_RIGHT_UPPER : ANCHOR_LEFT_UPPER; string lineName = "ChatGPT_MessageText_" + IntegerToString(lineIndex); if (currentY >= textY && currentY < endY) { createLabel(lineName, textX_pos, currentY, display_line, textCol, currFontSize, currFont, CORNER_LEFT_UPPER, textAnchor); } // Add icons if this is the time note line and it's the last AI's second-last line if (StringFind(line, "(Response in ") == 0 && StringFind(role, "AI") >= 0 && lineIndex == totalLines - 2) { // Calculate time note width for positioning TextSetFont(currFont, currFontSize); uint tw, th; TextGetSize(line, tw, th); int iconX = leftX + (int)tw + 50; // X offset for space int iconY = currentY - 3; // To raise up on Y axis // Regenerate icon string regenName = "ChatGPT_RegenIcon"; createLabel(regenName, iconX, iconY, REGEN_ICON, REGEN_COLOR, ICON_SIZE, REGEN_ICON_FONT, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); ObjectSetInteger(0, regenName, OBJPROP_SELECTABLE, true); ObjectSetInteger(0, regenName, OBJPROP_ZORDER, 10); // Export icon iconX += ICON_SIZE + ICON_SPACING; string exportName = "ChatGPT_ExportIcon"; createLabel(exportName, iconX, iconY, EXPORT_ICON, EXPORT_COLOR, ICON_SIZE, EXPORT_ICON_FONT, CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); ObjectSetInteger(0, exportName, OBJPROP_SELECTABLE, true); ObjectSetInteger(0, exportName, OBJPROP_ZORDER, 10); } currentY += lineHeights[lineIndex]; if (lineIndex < totalLines - 1 && StringFind(lineRoles[lineIndex], "_timestamp") >= 0 && StringFind(lineRoles[lineIndex + 1], "_timestamp") < 0) { currentY += messageMargin; } } ChartRedraw(); }
Wir beginnen damit, zu prüfen, ob ein Popup wie „small_history“, die „big_history“ oder die Suche angezeigt wird, und kehren in diesem Fall frühzeitig zurück, um eine Aktualisierung der Hauptanzeige zu vermeiden, genau wie zuvor. Anschließend durchlaufen wir eine Schleife durch alle Objekte im Chart, um die Objekte zu löschen, die sich auf frühere Antwortzeilen, Hintergründe der Nachrichten, Texte, Zeitstempel und nun auch auf unsere neuen Regenerierungssymbole und Exportsymbole beziehen, indem wir die Funktion ObjectDelete verwenden, um den Bereich zu leeren. Der Rest des Codes ist identisch, und wo wir Änderungen vorgenommen haben, haben wir diese hervorgehoben und zur besseren Verständlichkeit Kommentare hinzugefügt. Wir werden jedoch an der Stelle weitermachen, an der wir die hinzugefügten Symbole grundlegend überarbeitet haben.
Zunächst wird „numVisibleLines“ berechnet, indem die sichtbaren Höhen addiert werden, wobei darauf geachtet wird, dass die „visibleHeight“ nicht überschritten wird und die Ränder nach den Zeitstempeln einbezogen werden. Wir legen die linke und rechte X-Position fest und wählen Farben für den Nutzer, die KI und die Zeitstempel. Dann werden die sichtbaren Zeilen in einer Schleife durchlaufen: jede Zeile und jede Rolle wird ermittelt, es wird festgestellt, ob ein Zeitstempel vorhanden ist, um Größe, Farbe und Schriftart festzulegen, und die Anzeigezeile wird angepasst, wenn Platz vorhanden ist. Wir setzen die Position und den Anker auf der Grundlage der Rolle. Wenn die Linie innerhalb der y-Grenzen liegt, wird mit „createLabel“ ein Etikett erstellt. Für die Zeitangabe in der letzten AI-Nachricht (lineIndex totalLines-2) messen wir die Breite mit TextGetSize und berechnen dann „iconX“ und „iconY“. Wir erstellen das Regenerierungssymbol „ChatGPT_RegenIcon“ unter Verwendung von „REGEN_ICON“, Farbe, Größe und Schriftart, und das Exportsymbol „ChatGPT_ExportIcon“ mit ähnlichen Abständen, wählbarer Einstellung und zorder. Wir erhöhen „currentY“ um die Zeilenhöhe und fügen nach Zeitstempeln einen Rand hinzu, sofern es nicht der letzte ist. Schließlich rufen wir ChartRedraw auf, um die Anzeige zu aktualisieren.
Jetzt haben wir eine vollständige, aktualisierte UI-Komponenten-Datei. Nun müssen nur noch die entsprechenden Funktionen in der Hauptdatei aufgerufen werden, um die Änderungen zu übernehmen. In der Hauptdatei beginnen wir damit, die Animationskonstanten ganz oben zu definieren, um die Verwaltung zu erleichtern.
// Loading indicator constants string PrepBase = "AI: Preparing the Request"; // Preparing Text string LoadingPlaceholder = "AI: Thinking..."; // Thinking text string SpinnerDots[] = {"", ".", "..", "..."}; // Cycling dots for animation int PreAnimationCycles = 6; // Number of cycles (~1s total) ulong StartTimeMs = 0; // For timing API call
Hier definieren wir die Zeichenkette „PrepBase“ als „AI: Preparing the Request“ (Vorbereitung der Anfrage) als Basistext für die anfängliche Lademeldung während der Vorbereitung der API-Anfrage dienen. Sie können ihn in den von Ihnen gewünschten Text für die Erstvorbereitung ändern. Wir setzen „LoadingPlaceholder“ auf „AI: Thinking...“ als Text angezeigt, während Sie auf die KI-Antwort warten, die Sie auch ändern können. Dann erstellen wir das String-Array „SpinnerDots“ mit einem leeren String, einem einzelnen Punkt, doppelten Punkten und dreifachen Punkten für zyklische Animationseffekte, die an die Ladenachrichten angehängt werden. Wir legen „PreAnimationCycles“ als 6 fest, um die Anzahl der Animationsschleifen zu steuern, die auf der Grundlage der Schlafintervalle eine Gesamtdauer von etwa 1 Sekunde haben.
Wir initialisieren „StartTimeMs“ als unsigned long auf 0, um die Anzahl der Startticks für die Messung der API-Antwortzeit zu erfassen. Beim Absenden der Nachricht fügen wir dann diese Variablen hinzu, um den Ladezustand zu simulieren. Hier ist die aktualisierte Funktion.
void SubmitMessage(string prompt) { if (StringLen(prompt) == 0) return; string timestamp = TimeToString(TimeCurrent(), TIME_MINUTES); string response = ""; bool send_to_api = true; if (StringFind(prompt, "set title ") == 0) { string new_title = StringSubstr(prompt, 10); current_title = new_title; response = "Title set to " + new_title; send_to_api = false; UpdateCurrentHistory(); UpdateSidebarDynamic(); } // Save old height before adding prompt UpdateResponseDisplay(); int old_total = g_total_height; conversationHistory += "You: " + prompt + "\n" + timestamp + "\n"; // Get height after prompt UpdateResponseDisplay(); int after_prompt = g_total_height; int prompt_height = after_prompt - old_total; if (send_to_api) { conversationHistory += PrepBase + "\n" + timestamp + "\n\n"; // Get height after loading UpdateResponseDisplay(); int after_loading = g_total_height; int loading_height = after_loading - after_prompt; int new_content_height = prompt_height + loading_height; // Dynamic scroll: if fits, prompt at top (higher); else, loading at bottom if (new_content_height <= g_visible_height) { scroll_pos = MathMax(0, old_total); } else { scroll_pos = MathMax(0, after_loading - g_visible_height); } if (scroll_visible) { UpdateSliderPosition(); UpdateButtonColors(); } ChartRedraw(); for (int i = 0; i < PreAnimationCycles; i++) { // Sub-cycle for strict increasing: reset dots every 3 steps int subCycle = i % 3; string dots = ""; for (int d = 0; d <= subCycle; d++) { dots += "."; } int prepPos = StringFind(conversationHistory, PrepBase, 0); if (prepPos >= 0) { int endPos = StringFind(conversationHistory, "\n\n", prepPos) + 2; if (endPos < 2) endPos = StringLen(conversationHistory); string before = StringSubstr(conversationHistory, 0, prepPos); string after = StringSubstr(conversationHistory, endPos); conversationHistory = before + PrepBase + dots + "\n" + timestamp + "\n\n" + after; } UpdateResponseDisplay(); // Re-apply dynamic scroll after animation update (height same as loading) scroll_pos = (new_content_height <= g_visible_height) ? MathMax(0, old_total) : MathMax(0, g_total_height - g_visible_height); if (scroll_visible) { UpdateSliderPosition(); UpdateButtonColors(); } ChartRedraw(); Sleep(200); } int prepPos = StringFind(conversationHistory, PrepBase, 0); if (prepPos >= 0) { int endPos = StringFind(conversationHistory, "\n\n", prepPos) + 2; if (endPos < 2) endPos = StringLen(conversationHistory); string before = StringSubstr(conversationHistory, 0, prepPos); string after = StringSubstr(conversationHistory, endPos); conversationHistory = before + LoadingPlaceholder + "\n" + timestamp + "\n\n" + after; } else { conversationHistory += LoadingPlaceholder + "\n" + timestamp + "\n\n"; } UpdateResponseDisplay(); // Re-apply dynamic scroll after placeholder scroll_pos = (new_content_height <= g_visible_height) ? MathMax(0, old_total) : MathMax(0, g_total_height - g_visible_height); if (scroll_visible) { UpdateSliderPosition(); UpdateButtonColors(); } ChartRedraw(); StartTimeMs = GetTickCount(); Print("Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); FileWrite(logFileHandle, "Chat ID: " + IntegerToString(current_chat_id) + ", Title: " + current_title); Print("User: " + prompt); FileWrite(logFileHandle, "User: " + prompt); response = GetChatGPTResponse(prompt); Print("AI: " + response); FileWrite(logFileHandle, "AI: " + response); ulong elapsedMs = GetTickCount() - StartTimeMs; int elapsedSec = (int)(elapsedMs / 1000); string timeNote = "\n(Response in " + IntegerToString(elapsedSec) + "s)"; int placeholderPos = StringFind(conversationHistory, LoadingPlaceholder, 0); if (placeholderPos >= 0) { int endPos = StringFind(conversationHistory, "\n\n", placeholderPos) + 2; if (endPos < 2) endPos = StringLen(conversationHistory); string before = StringSubstr(conversationHistory, 0, placeholderPos); string after = StringSubstr(conversationHistory, endPos); conversationHistory = before + "AI: " + response + timeNote + "\n" + timestamp + "\n\n" + after; } else { conversationHistory += "AI: " + response + timeNote + "\n" + timestamp + "\n\n"; } if (StringFind(current_title, "Chat ") == 0) { current_title = StringSubstr(prompt, 0, 30); if (StringLen(prompt) > 30) current_title += "..."; UpdateCurrentHistory(); UpdateSidebarDynamic(); } } else { conversationHistory += "AI: " + response + "\n" + timestamp + "\n\n"; } UpdateCurrentHistory(); UpdateResponseDisplay(); // For final response: always scroll to bottom (response may be long) scroll_pos = MathMax(0, g_total_height - g_visible_height); if (scroll_visible) { UpdateSliderPosition(); UpdateButtonColors(); } ChartRedraw(); }
Wir beginnen die Funktion „SubmitMessage“, indem wir prüfen, ob die Eingabe „prompt“ eine Länge hat, und wenn sie leer ist, vorzeitig zurückkehren, und den aktuellen Zeitstempel mit TimeToString unter Verwendung von TimeCurrent und „TIME_MINUTES“ ermitteln. Wir initialisieren eine leere „response“ und setzen „send_to_api“ auf true, prüfen dann, ob „prompt“ mit „set title „ beginnt, indem wir StringFind verwenden, extrahieren den neuen Titel mit StringSubstr, aktualisieren „current_title“, setzen „response“ auf Bestätigung, „send_to_api“ auf false und rufen „UpdateCurrentHistory“ und „UpdateSidebarDynamic“ auf. Wir rufen „UpdateResponseDisplay“ auf, um die alte „g_total_height“ zu erhalten, fügen die Nutzereingabeaufforderung und den Zeitstempel an „conversationHistory“ an und rufen „UpdateResponseDisplay“ erneut auf, um die Höhe nach der Eingabeaufforderung zu erhalten, wobei „prompt_height“ als Differenz berechnet wird.
Wenn „send_to_api“ true ist, hängen wir „PrepBase“ mit Zeitstempel an „conversationHistory“ an, rufen „UpdateResponseDisplay“ auf, um die Höhe nach dem Laden zu erhalten, berechnen „loading_height“ und „new_content_height“ als Summe der Prompt- und Ladehöhen; wir setzen „scroll_pos“ dynamisch mit MathMax entweder auf die alte Gesamthöhe, wenn der neue Inhalt in die „g_visible_height“ passt, oder andernfalls auf den unteren Rand nach dem Laden, dann, wenn „scroll_visible“ „UpdateSliderPosition“ und „UpdateButtonColors“ aufruft, und ChartRedraw. Wir durchlaufen eine Schleife von 0 bis „PreAnimationCycles“-1, berechnen „subCycle“ als i mod 3, erstellen „dots“, indem wir Punkte bis zu subCycle+1 anhängen, suchen „prepPos“ in „conversationHistory“ mit „StringFind“, extrahieren den Vor- und Nachteil mit „StringSubstr“, aktualisieren den Verlauf mit „PrepBase“ plus Punkten und Zeitstempel, rufen „UpdateResponseDisplay“ auf, wenden die dynamische „scroll_pos“ erneut an, aktualisieren den Schieberegler und die Schaltflächen, falls sichtbar, zeichnen mit „ChartRedraw“ neu und warten 200 ms mit „Sleep“.
Wir suchen erneut nach „prepPos“, ersetzen es durch „LoadingPlaceholder“ und fügen, falls vorhanden, einen Zeitstempel hinzu; andernfalls fügen wir ihn an, rufen „UpdateResponseDisplay“ auf, wenden das dynamische „scroll_pos“ erneut an, aktualisieren den Schieberegler und die Schaltflächen, sofern diese sichtbar sind, und rufen „ChartRedraw“ auf. Wir setzen „StartTimeMs“ auf „GetTickCount“, geben die Chat-ID und den Titel mit „Print“ und „FileWrite“ aus und schreiben sie in die Protokolldatei, geben die Nutzeraufforderung aus und schreiben sie, rufen die „Antwort“ über „GetChatGPTResponse“ ab, geben die KI-Antwort aus und schreiben sie. Wir berechnen „elapsedMs“ als „GetTickCount“ minus „StartTimeMs“, „elapsedSec“ als Ganzzahl in Sekunden, erstellen „timeNote“ mit der Zeichenfolge der Antwortzeit, ermitteln „placeholderPos“ für „LoadingPlaceholder“, ersetzen diese durch „AI: “ gefolgt von der Antwort, timeNote und dem Zeitstempel, falls vorhanden, andernfalls wird sie unter Verwendung der Funktionen StringFind und StringSubstr angehängt. Der Rest bleibt, wie er war. Die wichtigsten Überarbeitungen haben wir der Übersichtlichkeit halber hervorgehoben. Es ist wichtig zu wissen, dass wir keine Live-Simulationen durchführen können, da die Webanforderung Interaktionen blockiert. Jetzt brauchen wir Hilfsfunktionen für die Symbole, die wir hinzugefügt haben, wenn sie angeklickt werden.
// Extract last AI response from history string GetLastAIResponse() { int ai_pos = StringFind(conversationHistory, "AI: ", -1); // Search backward if (ai_pos < 0) return ""; int end_pos = StringFind(conversationHistory, "\n\n", ai_pos); if (end_pos < 0) end_pos = StringLen(conversationHistory); string response = StringSubstr(conversationHistory, ai_pos + 4, end_pos - ai_pos - 4); StringTrimLeft(response); StringTrimRight(response); return response; } // Extract last user prompt from history string GetLastUserPrompt() { int you_pos = StringFind(conversationHistory, "You: ", -1); // Search backward if (you_pos < 0) return ""; int ts_start = StringFind(conversationHistory, "\n", you_pos + 5) + 1; string prompt = StringSubstr(conversationHistory, you_pos + 5, ts_start - you_pos - 6); StringTrimLeft(prompt); StringTrimRight(prompt); return prompt; } // Remove last AI block from history (AI: ... \n timestamp \n\n) void RemoveLastAIResponse() { int last_nn = StringFind(conversationHistory, "\n\n", -1); if (last_nn >= 0) { int ai_pos = StringFind(conversationHistory, "AI: ", last_nn - 100); // Rough backward search if (ai_pos >= 0 && ai_pos < last_nn) { conversationHistory = StringSubstr(conversationHistory, 0, ai_pos); } } UpdateCurrentHistory(); }
Hier definieren wir die Funktion „GetLastAIResponse“, um die letzte AI-Nachricht aus „conversationHistory“ zu extrahieren, indem wir StringFind mit -1 verwenden, um rückwärts nach „AI: “, lokalisiert das Ende mit „\n\n“ oder der Stringlänge, wenn es nicht gefunden wird, unterteilt den Inhalt ab „AI: “, schneidet die Leerzeichen mit StringTrimLeft und StringTrimRight ab und gibt ihn zurück, oder eine leere Zeichenkette, wenn nichts gefunden worden ist. Wir erstellen die Funktion „GetLastUserPrompt“, um die letzte Nutzereingabe abzurufen. Dazu suchen wir mit „StringFind“ rückwärts nach „You: “, ermitteln die Position des nächsten „\n“ nach dem Eingabeaufforderungstext, extrahieren Teilzeichenfolgen vom Ende von „You: “ bis zum Beginn des Zeitstempels, entfernen überflüssige Zeichen und geben die Eingabeaufforderung zurück – oder einen leeren Wert, falls keine vorhanden ist.
Wir implementieren die Funktion „RemoveLastAIResponse“, um den letzten KI-Block aus „conversationHistory“ zu löschen. Dazu suchen wir mit „StringFind“ nach dem letzten „\n\n“, suchen dann rückwärts innerhalb von 100 Zeichen davor nach „AI: “, kürzen den Verlauf mit „StringSubstr“ bis vor „ai_pos“ ab, sofern die Zeichenfolge gefunden und korrekt positioniert wurde, und rufen „UpdateCurrentHistory“ auf, um die Änderungen zu speichern. Dies sind nun die Funktionen, die wir aufrufen werden, wenn wir auf die Symbole klicken, aber wir müssen auf ihr Klicken hören. Hier ist die Logik, mit der wir das erreichen.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- rest of the logic else if (sparam == "ChatGPT_EditIcon") { string response = GetLastAIResponse(); if (response != "") { currentPrompt = response; DeletePlaceholder(); UpdatePromptDisplay(); p_scroll_pos = MathMax(0, p_total_height - p_visible_height); if (p_scroll_visible) { UpdatePromptSliderPosition(); UpdatePromptButtonColors(); } ChartRedraw(); } } else if (sparam == "ChatGPT_RegenIcon") { string prompt = GetLastUserPrompt(); if (prompt != "") { RemoveLastAIResponse(); SubmitMessage(prompt); // Regenerates } } else if (sparam == "ChatGPT_ExportIcon") { string response = GetLastAIResponse(); if (response != "") { int handle = FileOpen("LastAIResponse.txt", FILE_WRITE | FILE_TXT); if (handle != INVALID_HANDLE) { FileWrite(handle, response); FileClose(handle); Print("Exported to LastAIResponse.txt"); } } } //--- rest of the logic }
Hier fügen wir die Behandlung für den Klick auf das Bearbeitungssymbol in die Ereignisbehandlung durch OnChartEvent ein. Wenn „sparam“ gleich „ChatGPT_EditIcon“ ist, rufen wir die letzte AI-Antwort mit „GetLastAIResponse“ ab. Wenn er nicht leer ist, weisen wir ihn „currentPrompt“ zu, rufen „DeletePlaceholder“ auf und aktualisieren die Anzeige der Eingabeaufforderung mit „UpdatePromptDisplay“. Außerdem setzen wir „p_scroll_pos“ mit MathMax von 0 und „p_total_height“ minus „p_visible_height“ an den unteren Rand. Wenn „p_scroll_visible“, rufen wir „UpdatePromptSliderPosition“ und „UpdatePromptButtonColors“ vor dem erneuten Zeichnen.
Wenn „sparam“ „ChatGPT_RegenIcon“ ist, wird die letzte Nutzerabfrage mit „GetLastUserPrompt“ abgerufen, und wenn diese nicht leer ist, wird die letzte AI-Antwort mit „RemoveLastAIResponse“ entfernt und die Abfrage mit „SubmitMessage“ erneut gesendet, um eine neue Antwort zu erzeugen. Wenn „sparam“ mit „ChatGPT_ExportIcon“ übereinstimmt, holen wir die letzte AI-Antwort und öffnen, falls vorhanden, „LastAIResponse.txt“ zum Schreiben im Textmodus mit FileOpen, prüfen, ob „handle“ nicht INVALID_HANDLE ist, schreiben die Antwort mit FileWrite, schließen die Datei mit FileClose und drucken eine Erfolgsmeldung. Hier ist ein Beispiel für einen Downloadvorgang.

Die endgültige Nutzeroberfläche sieht wie folgt aus.

Anhand der Visualisierung können wir sehen, dass wir das Programm durch Hinzufügen oder Anpassen der neuen UI-Elemente aktualisieren und somit unsere Ziele erreichen können. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Anhand der Visualisierung können wir sehen, dass die UI-Komponenten gut sind, aber wenn wir auf die Symbole klicken, erhalten sie die erste Nachricht anstelle der letzten Nachricht, was nicht unserer Absicht entspricht. Wir müssen also die Reihenfolge der Identifizierung umkehren, damit es uns gut geht. Das Problem ist, dass wir von der Schleife ausgegangen sind und vergessen haben, dass wir eine komplexe Analyse benötigen, um mehrzeilige Antworten und Aufforderungen zu verarbeiten.
// Extract last user prompt from history string GetLastUserPrompt() { string blocks[]; int num_blocks = SplitOnString(conversationHistory, "\n\n", blocks); if (num_blocks == 0) return ""; // Find the last You block (reverse) for (int i = num_blocks - 1; i >= 0; i--) { string block = blocks[i]; if (StringFind(block, "You: ") == 0) { // Extract content after "You: " up to timestamp int ts_pos = StringFind(block, "\n", 5); // After "You: " if (ts_pos > 0) { string prompt = StringSubstr(block, 5, ts_pos - 5); StringTrimLeft(prompt); StringTrimRight(prompt); Print("DEBUG: Full history before extract prompt: " + conversationHistory); Print("DEBUG: Last You block: " + block); Print("DEBUG: Extracted last prompt: " + prompt); return prompt; } } } return ""; } string GetLastAIResponse() { // Split entire history into lines string lines[]; int num_lines = StringSplit(conversationHistory, '\n', lines); if (num_lines == 0) { Print("DEBUG: No lines in history."); return ""; } Print("DEBUG: Total lines in history: " + IntegerToString(num_lines)); for (int j = 0; j < num_lines; j++) { Print("DEBUG: History Line " + IntegerToString(j) + ": " + lines[j]); } // Find start of last AI response (reverse search for "AI: ") int ai_start = -1; for (int i = num_lines - 1; i >= 0; i--) { string trimmed = lines[i]; StringTrimLeft(trimmed); StringTrimRight(trimmed); if (StringFind(trimmed, "AI: ") == 0) { ai_start = i; break; } } if (ai_start == -1) { Print("DEBUG: No AI: line found in history. Full history: " + conversationHistory); return ""; } Print("DEBUG: Last AI starts at line " + IntegerToString(ai_start)); string response_build = ""; // Extract from AI: line string first_line = lines[ai_start]; int prefix_pos = StringFind(first_line, "AI: "); if (prefix_pos >= 0) { first_line = StringSubstr(first_line, prefix_pos + 4); StringTrimLeft(first_line); StringTrimRight(first_line); if (StringLen(first_line) > 0 && StringFind(first_line, "(Response in ") != 0 && StringFind(first_line, "(Regenerated in ") != 0 && !IsTimestamp(first_line)) { response_build = first_line; } } // Collect subsequent lines until next message start (You: or AI: ) or end for (int j = ai_start + 1; j < num_lines; j++) { string orig_line = lines[j]; string trimmed = orig_line; StringTrimLeft(trimmed); StringTrimRight(trimmed); // Stop if new message starts if (StringFind(trimmed, "You: ") == 0 || StringFind(trimmed, "AI: ") == 0) { break; } // Skip notes and timestamps if (StringFind(trimmed, "(Response in ") == 0 || StringFind(trimmed, "(Regenerated in ") == 0 || IsTimestamp(trimmed)) { continue; } // Add original line (preserve empties as \n) if (response_build != "") response_build += "\n"; response_build += orig_line; } Print("DEBUG: Extracted last response: '" + response_build + "'"); return response_build; }
Wir haben Kommentare zu den jeweiligen Zeilen hinzugefügt, um Klarheit zu schaffen und um sicherzugehen, dass wir die richtigen Ergebnisse erhalten. Sie können sie auskommentieren, wenn Sie sie nicht brauchen, aber wir werden sie für eine spätere Verwendung aufbewahren und sie zum Polieren auskommentieren. Außerdem haben wir eine zusätzliche neue Zeile in die Submit-Funktion eingefügt, damit sie alle Antworten mit leeren und neuen Zeilen wie unten beschrieben verarbeiten kann.
void SubmitMessage(string prompt) { //--- conversationHistory += "You: " + prompt + "\n" + timestamp + "\n\n"; // Add extra \n for separation //--- }
Nach der Kompilierung erhalten wir das folgende zufriedenstellende Endergebnis.

Schlussfolgerung
Abschließend haben wir die Nutzeroberfläche unseres KI-gesteuerten Handelssystems in MQL5 mit Ladeanimationen für die Vorbereitungs- und Denkphasen von Anfragen, Zeitmetriken zur Anzeige der Antwortdauer in Sekunden und Verwaltungstools wie Regenerierungsschaltflächen für die erneute Abfrage von Prompts und Exportoptionen zum Speichern von Ausgaben in Dateien aufpoliert. Diese Funktionen in Kombination mit Hover-Effekten, skalierten Bildern und dynamischen Seitenleisten sorgen für eine reaktionsschnellere und optisch ansprechendere Darstellung, während der modulare Code für eine einfache Erweiterbarkeit erhalten bleibt. In den nächsten Teilen werden wir uns mit der Integration von Sentiment-Analysen oder der Bestätigung von Signalen mit mehreren Zeitrahmen für noch intelligentere Handelsentscheidungen beschäftigen. Bleiben Sie dran.
Anlagen
| S/N | Name | Typ | Beschreibung |
|---|---|---|---|
| 1 | AI_JSON_FILE.mqh | JSON-Klassenbibliothek | Klasse zur Handhabung der JSON-Serialisierung und -Deserialisierung |
| 2 | AI_CREATE_OBJECTS_FNS.mqh | Bibliothek der Objektfunktionen | Funktionen zur Erstellung von Visualisierungsobjekten wie Beschriftungen und Schaltflächen |
| 3 | AI_UI_COMPONENTS.mqh | Bibliothek der Komponenten der Nutzeroberfläche | Datei mit den Komponenten der Nutzeroberfläche und deren Organisation |
| 4 | AI_BMP_FILES_ZIP | Bitmap-Dateien Zip | Datei mit den Bitmap-Bildern |
| 5 | AI_ChatGPT_EA_Part_8.mq5 | Hauptdatei des Expert Advisors | Haupt-Expertenratgeber für die KI-Integration |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20722
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.
Larry Williams Marktgeheimnisse (Teil 4): Automatisieren von kurzfristigen hohen und tiefen Umkehrpunkten in MQL5
Sigma-Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor
Aufbau von Volatilitätsmodellen in MQL5 (Teil I): Die erste Implementierung
Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur
- 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.
Dieser Artikel hat mir gut gefallen.
Wie hoch sind Ihrer Meinung nach die monatlichen API-Kosten?
Ich war auf der Suche nach einem Weg, wo wir die DeepSeek-basierte KI, die auf dem System läuft und kann in diesem Programm verwendet werden, verwenden können.
Vielen Dank für einen kreativen Weg, dies auf MT5 zu bringen.
Dieser Artikel hat mir gut gefallen.
Wie hoch sind Ihrer Meinung nach die monatlichen API-Kosten?
Ich war auf der Suche nach einer Möglichkeit, wo wir die DeepSeek-basierte KI verwenden können, die auf dem System läuft und in diesem Programm verwendet werden kann.
Vielen Dank für einen kreativen Weg, dies auf den MT5 zu bringen.
Vielen Dank für das nette Feedback und die freundliche Aufnahme. Das hängt von dem Chart ab, auf dem es läuft, und von dem gewählten Modus. Wenn der automatische Modus ausgewählt ist, wird die Kommunikation bei jedem Balken erfolgen, was die Anzahl der benötigten Token erhöht. Noch besser ist es, wenn Sie die Sitzung definieren, in der es automatisch läuft, um unnötige Durchläufe zu vermeiden, oder neue oder erweiterte Regeln definieren, denen es folgen soll. Es kann für jede KI verwendet werden, es müssen nur die API-Schlüssel konfiguriert werden.
Danke!
Dieser Artikel hat mir gut gefallen.
Wie hoch sind Ihrer Meinung nach die monatlichen API-Kosten?
Ich war auf der Suche nach einem Weg, wo wir die DeepSeek-basierte KI verwenden können, die auf dem System läuft und in diesem Programm verwendet werden kann.
Vielen Dank für einen kreativen Weg, dies auf den MT5 zu bringen.
Vielen Dank für das freundliche Feedback und die freundliche Aufnahme. Das hängt von dem Diagramm ab, auf dem es läuft, und von dem gewählten Modus. Wenn der automatische Modus ausgewählt ist, wird er bei jedem Balken kommunizieren, was die Anzahl der benötigten Token erhöht. Noch besser ist es, wenn Sie die Sitzung definieren, die automatisch ausgeführt werden soll, um unnötige Durchläufe zu vermeiden, oder wenn Sie neue oder erweiterte Regeln definieren, denen er folgen soll. Es kann für jede KI verwendet werden, es müssen nur die API-Schlüssel konfiguriert werden.
Danke!
Das ist wirklich fantastisch! Sogar chatGPT hat mir nicht gesagt, dass dies möglich ist, und es gab eine sichere Antwort, dass dies unmöglich ist!
Hallo, auch ich bin von den Artikeln begeistert. Bei den Tests, die ich durchgeführt habe, kann ich sagen, dass innerhalb der Kapazität des Kontextfensters mit dem Gpt4.o-Modell die Kosten pro Anfrage $0,01 betragen. Bei den Tests mit dem Modell Gpt4.1 liegen die Kosten jedoch bei 0,05 $.
Ich habe in der Vergangenheit das Modell ChatGPT 4.0 verwendet und es war ziemlich genau richtig!