327 lines
11 KiB
MQL5
327 lines
11 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| AI Canvas Shell.mqh |
|
||
|
|
//| Copyright 2026, Allan Munene Mutiiria. |
|
||
|
|
//| https://t.me/Forex_Algo_Trader |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "Copyright 2026, Allan Munene Mutiiria."
|
||
|
|
#property link "https://t.me/Forex_Algo_Trader"
|
||
|
|
|
||
|
|
//--- Include guard
|
||
|
|
#ifndef AI_CANVAS_SHELL_MQH
|
||
|
|
#define AI_CANVAS_SHELL_MQH
|
||
|
|
|
||
|
|
//--- Include required modules
|
||
|
|
#include "AI Canvas State.mqh"
|
||
|
|
#include "AI Canvas Primitives.mqh"
|
||
|
|
#include "AI Canvas Render.mqh"
|
||
|
|
#include "AI Canvas Editor.mqh"
|
||
|
|
#include "AI Canvas Interact.mqh"
|
||
|
|
#include "AI Logic.mqh"
|
||
|
|
|
||
|
|
//--- Forward declare main EA inputs only when not compiled from main
|
||
|
|
#ifndef AI_COMPILED_FROM_MAIN
|
||
|
|
extern int MagicNumber; // Magic number for trade ops
|
||
|
|
extern bool EnableAutoSignal; // Enable auto signal on new bar
|
||
|
|
#endif
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Configure prompt and search editors with current layout |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_ConfigureEditor()
|
||
|
|
{
|
||
|
|
//--- Compute prompt content width from current pane rect
|
||
|
|
int pL, pT, pR, pB;
|
||
|
|
Ai_GetPromptPaneRect(pL, pT, pR, pB);
|
||
|
|
int contentW = (pR - pL) - 2 * AI_TEXT_PAD - 8;
|
||
|
|
|
||
|
|
//--- Initialize main prompt editor
|
||
|
|
g_ai_editor.Init("Arial", AI_FONT_BODY, AI_TEXT_PAD, AI_TEXT_PAD);
|
||
|
|
g_ai_editor.SetWrapWidth(contentW);
|
||
|
|
g_ai_editor.SetPlaceholder("Type your prompt here... (Enter for newline, Shift+Enter to send)");
|
||
|
|
|
||
|
|
//--- Initialize search popup editor
|
||
|
|
g_ai_searchEditor.Init("Arial", AI_FONT_BODY, 4, 4);
|
||
|
|
g_ai_searchEditor.SetWrapWidth(2000);
|
||
|
|
g_ai_searchEditor.SetPlaceholder("Search across chat titles and conversations...");
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Recompute layout and reposition prompt overlay |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_RecomputeLayout()
|
||
|
|
{
|
||
|
|
//--- Bail when main canvas does not exist
|
||
|
|
if(!g_ai_canvMainExists) return;
|
||
|
|
|
||
|
|
//--- Reconfigure editors with current dimensions
|
||
|
|
Ai_ConfigureEditor();
|
||
|
|
|
||
|
|
//--- Reposition prompt overlay to match new sidebar width
|
||
|
|
if(g_ai_canvPromptExists)
|
||
|
|
{
|
||
|
|
int dpL, dpT, dpR, dpB;
|
||
|
|
Ai_GetPromptPaneRect(dpL, dpT, dpR, dpB);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_XDISTANCE, AI_DASHBOARD_X + dpL);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_YDISTANCE, AI_DASHBOARD_Y + dpT);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Create main dashboard canvas and prompt overlay |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool Ai_CreateMainCanvas()
|
||
|
|
{
|
||
|
|
//--- Skip if already created
|
||
|
|
if(g_ai_canvMainExists) return true;
|
||
|
|
|
||
|
|
//--- Compute full dashboard size
|
||
|
|
const int dW = AI_SIDEBAR_W_EXPANDED + AI_MAIN_W;
|
||
|
|
const int dH = AI_HEADER_H + AI_PAD + AI_DISPLAY_H + AI_PAD + AI_PROMPT_H + AI_PAD + AI_FOOTER_H;
|
||
|
|
|
||
|
|
//--- Create main canvas as bitmap label
|
||
|
|
if(!g_ai_canvMain.CreateBitmapLabel(AI_CANVAS_NAME_MAIN,
|
||
|
|
AI_DASHBOARD_X, AI_DASHBOARD_Y,
|
||
|
|
dW, dH, COLOR_FORMAT_ARGB_NORMALIZE))
|
||
|
|
{
|
||
|
|
Print("Ai_CreateMainCanvas: CreateBitmapLabel failed: ", GetLastError());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Configure main canvas object properties
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_BACK, false);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_SELECTABLE, false);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_HIDDEN, true);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_ZORDER, 1000);
|
||
|
|
g_ai_canvMainExists = true;
|
||
|
|
|
||
|
|
//--- Compute prompt overlay screen position and size
|
||
|
|
int ppL, ppT, ppR, ppB;
|
||
|
|
Ai_GetPromptPaneRect(ppL, ppT, ppR, ppB);
|
||
|
|
const int promptW = ppR - ppL;
|
||
|
|
const int promptH = ppB - ppT;
|
||
|
|
const int promptScreenX = AI_DASHBOARD_X + ppL;
|
||
|
|
const int promptScreenY = AI_DASHBOARD_Y + ppT;
|
||
|
|
|
||
|
|
//--- Try create prompt overlay - non-fatal if it fails
|
||
|
|
if(g_ai_canvPrompt.CreateBitmapLabel(AI_CANVAS_NAME_PROMPT,
|
||
|
|
promptScreenX, promptScreenY,
|
||
|
|
promptW, promptH, COLOR_FORMAT_ARGB_NORMALIZE))
|
||
|
|
{
|
||
|
|
//--- Configure prompt overlay base properties
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_BACK, false);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_SELECTABLE, false);
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_HIDDEN, true);
|
||
|
|
|
||
|
|
//--- Suppress default object-name tooltip
|
||
|
|
ObjectSetString(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TOOLTIP, "\n");
|
||
|
|
|
||
|
|
//--- Always-visible flag - hide done via bitmap content not visibility
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
|
||
|
|
|
||
|
|
//--- High zorder for click event routing (visual stacking is by creation order)
|
||
|
|
ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_ZORDER, 1001);
|
||
|
|
g_ai_canvPromptExists = true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
//--- Log failure and fall back to main-only renders
|
||
|
|
Print("Ai_CreateMainCanvas: prompt overlay create failed (",
|
||
|
|
GetLastError(), ") - falling back to main-only renders.");
|
||
|
|
g_ai_canvPromptExists = false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Destroy main canvas and prompt overlay |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_DestroyMainCanvas()
|
||
|
|
{
|
||
|
|
//--- Bail when nothing to destroy
|
||
|
|
if(!g_ai_canvMainExists) return;
|
||
|
|
|
||
|
|
//--- Destroy prompt overlay first
|
||
|
|
if(g_ai_canvPromptExists)
|
||
|
|
{
|
||
|
|
g_ai_canvPrompt.Destroy();
|
||
|
|
ObjectDelete(0, AI_CANVAS_NAME_PROMPT);
|
||
|
|
g_ai_canvPromptExists = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy main canvas
|
||
|
|
g_ai_canvMain.Destroy();
|
||
|
|
ObjectDelete(0, AI_CANVAS_NAME_MAIN);
|
||
|
|
g_ai_canvMainExists = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Show dashboard - load images, create canvas, render |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_ShowDashboard()
|
||
|
|
{
|
||
|
|
//--- Load images for icons and logos
|
||
|
|
if(!Ai_LoadImages()) Print("Ai_ShowDashboard: image load partial");
|
||
|
|
|
||
|
|
//--- Create canvases and bail on failure
|
||
|
|
if(!Ai_CreateMainCanvas())
|
||
|
|
{
|
||
|
|
Print("Ai_ShowDashboard: failed to create canvas");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Configure editors and restore prompt text
|
||
|
|
Ai_ConfigureEditor();
|
||
|
|
g_ai_editor.SetText(g_ai_currentPrompt);
|
||
|
|
|
||
|
|
//--- Enable mouse events on chart
|
||
|
|
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
|
||
|
|
ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
|
||
|
|
ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
|
||
|
|
|
||
|
|
//--- Mark visible and trigger first render
|
||
|
|
g_ai_dashboardVisible = true;
|
||
|
|
Ai_RenderAll();
|
||
|
|
ChartRedraw();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Hide dashboard - persist state and destroy canvas |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_HideDashboard()
|
||
|
|
{
|
||
|
|
//--- Save current chat state to disk
|
||
|
|
AiUpdateCurrentHistory();
|
||
|
|
|
||
|
|
//--- Release keyboard override
|
||
|
|
Ai_EndKeyboardOverride();
|
||
|
|
|
||
|
|
//--- Restore default chart scroll behavior
|
||
|
|
ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
|
||
|
|
|
||
|
|
//--- Destroy canvases and mark hidden
|
||
|
|
Ai_DestroyMainCanvas();
|
||
|
|
g_ai_dashboardVisible = false;
|
||
|
|
ChartRedraw();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Initialize EA - called from OnInit |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool Ai_Init()
|
||
|
|
{
|
||
|
|
//--- Apply default theme
|
||
|
|
Ai_ApplyTheme(false);
|
||
|
|
|
||
|
|
//--- Initialize all scroll states
|
||
|
|
AiScrollInit(g_ai_chatScroll);
|
||
|
|
AiScrollInit(g_ai_bigScroll);
|
||
|
|
AiScrollInit(g_ai_searchScroll);
|
||
|
|
|
||
|
|
//--- Configure trade helper magic number
|
||
|
|
g_ai_trade.SetExpertMagicNumber(MagicNumber);
|
||
|
|
|
||
|
|
//--- Open log file in append mode
|
||
|
|
g_ai_logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT);
|
||
|
|
if(g_ai_logFileHandle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
Print("Failed to open log file: ", GetLastError());
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
FileSeek(g_ai_logFileHandle, 0, SEEK_END);
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Load saved chats from disk and show dashboard
|
||
|
|
AiLoadChats();
|
||
|
|
Ai_ShowDashboard();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Deinitialize EA - called from OnDeinit |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_Deinit()
|
||
|
|
{
|
||
|
|
//--- Save current chat state and hide dashboard
|
||
|
|
AiUpdateCurrentHistory();
|
||
|
|
Ai_HideDashboard();
|
||
|
|
|
||
|
|
//--- Close log file
|
||
|
|
if(g_ai_logFileHandle != INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
FileClose(g_ai_logFileHandle);
|
||
|
|
g_ai_logFileHandle = INVALID_HANDLE;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy persistent chat scratch canvas
|
||
|
|
if(g_ai_canvChatTmpReady)
|
||
|
|
{
|
||
|
|
g_ai_canvChatTmp.Destroy();
|
||
|
|
g_ai_canvChatTmpReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy persistent search scratch canvas
|
||
|
|
if(g_ai_canvSearchTmpReady)
|
||
|
|
{
|
||
|
|
g_ai_canvSearchTmp.Destroy();
|
||
|
|
g_ai_canvSearchTmpReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy primitives high-res fill canvas
|
||
|
|
if(g_ai_prim.m_hrFillReady)
|
||
|
|
{
|
||
|
|
g_ai_prim.m_hrFill.Destroy();
|
||
|
|
g_ai_prim.m_hrFillReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy primitives high-res border canvas
|
||
|
|
if(g_ai_prim.m_hrBorderReady)
|
||
|
|
{
|
||
|
|
g_ai_prim.m_hrBorder.Destroy();
|
||
|
|
g_ai_prim.m_hrBorderReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy main editor scratch canvas
|
||
|
|
if(g_ai_editor.tmpCanvasReady)
|
||
|
|
{
|
||
|
|
g_ai_editor.tmpCanvas.Destroy();
|
||
|
|
g_ai_editor.tmpCanvasReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Destroy search editor scratch canvas
|
||
|
|
if(g_ai_searchEditor.tmpCanvasReady)
|
||
|
|
{
|
||
|
|
g_ai_searchEditor.tmpCanvas.Destroy();
|
||
|
|
g_ai_searchEditor.tmpCanvasReady = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Free image pixel buffers
|
||
|
|
ArrayResize(g_ai_pixHeader, 0);
|
||
|
|
ArrayResize(g_ai_pixSidebarBig, 0);
|
||
|
|
ArrayResize(g_ai_pixSidebarSmall, 0);
|
||
|
|
ArrayResize(g_ai_pixNewChat, 0);
|
||
|
|
ArrayResize(g_ai_pixClear, 0);
|
||
|
|
ArrayResize(g_ai_pixHistory, 0);
|
||
|
|
g_ai_imagesLoaded = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Process new bar tick for auto signal generation |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void Ai_OnTick()
|
||
|
|
{
|
||
|
|
//--- Read latest bar timestamp
|
||
|
|
datetime currentBarTime[1];
|
||
|
|
if(CopyTime(Symbol(), Period(), 0, 1, currentBarTime) != 1) return;
|
||
|
|
|
||
|
|
//--- Trigger auto signal on new bar when enabled
|
||
|
|
if(currentBarTime[0] != g_ai_lastBarTime && EnableAutoSignal)
|
||
|
|
{
|
||
|
|
g_ai_lastBarTime = currentBarTime[0];
|
||
|
|
Print("New bar detected at ", TimeToString(g_ai_lastBarTime, TIME_DATE | TIME_MINUTES),
|
||
|
|
". Running auto signal check...");
|
||
|
|
AiGetTradeSignal(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif // AI_CANVAS_SHELL_MQH
|