forked from animatedread/Warrior_EA
The //| box blocks were excluded from0b06f8eand5efdb48and were what remained: 160 of them ran to 10+ lines, the longest to 88. Compressed to their leading topic sentences - 5 lines for a function header, 8 for a file header - keeping the box format and the standard MQL5 name/author lines verbatim. Verified at the BYTE level this time, across every in-scope file: the list of non-comment lines is byte-identical to HEAD and braces balance. The first check compared a locale-decoded 'git show' against a UTF-8 read and flagged 25 files that had not changed at all - every BOM and every non-ASCII line mismatched. 47,696 -> 40,665 lines in scope; comment share 38% -> 26%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
265 lines
12 KiB
MQL5
265 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| StatusLabel.mqh |
|
|
//| AnimateDread |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "AnimateDread"
|
|
#property link "https://www.mql5.com"
|
|
//+------------------------------------------------------------------+
|
|
//| Replaces Comment() for on-chart status text. Comment() draws |
|
|
//| directly onto the chart surface with no background, so status |
|
|
//| text becomes unreadable over candles. |
|
|
//+------------------------------------------------------------------+
|
|
#define STATUS_LABEL_PREFIX "WarriorStatusLine"
|
|
#define STATUS_LABEL_FONTSIZE 9
|
|
#define STATUS_LABEL_FONT "Consolas"
|
|
#define STATUS_LABEL_PAD_X 3
|
|
#define STATUS_LABEL_PAD_Y 1
|
|
#define STATUS_LABEL_LINE_GAP 2 // extra pixels between successive lines' backgrounds
|
|
#define STATUS_LABEL_RIGHT_MARGIN 20
|
|
#define STATUS_LABEL_MIN_WRAP_WIDTH 150
|
|
#define STATUS_LABEL_MAX_CHARS 55 // safe margin under OBJPROP_TEXT's observed ~63-char truncation
|
|
#define STATUS_LABEL_MAX_LINES 64
|
|
int g_statusLabelLineCount = 0;
|
|
//--- Y (chart pixels, CORNER_LEFT_UPPER) just below the last line SetStatusLabel() drew - i.e. where
|
|
//--- something else can be placed to sit UNDER the label instead of overlapping it. Defaults to the
|
|
//--- label's own default top (see SetStatusLabel()'s y=20 default) before the label has ever drawn.
|
|
int g_statusLabelBottomY = 20;
|
|
int StatusLabelBottomY(void) { return g_statusLabelBottomY; }
|
|
void EnsureStatusLine(int idx)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)idx + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)idx + "Text";
|
|
if(ObjectFind(0, bg) < 0)
|
|
{
|
|
ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, (color)ColorToARGB(clrBlack, 150));
|
|
ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
|
ObjectSetInteger(0, bg, OBJPROP_COLOR, clrNONE);
|
|
ObjectSetInteger(0, bg, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
|
|
}
|
|
if(ObjectFind(0, txt) < 0)
|
|
{
|
|
ObjectCreate(0, txt, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, txt, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, txt, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
|
|
ObjectSetInteger(0, txt, OBJPROP_FONTSIZE, STATUS_LABEL_FONTSIZE);
|
|
ObjectSetString(0, txt, OBJPROP_FONT, STATUS_LABEL_FONT);
|
|
ObjectSetInteger(0, txt, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, txt, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_HIDDEN, true);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Appends one already-short-enough chunk to outLines/outCount, |
|
|
//| force-splitting it further if it still exceeds |
|
|
//| STATUS_LABEL_MAX_CHARS (a single word longer than the cap, with |
|
|
//| no spaces to break on). |
|
|
//+------------------------------------------------------------------+
|
|
void AppendStatusChunk(string chunk, string &outLines[], int &outCount)
|
|
{
|
|
int pos = 0;
|
|
int len = StringLen(chunk);
|
|
while(len - pos > STATUS_LABEL_MAX_CHARS)
|
|
{
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = StringSubstr(chunk, pos, STATUS_LABEL_MAX_CHARS);
|
|
pos += STATUS_LABEL_MAX_CHARS;
|
|
}
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = StringSubstr(chunk, pos);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Greedily breaks one logical line into as many visual lines as |
|
|
//| needed to keep each one's measured TextGetSize() width within |
|
|
//| maxWidth AND its character count within STATUS_LABEL_MAX_CHARS - |
|
|
//| the latter is what actually matters (see this file's header |
|
|
//| comment on ObjectSetString's ~63-char truncation), the pixel width |
|
|
//| check just wraps sooner on a narrow chart. Requires TextSetFont() |
|
|
//| to already have been called by the caller so TextGetSize() |
|
|
//| measures against the same font actually rendered. |
|
|
//+------------------------------------------------------------------+
|
|
void WrapLineInto(string line, int maxWidth, string &outLines[], int &outCount)
|
|
{
|
|
uint dummyW = 0, dummyH = 0;
|
|
TextGetSize(line, dummyW, dummyH);
|
|
if(StringLen(line) == 0 || ((int)dummyW <= maxWidth && StringLen(line) <= STATUS_LABEL_MAX_CHARS))
|
|
{
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = line;
|
|
return;
|
|
}
|
|
string words[];
|
|
int wordCount = StringSplit(line, ' ', words);
|
|
string current = "";
|
|
for(int w = 0; w < wordCount; w++)
|
|
{
|
|
string candidate = (StringLen(current) == 0) ? words[w] : current + " " + words[w];
|
|
uint cw = 0, ch = 0;
|
|
TextGetSize(candidate, cw, ch);
|
|
bool tooWide = ((int)cw > maxWidth || StringLen(candidate) > STATUS_LABEL_MAX_CHARS);
|
|
if(tooWide && StringLen(current) > 0)
|
|
{
|
|
AppendStatusChunk(current, outLines, outCount);
|
|
current = words[w];
|
|
}
|
|
else
|
|
current = candidate;
|
|
}
|
|
if(StringLen(current) > 0)
|
|
AppendStatusChunk(current, outLines, outCount);
|
|
}
|
|
void SetStatusLabel(string text, int x = 10, int y = 20)
|
|
{
|
|
//--- TextSetFont's size argument is tenths of a point when negative (matches OBJPROP_FONTSIZE's
|
|
//--- point-based units above) - must be set before any TextGetSize() call since it's global state
|
|
TextSetFont(STATUS_LABEL_FONT, -STATUS_LABEL_FONTSIZE * 10, 0);
|
|
long chartWidthPx = 0;
|
|
ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidthPx);
|
|
int maxWidth = (int)chartWidthPx - x - STATUS_LABEL_RIGHT_MARGIN;
|
|
if(maxWidth < STATUS_LABEL_MIN_WRAP_WIDTH)
|
|
maxWidth = STATUS_LABEL_MIN_WRAP_WIDTH;
|
|
string rawLines[];
|
|
int rawCount = StringSplit(text, '\n', rawLines);
|
|
string lines[];
|
|
int lineCount = 0;
|
|
for(int r = 0; r < rawCount && lineCount < STATUS_LABEL_MAX_LINES; r++)
|
|
WrapLineInto(rawLines[r], maxWidth, lines, lineCount);
|
|
if(lineCount > STATUS_LABEL_MAX_LINES)
|
|
lineCount = STATUS_LABEL_MAX_LINES;
|
|
int lineY = y;
|
|
for(int i = 0; i < lineCount; i++)
|
|
{
|
|
EnsureStatusLine(i);
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
uint textW = 0, textH = 0;
|
|
//--- an empty line measures 0x0, which would collapse its background - fall back to a single
|
|
//--- space so blank separator lines still reserve a row of height
|
|
TextGetSize(StringLen(lines[i]) > 0 ? lines[i] : " ", textW, textH);
|
|
ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, x - STATUS_LABEL_PAD_X);
|
|
ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, lineY - STATUS_LABEL_PAD_Y);
|
|
ObjectSetInteger(0, bg, OBJPROP_XSIZE, (int)textW + STATUS_LABEL_PAD_X * 2);
|
|
ObjectSetInteger(0, bg, OBJPROP_YSIZE, (int)textH + STATUS_LABEL_PAD_Y * 2);
|
|
ObjectSetInteger(0, txt, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, txt, OBJPROP_YDISTANCE, lineY);
|
|
ObjectSetString(0, txt, OBJPROP_TEXT, lines[i]);
|
|
lineY += (int)textH + STATUS_LABEL_LINE_GAP;
|
|
}
|
|
g_statusLabelBottomY = lineY;
|
|
//--- fewer lines than last call - remove the now-unused trailing line objects rather than
|
|
//--- leaving stale text (e.g. a previous longer status block) hanging below the new one
|
|
for(int i = lineCount; i < g_statusLabelLineCount; i++)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
if(ObjectFind(0, bg) >= 0)
|
|
ObjectDelete(0, bg);
|
|
if(ObjectFind(0, txt) >= 0)
|
|
ObjectDelete(0, txt);
|
|
}
|
|
g_statusLabelLineCount = lineCount;
|
|
//--- ObjectSet*() alone does not trigger a repaint - without this the new text/background only
|
|
//--- appears once something else forces a redraw (dragging the chart, clicking an object, a new
|
|
//--- bar), which made updates look stalled between those events
|
|
ChartRedraw(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Removes every status line object. Mirrors the old Comment("") |
|
|
//| clear on shutdown - ObjectsDeleteAll(0) elsewhere clears chart |
|
|
//| objects, but callers still need an explicit clear at points where |
|
|
//| only the status text (not the whole chart) should be reset. |
|
|
//+------------------------------------------------------------------+
|
|
void ClearStatusLabel(void)
|
|
{
|
|
for(int i = 0; i < g_statusLabelLineCount; i++)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
if(ObjectFind(0, bg) >= 0)
|
|
ObjectDelete(0, bg);
|
|
if(ObjectFind(0, txt) >= 0)
|
|
ObjectDelete(0, txt);
|
|
}
|
|
g_statusLabelLineCount = 0;
|
|
ChartRedraw(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| ENSEMBLE PANEL (two or more NNs enabled). |
|
|
//+------------------------------------------------------------------+
|
|
#define ENSEMBLE_PANEL_MAX_MEMBERS 6
|
|
string g_ensPanelTag[ENSEMBLE_PANEL_MAX_MEMBERS];
|
|
string g_ensPanelLine[ENSEMBLE_PANEL_MAX_MEMBERS];
|
|
//--- ROW OWNERSHIP, so the panel is ordered by the ENSEMBLE's own member order and not by which
|
|
//--- member happened to publish first.
|
|
bool g_ensPanelSlotUsed[ENSEMBLE_PANEL_MAX_MEMBERS];
|
|
int g_ensPanelUsed = 0;
|
|
uint g_ensPanelLastRender = 0;
|
|
string g_ensPanelLastText = "";
|
|
//--- The ensemble's COMBINED-vote OOS score line, written once per era by the last member to finish
|
|
//--- its pass-3 scan (CExpertSignalAIBase::EnsembleScoreCombinedVote) and rendered under the member
|
|
//--- lines below.
|
|
string g_ensembleVoteLine = "";
|
|
//--- index = the caller's ensemble registration index. Idempotent: safe to call on every publish, which
|
|
//--- is what keeps the tag current for a member whose ID was not final when it first claimed its row.
|
|
int ClaimEnsemblePanelSlot(string tag, int index)
|
|
{
|
|
int slot = index;
|
|
//--- Unregistered or out of range (should not happen for a real member): fall back to the first free
|
|
//--- row rather than dropping the model off the panel entirely.
|
|
if(slot < 0 || slot >= ENSEMBLE_PANEL_MAX_MEMBERS)
|
|
{
|
|
slot = -1;
|
|
for(int i = 0; i < ENSEMBLE_PANEL_MAX_MEMBERS; i++)
|
|
if(!g_ensPanelSlotUsed[i])
|
|
{
|
|
slot = i;
|
|
break;
|
|
}
|
|
if(slot < 0)
|
|
slot = ENSEMBLE_PANEL_MAX_MEMBERS - 1;
|
|
}
|
|
if(!g_ensPanelSlotUsed[slot])
|
|
{
|
|
g_ensPanelSlotUsed[slot] = true;
|
|
g_ensPanelLine[slot] = "";
|
|
}
|
|
if(tag != "")
|
|
g_ensPanelTag[slot] = tag;
|
|
//--- High-water mark of CLAIMED rows, not a running count - rows are addressed by index now, so a
|
|
//--- member that has not published yet leaves a gap the render skips rather than shifting everyone up.
|
|
if(slot + 1 > g_ensPanelUsed)
|
|
g_ensPanelUsed = slot + 1;
|
|
return slot;
|
|
}
|
|
void PublishEnsembleStatus(int slot, string headline, bool force = false)
|
|
{
|
|
if(slot < 0 || slot >= g_ensPanelUsed || !g_ensPanelSlotUsed[slot])
|
|
return;
|
|
g_ensPanelLine[slot] = headline;
|
|
int shown = 0;
|
|
for(int i = 0; i < g_ensPanelUsed; i++)
|
|
if(g_ensPanelSlotUsed[i])
|
|
shown++;
|
|
string s = "AI ensemble - " + (string)shown + " independent models, one line each";
|
|
for(int i = 0; i < g_ensPanelUsed; i++)
|
|
if(g_ensPanelSlotUsed[i])
|
|
s += "\n" + g_ensPanelTag[i] + ": " + (g_ensPanelLine[i] == "" ? "starting..." : g_ensPanelLine[i]);
|
|
if(g_ensembleVoteLine != "")
|
|
s += "\n" + g_ensembleVoteLine;
|
|
//--- unchanged text needs no relayout; changed text still respects a minimum redraw interval
|
|
if(s == g_ensPanelLastText)
|
|
return;
|
|
uint now = GetTickCount();
|
|
if(!force && g_ensPanelLastRender != 0 && now - g_ensPanelLastRender < 300)
|
|
return;
|
|
g_ensPanelLastText = s;
|
|
g_ensPanelLastRender = now;
|
|
SetStatusLabel(s);
|
|
}
|