293 lines
11 KiB
MQL5
293 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
| |||
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
| |||
//+------------------------------------------------------------------+
|