//+------------------------------------------------------------------+ //| Tiltnote_Toolkit.mq5 | //| Tiltnote ツール集 / Tiltnote Toolkit (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. // // (自由に使用・改変・再配布できます。上の著作権の表記とこの許諾の文を残してください。無保証です。) // // 注文を出す関数は使っていません(注文・決済・注文の変更をしません。Order で始まる関数は計算と履歴の読み取りだけ)。チャートの線から計算した目安の値です。 // This indicator uses no order functions (it never places, closes or modifies orders). // All numbers are estimates calculated from the lines on the chart. Trading decisions are your own. // DLL・WebRequest も使いません / No DLL, no WebRequest. // MetaTrader 4/5(MT4/MT5)は MetaQuotes 社の商標で、Tiltnote は同社と関係ありません。 // MetaTrader 4 and 5 (MT4/MT5) are trademarks of MetaQuotes Ltd. Tiltnote is not affiliated with MetaQuotes. // // 使い方 / How to use (2.00, 2026-10-22): // ・灰の線 = 入る値(はじめは今の値段について動く。ドラッグすると指値の想定で止まる) // Gray line = entry (follows the price until you drag it; then it stays, like a limit order) // ・赤の線 = 損切り。ドラッグするとロットを計算し直す。入る値より下=買い、上=売り と自動で判断 // Red line = stop. Drag it and the lot size is recalculated. Below entry = long, above = short // ・青の線 = 利確(任意。パネルのボタンで出す・消す)/ Blue line = target (optional, panel button) // ・パネル: つまみ「::」でドラッグして動かす。たたむ・字の大きさ・リスク% はパネルで変えられ、次も同じ // Panel: drag the "::" grip to move it. Fold, text size and risk % are changed on the panel and remembered // ・「詳しく」: 証拠金と維持率・期間の記録・スプレッドと足の残り・計算の元・注意書き // "More": margin, period record, spread and bar timer, calculation basis, notes //+------------------------------------------------------------------+ #property copyright "Copyright (c) 2026 Tiltnote 奥村 (Okumura) (MIT License)" #property link "https://tiltnote.com/" #property version "2.00" #property description "Tiltnote Toolkit: lot size from chart lines (entry / stop / optional target), margin, period record, spread & bar timer." #property description "Places no orders (Order* functions are used only to calculate or read history). Estimates calculated from the lines on the chart." #property description "注文を出す命令は入っていません(計算と履歴の読み取りだけ)。チャートの線から計算した目安の値です。注文はご自身の判断で。" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 //--- 選び肢 / Choices -------------------------------------------------- enum ENUM_TK_LANG { TK_LANG_AUTO = 0, // Auto (terminal language) / 端末の言語 TK_LANG_JA = 1, // 日本語 TK_LANG_EN = 2 // English }; // パネルの背景(2026-10-22 社長「背景を透過できる設定」)/ panel background enum ENUM_TK_BG { TK_BG_SOLID = 0, // Opaque / 透けない TK_BG_LIGHT = 1, // Slightly see-through / 少し透ける TK_BG_HALF = 2, // More see-through / よく透ける TK_BG_NONE = 3 // Text only, no background / 背景なし(字だけ) }; enum ENUM_TK_RISK_MODE { TK_RISK_PERCENT = 0, // Percent of base (%) / 元にする額の割合 TK_RISK_MONEY = 1 // Fixed amount / 金額 }; enum ENUM_TK_BASE { TK_BASE_BALANCE = 0, // Balance / 残高 TK_BASE_EQUITY = 1 // Equity / 有効証拠金 }; enum ENUM_TK_GOLDPIP { TK_GOLDPIP_01 = 0, // 1 pip = 0.1 TK_GOLDPIP_001 = 1, // 1 pip = 0.01 TK_GOLDPIP_1 = 2 // 1 pip = 1.0 }; enum ENUM_TK_CONTRACT { TK_CONTRACT_AUTO = 0, // Auto (broker setting) / 自動(業者の設定) TK_CONTRACT_1K = 1000, // 1,000 TK_CONTRACT_10K = 10000, // 10,000 TK_CONTRACT_100K = 100000, // 100,000 TK_CONTRACT_CUSTOM = 1 // Custom (below) / 任意の数(下の欄) }; enum ENUM_TK_INITSTOP { TK_INIT_AUTO = 0, // Auto: ATR(14), rounded (FX at least 10 pips) / 自動: ATR(14) を切りのよい数に(FX は最低 10pips) TK_INIT_PIPS = 1, // Fixed pips / 決まった pips TK_INIT_ATR = 2 // ATR(14) x multiplier, not rounded / ATR(14) × 倍率(丸めない) }; enum ENUM_TK_PERIOD { TK_PERIOD_DAY = 0, // 1 day / 1日 TK_PERIOD_WEEK = 1, // 1 week (from Monday) / 1週間(月曜から) TK_PERIOD_MONTH = 2 // 1 month (from the 1st) / 1か月(1日から) }; enum ENUM_TK_BOUNDARY { TK_BOUND_NY1700 = 0, // New York 17:00 (default, DST auto) / NY 17:00(既定) TK_BOUND_SERVER = 1, // Broker server 00:00 / 業者のサーバーの 0:00 TK_BOUND_JST = 2 // Custom hour (Japan time) / 好きな時刻(日本時間) }; enum ENUM_TK_SCOPE { TK_SCOPE_SYMBOL = 0, // This symbol only / この銘柄だけ TK_SCOPE_ALL = 1 // All symbols / すべて }; //--- 入力 / Inputs(欄の名前は英語。括弧に日本語) --------------------- input group "Display (表示)" input ENUM_TK_LANG InpLanguage = TK_LANG_AUTO; // Language (言語) input ENUM_BASE_CORNER InpCorner = CORNER_RIGHT_UPPER; // First 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 (縦の余白) input color InpTextColor = clrNONE; // Text color (none = auto) (字の色) input color InpPanelColor = clrNONE; // Panel color (none = auto) (パネルの色) input ENUM_TK_BG InpBackground = TK_BG_SOLID; // Panel background (パネルの背景・透け具合) input group "Lot size (ロット計算)" input ENUM_TK_RISK_MODE InpRiskMode = TK_RISK_PERCENT; // Risk per trade as (1回に失ってよい額の決め方) input double InpRiskPercent = 1.0; // Risk percent %, first value (割合 %。はじめの値。パネルで変えられます) input double InpRiskMoney = 0.0; // Risk amount, account currency (金額) input ENUM_TK_BASE InpRiskBase = TK_BASE_BALANCE; // Base for percent (元にする額) input ENUM_TK_INITSTOP InpInitStop = TK_INIT_AUTO; // First stop line distance (はじめの損切りの線の離し方) input double InpStopPips = 20.0; // Fixed pips / no ATR data (決まった pips・ATR が無い時の幅) input double InpAtrMult = 1.0; // ATR multiplier (ATR の倍率) input ENUM_TK_GOLDPIP InpGoldPip = TK_GOLDPIP_01; // Gold: size of 1 pip (ゴールドの 1pip。業者で違う) input double InpPipSize = 0.0; // Pip size override, 0 = auto (1pip の大きさの上書き。0=自動) input ENUM_TK_CONTRACT InpContract = TK_CONTRACT_AUTO; // Units per 1 lot, FX only (1ロットの通貨量。FX だけ。指数・CFD は上書き不可) input double InpContractCustom = 100000; // Custom units per lot (任意の数) input group "More (詳しく の中身)" input bool InpShowMargin = true; // Margin and margin level (証拠金と維持率) input bool InpShowPeriod = true; // Period record (期間の記録) input bool InpShowSpread = true; // Spread and bar countdown (スプレッドと足の残り) input group "Period record (期間の記録)" input ENUM_TK_PERIOD InpPeriod = TK_PERIOD_DAY; // Period (期間) input ENUM_TK_BOUNDARY InpBoundary = TK_BOUND_NY1700; // Period boundary (区切り) input int InpJstHour = 7; // Custom hour, Japan time 0-23 (好きな時刻・日本時間の時) input ENUM_TK_SCOPE InpScope = TK_SCOPE_ALL; // Symbols (対象の銘柄) input double InpLimitLoss = 0.0; // Loss limit for the period, 0 = off (損失の上限・金額) input int InpLimitTrades = 0; // Trade count limit, 0 = off (回数の上限) //--- 内部の値 / Internal state ------------------------------------------ // オブジェクト名の頭(固定)/ Object name prefix (fixed) string TK_PFX = "TKTA_"; string TK_PANEL = "TKTA_P"; // パネルの部品はすべてこの頭 / all panel objects start with this string TK_RR_ENTRY = "TKTA_RR_ENTRY"; string TK_RR_SL = "TKTA_RR_SL"; string TK_RR_TP = "TKTA_RR_TP"; string TK_RR_SYM = "TKTA_RR_SYM"; int g_state = 0; // 0=待ち waiting, 1=動いている running, 2=2つ目なので何もしない duplicate double g_instId = 0; // この起動の番号(自分の印を他人と見誤らないため)/ this instance's number uint g_waitFrom = 0; // 待ち始めた時刻 / when waiting started // 同じチャートで動いている印(端末の共通の変数。チャートの番号ごと) // "Running on this chart" markers: terminal global variables per chart ID // TKTA_RUN_<チャート> = 最後に動いた時刻(毎秒更新)/ last heartbeat (every second) // TKTA_ID_<チャート> = 動いているコピーの起動の番号 / instance number of the running copy string TkRunName() { return "TKTA_RUN_" + IntegerToString(ChartID()); } string TkIdName() { return "TKTA_ID_" + IntegerToString(ChartID()); } string TkForeName() { return "TKTA_FG_" + IntegerToString(ChartID()); } // 元の「前面に表示」/ original foreground setting string TkScrollName() { return "TKTA_MS_" + IntegerToString(ChartID()); } // 元の「マウスでスクロール」/ original mouse scroll string TkMouseName() { return "TKTA_MM_" + IntegerToString(ChartID()); } // 元のマウスの動きの知らせ / original mouse-move events string TkMovedName() { return "TKTA_MV_" + IntegerToString(ChartID()); } // どの線を動かしたか / which lines were dragged // 自分以外のコピーが、この3秒の間に印を更新しているか / is another copy refreshing the marker? bool TkOtherRunning() { if(!GlobalVariableCheck(TkRunName()) || !GlobalVariableCheck(TkIdName())) return false; if(GlobalVariableGet(TkIdName()) == g_instId) return false; // 自分の印 / my own marker return (MathAbs((double)GetTickCount() - GlobalVariableGet(TkRunName())) < 3000.0); } string TkInstName() { return TkRunName(); } void TkMarkRunning() { GlobalVariableSet(TkIdName(), g_instId); GlobalVariableSet(TkRunName(), (double)GetTickCount()); } // 前の版の名前の線・文字・パネルを消す / remove objects named by older builds void TkRemoveOldNames() { ObjectsDeleteAll(0, "TKT_"); for(int n = 0; n < 20; n++) ObjectsDeleteAll(0, "TKT" + IntegerToString(n) + "_"); ObjectsDeleteAll(0, TK_PFX + "L"); // 1.00 のパネルの行 / panel lines of 1.00 ObjectDelete(0, TK_PFX + "BG"); // 1.00 のパネルの背景 / panel background of 1.00 } bool g_ja = true; // 日本語で出すか / Japanese text? double g_pip = 0.0; // 1pip の価格の幅 / pip size in price long g_srvOffset = 0; // サーバー時刻 − UTC(秒) / server time minus UTC (seconds) datetime g_lastHist = 0; // 最後に履歴を数えた時刻 / last history scan (PC time) // 期間の記録の結果 / Period record results double g_pNet = 0.0; int g_pTrades = 0; int g_pStreak = 0; bool g_pOk = false; datetime g_pStart = 0; // 期間の始まり(サーバー時刻) / period start (server time) bool g_pHistShort = false; // 読めた履歴が期間の途中から(MT4 の口座履歴タブ。MT5 は常に false)2026-10-22 // loaded history starts after the period start (MT4 history tab; always false on MT5) // 色 / Colors (OnInit で決める / set in OnInit) color g_cText, g_cMuted, g_cHead, g_cWarn, g_cAlert, g_cPanel, g_cBorder, g_cBtn; color g_cEntry = C'150,150,150', g_cSL = C'220,70,70', g_cTP = C'60,130,220'; color g_cEntryText = C'90,90,90'; // 入る値の線の横の文字(背景の明暗で決める)/ entry line text, set by background color g_lastBg = clrNONE; // 前に見たチャートの背景色 / last seen chart background string g_font = "Segoe UI"; string T(const string ja, const string en) { return g_ja ? ja : en; } //--- 覚えておく設定(端末の共通の変数。次に付けた時も同じ)/ Remembered settings ---- double g_riskPct = 1.0; // リスク % / risk percent bool g_fold = false; // たたんでいる / folded bool g_det = false; // 「詳しく」を開いている / details open int g_fontLv = 2; // 字の大きさの段 0〜4(2=ふつう)/ text size level 0-4 (2 = normal) double g_rrSel = 0; // 選んでいる比(0=選んでいない・手で動かした)/ selected ratio (0 = none / moved by hand) bool g_showTP = false; // 利確の線を出す / show target line int g_dir = 1; // 選んだ向き 1=買い -1=売り(ボタンで選ぶ。2026-10-22)/ chosen side 1 long, -1 short bool g_guideOff = false; // 案内を消した / guide dismissed int g_px = -1, g_py = -1; // パネルの左上(-1 = はじめの隅から)/ panel top-left (-1 = from the corner) int g_mv = 0; // 動かした線: 1=入る値 2=損切り 4=利確 / dragged lines void TkLoadSettings() { double pct = InpRiskPercent; // 設定の値を変えた時は、設定の値を使う / if the input changed, the input wins if(GlobalVariableCheck("TKTA_S_RISK") && GlobalVariableCheck("TKTA_S_RISKIN") && GlobalVariableGet("TKTA_S_RISKIN") == InpRiskPercent) pct = GlobalVariableGet("TKTA_S_RISK"); g_riskPct = MathMax(0.01, MathMin(10.0, pct)); g_fold = GlobalVariableCheck("TKTA_S_FOLD") && GlobalVariableGet("TKTA_S_FOLD") > 0.5; g_det = GlobalVariableCheck("TKTA_S_DET") && GlobalVariableGet("TKTA_S_DET") > 0.5; // 字の大きさの段。前の版の「大きく」(TKTA_S_BIG)は 3 段目にする / text size level (old "big" → level 3) if(GlobalVariableCheck("TKTA_S_FLV")) g_fontLv = (int)GlobalVariableGet("TKTA_S_FLV"); else g_fontLv = (GlobalVariableCheck("TKTA_S_BIG") && GlobalVariableGet("TKTA_S_BIG") > 0.5) ? 3 : 2; g_fontLv = MathMax(0, MathMin(4, g_fontLv)); g_rrSel = GlobalVariableCheck("TKTA_S_RR") ? GlobalVariableGet("TKTA_S_RR") : 0; g_showTP = GlobalVariableCheck("TKTA_S_TP") && GlobalVariableGet("TKTA_S_TP") > 0.5; g_dir = (GlobalVariableCheck("TKTA_S_DIR") && GlobalVariableGet("TKTA_S_DIR") < 0) ? -1 : 1; g_guideOff = GlobalVariableCheck("TKTA_S_GUIDE") && GlobalVariableGet("TKTA_S_GUIDE") > 0.5; if(GlobalVariableCheck("TKTA_S_X") && GlobalVariableCheck("TKTA_S_Y")) { g_px = (int)GlobalVariableGet("TKTA_S_X"); g_py = (int)GlobalVariableGet("TKTA_S_Y"); } g_mv = GlobalVariableCheck(TkMovedName()) ? (int)GlobalVariableGet(TkMovedName()) : 0; } void TkSaveSettings() { GlobalVariableSet("TKTA_S_RISK", g_riskPct); GlobalVariableSet("TKTA_S_RISKIN", InpRiskPercent); GlobalVariableSet("TKTA_S_FOLD", g_fold ? 1 : 0); GlobalVariableSet("TKTA_S_DET", g_det ? 1 : 0); GlobalVariableSet("TKTA_S_FLV", g_fontLv); GlobalVariableSet("TKTA_S_RR", g_rrSel); GlobalVariableSet("TKTA_S_TP", g_showTP ? 1 : 0); GlobalVariableSet("TKTA_S_DIR", g_dir); GlobalVariableSet("TKTA_S_GUIDE", g_guideOff ? 1 : 0); if(g_px >= 0 && g_py >= 0) { GlobalVariableSet("TKTA_S_X", g_px); GlobalVariableSet("TKTA_S_Y", g_py); } GlobalVariablesFlush(); } void TkSaveMoved() { GlobalVariableSet(TkMovedName(), g_mv); } //+------------------------------------------------------------------+ //| 数の見せ方 / Number formatting | //+------------------------------------------------------------------+ // 3桁ごとのカンマ / thousands separators string TkCommas(const string s) { 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++) { if(i > 0 && (n - i) % 3 == 0) out += ","; out += StringSubstr(ip, i, 1); } return out + fp; } // 口座の通貨の小数の桁(円などは0) / decimals of the account currency int TkMoneyDigits() { string c = AccountInfoString(ACCOUNT_CURRENCY); if(c == "JPY" || c == "KRW" || c == "HUF" || c == "IDR" || c == "VND" || c == "CLP") return 0; return 2; } // 金額。日本語で円の口座なら「10,000円」、それ以外は「100.00 USD」。マイナスは「−」 // Money: "10,000円" for a JPY account in Japanese, otherwise "100.00 USD". Minus is "−" string TkMoney(const double v, const bool plusSign) { int d = TkMoneyDigits(); string a = DoubleToString(MathAbs(v), d); string sign = ""; if(StringToDouble(a) != 0.0) sign = (v < 0) ? "−" : (plusSign ? "+" : ""); string cur = AccountInfoString(ACCOUNT_CURRENCY); if(g_ja && cur == "JPY") return sign + TkCommas(a) + "円"; return sign + TkCommas(a) + " " + cur; } // 割合の見せ方(0.25 刻みなら 1.25%、それ以外は 1 桁)/ percent text string TkPct(const double p) { double q = MathRound(p * 100.0) / 100.0; int d = (MathAbs(q * 10.0 - MathRound(q * 10.0)) > 1e-6) ? 2 : 1; return DoubleToString(q, d); } // ==== LOT-CORE BEGIN ==== // ロットの刻みから小数の桁を出す(0.01 → 2) / decimals from the lot step int TkLotDigits(const double step) { if(step <= 0) return 2; int d = 0; double s = step; while(d < 8 && MathAbs(s - MathRound(s)) > 1e-9) { s *= 10.0; d++; } return d; } //+------------------------------------------------------------------+ //| pip の大きさと、1ロット 1pip の金額 / Pip size and pip value | //| MT4 版もほぼ同じ中身 / Almost the same code in the MT4 version. | //+------------------------------------------------------------------+ string TkUpper(string s) { StringToUpper(s); return s; } bool TkIsLetter(const ushort c) { return (c >= 'A' && c <= 'Z'); } // 金か(2026-10-22): XAU を含めば金。GOLD は銘柄の形(GOLD・GOLDm・GOLD.a・GOLDUSD・#GOLD 等)の時だけ。 // BARRICKGOLD・GOLDMAN などの株・ETF は金にしない // Gold (2026-10-22): any name with XAU. GOLD only in symbol form (GOLD, GOLDm, GOLD.a, GOLDUSD, #GOLD); // stock/ETF names such as BARRICKGOLD or GOLDMAN are not gold bool TkIsGold(const string sym) { string u = TkUpper(sym); if(StringFind(u, "XAU") >= 0) return true; int p = StringFind(u, "GOLD"); if(p < 0) return false; if(p > 0 && TkIsLetter(StringGetCharacter(u, p - 1))) return false; // 前に英字 → 別の名前 / letter before → another name string rest = StringSubstr(u, p + 4); int n = 0; while(n < StringLen(rest) && TkIsLetter(StringGetCharacter(rest, n))) n++; if(n <= 2) return true; // 無し・GOLDm など / none or a short tag string w = StringSubstr(rest, 0, n); if(w == "MINI" || w == "MICRO") return true; if(n != 3) return false; // 後ろが通貨3字(GOLDUSD など)/ followed by a 3-letter currency return (w == "USD" || w == "EUR" || w == "JPY" || w == "GBP" || w == "AUD" || w == "CHF" || w == "CAD" || w == "NZD" || w == "SGD" || w == "HKD" || w == "CNH"); } // pip の大きさ / Pip size // ・overridePip > 0 ならそれを使う(設定で上書き)/ overridePip > 0 wins. // ・ゴールド(名前に XAU か、銘柄の形の GOLD。TkIsGold)は決まった定義がない(業者・説明で 0.01・0.1 などと違う)。 // 設定 goldPip の値を使い、パネルに「1pip = 0.1(ゴールドの設定)」と出す。 // Gold has no single pip definition; the goldPip setting is used and shown on the panel. // ・価格の桁(Digits)が 3 か 5 → 最小単位(Point)の10倍(USDJPY 0.001→0.01、EURUSD 0.00001→0.0001)。 // Digits 3 or 5 → pip = 10 x Point. // ・それ以外(2・4・1・0 桁。古い桁の業者・株価指数など)→ Point そのまま。 // Otherwise (2/4-digit brokers, indices, ...) → pip = Point. double TkPipSizeOf(const string sym, const double overridePip, const double goldPip) { if(overridePip > 0) return overridePip; if(TkIsGold(sym)) return goldPip; int dg = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); double pt = SymbolInfoDouble(sym, SYMBOL_POINT); if(dg == 3 || dg == 5) return pt * 10.0; return pt; } // 換算の銘柄を探す / Find the conversion symbol // from(利益の通貨)1 単位が、to(口座の通貨)でいくらかを rate に入れる。mid = その銘柄の今の値段(売り買いの中間)。 // rate = how many 'to' for 1 'from'. Direct pair (from+to, e.g. USDJPY for USD→JPY) or inverse (to+from). // 業者の接尾辞・接頭辞つき(USDJPY.m / USDJPYmicro など)も、名前に含まれていて通貨の設定が合えば使う。 // Broker suffixes/prefixes are accepted when the name contains the pair and the currencies match. // 戻り値 / returns: 1=同じ通貨 same currency, 0=見つかった found, -1=無い none, -2=あるが気配値表示に出ていない・値段が無い hidden or no price // 1つの銘柄の今の値段から、from 1 単位が相手の通貨でいくらかを出す / rate of 1 'from' from one symbol bool TkRateFromName(const string nm, const string from, double &rate, double &mid) { double b = SymbolInfoDouble(nm, SYMBOL_BID), a = SymbolInfoDouble(nm, SYMBOL_ASK); if(!(bool)SymbolInfoInteger(nm, SYMBOL_SELECT) || b <= 0 || a <= 0) return false; mid = (a + b) / 2.0; rate = (SymbolInfoString(nm, SYMBOL_CURRENCY_BASE) == from) ? mid : 1.0 / mid; return true; } // from と to の直の銘柄を、すべての銘柄から探す(業者の接尾辞・接頭辞つきも) // Scan all symbols for a direct pair (broker suffixes/prefixes accepted) // 戻り値: 0=使える found, -2=あるが気配値表示に無い・値段が無い hidden, -1=無い none int TkScanDirect(const string from, const string to, string &pair) { string d1 = from + to, d2 = to + from, hidden = ""; int n = SymbolsTotal(false); for(int i = 0; i < n; i++) { string nm = SymbolName(i, false); string up = TkUpper(nm); string cb = SymbolInfoString(nm, SYMBOL_CURRENCY_BASE); string cp = SymbolInfoString(nm, SYMBOL_CURRENCY_PROFIT); bool direct = (StringFind(up, d1) >= 0 && cb == from && cp == to); bool inverse = (StringFind(up, d2) >= 0 && cb == to && cp == from); if(!direct && !inverse) continue; double r = 0, m = 0; if(!TkRateFromName(nm, from, r, m)) { if(hidden == "") hidden = nm; continue; } pair = nm; return 0; } pair = (hidden != "") ? hidden : d1; return (hidden != "") ? -2 : -1; } // 探した結果を覚えておく(全部を探すのは、通貨が変わった時と、失敗して10秒たった時だけ) // Cache the scan result (full scan only when the currencies change, or 10 s after a failure) string g_cfKey = "", g_cfN1 = "", g_cfN2 = "", g_cfFail = ""; int g_cfCode = -1; uint g_cfScanAt = 0; void TkConvScan(const string from, const string to) { g_cfKey = from + "/" + to; g_cfScanAt = GetTickCount(); g_cfN1 = ""; g_cfN2 = ""; g_cfFail = ""; string p1 = ""; int c1 = TkScanDirect(from, to, p1); if(c1 == 0) { g_cfN1 = p1; g_cfCode = 0; return; } // 直の銘柄が無ければドルを経由(例: HKD → USDHKD → USDJPY → JPY)/ otherwise via USD if(from != "USD" && to != "USD") { string pa = "", pb = ""; int ca = TkScanDirect(from, "USD", pa); int cb = TkScanDirect("USD", to, pb); if(ca == 0 && cb == 0) { g_cfN1 = pa; g_cfN2 = pb; g_cfCode = 0; return; } if(c1 == -1 && (ca == -2 || cb == -2)) { g_cfFail = (ca == -2) ? pa : pb; g_cfCode = -2; return; } } g_cfFail = p1; g_cfCode = c1; } // from(利益の通貨)1 単位が、to(口座の通貨)でいくらかを rate に入れる。mid = 使った銘柄の今の値段。 // rate = how many 'to' for 1 'from'. pair = the symbol used ("A+B" when going via USD). // 戻り値 / returns: 1=同じ通貨 same currency, 0=見つかった found, -1=無い none, -2=気配値表示に無い・値段が無い hidden or no price int TkFindConv(const string from, const string to, string &pair, double &rate, double &mid) { pair = ""; rate = 0; mid = 0; if(from == "" || to == "") return -1; if(from == to) { rate = 1.0; return 1; } if(from + "/" + to != g_cfKey) TkConvScan(from, to); for(int attempt = 0; attempt < 2; attempt++) { if(g_cfCode == 0) { double r1 = 0, m1 = 0, r2 = 0, m2 = 0; if(g_cfN2 == "") { if(TkRateFromName(g_cfN1, from, r1, m1)) { pair = g_cfN1; rate = r1; mid = m1; return 0; } } else if(TkRateFromName(g_cfN1, from, r1, m1) && TkRateFromName(g_cfN2, "USD", r2, m2)) { pair = g_cfN1 + "+" + g_cfN2; rate = r1 * r2; mid = m1; return 0; } } if(attempt == 0 && GetTickCount() - g_cfScanAt >= 10000) TkConvScan(from, to); // 10秒に1回だけ探し直す else break; } if(g_cfCode == 0) { pair = g_cfN1; return -2; } // 覚えた銘柄の値段が無くなった / the cached symbol lost its price pair = g_cfFail; return g_cfCode; } // 1ロットで 1pip 動いた時の金額を、次の順で求める / Pip value per lot, in this order: // 1) MT5: 端末自身の損益の計算(OrderCalcProfit。計算だけで注文はしない)。実際の損益と同じ計算。 // MT5: the terminal's own profit calculation (OrderCalcProfit, calculation only) = the same math as real P/L. // 2) 1) が使えない時(MT4・別の通貨を試す時): 1ロットの通貨量 × pip × 換算の値段。 // 業者の TICK_VALUE と照らし、5% より食い違えば数字を出さない(code=3)。ただし TICK_VALUE が // 「利益の通貨のまま(換算されていない)」の型は、業者の表示の癖として認める(指数でよくある)。 // Otherwise: contract size x pip x conversion rate, cross-checked with TICK_VALUE (>5% → no number, code 3), // except the common pattern where TICK_VALUE is still in the profit currency (not converted). // 3) 先物など、値段の差 × 通貨量 で損益が決まらない銘柄は TICK_VALUE だけ。 // Futures and similar: TICK_VALUE only. // 1ロットの通貨量の上書き(contractOverride > 0)は FX だけ。上書きした量 × pip × 換算の値段 で計算する。 // Contract-size override is for FX only. // 戻り値 code / returns: 0=OK, 1=換算の銘柄が無い no pair, 2=気配値表示に無い hidden, 3=食い違い mismatch, 4=銘柄の情報が無い no data, 5=上書きできない銘柄 override not possible double g_cvRate = 0, g_cvMid = 0, g_cvContract = 0, g_cvTick = 0, g_cvCross = 0; string g_cvPair = ""; int g_cvState = -1; bool g_cvLinear = true; // 値段の差 × 通貨量 で損益が決まる / profit = price move x contract size bool g_cvFx = true; // FX(損切りは pips)/ FX (stop in pips) bool g_cvIndex = false; // CFD 指数 / CFD index int g_cvSource = 0; // 1=端末の計算 terminal, 2=通貨量 × 換算 contract x rate, 3=TICK_VALUE だけ tick value only // 端末自身の損益の計算で 1ロット 1pip の金額を出す(MT5 だけ。計算だけの関数) // Pip value per lot from the terminal's own profit calculation (MT5 only; calculation only) bool TkTerminalVpp(const string sym, const double pip, double &vpp) { vpp = 0; double p = SymbolInfoDouble(sym, SYMBOL_BID); if(p <= 0 || pip <= 0) return false; double d = pip * 10.0; // 10 pips 動いた時で計算して 10 で割る / compute for 10 pips, divide by 10 // 端末の答えは 1セント単位に丸まるので、100ロット分で計算して誤差を小さくする(だめなら 1ロット) // The terminal rounds to cents, so compute for 100 lots to shrink the rounding error (fall back to 1 lot) double vols[] = {100.0, 1.0}; for(int k = 0; k < 2; k++) { double up = 0, dn = 0; if(!OrderCalcProfit(ORDER_TYPE_BUY, sym, vols[k], p, p + d, up) || up <= 0) continue; if(p - d > 0 && OrderCalcProfit(ORDER_TYPE_BUY, sym, vols[k], p, p - d, dn) && -dn > up) up = -dn; // 損の側が大きければそちら / use the larger (loss side) vpp = up / 10.0 / vols[k]; return true; } return false; } // acc = 口座の通貨(ふだんは ACCOUNT_CURRENCY。確かめの道具では JPY / USD / EUR を渡す) // acc = account currency (normally ACCOUNT_CURRENCY; the check script passes JPY / USD / EUR) int TkCalcVpp(const string sym, const double pip, const double contractOverride, const string acc, double &vpp) { vpp = 0; g_cvTick = 0; g_cvCross = 0; g_cvSource = 0; string prof = SymbolInfoString(sym, SYMBOL_CURRENCY_PROFIT); double brokerContract = SymbolInfoDouble(sym, SYMBOL_TRADE_CONTRACT_SIZE); g_cvContract = (contractOverride > 0) ? contractOverride : brokerContract; double ts = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE); double tv = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE); g_cvTick = (ts > 0 && tv > 0 && pip > 0) ? tv * pip / ts : 0; // TICK_VALUE は今の口座の通貨で出る。別の通貨を試す時は使わない / TICK_VALUE is in the real account currency bool realAcc = (acc == AccountInfoString(ACCOUNT_CURRENCY)); if(!realAcc) g_cvTick = 0; long mode = SymbolInfoInteger(sym, SYMBOL_TRADE_CALC_MODE); g_cvFx = (mode == SYMBOL_CALC_MODE_FOREX || mode == SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE); g_cvIndex = (mode == SYMBOL_CALC_MODE_CFDINDEX); g_cvLinear = (g_cvFx || mode == SYMBOL_CALC_MODE_CFD || g_cvIndex || mode == SYMBOL_CALC_MODE_CFDLEVERAGE); g_cvState = TkFindConv(prof, acc, g_cvPair, g_cvRate, g_cvMid); if(pip <= 0) return 4; if(contractOverride > 0 && !g_cvFx) return 5; // 1) 端末の計算 / terminal calculation if(contractOverride <= 0 && realAcc && TkTerminalVpp(sym, pip, vpp)) { g_cvSource = 1; return 0; } // 3) 先物など / futures etc. if(!g_cvLinear) { if(g_cvTick <= 0) return 4; vpp = g_cvTick; g_cvSource = 3; return 0; } // 2) 通貨量 × pip × 換算 / contract x pip x rate if(g_cvState == -1) return 1; if(g_cvState == -2) return 2; g_cvCross = brokerContract * pip * g_cvRate; if(g_cvTick > 0 && g_cvCross > 0 && MathAbs(g_cvTick - g_cvCross) > 0.05 * g_cvCross) { // 業者の表示の癖として認める型 / accepted broker patterns: // a) TICK_VALUE が利益の通貨のまま(換算されていない)/ left in the profit currency // b) CFD 指数で、TICK_VALUE が通貨量 1 単位ごとの値(利益の通貨)/ CFD index: per one contract unit // MT5 の CFD 指数の損益の式 =(値段の差)× 通貨量 × TICK_VALUE ÷ TICK_SIZE と同じ考え方 double unconv = brokerContract * pip; bool a = (g_cvRate != 1.0 && unconv > 0 && MathAbs(g_cvTick - unconv) <= 0.05 * unconv); bool b = (g_cvIndex && brokerContract > 0 && MathAbs(g_cvTick * brokerContract / g_cvRate - unconv) <= 0.05 * unconv); if(!a && !b) return 3; } vpp = (contractOverride > 0) ? contractOverride * pip * g_cvRate : g_cvCross; g_cvSource = 2; return (vpp > 0) ? 0 : 4; } // ロット数(刻みに合わせて切り捨て)/ Lots rounded DOWN to the volume step // 返り値: ロット数。最小ロットに届かない時は 0 / returns 0 when below the minimum lot bool g_lotCapped = false; // 業者の最大ロットで頭打ちにした / capped at the broker maximum double TkLotsFor(const string sym, const double riskMoney, const double stopPips, const double vpp) { g_lotCapped = false; double mn = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN); double mx = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX); double st = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP); if(riskMoney <= 0 || stopPips <= 0 || vpp <= 0 || st <= 0) return 0; double raw = riskMoney / (stopPips * vpp); // 小さな誤差で 1 刻み落ちないよう 1e-7 を足して切り捨て / +1e-7 guards against floating point error double lots = MathFloor(raw / st + 1e-7) * st; if(mx > 0 && lots > mx) { lots = MathFloor(mx / st + 1e-7) * st; g_lotCapped = true; } lots = NormalizeDouble(lots, TkLotDigits(st)); if(lots < mn - 1e-9) return 0; return lots; } // ==== LOT-CORE END ==== //+------------------------------------------------------------------+ //| ロット計算 / Lot size | //+------------------------------------------------------------------+ double g_lotRisk = 0, g_lotLots = 0, g_lotLoss = 0, g_lotVpp = 0, g_lotMin = 0, g_lotBase = 0; bool g_lotOk = false, g_lotBelowMin = false; double TkGoldPip() { return (InpGoldPip == TK_GOLDPIP_001) ? 0.01 : (InpGoldPip == TK_GOLDPIP_1) ? 1.0 : 0.1; } double TkContractOverride() { if(InpContract == TK_CONTRACT_AUTO) return 0; if(InpContract == TK_CONTRACT_CUSTOM) return (InpContractCustom > 0) ? InpContractCustom : 0; return (double)InpContract; } int g_vppCode = 4; // TkCalcVpp の結果 / result code double g_stopPips = 0; // 線から出した損切りの幅(pips)/ stop distance from the lines, in pips int g_side = 0; // 1=買い(損切りが下)-1=売り(損切りが上)0=同じ値 / 1 long, -1 short, 0 same price int g_lastSide = 0; // 前に見た買い/売り(入れ替わりを見つける。2026-10-22)/ previous side, to detect a flip uint g_flipAt = 0; // 入れ替わった時刻(GetTickCount)。0=無し / when the side flipped (0 = never) double g_defDist = 0; // はじめの損切りの線の離れ(値段)/ first stop line distance (price) bool g_defAtrOk = false; // g_defDist を ATR で決められた(決まった pips の設定も true)/ g_defDist came from ATR (or fixed-pips mode) // 値段の幅の見せ方: FX は pips、FX 以外は値段で / Distance text: pips for FX, price for others string TkDistText(const double priceDiff) { // FX 扱いでも pips が大きすぎる時(BTCUSD など。10,000 pips 超)は値段の幅で / huge pip counts (e.g. BTCUSD) → price bool hugePips = (g_pip > 0 && priceDiff / g_pip > 10000.0); if(g_pip <= 0 || (g_cvFx && !hugePips)) return DoubleToString((g_pip > 0) ? priceDiff / g_pip : 0, 1) + T("pips", " pips"); return T("値段で ", "price ") + DoubleToString(priceDiff, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); } // 2本の線の離れを pips に直す。値段の最小単位にそろえてから割るので、20pips は 20.0 ちょうどになる // Distance between two lines in pips. Snapped to the point first, so 20 pips becomes exactly 20.0 double TkPipsBetween(const double a, const double b) { double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(g_pip <= 0 || pt <= 0) return 0; double pts = MathRound(MathAbs(a - b) / pt); return NormalizeDouble(pts * pt / g_pip, 4); } // ロット(計算の中身は 1.00 と同じ。損切りの幅が線から来るところだけ違う) // Lots (same math as 1.00; only the stop distance now comes from the lines) void TkCalcLot() { g_lotOk = false; g_lotBelowMin = false; g_lotLots = 0; g_lotLoss = 0; g_lotBase = (InpRiskBase == TK_BASE_EQUITY) ? AccountInfoDouble(ACCOUNT_EQUITY) : AccountInfoDouble(ACCOUNT_BALANCE); g_lotRisk = (InpRiskMode == TK_RISK_PERCENT) ? g_lotBase * g_riskPct / 100.0 : InpRiskMoney; g_lotMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); if(g_vppCode != 0 || g_lotRisk <= 0 || g_stopPips <= 0) return; double lots = TkLotsFor(_Symbol, g_lotRisk, g_stopPips, g_lotVpp); if(lots <= 0) { g_lotBelowMin = true; g_lotLoss = g_lotMin * g_stopPips * g_lotVpp; return; } g_lotLots = lots; g_lotLoss = lots * g_stopPips * g_lotVpp; g_lotOk = true; } // 換算できない時の説明 / Message when the value cannot be converted string TkVppMessage() { if(g_vppCode == 1 || g_vppCode == 2) return T("換算できません(" + g_cvPair + " を気配値表示に出してください)", "Cannot convert (please show " + g_cvPair + " in Market Watch)"); if(g_vppCode == 3) return T("計算できません(業者の値 TICK_VALUE と通貨量の計算が合わないため)", "Cannot calculate (the broker's TICK_VALUE does not match the contract size)"); if(g_vppCode == 5) return T("この銘柄は1ロットの通貨量を上書きできません(自動にしてください)", "Units per lot cannot be overridden for this symbol (use Auto)"); return T("計算できません(銘柄の情報がまだありません)", "Cannot calculate (no symbol data yet)"); } string TkLotText(const double lots) { return DoubleToString(lots, TkLotDigits(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP))); } // ==== TIME-CORE BEGIN ==== //+------------------------------------------------------------------+ //| 期間の区切りの時刻 / Period boundary time | //| MT4 版と同じ中身 / Same code as the MT4 version. | //+------------------------------------------------------------------+ // 考え方 / Idea: // 1) 今のサーバー時刻 → UTC → 区切りの地域の時刻(NY か 日本)。 // 2) 区切りの時刻が「0:00」になるようにずらす(NY 17:00 は +7時間 / 日本の H 時は −H 時間)。 // NY 17:00 は「次の日の取引日の始まり」なので +7 にすると、日曜 17:00 が「月曜 0:00」になる。 // Shift so the boundary becomes 00:00 (NY 17:00: +7h, so Sunday 17:00 NY = Monday of the trading week). // 3) ずらした時刻で、日の始め・月曜の始め・1日の始めに切り捨てる。 // 4) 逆にずらして戻し、UTC → サーバー時刻にする。 // NY の夏時間は米国の規則(3月の第2日曜 2:00 から 11月の第1日曜 2:00 まで)で自動。 // US DST: from the 2nd Sunday of March 02:00 to the 1st Sunday of November 02:00 (local). // 区切りが NY 17:00 なら、日本時間で冬 7:00・夏 6:00 になる。 // 注意 / Note: サーバー時刻 − UTC の差は「今」の値を使う。業者が月の途中で夏時間を切り替えると、 // 1か月の期間の始まりが1時間ずれることがある。/ The current server offset is used for past boundaries. // 曜日(0=日曜)。1970-01-01 は木曜(4) / Day of week, 0 = Sunday (1970-01-01 was a Thursday) 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; } // y年 m月の第 n 日曜の 0:00 / 0:00 of the n-th Sunday of month m 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); } // UTC の時刻で、米国が夏時間か / Is the US on DST at this UTC instant? // 始まり=3月第2日曜 2:00 EST = 7:00 UTC、終わり=11月第1日曜 2:00 EDT = 6:00 UTC 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); } // NY の地元の時刻で、夏時間か(区切りは 17:00 なので、2:00 前後のあいまいな1時間は関係しない) // Is NY local time on DST? (The ambiguous hour around 02:00 never matters for a 17:00 boundary.) 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); } // 日・週(月曜)・月(1日)の始まりに切り捨て / Floor to day, Monday, or 1st of month // period: 0=日 day, 1=週 week, 2=月 month datetime TkFloorPeriod(const datetime s, const int period) { long v = (long)s; long day = v - (v % 86400); if(period == 1) { int back = (TkDow((datetime)day) + 6) % 7; // 月曜まで戻る日数 / days back to Monday return (datetime)(day - (long)back * 86400); } if(period == 2) { MqlDateTime d; TimeToStruct(s, d); return TkDate(d.year, d.mon, 1); } return (datetime)day; } // 期間の始まり(サーバー時刻)/ Period start in server time // serverNow: 今のサーバー時刻 / serverOffset: サーバー時刻 − UTC(秒) // boundary: 0=NY 17:00, 1=サーバー 0:00, 2=日本時間の jstHour 時 / period: 0=日 1=週 2=月 datetime TkPeriodStartServer(const datetime serverNow, const long serverOffset, const int boundary, const int jstHour, const int period) { if(boundary == 1) return TkFloorPeriod(serverNow, period); // サーバーの 0:00 はそのまま / server 00:00 long utc = (long)serverNow - serverOffset; long utcStart; if(boundary == 0) { long ny = utc + (TkUsDstUtc((datetime)utc) ? -4 : -5) * 3600; // NY の時刻 / NY local long shifted = ny + 7 * 3600; // 17:00 → 0:00 long ps = (long)TkFloorPeriod((datetime)shifted, period); 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; // 日本時間(夏時間なし)/ JST (no DST) long shifted = jst - (long)h * 3600; // H:00 → 0:00 long ps = (long)TkFloorPeriod((datetime)shifted, period); utcStart = ps + (long)h * 3600 - 9 * 3600; } return (datetime)(utcStart + serverOffset); } // サーバー時刻 − UTC を 15分単位に丸める / Round the server offset to 15 minutes long TkRoundOffset(const long diff) { return (long)MathRound((double)diff / 900.0) * 900; } // ==== TIME-CORE END ==== //+------------------------------------------------------------------+ //| 期間の記録(MT5 は取引=deal 単位)/ Period record from deals | //+------------------------------------------------------------------+ // ・売り買いの deal だけを数える(入金・出金・ボーナスなどは除く)。 // Only BUY/SELL deals (balance, credit, bonus... are excluded). // ・数えるのは「期間の中で決済した取引」。ポジション ID でまとめるので、分割決済で deal がいくつに // 分かれても1回と数える(二重に数えない)。まだ決済していない玉は入れない。 // Counted: trades closed inside the period, grouped by position ID (partial closes count once). Open positions are excluded. // ・損益 = 期間の中の決済の deal の 利益 + スワップ + 手数料 + その他の費用 // + 建てた時の deal の手数料・費用(そのポジションの最初の決済が期間の中にある時だけ。2つの期間で二重に入れない)。 // MT4 版(決済した注文の手数料をまるごと入れる)と同じ考え方。建てた時の deal は 90日前まで探す。 // Net = closing deals in the period (profit + swap + commission + fee) + the entry deal's commission/fee // (only when the position's first close is inside the period). Same idea as the MT4 version. Entry deals are searched 90 days back. // ・連敗 = 期間の中で決済したポジションを新しい順に見て、損益がマイナスの続いた数。 // Losing streak = consecutive losing positions, newest first, within the period. // ポジション ID → 行の番号 の辞書(開番地法のハッシュ)/ Position ID → row index (open addressing hash) long g_hKeys[]; int g_hVals[]; int g_hMask = 0; void TkHashInit(const int n) { int size = 64; while(size < n * 2) size *= 2; ArrayResize(g_hKeys, size); ArrayResize(g_hVals, size); ArrayInitialize(g_hVals, -1); g_hMask = size - 1; } // 見つかれば行の番号、無ければ newIndex を入れて -1 を返す / returns the row, or inserts newIndex and returns -1 int TkHashGetOrAdd(const long key, const int newIndex) { int h = (int)((ulong)(key * 2654435761) & (ulong)g_hMask); while(g_hVals[h] >= 0) { if(g_hKeys[h] == key) return g_hVals[h]; h = (h + 1) & g_hMask; } g_hKeys[h] = key; g_hVals[h] = newIndex; return -1; } int g_pDealsSeen = -1; // 前に数えた時の deal の件数 / deal count at the last scan datetime g_pStartSeen = 0; // 前に数えた時の期間の始まり / period start at the last scan void TkCalcPeriod(const datetime serverNow) { datetime start = TkPeriodStartServer(serverNow, g_srvOffset, (int)InpBoundary, InpJstHour, (int)InpPeriod); if(!HistorySelect((datetime)((long)start - 90 * 86400), (datetime)((long)serverNow + 86400))) { g_pOk = false; return; } int total = HistoryDealsTotal(); // 件数も始まりも同じなら読み直さない(決済済みの deal は変わらない)/ nothing new → keep the last result if(g_pOk && total == g_pDealsSeen && start == g_pStartSeen) return; g_pDealsSeen = total; g_pStartSeen = start; g_pOk = false; g_pNet = 0; g_pTrades = 0; g_pStreak = 0; g_pStart = start; long ids[]; double outNet[], entryCost[]; datetime firstOut[], last[]; bool closedIn[]; int m = 0; TkHashInit(total); 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_BUY && type != DEAL_TYPE_SELL) continue; if(InpScope == TK_SCOPE_SYMBOL && HistoryDealGetString(tk, DEAL_SYMBOL) != _Symbol) continue; datetime t = (datetime)HistoryDealGetInteger(tk, DEAL_TIME); long pid = HistoryDealGetInteger(tk, DEAL_POSITION_ID); long entry = HistoryDealGetInteger(tk, DEAL_ENTRY); int k = TkHashGetOrAdd(pid, m); if(k < 0) { k = m; m++; if(ArraySize(ids) < m) { int cap = MathMax(64, m * 2); ArrayResize(ids, cap); ArrayResize(outNet, cap); ArrayResize(entryCost, cap); ArrayResize(firstOut, cap); ArrayResize(last, cap); ArrayResize(closedIn, cap); } ids[k] = pid; outNet[k] = 0; entryCost[k] = 0; firstOut[k] = 0; last[k] = 0; closedIn[k] = false; } double cost = HistoryDealGetDouble(tk, DEAL_COMMISSION) + HistoryDealGetDouble(tk, DEAL_FEE); if(entry == DEAL_ENTRY_IN) { entryCost[k] += cost; continue; } if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT && entry != DEAL_ENTRY_OUT_BY) continue; if(firstOut[k] == 0 || t < firstOut[k]) firstOut[k] = t; if(t < start) continue; outNet[k] += HistoryDealGetDouble(tk, DEAL_PROFIT) + HistoryDealGetDouble(tk, DEAL_SWAP) + cost; closedIn[k] = true; if(t > last[k]) last[k] = t; } // 期間の中で決済したものだけ取り出し、決済の新しい順に並べて連敗を数える // Take the positions closed in the period, sort by last close (newest first), count the losing streak double order[][2]; // [決済の時刻, 行の番号] / [close time, row] int c = 0; ArrayResize(order, m); for(int j = 0; j < m; j++) { if(!closedIn[j]) continue; double net = outNet[j] + ((firstOut[j] >= start) ? entryCost[j] : 0.0); outNet[j] = net; g_pNet += net; g_pTrades++; order[c][0] = (double)last[j]; order[c][1] = (double)j; c++; } ArrayResize(order, c); if(c > 0) ArraySort(order); // 古い順 / oldest first for(int r = c - 1; r >= 0; r--) { if(outNet[(int)order[r][1]] < 0) g_pStreak++; else break; } g_pOk = true; } //+------------------------------------------------------------------+ //| 色 / Colors | //+------------------------------------------------------------------+ int TkLum(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; } // 色を明るく(+)・暗く(−)ずらす / shift a color lighter (+) or darker (−) color TkShift(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 TkSetColors() { color bg = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND); g_lastBg = bg; // 入る値の文字は、明るい背景では濃い灰、暗い背景では明るい灰 / entry text: darker on light charts g_cEntryText = (TkLum(bg) < 128) ? C'175,175,175' : C'85,85,85'; // パネルの色を選んだ時は、パネルの明るさで字の色の組を決める(2026-10-22 社長の確認で、黒のパネルに暗い字が出て読めなかった) // If a panel color is chosen, pick the text palette by the panel's brightness bool custom = (InpPanelColor != clrNONE && !(InpBackground == TK_BG_NONE)); bool dark = custom ? (TkLum(InpPanelColor) < 128) : (TkLum(bg) < 128); if(dark) { g_cText = C'210,210,210'; g_cMuted = C'140,140,140'; g_cHead = C'240,240,240'; 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_cWarn = C'170,105,0'; g_cAlert = C'200,30,30'; } // 背景はチャートの背景色から少しずらした控えめな色。透ける時は絵(ARGB)で描く // Background: the chart background shifted a little; see-through modes draw it as an ARGB bitmap g_cPanel = custom ? InpPanelColor : TkShift(bg, dark ? 18 : -12); g_cBorder = TkShift(g_cPanel, dark ? 28 : -34); g_cBtn = TkShift(g_cPanel, dark ? 22 : -20); // 透ける時・背景なしの時は、後ろのローソク足と重なっても読めるように、字をいちばん濃い黒か白に寄せる(2026-10-22) // See-through or no background: push the text to near black / near white so it stays readable over candles if(InpBackground != TK_BG_SOLID) { g_cText = g_cHead; g_cMuted = dark ? C'190,190,190' : C'70,70,70'; } if(InpTextColor != clrNONE) g_cText = InpTextColor; // 字とパネルの明るさが近いと読めない(両方 黒にした時など)。その時は字を自動の色に戻す // Text too close to the panel in brightness (e.g. both black) → fall back to the automatic text color if(MathAbs(TkLum(g_cText) - TkLum((InpBackground == TK_BG_NONE) ? bg : g_cPanel)) < 80) g_cText = dark ? C'210,210,210' : C'50,50,50'; g_font = g_ja ? "Meiryo UI" : "Segoe UI"; } //+------------------------------------------------------------------+ //| パネルの組み立て / Panel layout | //| 行(row)の中に、字の切れ端(seg)を左から並べる。right=true は右寄せ | //| Each row holds text pieces left to right; right=true pieces go right | //+------------------------------------------------------------------+ #define BTN_NONE -1 #define BTN_GRIP 1 // つまみ(ドラッグで動かす)/ grip (drag to move) #define BTN_FOLD 2 #define BTN_UNFOLD 3 #define BTN_RMINUS 4 #define BTN_RPLUS 5 #define BTN_FMINUS 6 #define BTN_FPLUS 7 #define BTN_TP 8 #define BTN_DETAIL 9 #define BTN_GUIDE 10 #define BTN_RESET 11 #define BTN_RR1 12 // 1:1 #define BTN_RR15 13 // 1:1.5 #define BTN_RR2 14 // 1:2 #define BTN_RR3 15 // 1:3 #define BTN_BUY 16 // 買い / long #define BTN_SELL 17 // 売り / short #define BTN_TPOFF 18 // 利確の線を消す / hide the target line #define BTN_OFF 99 // 押せないボタン(端に来た)/ disabled button color g_sBg[]; // ボタンの地の色 / button background string g_sT[]; int g_sF[]; color g_sC[]; int g_sB[]; bool g_sR[]; bool g_sBox[]; int g_sRow[]; int g_sX[]; int g_sY[]; int g_sW[]; int g_sH[]; int g_rGap[]; int g_nS = 0, g_nR = 0, g_objN = 0; int g_panX = 0, g_panY = 0, g_panW = 0, g_panH = 0; void TkClear() { g_nS = 0; g_nR = 0; } void TkRow(const int gap = 0) { g_nR++; ArrayResize(g_rGap, g_nR); g_rGap[g_nR - 1] = gap; } void TkSeg(const string s, const int fs, const color c, const int btn = BTN_NONE, const bool right = false, const bool box = false) { if(g_nR == 0) TkRow(); g_nS++; ArrayResize(g_sT, g_nS); ArrayResize(g_sF, g_nS); ArrayResize(g_sC, g_nS); ArrayResize(g_sB, g_nS); ArrayResize(g_sR, g_nS); ArrayResize(g_sBox, g_nS); ArrayResize(g_sRow, g_nS); ArrayResize(g_sBg, g_nS); int i = g_nS - 1; g_sT[i] = s; g_sF[i] = MathMax(6, fs); g_sC[i] = c; g_sB[i] = btn; g_sR[i] = right; g_sBox[i] = box; g_sRow[i] = g_nR - 1; g_sBg[i] = g_cBtn; } void TkBtn(const string s, const int fs, const int btn, const bool right = false) { TkSeg(s, fs, g_cText, btn, right, true); } // 色を決めたボタン / button with its own colors void TkBtnC(const string s, const int fs, const int btn, const bool right, const color txt, const color bg) { TkSeg(s, fs, txt, btn, right, true); g_sBg[g_nS - 1] = bg; } // 押せないボタン(薄い字・地はパネルと同じ)/ disabled button (faint text, flat background) void TkBtnOff(const string s, const int fs, const bool right) { TkBtnC(s, fs, BTN_OFF, right, TkShift(g_cMuted, (TkLum(g_cPanel) < 128) ? -45 : 70), g_cPanel); } void TkLabel(const string nm, const int x, const int y, const string s, const int fs, const color c) { 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); ObjectSetInteger(0, nm, OBJPROP_ZORDER, 10); } ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y); ObjectSetString(0, nm, OBJPROP_TEXT, s); ObjectSetString(0, nm, OBJPROP_FONT, g_font); ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, fs); ObjectSetInteger(0, nm, OBJPROP_COLOR, c); } void TkBox(const string nm, const int x, const int y, const int w, const int h, const color bg, 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_ZORDER, 10); } ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, nm, OBJPROP_XSIZE, w); ObjectSetInteger(0, nm, OBJPROP_YSIZE, h); ObjectSetInteger(0, nm, OBJPROP_BGCOLOR, bg); ObjectSetInteger(0, nm, OBJPROP_COLOR, border); } // 透ける背景(絵で描く。字やボタンは今までどおり)2026-10-22 / see-through background drawn as a bitmap string g_bgRes = "::TKTA_BG"; int g_bgW = 0, g_bgH = 0; color g_bgC = clrNONE; void TkBmpBox(const string nm, const int x, const int y, const int w, const int h, const uchar alpha) { if(w <= 0 || h <= 0) return; if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_BITMAP_LABEL, 0, 0, 0); ObjectSetInteger(0, nm, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); ObjectSetInteger(0, nm, OBJPROP_BACK, false); ObjectSetInteger(0, nm, OBJPROP_ZORDER, 10); g_bgW = 0; // 絵を作り直す / rebuild the bitmap } if(w != g_bgW || h != g_bgH || g_cPanel != g_bgC) { uint px[]; ArrayResize(px, w * h); uint fill = ColorToARGB(g_cPanel, alpha); uint edge = ColorToARGB(g_cBorder, (uchar)MathMin(255, alpha + 60)); for(int j = 0; j < h; j++) for(int i = 0; i < w; i++) px[j * w + i] = (i == 0 || j == 0 || i == w - 1 || j == h - 1) ? edge : fill; if(ResourceCreate(g_bgRes, px, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE)) { g_bgW = w; g_bgH = h; g_bgC = g_cPanel; } ObjectSetString(0, nm, OBJPROP_BMPFILE, g_bgRes); } ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, y); } void TkRender() { int bpx = 6, bpy = 2, gapX = 6, pad = 8, rowSp = 3; ArrayResize(g_sX, g_nS); ArrayResize(g_sY, g_nS); ArrayResize(g_sW, g_nS); ArrayResize(g_sH, g_nS); int rowH[], leftW[], rightW[]; ArrayResize(rowH, g_nR); ArrayResize(leftW, g_nR); ArrayResize(rightW, g_nR); ArrayInitialize(rowH, 0); ArrayInitialize(leftW, 0); ArrayInitialize(rightW, 0); // 字の大きさを測る(負の値=0.1ポイント単位。OBJ_LABEL の字の大きさと同じ尺度) // Measure text (negative size = tenths of a point, same scale as the label font size) for(int i = 0; i < g_nS; i++) { TextSetFont(g_font, -g_sF[i] * 10); uint w = 0, h = 0; TextGetSize(g_sT[i], w, h); int wi = (int)w, hi = (int)h; // 前に描いた同じ字があれば、実際に描いた幅・高さ(OBJ_LABEL の XSIZE/YSIZE は読み取り専用で実寸)の大きい方を使う。 // 画面の拡大率などで、測った幅が実際より小さいことがある(2026-10-22 試し用の MT5 で右がはみ出した) // If the same text was drawn before, use the larger of the measured and the actual drawn size string mn = TK_PANEL + "L" + IntegerToString(i); if(ObjectFind(0, mn) >= 0 && ObjectGetString(0, mn, OBJPROP_TEXT) == g_sT[i] && ObjectGetInteger(0, mn, OBJPROP_FONTSIZE) == g_sF[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; } // 実寸がまだ無い時のため、少しだけ余裕を見る / a little slack until the actual size is known wi = (int)MathCeil(wi * 1.06) + 1; if(g_sBox[i]) { wi += 2 * bpx; hi += 2 * bpy; } g_sW[i] = wi; g_sH[i] = hi; int r = g_sRow[i]; if(hi > rowH[r]) rowH[r] = hi; if(g_sR[i]) rightW[r] += ((rightW[r] > 0) ? gapX : 0) + wi; else leftW[r] += ((leftW[r] > 0) ? gapX : 0) + wi; } int maxW = 0, sumH = 0; for(int r = 0; r < g_nR; r++) { int rw = leftW[r] + ((rightW[r] > 0) ? ((leftW[r] > 0) ? 18 : 0) + rightW[r] : 0); if(rw > maxW) maxW = rw; sumH += rowH[r] + g_rGap[r]; } int pw = maxW + pad * 2; int ph = sumH + pad * 2 + rowSp * MathMax(0, g_nR - 1); int cw = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); int ch = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); int x0, y0; if(g_px < 0 || g_py < 0) { // まだ動かしていない: 設定の隅から / not moved yet: from the chosen corner bool right = (InpCorner == CORNER_RIGHT_UPPER || InpCorner == CORNER_RIGHT_LOWER); bool bottom = (InpCorner == CORNER_LEFT_LOWER || InpCorner == CORNER_RIGHT_LOWER); x0 = right ? cw - InpOffsetX - pw : InpOffsetX; y0 = bottom ? ch - InpOffsetY - ph : InpOffsetY; } else { x0 = g_px; y0 = g_py; } // チャートの外に出ないように / keep inside the chart // 右端はチャートの幅 − 10px まで、下にはみ出す時は上に寄せる / right edge ≤ width − 10 px; move up if too low x0 = MathMax(0, MathMin(x0, cw - 10 - pw)); y0 = MathMax(0, MathMin(y0, ch - 4 - ph)); // 最小化などでチャートがパネルより小さい時は、覚えた位置を書き換えない(元の大きさで元の所に戻す)2026-10-22 // When the chart is smaller than the panel (e.g. minimized), keep the stored position if(g_px >= 0 && g_py >= 0 && cw >= pw + 10 && ch >= ph + 4) { g_px = x0; g_py = y0; } g_panX = x0; g_panY = y0; g_panW = pw; g_panH = ph; // 位置を決める / positions int y = y0 + pad, cur = -1, lx = 0, rx = 0; for(int i = 0; i < g_nS; i++) { int r = g_sRow[i]; while(cur < r) { if(cur >= 0) y += rowH[cur] + rowSp; cur++; y += g_rGap[cur]; lx = x0 + pad; rx = x0 + pw - pad - rightW[cur]; } if(g_sR[i]) { g_sX[i] = rx; rx += g_sW[i] + gapX; } else { g_sX[i] = lx; lx += g_sW[i] + gapX; } g_sY[i] = y + (rowH[r] - g_sH[i]); // 下でそろえる / align to the bottom of the row } // 描く / draw string bgName = TK_PANEL + "ANEL"; if(InpBackground == TK_BG_NONE) ObjectDelete(0, bgName); else { // 背景が無い時は、全部作り直す(背景が字の上に来ないように)/ recreate all so the background stays below if(ObjectFind(0, bgName) < 0) { ObjectsDeleteAll(0, TK_PANEL); g_objN = 0; } if(InpBackground == TK_BG_SOLID) TkBox(bgName, x0, y0, pw, ph, g_cPanel, g_cBorder); else TkBmpBox(bgName, x0, y0, pw, ph, (uchar)((InpBackground == TK_BG_LIGHT) ? 190 : 150)); // 少し透ける 約75% / よく透ける 約60% } for(int i = 0; i < g_nS; i++) { string ln = TK_PANEL + "L" + IntegerToString(i); string bn = TK_PANEL + "B" + IntegerToString(i); if(g_sBox[i]) { if(ObjectFind(0, bn) < 0) ObjectDelete(0, ln); // 字をボタンの後に作る / text after its box TkBox(bn, g_sX[i], g_sY[i], g_sW[i], g_sH[i], g_sBg[i], g_cBorder); TkLabel(ln, g_sX[i] + bpx, g_sY[i] + bpy, g_sT[i], g_sF[i], g_sC[i]); } else { ObjectDelete(0, bn); TkLabel(ln, g_sX[i], g_sY[i], g_sT[i], g_sF[i], g_sC[i]); } } for(int i = g_nS; i < g_objN; i++) { ObjectDelete(0, TK_PANEL + "L" + IntegerToString(i)); ObjectDelete(0, TK_PANEL + "B" + IntegerToString(i)); } g_objN = g_nS; } bool TkInPanel(const int x, const int y) { return (x >= g_panX && x < g_panX + g_panW && y >= g_panY && y < g_panY + g_panH); } bool TkInGrip(const int x, const int y) { for(int i = 0; i < g_nS && i < ArraySize(g_sX); i++) if(g_sB[i] == BTN_GRIP && x >= g_sX[i] - 4 && x < g_sX[i] + g_sW[i] + 4 && y >= g_sY[i] - 4 && y < g_sY[i] + g_sH[i] + 4) return true; return false; } //+------------------------------------------------------------------+ //| チャートの線(表示だけ。注文はしない)/ Chart lines (display only) | //+------------------------------------------------------------------+ void TkMakeLine(const string name, const double price, const color c, const int style, const int width, const string desc) { if(ObjectFind(0, name) >= 0) return; // 前の位置を残す(時間足を変えた時など)/ keep the old position ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_STYLE, style); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true); ObjectSetInteger(0, name, OBJPROP_SELECTED, true); // すぐドラッグできるように / draggable right away ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_ZORDER, 5); ObjectSetString(0, name, OBJPROP_TOOLTIP, desc); } void TkSetLine(const string name, const double price) { double p = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(MathAbs(ObjectGetDouble(0, name, OBJPROP_PRICE) - p) > pt * 0.1) ObjectSetDouble(0, name, OBJPROP_PRICE, p); } // 線をいつも「選んだ」状態にしておく(1回押すだけでドラッグできる)/ keep lines selected so one press drags them void TkKeepSelected(const string name) { if(ObjectFind(0, name) >= 0 && !(bool)ObjectGetInteger(0, name, OBJPROP_SELECTED)) ObjectSetInteger(0, name, OBJPROP_SELECTED, true); } void TkRRDelete() { ObjectDelete(0, TK_RR_ENTRY); ObjectDelete(0, TK_RR_ENTRY + "_T"); ObjectDelete(0, TK_RR_SL); ObjectDelete(0, TK_RR_SL + "_T"); ObjectDelete(0, TK_RR_TP); ObjectDelete(0, TK_RR_TP + "_T"); ObjectDelete(0, TK_RR_SYM); } // 線が別の銘柄のものなら消す / remove lines that belong to another symbol void TkRRCheckSymbol() { if(ObjectFind(0, TK_RR_SYM) >= 0 && ObjectGetString(0, TK_RR_SYM, OBJPROP_TEXT) != _Symbol) { TkRRDelete(); // 銘柄が変わった / symbol changed g_mv = 0; TkSaveMoved(); } } // ATR(14 本。ひとつ前の足まで)/ ATR over the last 14 closed bars double TkAtr(const int n) { if(Bars(_Symbol, _Period) < n + 2) return 0; double s = 0; for(int i = 1; i <= n; i++) { double h = iHigh(_Symbol, _Period, i), l = iLow(_Symbol, _Period, i), pc = iClose(_Symbol, _Period, i + 1); if(h <= 0 || l <= 0 || pc <= 0) return 0; s += MathMax(h - l, MathMax(MathAbs(h - pc), MathAbs(l - pc))); } return s / n; } // はじめの損切りの線の離れ(値段)/ First stop line distance in price double TkDefaultDist() { double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double d = ((InpStopPips > 0) ? InpStopPips : 20.0) * g_pip; double a = (InpInitStop == TK_INIT_PIPS) ? 0 : TkAtr(14); g_defAtrOk = (InpInitStop == TK_INIT_PIPS) || (a > 0); if(a > 0) { d = a * MathMax(0.1, InpAtrMult); if(InpInitStop == TK_INIT_AUTO) { // 切りのよい数に丸める(FX は pips で、それ以外は値段で。例 23.7→25、2.37→2.5) // Round to a tidy number (pips for FX, price otherwise; e.g. 23.7 → 25, 2.37 → 2.5) bool inPips = (g_cvFx && g_pip > 0); double v = inPips ? d / g_pip : d; if(v > 0) { double half = MathPow(10.0, MathFloor(MathLog10(v))) / 2.0; v = MathMax(half, MathRound(v / half) * half); } if(inPips) v = MathMax(10.0, v); // FX は最低 10pips / FX: at least 10 pips d = inPips ? v * g_pip : v; } } if(pt > 0) d = MathMax(pt, MathRound(d / pt) * pt); return d; } double TkNowPrice() { double p = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(p <= 0) p = iClose(_Symbol, _Period, 0); return p; } // 線を出す・動かしていない線を今の値段に合わせる / create lines, keep undragged lines in place void TkLinesEnsure() { TkRRCheckSymbol(); if(ObjectFind(0, TK_RR_SYM) < 0) { ObjectCreate(0, TK_RR_SYM, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, TK_RR_SYM, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); ObjectSetInteger(0, TK_RR_SYM, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, TK_RR_SYM, OBJPROP_HIDDEN, true); } ObjectSetString(0, TK_RR_SYM, OBJPROP_TEXT, _Symbol); int mv0 = g_mv; double now = TkNowPrice(); if(now <= 0) return; if(ObjectFind(0, TK_RR_ENTRY) < 0) { TkMakeLine(TK_RR_ENTRY, now, g_cEntry, STYLE_DASH, 1, T("入る値(目安の線。ドラッグで動かせます)", "Entry (reference line, drag to move)")); g_mv &= ~1; } if((g_mv & 1) == 0) TkSetLine(TK_RR_ENTRY, now); // 動かすまでは今の値段 / follows the price until dragged double e = ObjectGetDouble(0, TK_RR_ENTRY, OBJPROP_PRICE); if(ObjectFind(0, TK_RR_SL) < 0) { TkMakeLine(TK_RR_SL, e - g_dir * g_defDist, g_cSL, STYLE_SOLID, 2, T("損切り(目安の線。ドラッグで動かせます)", "Stop loss (reference line, drag to move)")); g_mv &= ~2; } if((g_mv & 2) == 0) TkSetLine(TK_RR_SL, e - g_dir * g_defDist); // 選んだ向きの側 / on the chosen side double s = ObjectGetDouble(0, TK_RR_SL, OBJPROP_PRICE); if(g_showTP) { // はじめの利確の線は 1:2 の所(損切りの反対側)/ first target line at 1:2, opposite the stop double d = (MathAbs(e - s) > 0) ? (e - s) : g_defDist; double tp = e + ((g_rrSel > 0) ? g_rrSel : 2.0) * d; // 選んだ比のまま付いてくる / follows at the chosen ratio if(tp <= 0) tp = _Point; // 売りで値段が極端に小さい時の守り / guard for a very low price on a short if(ObjectFind(0, TK_RR_TP) < 0) { TkMakeLine(TK_RR_TP, tp, g_cTP, STYLE_SOLID, 1, T("利確(目安の線。ドラッグで動かせます)", "Target (reference line, drag to move)")); g_mv &= ~4; } if((g_mv & 4) == 0) TkSetLine(TK_RR_TP, tp); } else { ObjectDelete(0, TK_RR_TP); ObjectDelete(0, TK_RR_TP + "_T"); g_mv &= ~4; } TkKeepSelected(TK_RR_ENTRY); TkKeepSelected(TK_RR_SL); TkKeepSelected(TK_RR_TP); if(g_mv != mv0) TkSaveMoved(); } // 線の横の文字(見えている一番左の足の位置に置く)/ Text next to a line, at the left edge of the visible chart // above=true は線の上、false は線の下(近い線どうしで文字が重ならないように)/ above or below the line void TkLineText(const string line, const string s, const color c, const int fs, const bool above) { string nm = line + "_T"; if(ObjectFind(0, line) < 0) { ObjectDelete(0, nm); return; } double p = ObjectGetDouble(0, line, OBJPROP_PRICE); // 画面の左から 40px の所の時刻に置く(字の左端が見える範囲に入るように) // Place at the time found 40 px from the left edge, so the start of the text is visible datetime t = 0; int sub = 0; double pp = 0; if(!ChartXYToTimePrice(0, 40, 20, sub, t, pp) || t <= 0) { // 求められない時は、見えている左から 3 本目の足 / fallback: the 3rd visible bar from the left long first = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR, 0) - 3; int bars = Bars(_Symbol, _Period); if(first >= bars) first = bars - 1; if(first < 0) first = 0; t = iTime(_Symbol, _Period, (int)first); } if(ObjectFind(0, nm) < 0) { ObjectCreate(0, nm, OBJ_TEXT, 0, t, p); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); } ObjectSetInteger(0, nm, OBJPROP_ANCHOR, above ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); ObjectMove(0, nm, 0, t, p); ObjectSetString(0, nm, OBJPROP_TEXT, s); ObjectSetString(0, nm, OBJPROP_FONT, g_font); ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, fs); ObjectSetInteger(0, nm, OBJPROP_COLOR, c); } //+------------------------------------------------------------------+ //| 表示を作り直す / Rebuild the panel | //+------------------------------------------------------------------+ // ロスカットまでの距離(確かめの道具でも同じ式)/ Room before stop-out (same formula in the check tool) double TkStopOutRoom(const double equity, const double margin, const double level, const bool percentMode) { if(percentMode) return equity - margin * level / 100.0; return (equity - margin) - level; } string TkTwo(const int v) { return (v < 10 ? "0" : "") + IntegerToString(v); } // 出したロットの必要証拠金(計算だけ。注文は出さない)。分からない時は 0(2026-10-22 関数にした) // Margin for the calculated lot (calculation only). 0 when unknown double TkLotMargin() { if(!g_lotOk || g_side == 0) return 0; double m1 = 0; ENUM_ORDER_TYPE ot = (g_side > 0) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; double px = (g_side > 0) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); // OrderCalcMargin は計算だけの関数(注文は出さない)/ calculation only, sends nothing if(!OrderCalcMargin(ot, _Symbol, g_lotLots, px, m1)) return 0; return (m1 > 0) ? m1 : 0; } // 出したロットの必要証拠金が余力を超えるか(ロットは減らさない)2026-10-22 // Does the margin for the lot exceed free margin? (the lot is not reduced) bool TkMarginShort(const double m1) { return (m1 > 0 && m1 > AccountInfoDouble(ACCOUNT_MARGIN_FREE)); } // 「詳しく」の中身 / Contents of "More" void TkBuildDetails(const datetime serverNow, const bool forceHistory, const int fsN, const int fsS) { int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); //--- 証拠金と維持率 / Margin if(InpShowMargin) { TkRow(6); TkSeg(T("証拠金と維持率", "Margin"), fsS, g_cHead); double eq = AccountInfoDouble(ACCOUNT_EQUITY); double mg = AccountInfoDouble(ACCOUNT_MARGIN); double free = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double so = AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); double call = AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); bool pct = (AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE) == ACCOUNT_STOPOUT_MODE_PERCENT); string soTxt = pct ? DoubleToString(so, 0) + "%" : TkMoney(so, false); if(mg <= 0) { TkRow(); TkSeg(T("今はポジションがありません(維持率 -)", "No open positions (margin level -)"), fsN, g_cText); TkRow(); TkSeg(T("ロスカットの水準 ", "Stop-out level ") + soTxt, fsS, g_cMuted); } else { double lvl = eq / mg * 100.0; // パーセント方式: 有効証拠金 − 必要証拠金 × 水準% / 金額方式: 余剰証拠金 − ロスカット額 double dist = TkStopOutRoom(eq, mg, so, pct); color c = g_cText; if(pct && call > 0 && lvl <= call) c = g_cAlert; else if(pct && call > 0 && lvl <= call * 1.5) c = g_cWarn; TkRow(); if(dist > 0) TkSeg(T("あと約 " + TkMoney(dist, false) + " の損失でロスカットの水準", "About " + TkMoney(dist, false) + " more loss reaches the stop-out level"), fsN, c); else TkSeg(T("ロスカットの水準に達しています", "At or below the stop-out level"), fsN, g_cAlert); TkRow(); TkSeg(T("維持率 ", "Margin level ") + DoubleToString(lvl, 1) + "%" + T("(ロスカット ", " (stop-out ") + soTxt + ")", fsS, c); } TkRow(); TkSeg(T("使っている証拠金 ", "Margin used ") + TkMoney(mg, false) + T(" / 余力 ", " / free ") + TkMoney(free, false), fsS, g_cMuted); double m1 = TkLotMargin(); if(m1 > 0) { bool shortM = TkMarginShort(m1); // 余力より多い → 赤 / more than free margin → red TkRow(); TkSeg(T("このロットの必要証拠金 約 ", "Margin for this lot size about ") + TkMoney(m1, false), fsS, shortM ? g_cAlert : g_cMuted); } } //--- 期間の記録 / Period record if(InpShowPeriod) { if(forceHistory || TimeLocal() - g_lastHist >= 5) { TkCalcPeriod(serverNow); g_lastHist = TimeLocal(); } string pName = (InpPeriod == TK_PERIOD_DAY) ? T("1日", "day") : (InpPeriod == TK_PERIOD_WEEK) ? T("1週間", "week") : T("1か月", "month"); string bName = (InpBoundary == TK_BOUND_NY1700) ? T("NY 17:00 区切り", "NY 17:00 close") : (InpBoundary == TK_BOUND_SERVER) ? T("サーバー 0:00 区切り", "server 00:00") : T("日本時間 " + IntegerToString(InpJstHour) + ":00 区切り", "Japan time " + IntegerToString(InpJstHour) + ":00"); string head = (InpScope == TK_SCOPE_SYMBOL) ? T("この銘柄の確定した損益", "Closed P/L of this symbol") : T("口座全体の確定した損益", "Closed P/L of the whole account"); TkRow(6); TkSeg(head + T("(" + pName + "・" + bName + ")", " (" + pName + ", " + bName + ")"), fsS, g_cHead); if(!g_pOk) { TkRow(); TkSeg(T("履歴を読めませんでした", "Could not read the history"), fsN, g_cWarn); } else { // MT4: 読めた履歴が期間の途中から → 確定した損益が少なく出る(2026-10-22) // MT4: the loaded history starts inside the period, so the closed P/L may be too small if(g_pHistShort) { TkRow(); TkSeg(T("口座履歴のタブで「全履歴」を選んでください", "Choose \"All History\" in the Account History tab"), fsN, g_cAlert); TkRow(); TkSeg(T("(読めた履歴が期間の途中からで、損益が少なく出ているかもしれません)", "(the loaded history starts after the period start, so the P/L may be incomplete)"), fsS, g_cWarn); } color cNet = g_cText; if(InpLimitLoss > 0 && g_pNet < 0) { if(-g_pNet > InpLimitLoss) cNet = g_cAlert; else if(-g_pNet >= InpLimitLoss * 0.8) cNet = g_cWarn; } string lim = (InpLimitLoss > 0) ? T(" / 上限 ", " / limit ") + TkMoney(-InpLimitLoss, false) : ""; TkRow(); TkSeg(TkMoney(g_pNet, true) + lim, fsN, cNet); color cCnt = g_cText; if(InpLimitTrades > 0) { if(g_pTrades > InpLimitTrades) cCnt = g_cAlert; else if(g_pTrades >= InpLimitTrades * 0.8) cCnt = g_cWarn; } string limT = (InpLimitTrades > 0) ? T(" / 上限 ", " / limit ") + IntegerToString(InpLimitTrades) : ""; TkRow(); TkSeg(T("取引 ", "Trades ") + IntegerToString(g_pTrades) + T(" 回", "") + limT + T(" / 今の連敗 ", " / losing streak ") + IntegerToString(g_pStreak), fsS, cCnt); // 始まりを PC の時刻で見せる / show the start in PC local time MqlDateTime ls; TimeToStruct((datetime)((long)g_pStart - g_srvOffset + ((long)TimeLocal() - (long)TimeGMT())), ls); TkRow(); TkSeg(T("始まり ", "Since ") + IntegerToString(ls.mon) + "/" + IntegerToString(ls.day) + " " + TkTwo(ls.hour) + ":" + TkTwo(ls.min) + T("(PC の時刻)・手数料とスワップ込み", " (PC time), incl. commission and swap"), fsS, g_cMuted); } } //--- スプレッドと足の残り / Spread and bar countdown if(InpShowSpread) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); string sp = (g_pip > 0 && ask > 0 && bid > 0) ? DoubleToString((ask - bid) / g_pip, 1) + " pips" : "-"; datetime barStart = iTime(_Symbol, _Period, 0); long next; if(_Period == PERIOD_MN1) { MqlDateTime d; TimeToStruct(barStart, d); next = (long)TkDate(d.mon == 12 ? d.year + 1 : d.year, d.mon == 12 ? 1 : d.mon + 1, 1); } else next = (long)barStart + PeriodSeconds(_Period); long rem = next - (long)serverNow; string remTxt = "-"; if(barStart > 0 && rem >= 0 && rem <= PeriodSeconds(_Period) + 86400) { long hh = rem / 3600, mm = (rem % 3600) / 60, ss = rem % 60; remTxt = (hh > 0 ? IntegerToString(hh) + ":" + TkTwo((int)mm) : IntegerToString(mm)) + ":" + TkTwo((int)ss); } TkRow(6); TkSeg(T("スプレッド ", "Spread ") + sp + T(" / 足の確定まで ", " / bar closes in ") + remTxt, fsS, g_cText); // 今のスプレッドで増えうる額の目安(表示だけ。ロットの計算は変えない)2026-10-22 // Rough extra loss from the current spread (display only; the lot calculation is unchanged) if(g_lotOk && g_vppCode == 0 && g_lotVpp > 0 && g_pip > 0 && ask > bid && bid > 0) { double spCost = g_lotLots * ((ask - bid) / g_pip) * g_lotVpp; TkRow(); TkSeg(T("このロットでスプレッドの分 約 ", "Spread on this lot size about ") + TkMoney(spCost, false), fsS, g_cMuted); // 注意の文は下に1つだけ(2026-10-22 確認係: 重複) } } //--- 計算の元 / What the numbers are based on TkRow(6); TkSeg(T("計算の元", "Calculation basis"), fsS, g_cHead); bool over = (TkContractOverride() > 0); TkRow(); TkSeg(T("1ロット = ", "1 lot = ") + TkCommas(DoubleToString(g_cvContract, 0)) + " " + SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE) + (over ? T("(上書き中)", " (override)") : T("(業者の設定)", " (broker setting)")), fsS, over ? g_cWarn : g_cMuted); if(g_cvState == 1) { TkRow(); TkSeg(T("換算: なし(口座と同じ通貨 ", "Conversion: none (account currency ") + AccountInfoString(ACCOUNT_CURRENCY) + ")", fsS, g_cMuted); } else if(g_cvState == 0) { TkRow(); if(StringFind(g_cvPair, "+") >= 0) TkSeg(T("換算: ", "Conversion: ") + g_cvPair + T("(ドル経由)", " (via USD)"), fsS, g_cMuted); else TkSeg(T("換算: ", "Conversion: ") + g_cvPair + " " + DoubleToString(g_cvMid, (int)SymbolInfoInteger(g_cvPair, SYMBOL_DIGITS)), fsS, g_cMuted); } string pipNote = (InpPipSize > 0) ? T("(上書き中)", " (override)") : TkIsGold(_Symbol) ? T("(ゴールドの設定)", " (gold setting)") : ""; TkRow(); TkSeg("1 pip = " + DoubleToString(g_pip, TkLotDigits(g_pip)) + pipNote, fsS, (InpPipSize > 0) ? g_cWarn : g_cMuted); if(g_vppCode == 0 && g_lotVpp > 0) { string src = (g_cvSource == 1) ? T("端末の損益の計算", "terminal profit calc") : (g_cvSource == 2) ? T("通貨量 × 換算", "contract x rate") : "TICK_VALUE"; TkRow(); if(g_cvFx) TkSeg(T("1ロットで 1pip = ", "1 pip per 1 lot = ") + TkMoney(g_lotVpp, false) + " (" + src + ")", fsS, g_cMuted); else TkSeg(T("1ロットで値段 1 = ", "Price move 1 per 1 lot = ") + TkMoney(g_lotVpp / g_pip, false) + " (" + src + ")", fsS, g_cMuted); } //--- 注意書き / Notes TkRow(6); TkSeg(T("スリッページ・手数料・窓開けは含みません", "Excludes slippage, fees and price gaps"), fsS, g_cMuted); // 2026-10-22 足した / added TkRow(); TkSeg(T("スプレッドの分、実際の損失は少し大きくなることがあります", "The spread can make the actual loss a little larger"), fsS, g_cMuted); TkRow(); TkSeg(T("注文の前に、端末の注文画面で確かめてください", "Check your terminal's order window before trading"), fsS, g_cMuted); TkRow(4); TkBtn(T("線をはじめの位置に戻す", "Reset the lines"), fsS, BTN_RESET); } void TkRefresh(const bool forceHistory) { datetime serverNow = TimeTradeServer(); // チャートの背景色が変わっていたら、色を決め直してパネルの部品を全部作り直す // (背景色の変更では CHARTEVENT_CHART_CHANGE が来ない・先に来ることがあるため、毎回の更新で見る) // If the chart background changed, redo the colors and rebuild every panel object (checked on every refresh) if((color)ChartGetInteger(0, CHART_COLOR_BACKGROUND) != g_lastBg) { TkSetColors(); ObjectsDeleteAll(0, TK_PANEL); g_objN = 0; ObjectDelete(0, TK_RR_ENTRY + "_T"); ObjectDelete(0, TK_RR_SL + "_T"); ObjectDelete(0, TK_RR_TP + "_T"); } g_srvOffset = TkRoundOffset((long)serverNow - (long)TimeGMT()); g_pip = TkPipSizeOf(_Symbol, InpPipSize, TkGoldPip()); g_vppCode = TkCalcVpp(_Symbol, g_pip, TkContractOverride(), AccountInfoString(ACCOUNT_CURRENCY), g_lotVpp); // ATR がまだ取れない時(銘柄を替えた直後など)は仮の幅。赤の線を動かしていない間は、取れた時に取り直す // (動かしていない赤の線は g_defDist の所に付いてくるので、線も置き直される) // Without ATR data a fallback width is used; until the stop line is dragged, retry and re-place the line if(g_defDist <= 0 || (!g_defAtrOk && (g_mv & 2) == 0)) g_defDist = TkDefaultDist(); //--- 線 / Lines TkLinesEnsure(); bool haveLines = (ObjectFind(0, TK_RR_ENTRY) >= 0 && ObjectFind(0, TK_RR_SL) >= 0); double e = haveLines ? ObjectGetDouble(0, TK_RR_ENTRY, OBJPROP_PRICE) : 0; double s = haveLines ? ObjectGetDouble(0, TK_RR_SL, OBJPROP_PRICE) : 0; g_stopPips = haveLines ? TkPipsBetween(e, s) : 0; g_side = 0; if(haveLines && g_stopPips > 0) g_side = (s < e) ? 1 : -1; // 値段が損切りの線を越えるなどで買い/売りが入れ替わったら、8秒間パネルで目立たせる(2026-10-22) // If the side flips (e.g. the price crossed the stop line), highlight it on the panel for 8 s if(g_side != 0 && g_lastSide != 0 && g_side != g_lastSide) { g_flipAt = GetTickCount(); if(g_flipAt == 0) g_flipAt = 1; } if(g_side != 0) g_lastSide = g_side; if(g_side != 0 && g_side != g_dir) { g_dir = g_side; TkSaveSettings(); } // 線で向きが変わった → ボタンも合わせる / buttons follow the lines bool flipped = (g_flipAt != 0 && GetTickCount() - g_flipAt < 8000); TkCalcLot(); //--- 今の状態(数字を出すか・理由)/ State: show the number or the reason bool slMoved = ((g_mv & 2) != 0); if(slMoved && !g_guideOff) { g_guideOff = true; TkSaveSettings(); } string why = ""; // 赤字の理由 / red reason string hint = ""; // 案内(灰)/ hint (muted) if(g_vppCode != 0) why = TkVppMessage(); else if(!haveLines) why = T("値段がまだありません", "No price yet"); else if(g_side == 0) why = T("損切りの線が入る値と同じ所にあります", "The stop line is at the entry price"); else if(!slMoved) hint = T("赤の線を損切りの所へ動かすと計算します", "Move the red line to your stop to calculate"); else if(g_lotRisk <= 0) why = T("リスクの額が 0 です(設定を確かめてください)", "The risk amount is 0 (check the settings)"); else if(g_lotBelowMin) why = T("最小ロット " + TkLotText(g_lotMin) + " でも 約 " + TkMoney(-g_lotLoss, false) + " になり、決めた額を超えます", // 符号を日本語とそろえた(2026-10-22)/ same sign as the Japanese text "Even the minimum lot " + TkLotText(g_lotMin) + " is about " + TkMoney(-g_lotLoss, false) + " at the stop, more than your risk amount"); else if(!g_lotOk) why = T("計算できません(入力を確かめてください)", "Cannot calculate (check the inputs)"); bool show = (why == "" && hint == "" && g_lotOk); //--- 字の大きさ / Text sizes // 5段: 小・やや小・ふつう・やや大・大 / 5 levels: small … large int lvAdd[] = {-2, -1, 0, 2, 4}; int base = MathMax(6, InpFontSize + lvAdd[MathMax(0, MathMin(4, g_fontLv))]); int fsS = MathMax(7, base - 1), fsN = base, fsL = base + 3, fsXL = base * 2 + 4; string lotStr = show ? TkLotText(g_lotLots) : "--"; string baseName = (InpRiskBase == TK_BASE_EQUITY) ? T("有効証拠金", "equity") : T("残高", "balance"); // 買い/売りの文字(入れ替わった直後は赤)2026-10-22 / side word (red right after a flip) string sideWord = (g_side > 0) ? T("買い", "BUY") : (g_side < 0) ? T("売り", "SELL") : ""; color sideCol = flipped ? g_cAlert : g_cHead; // 証拠金が足りるか(ロットは減らさない)/ enough free margin? (the lot is not reduced) bool marginShort = show && TkMarginShort(TkLotMargin()); TkClear(); if(g_fold) { // たたんだ時はロット1行の細い帯 / folded: one thin band with the lot size TkRow(); TkSeg("::", fsN, g_cMuted, BTN_GRIP); TkSeg(lotStr + T(" ロット", " lots"), fsL, show ? g_cHead : g_cMuted, BTN_GRIP); if(sideWord != "") TkSeg(sideWord, fsN, sideCol, BTN_GRIP); if(marginShort) TkSeg(T("証拠金不足", "Low margin"), fsN, g_cAlert, BTN_GRIP); TkBtn("▼", fsS, BTN_UNFOLD, true); } else { // 1行目(小): 銘柄・つまみ・字の大きさ・たたむ / row 1 (small) TkRow(); TkSeg("::", fsN, g_cMuted, BTN_GRIP); TkSeg(_Symbol + T(" ロット計算", " Lot size"), fsS, g_cMuted, BTN_GRIP); // 端に来たら薄くして押せなくする / faint and inactive at the ends if(g_fontLv > 0) TkBtn("A−", fsS, BTN_FMINUS, true); else TkBtnOff("A−", fsS, true); if(g_fontLv < 4) TkBtn("A+", fsS, BTN_FPLUS, true); else TkBtnOff("A+", fsS, true); TkBtn("▲", fsS, BTN_FOLD, true); // 案内(1回押すと消え、覚える)/ guide (click once to dismiss; remembered) if(!g_guideOff) { TkRow(2); TkSeg(T("① 灰の線を入る所へ ② 赤の線を損切りへ", "① Gray line to your entry ② Red line to your stop"), fsS, g_cWarn, BTN_GUIDE); TkBtn("×", fsS, BTN_GUIDE, true); } // 2行目(特大): ロット / row 2 (extra large): lots TkRow(2); TkSeg(lotStr + T(" ロット", " lots"), fsXL, show ? g_cHead : g_cMuted); // 買い/売りはボタンで選ぶ(押すと赤の線が入る値の反対側へ移る)2026-10-22 // Choose the side with buttons (the stop line moves to the other side of the entry) bool isSell = (g_side != 0) ? (g_side < 0) : (g_dir < 0); color onBg = flipped ? g_cAlert : g_cTP; if(!isSell) TkBtnC(T("買い", "BUY"), fsN, BTN_BUY, false, C'255,255,255', onBg); else TkBtn(T("買い", "BUY"), fsN, BTN_BUY); if(isSell) TkBtnC(T("売り", "SELL"), fsN, BTN_SELL, false, C'255,255,255', onBg); else TkBtn(T("売り", "SELL"), fsN, BTN_SELL); // 入れ替わった直後の説明(2026-10-22)/ explanation right after a flip if(flipped) { TkRow(); TkSeg((g_side > 0) ? T("売りから買いに変わりました(損切りの線が入る値より下)", "Changed from SELL to BUY (the stop line is below the entry)") : T("買いから売りに変わりました(損切りの線が入る値より上)", "Changed from BUY to SELL (the stop line is above the entry)"), fsN, g_cAlert); } // 3行目(大): 損切りの時の金額 / row 3 (large): amount at the stop TkRow(); if(show) { // 必要証拠金が余力を超える時は目立つ所に赤で(ロットは減らさない)2026-10-22 // Margin for this lot exceeds free margin: red, prominent (the lot is not reduced) if(marginShort) { TkSeg(T("証拠金が足りません(このロットは注文できない可能性)", "Not enough free margin (this lot may be rejected)"), fsN, g_cAlert); TkRow(); } double lossPct = (g_lotBase > 0) ? g_lotLoss / g_lotBase * 100.0 : 0; string pctTxt = (lossPct > 0 && lossPct < 0.1) ? DoubleToString(lossPct, 2) : DoubleToString(lossPct, 1); TkSeg(T("損切りで 約 ", "At stop: about ") + TkMoney(-g_lotLoss, false) + T("(" + baseName + "の " + pctTxt + "%)", " (" + pctTxt + "% of " + baseName + ")"), fsL, g_cText); if(g_lotCapped) { TkRow(); TkSeg(T("業者の最大ロット(" + TkLotText(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) + ")で止めています", "Limited to the broker's maximum lot (" + TkLotText(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) + ")"), fsS, g_cWarn); } } else if(why != "") TkSeg(why, fsN, g_cAlert); else TkSeg(hint, fsN, g_cMuted); // 4行目: リスク [−] 1.0% [+] / row 4: risk TkRow(2); if(InpRiskMode == TK_RISK_PERCENT) { bool large = (g_riskPct > 3.0 + 1e-9); TkSeg(T("リスク", "Risk"), fsN, g_cText); // 下限・上限では薄くして押せなくする(2026-10-22)/ faint and inactive at the limits if(g_riskPct > 0.25 + 1e-9) TkBtn("−", fsN, BTN_RMINUS); else TkBtnOff("−", fsN, false); TkSeg(TkPct(g_riskPct) + "%", fsN, large ? g_cAlert : g_cHead); if(g_riskPct < 10.0 - 1e-9) TkBtn("+", fsN, BTN_RPLUS); else TkBtnOff("+", fsN, false); if(large) TkSeg(T("リスクが3%を超えています", "Risk is above 3%"), fsS, g_cAlert); // 中立の言い方に(2026-10-22)/ neutral wording } else TkSeg(T("リスク ", "Risk ") + TkMoney(InpRiskMoney, false) + T("(設定の金額)", " (fixed amount in settings)"), fsN, g_cText); // 小さな注意 / small note TkRow(2); TkSeg(T("目安です。このツールは売買しません", "Estimate only. This tool never places trades"), fsS, g_cMuted); // ボタン: 利確の線・詳しく / buttons: target line, more TkRow(4); // 比のボタン: 選んでいる比は色を変える。もう一度押すと利確の線を消す // Ratio buttons: the selected one is colored; pressing it again hides the target line string rrT[] = {"1:1", "1:1.5", "1:2", "1:3"}; double rrV[] = {1.0, 1.5, 2.0, 3.0}; int rrB[] = {BTN_RR1, BTN_RR15, BTN_RR2, BTN_RR3}; for(int k = 0; k < 4; k++) { bool on = (g_showTP && MathAbs(g_rrSel - rrV[k]) < 1e-9); if(on) TkBtnC(rrT[k], fsS, rrB[k], false, C'255,255,255', g_cTP); else TkBtn(rrT[k], fsS, rrB[k]); } // 利確の線を手で動かした時は、今の比を横に出す(2026-10-22)/ target moved by hand: show the current ratio // 比の保存は全銘柄共通なので、この銘柄で動かした時(g_mv & 4)だけ出す / only when moved on this chart // 押せる物と見分けるため、箱にしない文字で出し、横に消す[×]を置く / plain text (not a button) + [×] to hide if(g_showTP && g_rrSel <= 0 && (g_mv & 4) != 0 && haveLines && g_side != 0 && ObjectFind(0, TK_RR_TP) >= 0) { double t = ObjectGetDouble(0, TK_RR_TP, OBJPROP_PRICE); bool okSide = (g_side > 0 && t > e) || (g_side < 0 && t < e); if(okSide && g_stopPips > 0) TkSeg(T("手動 ", "Manual ") + "1:" + DoubleToString(TkPipsBetween(t, e) / g_stopPips, 1), fsN, g_cTP); else TkSeg(T("手動 1:--(利確が損切り側)", "Manual 1:-- (target on the stop side)"), fsS, g_cAlert); TkBtn("×", fsS, BTN_TPOFF); } TkBtn(g_det ? T("閉じる ▲", "Less ▲") : T("詳しく ▼", "More ▼"), fsS, BTN_DETAIL, true); if(g_det) TkBuildDetails(serverNow, forceHistory, fsN, fsS); } //--- 線の横の文字 / Text next to the lines if(haveLines) { int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); string sideTxt = (g_side > 0) ? T("(買い)", " (buy)") : (g_side < 0) ? T("(売り)", " (sell)") : ""; // 文字はほかの線から離れる側に出す: 損切りが下なら 入る値=上・損切り=下・利確=上(売りは逆) // Text goes on the side away from the other line: long = entry above, stop below, target above (short: reversed) bool slBelow = (s <= e); TkLineText(TK_RR_ENTRY, T("入 ", "Entry ") + DoubleToString(e, dg) + sideTxt, g_cEntryText, fsS, slBelow); string slTxt = T("損切り ", "Stop ") + TkDistText(MathAbs(e - s)); if(show) slTxt += " / " + T("約 ", "about ") + TkMoney(-g_lotLoss, false); else if(!slMoved && g_side != 0) slTxt += T("(ドラッグで動かせます)", " (drag to move)"); TkLineText(TK_RR_SL, slTxt, g_cSL, fsS, !slBelow); if(g_showTP && ObjectFind(0, TK_RR_TP) >= 0) { double t = ObjectGetDouble(0, TK_RR_TP, OBJPROP_PRICE); bool ok = (g_side > 0 && t > e) || (g_side < 0 && t < e); bool tpAbove = (t >= e); if(!ok) TkLineText(TK_RR_TP, T("利確の線が損切りと同じ側にあります", "The target line is on the stop side"), g_cAlert, fsS, tpAbove); else { double tpPips = TkPipsBetween(t, e); double ratio = (g_stopPips > 0) ? tpPips / g_stopPips : 0; string tpTxt = T("利確 ", "Target ") + TkDistText(MathAbs(t - e)); if(show) tpTxt += " / " + T("届いた場合 ", "if reached ") + TkMoney(g_lotLots * tpPips * g_lotVpp, true); tpTxt += " / 1:" + DoubleToString(ratio, 1); TkLineText(TK_RR_TP, tpTxt, g_cTP, fsS, tpAbove); } } } // 1回描いてから実寸を読み、もう1回置き直す / draw once, read the actual sizes, place again TkRender(); ChartRedraw(0); TkRender(); ChartRedraw(0); } //+------------------------------------------------------------------+ //| パネルのボタン / Panel buttons | //+------------------------------------------------------------------+ void TkButton(const int id) { bool hist = false; if(id == BTN_FOLD) g_fold = true; else if(id == BTN_UNFOLD) g_fold = false; // [−] で上がらない・[+] で下がらない(設定で 0.25% 未満・10% 超えにした時)2026-10-22 // [-] never raises and [+] never lowers the value (when the setting is below 0.25% or above 10%) else if(id == BTN_RMINUS) { double nv = MathMax(0.25, MathRound((g_riskPct - 0.25) * 4.0) / 4.0); if(nv >= g_riskPct - 1e-9) return; g_riskPct = nv; } else if(id == BTN_RPLUS) { double nv = MathMin(10.0, MathRound((g_riskPct + 0.25) * 4.0) / 4.0); if(nv <= g_riskPct + 1e-9) return; g_riskPct = nv; } else if(id == BTN_FMINUS) { if(g_fontLv <= 0) return; g_fontLv--; } else if(id == BTN_FPLUS) { if(g_fontLv >= 4) return; g_fontLv++; } else if(id >= BTN_RR1 && id <= BTN_RR3) { double r = (id == BTN_RR1) ? 1.0 : (id == BTN_RR15) ? 1.5 : (id == BTN_RR2) ? 2.0 : 3.0; if(g_showTP && MathAbs(g_rrSel - r) < 1e-9) { g_showTP = false; g_rrSel = 0; } // もう一度押した → 消す / pressed again → hide else { g_showTP = true; g_rrSel = r; g_mv &= ~4; TkSaveMoved(); } // その比の所に置き、付いてくる / place and follow } else if(id == BTN_BUY || id == BTN_SELL) { int want = (id == BTN_BUY) ? 1 : -1; int cur = (g_side != 0) ? g_side : g_dir; g_dir = want; if(want != cur && ObjectFind(0, TK_RR_ENTRY) >= 0) { // 手で動かした線は、入る値をはさんで反対側の同じ幅の所へ移す(動かしていない線は自動で置き直される) // Dragged lines are mirrored across the entry (undragged lines are re-placed automatically) double e = ObjectGetDouble(0, TK_RR_ENTRY, OBJPROP_PRICE); if((g_mv & 2) != 0 && ObjectFind(0, TK_RR_SL) >= 0) { double ns = 2.0 * e - ObjectGetDouble(0, TK_RR_SL, OBJPROP_PRICE); if(ns > 0) TkSetLine(TK_RR_SL, ns); else { g_mv &= ~2; TkSaveMoved(); } } if((g_mv & 4) != 0 && ObjectFind(0, TK_RR_TP) >= 0) { double nt = 2.0 * e - ObjectGetDouble(0, TK_RR_TP, OBJPROP_PRICE); if(nt > 0) TkSetLine(TK_RR_TP, nt); else { g_mv &= ~4; TkSaveMoved(); } } g_lastSide = want; g_flipAt = 0; // ボタンで替えた時は「変わりました」を出さない / no flip notice for a button change } } else if(id == BTN_TPOFF) { g_showTP = false; g_rrSel = 0; } // 手で動かした利確の線を消す / hide the manual target line else if(id == BTN_DETAIL) { g_det = !g_det; hist = g_det; } else if(id == BTN_GUIDE) g_guideOff = true; else if(id == BTN_RESET) { g_mv = 0; TkSaveMoved(); g_defDist = 0; if(g_showTP && g_rrSel <= 0) g_rrSel = 2.0; } else return; TkSaveSettings(); TkRefresh(hist); } //+------------------------------------------------------------------+ //| マウス(つまみのドラッグ)/ Mouse (dragging the grip) | //+------------------------------------------------------------------+ bool g_mDown = false, g_drag = false, g_scrollOrig = true, g_scrollCur = true; int g_dragDX = 0, g_dragDY = 0; uint g_mLast = 0; // 最後のマウスの動きの時刻(GetTickCount)2026-10-22 / last mouse event time // パネルの上ではチャートが一緒に動かないように、マウスでのスクロールを止める(元がオンの時だけ) // Over the panel, stop the chart from scrolling with the mouse (only when it was on originally) void TkScroll(const bool on) { if(!g_scrollOrig) return; if(on == g_scrollCur) return; ChartSetInteger(0, CHART_MOUSE_SCROLL, on); g_scrollCur = on; } void TkMouse(const int x, const int y, const bool left) { g_mLast = GetTickCount(); if(left && !g_mDown && TkInGrip(x, y)) { g_drag = true; g_dragDX = x - g_panX; g_dragDY = y - g_panY; TkScroll(false); } else if(left && g_drag) { g_px = MathMax(0, x - g_dragDX); g_py = MathMax(0, y - g_dragDY); TkRender(); ChartRedraw(0); } else if(!left && g_drag) { g_drag = false; TkSaveSettings(); } if(!g_drag) TkScroll(!TkInPanel(x, y)); g_mDown = left; } //+------------------------------------------------------------------+ //| 始まりと終わり / Init and deinit | //+------------------------------------------------------------------+ // 動き始める(パネルを出す)/ Start working (draw the panel) void TkStart() { g_state = 1; TkMarkRunning(); IndicatorSetString(INDICATOR_SHORTNAME, "Tiltnote Toolkit"); TkRemoveOldNames(); TkSetColors(); TkLoadSettings(); // 「チャートを前面に表示」がオンだと、足と方眼がパネルの上に描かれて読めない(2026-10-22 社長の確認)。 // 動いている間だけ切り、ツールを外した時に元に戻す。元の値は時間足を替えても残るよう、チャートごとの印に覚える // With "Chart on foreground" on, bars and grid are drawn over the panel. Turn it off while running and restore on removal if(!GlobalVariableCheck(TkForeName())) GlobalVariableSet(TkForeName(), (double)ChartGetInteger(0, CHART_FOREGROUND)); if(ChartGetInteger(0, CHART_FOREGROUND)) ChartSetInteger(0, CHART_FOREGROUND, false); // マウスの動きを受け取る(つまみのドラッグ用)。元の値は外した時に戻す // Receive mouse moves (for dragging the grip). The original values are restored on removal if(!GlobalVariableCheck(TkScrollName())) GlobalVariableSet(TkScrollName(), (double)ChartGetInteger(0, CHART_MOUSE_SCROLL)); if(!GlobalVariableCheck(TkMouseName())) GlobalVariableSet(TkMouseName(), (double)ChartGetInteger(0, CHART_EVENT_MOUSE_MOVE)); g_scrollOrig = (GlobalVariableGet(TkScrollName()) > 0.5); if(g_scrollOrig) ChartSetInteger(0, CHART_MOUSE_SCROLL, true); g_scrollCur = g_scrollOrig; ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); g_pip = TkPipSizeOf(_Symbol, InpPipSize, TkGoldPip()); TkRefresh(true); } int OnInit() { if(InpLanguage == TK_LANG_AUTO) g_ja = (TerminalInfoString(TERMINAL_LANGUAGE) == "Japanese"); else g_ja = (InpLanguage == TK_LANG_JA); MathSrand((int)GetTickCount()); g_instId = (double)MathRand() * 32768.0 + (double)MathRand() + 1.0; EventSetTimer(1); // 時間足・銘柄・設定を替えた時は、古いコピーの後始末より先にここが動くことがある。 // だから印を見つけてもすぐ「2つ目」とはせず、待ってタイマーでもう一度見る。 // On timeframe/symbol/settings changes this can run before the old copy's OnDeinit, // so if a marker is found we wait and check again from the timer. if(TkOtherRunning()) { g_state = 0; g_waitFrom = GetTickCount(); return(INIT_SUCCEEDED); } TkStart(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { EventKillTimer(); if(g_state != 1) return; // 待ち・2つ目は何も作っていないので、何も消さない / nothing was drawn if(GlobalVariableCheck(TkIdName()) && GlobalVariableGet(TkIdName()) == g_instId) { GlobalVariableDel(TkInstName()); GlobalVariableDel(TkIdName()); } ObjectsDeleteAll(0, TK_PANEL); g_objN = 0; ResourceFree(g_bgRes); g_bgW = 0; // 透ける背景の絵 / see-through background bitmap // マウスの設定を元に戻す / restore the mouse settings if(GlobalVariableCheck(TkScrollName())) ChartSetInteger(0, CHART_MOUSE_SCROLL, GlobalVariableGet(TkScrollName()) > 0.5); if(GlobalVariableCheck(TkMouseName())) ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, GlobalVariableGet(TkMouseName()) > 0.5); // 時間足・設定を変えただけなら線の位置を残す。外した時は消す // Keep the lines on timeframe/parameter change; remove them when the indicator is removed if(reason != REASON_CHARTCHANGE && reason != REASON_PARAMETERS && reason != REASON_RECOMPILE) { TkRRDelete(); GlobalVariableDel(TkMovedName()); GlobalVariableDel(TkScrollName()); GlobalVariableDel(TkMouseName()); // 外した時は「前面に表示」を元に戻す / restore the foreground setting when removed if(GlobalVariableCheck(TkForeName())) { if(GlobalVariableGet(TkForeName()) > 0.5) ChartSetInteger(0, CHART_FOREGROUND, true); GlobalVariableDel(TkForeName()); } } else { ObjectDelete(0, TK_RR_ENTRY + "_T"); ObjectDelete(0, TK_RR_SL + "_T"); ObjectDelete(0, TK_RR_TP + "_T"); } ChartRedraw(0); } 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[]) { // ふだんは時計(1秒ごと)で更新。ストラテジーテスターでは時計が来ないことがあるので、値動きでも更新 // Normally refreshed by the 1-second timer; in the Strategy Tester also refresh on ticks if(MQLInfoInteger(MQL_TESTER) && g_state == 1) TkRefresh(false); return(rates_total); } void OnTimer() { if(g_state == 0) { // 待ち: 2.5秒たってもう一度見る / waiting: check again after 2.5 s if(GetTickCount() - g_waitFrom < 2500) return; if(TkOtherRunning()) { // 別のコピーが印を更新し続けている → 本当に2つ目 / another copy keeps running → real duplicate g_state = 2; EventKillTimer(); Alert(T("Tiltnote ツール集は、このチャートに既に付いています(2つ目は動きません)", "Tiltnote Toolkit is already on this chart (this second copy does nothing)")); return; } TkStart(); // 古いコピーが消えた → 普通に動く / the old copy is gone → start normally return; } if(g_state != 1) return; TkMarkRunning(); // 動いている印 / still running // チャートの外でボタンを離すと「離した」が来ない。5秒マウスの動きが無ければ押した状態を解く(2026-10-22) // Releasing the button outside the chart sends no event: clear the pressed state after 5 s without mouse events if((g_drag || g_mDown) && GetTickCount() - g_mLast > 5000) { if(g_drag) { g_drag = false; TkSaveSettings(); } g_mDown = false; TkScroll(true); } if(g_drag || g_mDown) return; // ボタンを押している間(線やパネルのドラッグ中)は描き直さない / not while the mouse button is down TkRefresh(false); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(g_state != 1) return; if(id == CHARTEVENT_MOUSE_MOVE) { TkMouse((int)lparam, (int)dparam, (((int)StringToInteger(sparam)) & 1) == 1); return; } if(id == CHARTEVENT_OBJECT_CLICK) { // パネルのボタン(字か、その四角)/ panel button (its text or its box) if(StringFind(sparam, TK_PANEL + "L") == 0 || StringFind(sparam, TK_PANEL + "B") == 0) { int idx = (int)StringToInteger(StringSubstr(sparam, StringLen(TK_PANEL) + 1)); if(idx >= 0 && idx < g_nS && g_sB[idx] > BTN_GRIP) TkButton(g_sB[idx]); return; } if(sparam == TK_RR_ENTRY || sparam == TK_RR_SL || sparam == TK_RR_TP) TkKeepSelected(sparam); return; } if(id == CHARTEVENT_CHART_CHANGE) { // 背景色の変化は TkRefresh の中で毎回見る(スクロールのたびに作り直さないため) // The background color is checked inside TkRefresh (no rebuild on every scroll) TkRefresh(false); return; } if(id == CHARTEVENT_OBJECT_DRAG || id == CHARTEVENT_OBJECT_CHANGE) { int bit = (sparam == TK_RR_ENTRY) ? 1 : (sparam == TK_RR_SL) ? 2 : (sparam == TK_RR_TP) ? 4 : 0; if(bit == 0) return; // 動かした線は、値段の刻みにそろえてそこに止める / a dragged line snaps to the price step and stays TkSetLine(sparam, ObjectGetDouble(0, sparam, OBJPROP_PRICE)); g_mv |= bit; TkSaveMoved(); // 赤の線を一度動かしたら案内を消して覚える / dismiss the guide once the stop line has been moved if(bit == 2 && !g_guideOff) { g_guideOff = true; TkSaveSettings(); } // 利確の線を手で動かしたら、比の選択を外してそこに固定(比の数字だけ変わる) // Target moved by hand: deselect the ratio and keep it there (only the ratio number changes) if(bit == 4 && g_rrSel != 0) { g_rrSel = 0; TkSaveSettings(); } TkRefresh(false); } } //+------------------------------------------------------------------+