//+------------------------------------------------------------------+ //| Tiltnote_DailyLossGuard.mq5 | //| Tiltnote Daily Loss Guard (MT5 indicator) | //+------------------------------------------------------------------+ // MIT License // // Copyright (c) 2026 Tiltnote Okumura // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Display-only tool: it never places, closes or modifies orders. It shows a small panel on the chart. // No DLL, no WebRequest. All numbers are estimates from the account and its history. // // What it shows: // - Today's P/L (closed + floating) against the day base (balance at the day start, or the higher of balance/equity) // - How much of your own daily loss limit is used, and the equity level where the limit is reached // - Optional: room left to a max drawdown level (fixed from the starting balance, or trailing from the highest balance) // - The color changes at the warning level and at the limit. An optional pop-up notice appears once a day per level. // It does not stop you from trading. The rules of your broker or prop firm are what count; check them yourself. // // MetaTrader 4 and 5 (MT4/MT5) are trademarks of MetaQuotes Ltd. This indicator is not affiliated with MetaQuotes. //+------------------------------------------------------------------+ #property copyright "Copyright (c) 2026 Tiltnote Okumura (MIT License)" #property version "1.00" #property description "Display-only tool. Shows today's P/L against your own daily loss limit, and optionally the room left to a max drawdown level." #property description "It never places, closes or modifies orders. Check your broker's or prop firm's own rules." #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 // Choices -------------------------------------------------- enum ENUM_DLG_LANG { DLG_LANG_AUTO = 0, // Auto (terminal language) DLG_LANG_JA = 1, // Japanese DLG_LANG_EN = 2 // English }; enum ENUM_DLG_LIMIT { DLG_LIMIT_PERCENT = 0, // Percent of the day base DLG_LIMIT_MONEY = 1 // Fixed amount, account currency }; enum ENUM_DLG_DAYBASE { DLG_BASE_BALANCE = 0, // Balance at the day start DLG_BASE_HIGHER = 1 // Higher of balance and equity at the day start }; enum ENUM_DLG_BOUNDARY { DLG_BOUND_NY1700 = 0, // New York 17:00 (DST auto) DLG_BOUND_SERVER = 1, // Broker server 00:00 DLG_BOUND_JST = 2 // Custom hour (Japan time) }; enum ENUM_DLG_MAXDD { DLG_DD_OFF = 0, // Off DLG_DD_STATIC = 1, // Fixed: below the starting balance DLG_DD_TRAILING = 2 // Trailing: below the highest balance }; //--- Inputs (field names are in English, with the Japanese name in parentheses) --------------------- input group "Daily loss limit (1日の損失の上限)" input ENUM_DLG_LIMIT InpLimitMode = DLG_LIMIT_PERCENT; // Daily limit as input double InpLimitPct = 5.0; // Daily limit %, of the day base input double InpLimitMoney = 0.0; // Daily limit, account currency input ENUM_DLG_DAYBASE InpDayBase = DLG_BASE_BALANCE; // Day base input ENUM_DLG_BOUNDARY InpBoundary = DLG_BOUND_NY1700; // Day starts at input int InpJstHour = 7; // Custom hour, Japan time 0-23 input double InpWarnAt = 80.0; // Warn when this % of the limit is used input group "Max drawdown (全体の損失の上限)" input ENUM_DLG_MAXDD InpDdMode = DLG_DD_OFF; // Max drawdown input double InpDdPct = 10.0; // Max drawdown %, of the starting balance input double InpStartBal = 0.0; // Starting balance, 0 = first deposit in history input group "Notice (知らせ)" input bool InpAlert = true; // Pop-up notice, once a day per level input group "Display (表示)" input ENUM_DLG_LANG InpLanguage = DLG_LANG_AUTO; // Language input ENUM_BASE_CORNER InpCorner = CORNER_LEFT_UPPER; // Panel corner input int InpFontSize = 9; // Font size input int InpOffsetX = 10; // Distance from the side, px input int InpOffsetY = 25; // Distance from top/bottom, px //--- State ---------------------------------------------------------- #define DLG_PFX "TNDLG_" bool g_ja = false; string g_font = "Segoe UI"; string g_ccy = ""; int g_ccyDig = 2; long g_login = 0; long g_srvOff = 0; // server time - UTC, seconds // today (from history) datetime g_dayStart = 0; int g_daySeen = -1; // deal count at the last scan datetime g_dayStartSeen = 0; bool g_dayOk = false; double g_closed = 0; // closed P/L today (trades, swap, commission, fees) double g_cash = 0; // deposits / withdrawals / corrections today (not P/L) // whole history (only for max drawdown) int g_allSeen = -1; bool g_allOk = false; double g_firstDep = 0; // balance after the first deposit double g_peakBal = 0; // highest balance after the first deposit int g_depCount = 0; // number of deposits in the history // colors color g_cPanel, g_cBorder, g_cText, g_cMuted, g_cHead, g_cOk, g_cWarn, g_cAlert; // panel rows string g_rows[]; color g_rowC[]; int g_rowFs[]; int g_nRows = 0; string T(const string ja, const string en) { return g_ja ? ja : en; } // ==== TIME-CORE BEGIN ==== // Same code as Tiltnote Toolkit (day boundary, NY DST auto). Only the day period is used here. int TkDow(const datetime t) { return (int)((((long)t / 86400) + 4) % 7); } datetime TkDate(const int y, const int m, const int d) { MqlDateTime s; ZeroMemory(s); s.year = y; s.mon = m; s.day = d; return StructToTime(s); } int TkYear(const datetime t) { MqlDateTime s; TimeToStruct(t, s); return s.year; } datetime TkNthSunday(const int y, const int m, const int n) { datetime f = TkDate(y, m, 1); int add = (7 - TkDow(f)) % 7; return (datetime)((long)f + (long)(add + 7 * (n - 1)) * 86400); } bool TkUsDstUtc(const datetime utc) { int y = TkYear(utc); long a = (long)TkNthSunday(y, 3, 2) + 7 * 3600; long b = (long)TkNthSunday(y, 11, 1) + 6 * 3600; return ((long)utc >= a && (long)utc < b); } bool TkUsDstNyLocal(const datetime ny) { int y = TkYear(ny); long a = (long)TkNthSunday(y, 3, 2) + 2 * 3600; long b = (long)TkNthSunday(y, 11, 1) + 1 * 3600; return ((long)ny >= a && (long)ny < b); } // day start (server time). boundary: 0 = NY 17:00, 1 = server 0:00, 2 = jstHour o'clock in Japan time datetime TkDayStartServer(const datetime serverNow, const long serverOffset, const int boundary, const int jstHour) { long v = (long)serverNow; if(boundary == 1) return (datetime)(v - (v % 86400)); long utc = v - serverOffset; long utcStart; if(boundary == 0) { long ny = utc + (TkUsDstUtc((datetime)utc) ? -4 : -5) * 3600; // NY local long shifted = ny + 7 * 3600; // 17:00 -> 0:00 long ps = shifted - (shifted % 86400); long nyStart = ps - 7 * 3600; utcStart = nyStart + (TkUsDstNyLocal((datetime)nyStart) ? 4 : 5) * 3600; } else { int h = MathMax(0, MathMin(23, jstHour)); long jst = utc + 9 * 3600; // Japan time (no daylight saving) long shifted = jst - (long)h * 3600; // H:00 -> 0:00 long ps = shifted - (shifted % 86400); utcStart = ps + (long)h * 3600 - 9 * 3600; } return (datetime)(utcStart + serverOffset); } long TkRoundOffset(const long diff) { return (long)MathRound((double)diff / 900.0) * 900; } // ==== TIME-CORE END ==== //+------------------------------------------------------------------+ //| History | //+------------------------------------------------------------------+ // Trading deals and costs count as P/L. Deposits, withdrawals, corrections and bonuses do not. // Credit is not part of the balance, so it is skipped. bool DlgIsCash(const long type) { return (type == DEAL_TYPE_BALANCE || type == DEAL_TYPE_CORRECTION || type == DEAL_TYPE_BONUS); } double DlgDealValue(const ulong tk) { return HistoryDealGetDouble(tk, DEAL_PROFIT) + HistoryDealGetDouble(tk, DEAL_SWAP) + HistoryDealGetDouble(tk, DEAL_COMMISSION) + HistoryDealGetDouble(tk, DEAL_FEE); } // Today: everything that changed the balance since the day start void DlgCalcToday(const datetime serverNow) { if(!HistorySelect(g_dayStart, (datetime)((long)serverNow + 86400))) { g_dayOk = false; return; } int total = HistoryDealsTotal(); if(g_dayOk && total == g_daySeen && g_dayStart == g_dayStartSeen) return; // closed deals do not change g_daySeen = total; g_dayStartSeen = g_dayStart; g_closed = 0; g_cash = 0; for(int i = 0; i < total; i++) { ulong tk = HistoryDealGetTicket(i); if(tk == 0) continue; if((datetime)HistoryDealGetInteger(tk, DEAL_TIME) < g_dayStart) continue; long type = HistoryDealGetInteger(tk, DEAL_TYPE); if(type == DEAL_TYPE_CREDIT) continue; double v = DlgDealValue(tk); if(DlgIsCash(type)) g_cash += v; else g_closed += v; } g_dayOk = true; } // Whole history: first deposit and highest balance (balance only; floating P/L is not in the history) void DlgCalcAll(const datetime serverNow) { if(!HistorySelect(0, (datetime)((long)serverNow + 86400))) { g_allOk = false; return; } int total = HistoryDealsTotal(); if(g_allOk && total == g_allSeen) return; g_allSeen = total; double sum = 0; for(int i = 0; i < total; i++) { ulong tk = HistoryDealGetTicket(i); if(tk == 0) continue; if(HistoryDealGetInteger(tk, DEAL_TYPE) == DEAL_TYPE_CREDIT) continue; sum += DlgDealValue(tk); } double bal = AccountInfoDouble(ACCOUNT_BALANCE) - sum; // balance before the first deal (normally 0) bool got = false; g_firstDep = 0; g_peakBal = 0; g_depCount = 0; for(int i = 0; i < total; i++) { ulong tk = HistoryDealGetTicket(i); if(tk == 0) continue; long type = HistoryDealGetInteger(tk, DEAL_TYPE); if(type == DEAL_TYPE_CREDIT) continue; double v = DlgDealValue(tk); bal += v; if(type == DEAL_TYPE_BALANCE && v > 0) g_depCount++; if(!got && type == DEAL_TYPE_BALANCE && v > 0) { got = true; g_firstDep = bal; g_peakBal = bal; } if(got && bal > g_peakBal) g_peakBal = bal; } g_allOk = got; } //+------------------------------------------------------------------+ //| Day-start equity (only known if the indicator was running then) | //+------------------------------------------------------------------+ string DlgKey(const string kind) { return "TNDLG." + kind + "." + IntegerToString(g_login) + "." + IntegerToString((long)g_dayStart); } // Within 5 minutes after the day start, remember the equity once (terminal global variable) bool DlgDayStartEquity(const datetime serverNow, double &eq) { string k = DlgKey("E"); if(GlobalVariableCheck(k)) { eq = GlobalVariableGet(k); return true; } if((long)serverNow - (long)g_dayStart <= 300) { eq = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_CREDIT); // credit (bonus) is not P/L GlobalVariableSet(k, eq); return true; } return false; } //+------------------------------------------------------------------+ //| Text | //+------------------------------------------------------------------+ string DlgMoney(const double v, const bool sign) { string s = DoubleToString(MathAbs(v), g_ccyDig); int dot = StringFind(s, "."); string ip = (dot >= 0) ? StringSubstr(s, 0, dot) : s; string fp = (dot >= 0) ? StringSubstr(s, dot) : ""; string out = ""; int n = StringLen(ip); for(int i = 0; i < n; i++) { out += StringSubstr(ip, i, 1); int rest = n - 1 - i; if(rest > 0 && rest % 3 == 0) out += ","; } string pre = (v < 0 && MathAbs(v) >= MathPow(10, -g_ccyDig)) ? "-" : ((sign && v > 0) ? "+" : ""); return pre + out + fp + " " + g_ccy; } string DlgPct(const double v) { return DoubleToString(v, (MathAbs(v) < 10) ? 1 : 0) + "%"; } //+------------------------------------------------------------------+ //| Colors | //+------------------------------------------------------------------+ int DlgLum(const color c) { int r = (int)(c & 0xFF), g = (int)((c >> 8) & 0xFF), b = (int)((c >> 16) & 0xFF); return (r * 299 + g * 587 + b * 114) / 1000; } color DlgShift(const color c, const int d) { int r = (int)(c & 0xFF), g = (int)((c >> 8) & 0xFF), b = (int)((c >> 16) & 0xFF); r = MathMax(0, MathMin(255, r + d)); g = MathMax(0, MathMin(255, g + d)); b = MathMax(0, MathMin(255, b + d)); return (color)(r | (g << 8) | (b << 16)); } void DlgSetColors() { color bg = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND); bool dark = (DlgLum(bg) < 128); if(dark) { g_cText = C'210,210,210'; g_cMuted = C'140,140,140'; g_cHead = C'240,240,240'; g_cOk = C'110,200,130'; g_cWarn = C'230,190,60'; g_cAlert = C'255,110,110'; } else { g_cText = C'50,50,50'; g_cMuted = C'115,115,115'; g_cHead = C'15,15,15'; g_cOk = C'20,130,60'; g_cWarn = C'170,105,0'; g_cAlert = C'200,30,30'; } g_cPanel = DlgShift(bg, dark ? 18 : -12); g_cBorder = DlgShift(g_cPanel, dark ? 28 : -34); } //+------------------------------------------------------------------+ //| Panel | //+------------------------------------------------------------------+ void DlgRow(const string s, const color c, const int fs) { ArrayResize(g_rows, g_nRows + 1); ArrayResize(g_rowC, g_nRows + 1); ArrayResize(g_rowFs, g_nRows + 1); g_rows[g_nRows] = s; g_rowC[g_nRows] = c; g_rowFs[g_nRows] = fs; g_nRows++; } // A row with font size 0 is drawn as a bar (used part of the limit). Only one bar per panel. double g_barRatio = 0; void DlgBarRow(const double ratio, const color c) { g_barRatio = MathMax(0.0, MathMin(1.0, ratio)); DlgRow("", c, 0); } void DlgRect(const string nm, const int x, const int y, const int w, const int h, const color fill, const color border) { if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, nm, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, nm, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); ObjectSetInteger(0, nm, OBJPROP_BACK, false); } ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, nm, OBJPROP_XSIZE, MathMax(1, w)); ObjectSetInteger(0, nm, OBJPROP_YSIZE, MathMax(1, h)); ObjectSetInteger(0, nm, OBJPROP_BGCOLOR, fill); ObjectSetInteger(0, nm, OBJPROP_COLOR, border); } void DlgDraw() { int pad = 8, gap = 3, barH = 8; int w = 0, h = pad; int widths[], heights[]; ArrayResize(widths, g_nRows); ArrayResize(heights, g_nRows); for(int i = 0; i < g_nRows; i++) { if(g_rowFs[i] == 0) { widths[i] = 0; heights[i] = barH + 2; h += heights[i] + gap; continue; } uint tw = 0, th = 0; TextSetFont(g_font, -g_rowFs[i] * 10); TextGetSize(g_rows[i], tw, th); int wi = (int)tw, hi = (int)th; // With display scaling the measured width can be smaller than the drawn one (2026-10-27: the last row was cut in the test MT5). // If the same text was drawn before, use the larger of the measured and the actual drawn size (same as Tiltnote Toolkit). string mn = DLG_PFX + "R" + IntegerToString(i); if(ObjectFind(0, mn) >= 0 && ObjectGetString(0, mn, OBJPROP_TEXT) == g_rows[i] && ObjectGetInteger(0, mn, OBJPROP_FONTSIZE) == g_rowFs[i]) { int aw = (int)ObjectGetInteger(0, mn, OBJPROP_XSIZE), ah = (int)ObjectGetInteger(0, mn, OBJPROP_YSIZE); if(aw > wi) wi = aw; if(ah > hi) hi = ah; } wi = (int)MathCeil(wi * 1.12) + 2; // a little slack until the actual size is known widths[i] = wi; heights[i] = hi; if(wi > w) w = wi; h += hi + gap; } w += pad * 2; h += pad - gap; int cw = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int ch = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); bool right = (InpCorner == CORNER_RIGHT_UPPER || InpCorner == CORNER_RIGHT_LOWER); bool lower = (InpCorner == CORNER_LEFT_LOWER || InpCorner == CORNER_RIGHT_LOWER); int x0 = right ? MathMax(0, cw - InpOffsetX - w) : InpOffsetX; int y0 = lower ? MathMax(0, ch - InpOffsetY - h) : InpOffsetY; string bgName = DLG_PFX + "BG"; if(ObjectFind(0, bgName) < 0) { ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, bgName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, bgName, OBJPROP_HIDDEN, true); ObjectSetInteger(0, bgName, OBJPROP_BACK, false); } ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, x0); ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, y0); ObjectSetInteger(0, bgName, OBJPROP_XSIZE, w); ObjectSetInteger(0, bgName, OBJPROP_YSIZE, h); ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, g_cPanel); ObjectSetInteger(0, bgName, OBJPROP_COLOR, g_cBorder); int y = y0 + pad; bool hasBar = false; for(int i = 0; i < g_nRows; i++) { string nm = DLG_PFX + "R" + IntegerToString(i); if(g_rowFs[i] == 0) { // bar: track + filled part if(ObjectFind(0, nm) >= 0) ObjectDelete(0, nm); int bw = w - pad * 2; int fw = (int)MathRound(bw * g_barRatio); DlgRect(DLG_PFX + "BarT", x0 + pad, y + 1, bw, barH, g_cBorder, g_cBorder); if(fw > 0) DlgRect(DLG_PFX + "BarF", x0 + pad, y + 1, fw, barH, g_rowC[i], g_rowC[i]); else ObjectDelete(0, DLG_PFX + "BarF"); hasBar = true; y += heights[i] + gap; continue; } if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, nm, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, nm, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); ObjectSetInteger(0, nm, OBJPROP_BACK, false); ObjectSetString(0, nm, OBJPROP_FONT, g_font); } ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x0 + pad); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, g_rowFs[i]); ObjectSetInteger(0, nm, OBJPROP_COLOR, g_rowC[i]); ObjectSetString(0, nm, OBJPROP_TEXT, g_rows[i]); y += heights[i] + gap; } if(!hasBar) { ObjectDelete(0, DLG_PFX + "BarT"); ObjectDelete(0, DLG_PFX + "BarF"); } // remove rows that are no longer used for(int i = g_nRows; i < 40; i++) { string nm = DLG_PFX + "R" + IntegerToString(i); if(ObjectFind(0, nm) < 0) break; ObjectDelete(0, nm); } ChartRedraw(0); } //+------------------------------------------------------------------+ //| Notice (once a day per level) | //+------------------------------------------------------------------+ void DlgNotice(const int level, const string msg) { if(!InpAlert) return; string k = DlgKey("A"); int done = GlobalVariableCheck(k) ? (int)GlobalVariableGet(k) : 0; if(level <= done) return; GlobalVariableSet(k, level); Alert(msg); } //+------------------------------------------------------------------+ //| Main update | //+------------------------------------------------------------------+ string DlgForeName() { return "TNDLG_FG_" + IntegerToString(ChartID()); } // original "Chart on foreground" setting // Account data can be empty right after the terminal starts (before the login), so it is read again on every update void DlgAccount() { long login = AccountInfoInteger(ACCOUNT_LOGIN); if(login != g_login) { g_dayOk = false; g_allOk = false; } // another account: read the history again g_login = login; g_ccy = AccountInfoString(ACCOUNT_CURRENCY); g_ccyDig = (int)AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS); if(g_ccyDig < 0 || g_ccyDig > 8) g_ccyDig = 2; } void DlgUpdate() { DlgAccount(); if(g_login == 0 || g_ccy == "") return; // not logged in yet datetime serverNow = TimeTradeServer(); if(serverNow <= 0) return; g_srvOff = TkRoundOffset((long)serverNow - (long)TimeGMT()); g_dayStart = TkDayStartServer(serverNow, g_srvOff, (int)InpBoundary, InpJstHour); DlgCalcToday(serverNow); if(InpDdMode != DLG_DD_OFF) DlgCalcAll(serverNow); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_CREDIT); // credit (bonus) is not P/L double floating = equity - balance; g_nRows = 0; int fsN = InpFontSize, fsS = MathMax(6, InpFontSize - 1), fsL = InpFontSize + 1; string title = "Tiltnote Daily Loss Guard"; if(!g_dayOk) { DlgRow(title, g_cHead, fsL); DlgRow(T("取引の履歴を読めません(しばらく待つと出ます)", "Cannot read the trade history yet (wait a moment)"), g_cWarn, fsN); DlgDraw(); return; } // Day base: balance at the day start (deposits today are added, so they are not counted as profit) double startBal = balance - g_closed - g_cash; double base = startBal + g_cash; bool eqUnknown = false; if(InpDayBase == DLG_BASE_HIGHER) { double eq0 = 0; if(DlgDayStartEquity(serverNow, eq0)) base = MathMax(base, eq0 + g_cash); else eqUnknown = true; } double limit = (InpLimitMode == DLG_LIMIT_PERCENT) ? base * InpLimitPct / 100.0 : InpLimitMoney; double todayPL = g_closed + floating; double used = MathMax(0.0, base - equity); double limEq = base - limit; double room = equity - limEq; double ratio = (limit > 0) ? used / limit : 0.0; double warnR = MathMax(0.0, MathMin(100.0, InpWarnAt)) / 100.0; int level = 0; // 0 = ok, 1 = warn, 2 = reached if(limit > 0 && room <= 0) level = 2; else if(limit > 0 && ratio >= warnR) level = 1; color cState = (level == 2) ? g_cAlert : (level == 1) ? g_cWarn : g_cOk; string state = (limit <= 0) ? T("上限が未設定", "no limit set") : (level == 2) ? T("上限に届きました", "limit reached") : (level == 1) ? T("上限に近づいています", "near the limit") : T("上限まで余裕あり", "within the limit"); DlgRow(title + " - " + state, cState, fsL); DlgRow(T("今日の損益 ", "Today P/L ") + DlgMoney(todayPL, true) + T("(確定 ", " (closed ") + DlgMoney(g_closed, true) + T(" / 含み ", " / floating ") + DlgMoney(floating, true) + ")", g_cText, fsN); if(limit > 0) { string baseName = (InpDayBase == DLG_BASE_HIGHER && !eqUnknown) ? T("日の始めの残高か有効証拠金の高い方", "higher of day-start balance/equity") : T("日の始めの残高", "day-start balance"); string limTxt = (InpLimitMode == DLG_LIMIT_PERCENT) ? T("1日の上限 ", "Daily limit ") + DlgMoney(limit, false) + T("(" + baseName + " " + DlgMoney(base, false) + " の " + DlgPct(InpLimitPct) + ")", " (" + DlgPct(InpLimitPct) + " of " + baseName + " " + DlgMoney(base, false) + ")") : T("1日の上限 ", "Daily limit ") + DlgMoney(limit, false) + T("(決めた額)", " (fixed amount)"); DlgRow(limTxt, g_cText, fsN); DlgBarRow(ratio, cState); DlgRow(T("使った ", "Used ") + DlgPct(ratio * 100.0) + T(" / 残り ", " / left ") + DlgMoney(MathMax(0.0, room), false), cState, fsN); DlgRow(T("有効証拠金がここまで下がると上限: ", "Limit is reached at equity ") + DlgMoney(limEq, false), g_cMuted, fsS); } else DlgRow(T("入力の「Daily limit」に上限を入れてください", "Set your limit in the inputs (Daily limit)"), g_cWarn, fsN); if(eqUnknown) DlgRow(T("日の始めの有効証拠金が分からないため、残高で計算しています", "Day-start equity unknown, so the balance is used"), g_cWarn, fsS); if(MathAbs(g_cash) >= MathPow(10, -g_ccyDig)) DlgRow(T("今日の入出金 ", "Deposits/withdrawals today ") + DlgMoney(g_cash, true) + T(" は損益に入れていません", " are not counted as P/L"), g_cMuted, fsS); // Max drawdown int ddLevel = 0; if(InpDdMode != DLG_DD_OFF) { double start = (InpStartBal > 0) ? InpStartBal : g_firstDep; if(start <= 0) DlgRow(T("全体の上限: 最初の入金が履歴に無いため、入力に開始の残高を入れてください", "Max drawdown: no first deposit in the history; set the starting balance in the inputs"), g_cWarn, fsS); else { double amount = start * InpDdPct / 100.0; double top = (InpDdMode == DLG_DD_TRAILING) ? MathMax(start, MathMax(g_peakBal, balance)) : start; double ddFloor = top - amount; double ddRoom = equity - ddFloor; double ddRatio = (amount > 0) ? MathMax(0.0, top - equity) / amount : 0.0; if(ddRoom <= 0) ddLevel = 2; else if(ddRatio >= warnR) ddLevel = 1; color cDd = (ddLevel == 2) ? g_cAlert : (ddLevel == 1) ? g_cWarn : g_cText; string kind = (InpDdMode == DLG_DD_TRAILING) ? T("一番高い残高 ", "highest balance ") : T("開始の残高 ", "starting balance "); if(InpStartBal <= 0 && g_depCount > 1) DlgRow(T("入金が " + IntegerToString(g_depCount) + " 回あります。開始の残高は入力で決めてください", IntegerToString(g_depCount) + " deposits found. Set the starting balance in the inputs"), g_cWarn, fsS); DlgRow(T("全体の上限まで 残り ", "Max drawdown: left ") + DlgMoney(MathMax(0.0, ddRoom), false) + T("(" + kind + DlgMoney(top, false) + " - " + DlgPct(InpDdPct) + ")", " (" + kind + DlgMoney(top, false) + " - " + DlgPct(InpDdPct) + ")"), cDd, fsN); } } DlgRow(T("表示だけです。注文・決済はしません", "Display only. It never opens or closes trades"), g_cMuted, fsS); DlgRow(T("業者やプロップの決まりもご自分で確かめてください", "Check your broker's or prop firm's own rules"), g_cMuted, fsS); DlgDraw(); // Notices int worst = MathMax(level, ddLevel); if(worst == 2) DlgNotice(2, T("Daily Loss Guard: 決めた損失の上限に届きました", "Daily Loss Guard: your loss limit has been reached")); else if(worst == 1) DlgNotice(1, T("Daily Loss Guard: 決めた損失の上限に近づいています", "Daily Loss Guard: you are near your loss limit")); } //+------------------------------------------------------------------+ //| Events | //+------------------------------------------------------------------+ int OnInit() { g_ja = (InpLanguage == DLG_LANG_JA) || (InpLanguage == DLG_LANG_AUTO && TerminalInfoString(TERMINAL_LANGUAGE) == "Japanese"); g_font = g_ja ? "Meiryo UI" : "Segoe UI"; DlgAccount(); IndicatorSetString(INDICATOR_SHORTNAME, "Tiltnote Daily Loss Guard"); DlgSetColors(); // With "Chart on foreground" on, the bars are drawn over the panel (2026-10-27 seen in the test MT4). // Turn it off only while running and restore it when removed (same as Tiltnote Toolkit) if(!GlobalVariableCheck(DlgForeName())) GlobalVariableSet(DlgForeName(), (double)ChartGetInteger(0, CHART_FOREGROUND)); if(ChartGetInteger(0, CHART_FOREGROUND)) ChartSetInteger(0, CHART_FOREGROUND, false); EventSetTimer(1); DlgUpdate(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { EventKillTimer(); ObjectsDeleteAll(0, DLG_PFX); // restore the foreground setting when removed (kept on timeframe or input changes) if(reason != REASON_CHARTCHANGE && reason != REASON_PARAMETERS && reason != REASON_RECOMPILE && GlobalVariableCheck(DlgForeName())) { if(GlobalVariableGet(DlgForeName()) > 0.5) ChartSetInteger(0, CHART_FOREGROUND, true); GlobalVariableDel(DlgForeName()); } ChartRedraw(0); } void OnTimer() { DlgUpdate(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) { DlgSetColors(); DlgUpdate(); } } int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { return(rates_total); } //+------------------------------------------------------------------+