پرش به محتوا
کریپکس

ایچیموکو · اندیکاتور

ایچیموکو پیشرفته: ابر تایم‌فریم بالاتر، حمایت و مقاومت و جدول وضعیت

ایچیموکو کامل روی چارت برای TradingView، متاتریدر ۵ و متاتریدر ۴، به‌همراه ابر تایم‌فریم بالاتر (بدون ریپینت)، سطوح حمایت و مقاومت رتبه‌بندی‌شده از کیجون و ابر، جدول وضعیت ۱۹ ردیفه با امتیاز ترکیبی و آلارم.

ichimoku-mtf-kumo.pine · Pine Script v6 · ۷۸۰ خط
// Crypex.org | Ichimoku Advanced: HTF Kumo + S/R levels + status table. Educational tool, not investment advice.
// © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org
// License: personal, non-commercial use only. Redistribution or resale without written permission is prohibited.
// مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون اجازه‌ی کتبی ممنوع است.
//@version=6
// Crypex — Ichimoku Kinko Hyo overlay with higher-timeframe Kumo, S/R levels and alerts.
// Math: Tenkan/Kijun = Donchian mid; Senkou A = avg(Tenkan,Kijun); Senkou B = Donchian(spanB);
// Displacement counts the current bar (TradingView convention): Senkou drawn displacement-1 bars ahead,
// Chikou displacement-1 bars behind. MTF values are confirmed HTF bars only (no repaint).
indicator(title = 'Crypex Ichimoku', shorttitle = 'Crypex Ichimoku', overlay = true, max_bars_back = 5000, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)

// Brand palette (crypex.org logo) + monochrome palette (fills use transparency)
STYLE_BRAND     = 'Crypex'
STYLE_COLORED   = 'Colored'
STYLE_MONO      = 'Monochrome'
BRAND_GREEN     = #0BA31C
BRAND_RED       = #FF0A17
BRAND_GREY      = #787B86
BRAND_DARK      = #1E2229
M_LIGHT         = color.rgb(178, 181, 190)
M_DARK          = color.rgb(93, 96, 107)
M_MID           = color.rgb(135, 139, 148)
M_ACCENT        = color.rgb(157, 160, 168)

//=== 1) Inputs ==================================================================
GRP_THEME = '0 · Theme'
GRP_PERIODS = '1 · Periods (9 / 26 / 52)'
GRP_SHIFT = '2 · Displacement (Senkou + / Chikou −)'
GRP_LINES = '3 · Lines & visibility'
GRP_CLOUD = '4 · Kumo (chart TF)'
GRP_MTF = '5 · Higher timeframe Kumo'
GRP_MARK = '6 · Chart markers (visual only)'
GRP_BG = '7 · Price vs Kumo'
GRP_TBL = '8 · Status table'

stylePreset = input.string(
     STYLE_BRAND,
     'Style',
     group = GRP_THEME,
     options = [STYLE_BRAND, STYLE_COLORED, STYLE_MONO],
     tooltip = 'Crypex = brand green / red theme (default).\nColored = theme uses your picker colours.\nMonochrome = grey/neutral Ichimoku for clean charts & screenshots.\nMono tints underlying candles whenever Color candles is OFF so underlying exchange candles do not bleed through (uses barcolor — low plot quota).')

isMono = stylePreset == STYLE_MONO
isBrand = stylePreset == STYLE_BRAND

conversionPeriods = input.int(9, 'Tenkan · Conversion', minval = 1, group = GRP_PERIODS)
basePeriods = input.int(26, 'Kijun · Base', minval = 1, group = GRP_PERIODS)
laggingSpanBPeriods = input.int(52, 'Senkou Span B', minval = 1, group = GRP_PERIODS)
flatBars = input.int(10, 'Min flat bars', minval = 2, maxval = 30, group = GRP_PERIODS, tooltip = 'Kijun / Senkou B count as flat only if they stayed at the same price for this many consecutive bars.\n3 = minimum  |  5 = standard  |  10+ = strong')

displacement = input.int(26, 'Displacement', minval = 1, group = GRP_SHIFT, tooltip = 'Classic: 26 (equals Kijun length). Counts the current bar, like the TradingView built-in: Senkou is drawn displacement-1 bars AHEAD, Chikou displacement-1 bars BEHIND.')
shift = displacement - 1

showTenkan = input.bool(true, 'Tenkan', group = GRP_LINES, inline = 'tk')
tenkanColor = input.color(color.new(#2962FF, 0), '', group = GRP_LINES, inline = 'tk')
tenkanWidth = input.int(2, 'W', minval = 1, maxval = 4, group = GRP_LINES, inline = 'tkw')
showKijun = input.bool(true, 'Kijun', group = GRP_LINES, inline = 'kj')
kijunColor = input.color(color.new(#B71C1C, 0), '', group = GRP_LINES, inline = 'kj')
kijunWidth = input.int(2, 'W', minval = 1, maxval = 4, group = GRP_LINES, inline = 'kjw')
showChikou = input.bool(true, 'Chikou', group = GRP_LINES, inline = 'ch')
chikouColor = input.color(color.new(#43A047, 0), '', group = GRP_LINES, inline = 'ch')
chikouWidth = input.int(2, 'W', minval = 1, maxval = 4, group = GRP_LINES, inline = 'chw')
showSenkouLines = input.bool(false, 'Senkou A/B outlines', group = GRP_LINES)
showKumoMid = input.bool(false, 'Kumo midpoint (equilibrium)', group = GRP_LINES, tooltip = 'Mid-price between current Kumo top & bottom (lagged Senkou).')

showKumo = input.bool(true, 'Kumo fill', group = GRP_CLOUD)
bullCloudColor = input.color(#089981, 'Bull', group = GRP_CLOUD, inline = 'cf')
bearCloudColor = input.color(#F23645, 'Bear', group = GRP_CLOUD, inline = 'cf')
cloudTransparency = input.int(75, 'Transparency', minval = 0, maxval = 95, group = GRP_CLOUD)
senkouAColor = input.color(color.new(#A5D6A7, 0), 'Line A', group = GRP_CLOUD, inline = 'sa')
senkouBColor = input.color(color.new(#EF9A9A, 0), 'Line B', group = GRP_CLOUD, inline = 'sb')

showMtfCloud   = input.bool(true,  'Show HTF Kumo', group = GRP_MTF, tooltip = 'Overlay the Ichimoku cloud of a higher timeframe.\nAuto mode picks the next standard TF above the chart (1m→1H, 5m/15m/30m→4H, 1H→4H, 4H→D, D→W, W→M).')
mtfAutoTf      = input.bool(true,  'Auto next TF', group = GRP_MTF, tooltip = 'When ON the higher TF is chosen automatically (chart TF → next standard step). Turn OFF to pick manually.')
mtfTimeframe   = input.timeframe('240', 'Manual TF', group = GRP_MTF, tooltip = 'Used only when "Auto next TF" is OFF.')
mtfBullColor   = input.color(color.new(#089981, 82), 'HTF bull', group = GRP_MTF, inline = 'mtfc')
mtfBearColor   = input.color(color.new(#F23645, 82), 'HTF bear', group = GRP_MTF, inline = 'mtfc')
mtfCloudTransp = input.int(82, 'Opacity', minval = 50, maxval = 99, group = GRP_MTF, inline = 'mtfc', tooltip = 'Transparency of the HTF Kumo fill (50 = more opaque, 99 = nearly invisible).')
calendarMode   = input.string('Auto', 'Market calendar', options = ['Auto', '24/7', 'Weekdays only'], group = GRP_MTF, tooltip = 'Positions the HTF Kumo ahead of price correctly.\nAuto: crypto = 24/7, everything else (forex, gold, indices, stocks) = weekdays only.\nOnly affects weekly/monthly HTF.')
ratioOverride  = input.float(0.0, 'Chart bars per HTF bar (0 = auto)', minval = 0, step = 0.5, group = GRP_MTF, tooltip = 'Advanced. For short-session markets (e.g. stocks on intraday charts) type the real number of chart bars inside one HTF bar.')
mtfShowLines   = input.bool(false, 'HTF Senkou A/B lines', group = GRP_MTF)
mtfShowKijun   = input.bool(true,  'HTF Kijun',   group = GRP_MTF, inline = 'htfkj')
mtfKijunColor  = input.color(color.new(#B71C1C, 20), '', group = GRP_MTF, inline = 'htfkj')
mtfKijunWidth  = input.int(1, 'W', minval = 1, maxval = 4, group = GRP_MTF, inline = 'htfkj')
mtfShowTenkan  = input.bool(false, 'HTF Tenkan',  group = GRP_MTF, inline = 'htftk')
mtfTenkanColor = input.color(color.new(#2962FF, 20), '', group = GRP_MTF, inline = 'htftk')
mtfTenkanWidth = input.int(1, 'W', minval = 1, maxval = 4, group = GRP_MTF, inline = 'htftk')

markTkCross = input.bool(true, 'TK cross', group = GRP_MARK, inline = 'tkx')
markTkSize = input.string('small', '', options = ['tiny', 'small', 'normal'], group = GRP_MARK, inline = 'tkx')
markKijunCross = input.bool(false, 'Price × Kijun', group = GRP_MARK, inline = 'pj')
markTenkanCross = input.bool(false, 'Price × Tenkan', group = GRP_MARK, inline = 'pt')
markKumoTwist = input.bool(false, 'Kumo twist', group = GRP_MARK, inline = 'ktw')
markKumoBreak = input.bool(false, 'Kumo breakout', group = GRP_MARK, tooltip = 'Close crosses Kumo top (bull) or Kumo bottom (bear).')
markTkFilter = input.bool(false, 'Only TK in trend', group = GRP_MARK, tooltip = 'If on: bullish TK only when price above Kumo & green cloud; bearish TK only when price below Kumo & red cloud.')

shadeVsKumo = input.bool(false, 'Tint bg vs Kumo', group = GRP_BG)
shadeBull = input.color(color.new(#089981, 93), 'Above', group = GRP_BG, inline = 'sh')
shadeBear = input.color(color.new(#F23645, 93), 'Below', group = GRP_BG, inline = 'sh')
shadeInside = input.color(color.new(#787B86, 94), 'Inside', group = GRP_BG, inline = 'sh')

colorBars = input.bool(false, 'Color candles', group = GRP_BG, tooltip = 'Overlay OHLC tint by regime.')
barPalette = input.string('Kumo', 'Palette', options = ['Kumo', 'TK stack', 'Both'], group = GRP_BG, tooltip = 'Kumo = above/inside/below cloud. TK stack = Tenkan vs Kijun bias layered. Both = average of the two tints.')
neutralBarColor = input.color(color.new(#787B86, 78), 'Neutral', group = GRP_BG, inline = 'nb')

showTable = input.bool(true, 'Show table', group = GRP_TBL)
tablePos = input.string(position.top_right, 'Position', options = [position.top_right, position.top_left, position.bottom_right, position.bottom_left], group = GRP_TBL)
tableSizeStr = input.string('small', 'Text', options = ['tiny', 'small', 'normal', 'large', 'huge'], group = GRP_TBL)
showDistRows = input.bool(true, 'Rows: thickness & distance', group = GRP_TBL)
showAtrRow = input.bool(true, 'Row: ATR context', group = GRP_TBL)
showMtfInTable = input.bool(true, 'Row: HTF regime', group = GRP_TBL)
showSRLines    = input.bool(true,  'S/R levels on chart', group = GRP_TBL)
srExtend       = input.int(20, 'Extend (bars →)', minval = 1, maxval = 200, group = GRP_TBL, inline = 'sre')
srCluster      = input.bool(true, 'Cluster zones', group = GRP_TBL, tooltip = 'Levels within 0.25 ATR of each other are merged into a shaded zone instead of separate lines.')
srMaxLevels    = input.int(10, 'Max levels per side', minval = 1, maxval = 20, group = GRP_TBL, tooltip = 'Maximum number of support levels and resistance levels to draw. Nearest ones are always shown first.')

GRP_WM = '9 · Watermark'
showWatermark = input.bool(true, 'Show crypex.org watermark', group = GRP_WM)
wmPos = input.string(position.bottom_left, 'Position', options = [position.bottom_left, position.bottom_center, position.bottom_right, position.middle_center, position.top_center, position.top_left], group = GRP_WM)
wmGap = input.float(5.0, 'Gap from left (%)', minval = 0, maxval = 30, step = 0.5, group = GRP_WM, tooltip = 'Bottom-left only: empty space (in % of chart width) so the watermark sits right beside the TradingView logo instead of on top of it.')
wmSizeStr = input.string('large', 'Size', options = ['small', 'normal', 'large', 'huge'], group = GRP_WM)
wmTransp = input.int(0, 'Transparency', minval = 0, maxval = 95, group = GRP_WM, tooltip = '0 = exact logo colours, 95 = barely visible.')

tableSize = tableSizeStr == 'tiny' ? size.tiny : tableSizeStr == 'normal' ? size.normal : tableSizeStr == 'large' ? size.large : tableSizeStr == 'huge' ? size.huge : size.small

// ── Auto next-TF selection ────────────────────────────────────────────────────
// Maps current chart TF → next standard step (1m→1H, ≤30m→4H, 1H→4H, 4H→D, D→W, W/M→M)
int _chartSec0 = timeframe.in_seconds()
string _autoNextTf =
     _chartSec0 <=    60 ? '60'   :
     _chartSec0 <=  3600 ? '240'  :
     _chartSec0 <= 14400 ? '1D'   :
     _chartSec0 <= 86400 ? '1W'   : '1M'
string mtfTfStr = mtfAutoTf ? _autoNextTf : mtfTimeframe

// Effective colours (Crypex = brand palette; Monochrome = greyscale; Colored = inputs)
cBull = isBrand ? BRAND_GREEN : #089981
cBear = isBrand ? BRAND_RED : #F23645
effTenkanCol = isMono ? M_LIGHT : isBrand ? BRAND_GREEN : tenkanColor
effKijunCol = isMono ? M_DARK : isBrand ? BRAND_RED : kijunColor
effChikouCol = isMono ? M_ACCENT : isBrand ? BRAND_GREY : chikouColor
effSenkAline = isMono ? color.new(M_LIGHT, 12) : isBrand ? color.new(BRAND_GREEN, 30) : senkouAColor
effSenkBline = isMono ? color.new(M_DARK, 12) : isBrand ? color.new(BRAND_RED, 30) : senkouBColor
effBullCloud = isMono ? M_LIGHT : isBrand ? BRAND_GREEN : bullCloudColor
effBearCloud = isMono ? M_DARK : isBrand ? BRAND_RED : bearCloudColor
effMtfBullFill = isMono ? color.new(M_LIGHT, mtfCloudTransp) : color.new(isBrand ? BRAND_GREEN : mtfBullColor, mtfCloudTransp)
effMtfBearFill = isMono ? color.new(M_DARK,  mtfCloudTransp) : color.new(isBrand ? BRAND_RED : mtfBearColor, mtfCloudTransp)
effMtfKijun  = isMono ? color.new(M_DARK,  20) : isBrand ? color.new(BRAND_RED, 20) : mtfKijunColor
effMtfTenkan = isMono ? color.new(M_LIGHT, 20) : isBrand ? color.new(BRAND_GREEN, 20) : mtfTenkanColor
effKumoMidCol = isMono ? color.new(M_MID, 25) : color.new(#B8860B, 30)
effShadeAbove = isMono ? color.new(M_LIGHT, 93) : isBrand ? color.new(BRAND_GREEN, 93) : shadeBull
effShadeBelow = isMono ? color.new(M_DARK, 93) : isBrand ? color.new(BRAND_RED, 93) : shadeBear
effShadeIn = isMono ? color.new(M_MID, 94) : shadeInside
effTblHdrBg = isMono ? color.new(M_DARK, 25) : isBrand ? color.new(BRAND_GREEN, 15) : color.new(#2157F3, 15)
effTwistUpCol = isMono ? M_LIGHT : isBrand ? BRAND_GREEN : color.lime
effTwistDnCol = isMono ? M_DARK : isBrand ? BRAND_RED : color.red
effKboBull = isMono ? M_LIGHT : cBull
effKboBear = isMono ? M_DARK : cBear
effNeutralBar = isMono ? color.new(M_MID, 0) : neutralBarColor

//=== 2) Core series ==============================================================
donchianMid(int len) =>
    (ta.lowest(low, len) + ta.highest(high, len)) / 2.0

// True flatness: the value stayed within one tick over the whole window, not just at both ends.
isFlat(float src, int len) =>
    ta.highest(src, len) - ta.lowest(src, len) <= syminfo.mintick

tenkan = donchianMid(conversionPeriods)
kijun = donchianMid(basePeriods)
senkouA = (tenkan + kijun) / 2.0
senkouB = donchianMid(laggingSpanBPeriods)

//=== 3) Current-bar Kumo (Senkou values from `shift` bars ago) ==================
float kumoTop = math.max(senkouA[shift], senkouB[shift])
float kumoBottom = math.min(senkouA[shift], senkouB[shift])
bool kumoBull = senkouA[shift] > senkouB[shift]
bool hasKumo = not na(kumoTop) and not na(kumoBottom)
float kumoThick = hasKumo ? kumoTop - kumoBottom : na

bool aboveKumo = hasKumo and close > kumoTop
bool belowKumo = hasKumo and close < kumoBottom
bool insideKumo = hasKumo and not aboveKumo and not belowKumo

bool tkStackBull = tenkan > kijun
bool tkStackBear = tenkan < kijun

tintKumoMono = not hasKumo ? effNeutralBar : aboveKumo ? color.new(M_LIGHT, 0) : belowKumo ? color.new(M_DARK, 0) : color.new(M_MID, 0)
tintTkMono = tkStackBull ? color.new(M_LIGHT, 0) : tkStackBear ? color.new(M_DARK, 0) : effNeutralBar
tintBothMono =
     hasKumo and aboveKumo and tkStackBull ? color.new(M_LIGHT, 0) :
     hasKumo and belowKumo and tkStackBear ? color.new(M_DARK, 0) :
     hasKumo and insideKumo ? color.new(M_MID, 0) :
     not hasKumo ? effNeutralBar : color.new(M_ACCENT, 0)

//=== 4) ATR & distances ============================================================
atr14 = ta.atr(14)
float dTop = hasKumo ? kumoTop - close : na
float dBot = hasKumo ? close - kumoBottom : na
float distNearest = hasKumo ? math.min(math.abs(dTop), math.abs(dBot)) : na
float distNearestAtr = atr14 > 0 and not na(distNearest) ? distNearest / atr14 : na
float thickTicks = hasKumo and syminfo.mintick > 0 ? kumoThick / syminfo.mintick : na
float dTopTicks = hasKumo and syminfo.mintick > 0 ? dTop / syminfo.mintick : na
float dBotTicks = hasKumo and syminfo.mintick > 0 ? dBot / syminfo.mintick : na

//=== 5) MTF Ichimoku (same parameters on HTF OHLC) =================================
chartSec = timeframe.in_seconds()
mtfSec = timeframe.in_seconds(mtfTfStr)
bool mtfHigherTf = mtfSec > chartSec and mtfSec > 0 and chartSec > 0
// Weekday-only markets (forex, gold, stocks) have 5 trading days per week, not 7: a weekly/monthly HTF bar holds
// only 5/7 of the chart bars that pure time maths gives. Crypto trades 24/7, so no correction.
bool weekdaysOnly = calendarMode == 'Weekdays only' or (calendarMode == 'Auto' and syminfo.type != 'crypto')
float weekFactor = weekdaysOnly and mtfSec >= 604800 and chartSec < 604800 ? 5.0 / 7.0 : 1.0
// Chart bars per HTF bar (exact on 24/7 markets; corrected for the 5-day week; short-session stocks: use the override).
float mtfRatio = mtfHigherTf ? (ratioOverride > 0 ? ratioOverride : float(mtfSec) / chartSec * weekFactor) : 1.0
// A confirmed HTF bar reaches the chart one HTF bar late, so the forward shift is (shift - 1) HTF bars.
int mtfForwardBars = mtfHigherTf ? math.max(0, math.round((shift - 1) * mtfRatio)) : shift
// Too large a gap (e.g. 1m chart with 1D HTF) would exceed max_bars_back and break the script.
bool mtfTooFar = mtfHigherTf and mtfForwardBars > 4900
bool mtfIsHigher = mtfHigherTf and not mtfTooFar

// Confirmed values only ([1] + lookahead_on): no repainting on the live HTF bar.
// The current HTF Kumo (Senkou from `shift` HTF bars ago) is read on the HTF itself, so it is exact.
htfIchimoku() =>
    t = donchianMid(conversionPeriods)
    k = donchianMid(basePeriods)
    a = (t + k) / 2.0
    b = donchianMid(laggingSpanBPeriods)
    kLag = math.max(shift, 1)
    // Flatness measured in HTF bars (on the chart TF the HTF series is a staircase and always looks flat).
    fTop = isFlat(math.max(a, b), flatBars + 1)
    fBot = isFlat(math.min(a, b), flatBars + 1)
    fB = isFlat(b, flatBars + 1)
    [t[1], k[1], a[1], b[1], a[kLag], b[kLag], fTop[kLag], fBot[kLag], fB[kLag]]

[mtfTenkan, mtfKijun, mtfSenkouA, mtfSenkouB, mtfKumoA, mtfKumoB, mtfTopFlat, mtfBotFlat, mtfKumoBFlat] = request.security(syminfo.tickerid, mtfTfStr, htfIchimoku(), lookahead = barmerge.lookahead_on)

float mtfTop = math.max(mtfKumoA, mtfKumoB)
float mtfBot = math.min(mtfKumoA, mtfKumoB)
bool mtfHas = not na(mtfTop) and not na(mtfBot)
bool mtfAbove = mtfHas and close > mtfTop
bool mtfBelow = mtfHas and close < mtfBot
bool mtfCloudBull = mtfKumoA > mtfKumoB

lineColorA = showSenkouLines ? effSenkAline : color(na)
lineColorB = showSenkouLines ? effSenkBline : color(na)
pA = plot(showKumo or showSenkouLines ? senkouA : na, title = 'Senkou Span A', color = lineColorA, linewidth = 1, offset = shift)
pB = plot(showKumo or showSenkouLines ? senkouB : na, title = 'Senkou Span B', color = lineColorB, linewidth = 1, offset = shift)

fill(pA, pB, title = 'Kumo', color = showKumo ? (senkouA > senkouB ? color.new(effBullCloud, cloudTransparency) : color.new(effBearCloud, cloudTransparency)) : na)

mtfLineColA = mtfShowLines ? effSenkAline : color(na)
mtfLineColB = mtfShowLines ? effSenkBline : color(na)
pMtfA = plot(showMtfCloud and mtfIsHigher ? mtfSenkouA : na, title = 'HTF Senkou A', color = mtfLineColA, linewidth = 1, offset = mtfForwardBars)
pMtfB = plot(showMtfCloud and mtfIsHigher ? mtfSenkouB : na, title = 'HTF Senkou B', color = mtfLineColB, linewidth = 1, offset = mtfForwardBars)
fill(
     pMtfA,
     pMtfB,
     title = 'HTF Kumo',
     color = showMtfCloud and mtfIsHigher ? (mtfSenkouA > mtfSenkouB ? effMtfBullFill : effMtfBearFill) : na)

// Tenkan / Kijun are not displaced — they belong at the current bar.
plot(showMtfCloud and mtfIsHigher and mtfShowKijun  ? mtfKijun  : na, title = 'HTF Kijun',  color = effMtfKijun,  linewidth = mtfKijunWidth)
plot(showMtfCloud and mtfIsHigher and mtfShowTenkan ? mtfTenkan : na, title = 'HTF Tenkan', color = effMtfTenkan, linewidth = mtfTenkanWidth)

//=== 6) Lines ====================================================================
plot(showTenkan ? tenkan : na, title = 'Tenkan-sen', color = effTenkanCol, linewidth = tenkanWidth)
plot(showKijun ? kijun : na, title = 'Kijun-sen', color = effKijunCol, linewidth = kijunWidth)
plot(showChikou ? close : na, title = 'Chikou Span', color = effChikouCol, linewidth = chikouWidth, offset = -shift)

kumoMid = hasKumo ? (kumoTop + kumoBottom) / 2 : na
plot(showKumoMid and hasKumo ? kumoMid : na, title = 'Kumo midpoint', color = effKumoMidCol, linewidth = 1, style = plot.style_line)

//=== 7) Marker logic =============================================================
tkBull = ta.crossover(tenkan, kijun)
tkBear = ta.crossunder(tenkan, kijun)
tkBullF = markTkFilter ? tkBull and aboveKumo and kumoBull : tkBull
tkBearF = markTkFilter ? tkBear and belowKumo and not kumoBull : tkBear

pkBull = ta.crossover(close, kijun)
pkBear = ta.crossunder(close, kijun)
ptBull = ta.crossover(close, tenkan)
ptBear = ta.crossunder(close, tenkan)

kumoTwistUp = ta.crossover(senkouA, senkouB)
kumoTwistDn = ta.crossunder(senkouA, senkouB)

// Breakouts vs cloud boundaries (lagged Kumo at this bar).
kumoBullBO = ta.crossover(close, kumoTop)
kumoBearBO = ta.crossunder(close, kumoBottom)

plotshape(markTkCross and tkBullF and markTkSize == 'tiny', title = 'TK+ tiny', style = shape.triangleup, location = location.belowbar, size = size.tiny, color = color.new(effTenkanCol, 10), text = 'TK+', textcolor = effTenkanCol)
plotshape(markTkCross and tkBullF and markTkSize == 'small', title = 'TK+ sm', style = shape.triangleup, location = location.belowbar, size = size.small, color = color.new(effTenkanCol, 10), text = 'TK+', textcolor = effTenkanCol)
plotshape(markTkCross and tkBullF and markTkSize == 'normal', title = 'TK+ md', style = shape.triangleup, location = location.belowbar, size = size.normal, color = color.new(effTenkanCol, 10), text = 'TK+', textcolor = effTenkanCol)
plotshape(markTkCross and tkBearF and markTkSize == 'tiny', title = 'TK− tiny', style = shape.triangledown, location = location.abovebar, size = size.tiny, color = color.new(effKijunCol, 10), text = 'TK−', textcolor = effKijunCol)
plotshape(markTkCross and tkBearF and markTkSize == 'small', title = 'TK− sm', style = shape.triangledown, location = location.abovebar, size = size.small, color = color.new(effKijunCol, 10), text = 'TK−', textcolor = effKijunCol)
plotshape(markTkCross and tkBearF and markTkSize == 'normal', title = 'TK− md', style = shape.triangledown, location = location.abovebar, size = size.normal, color = color.new(effKijunCol, 10), text = 'TK−', textcolor = effKijunCol)

plotshape(markKijunCross and pkBull, title = 'C>K', style = shape.circle, location = location.belowbar, size = size.tiny, color = color.new(effKijunCol, 25))
plotshape(markKijunCross and pkBear, title = 'C<K', style = shape.circle, location = location.abovebar, size = size.tiny, color = color.new(effKijunCol, 25))

plotshape(markTenkanCross and ptBull, title = 'C>T', style = shape.square, location = location.belowbar, size = size.tiny, color = color.new(effTenkanCol, 35))
plotshape(markTenkanCross and ptBear, title = 'C<T', style = shape.square, location = location.abovebar, size = size.tiny, color = color.new(effTenkanCol, 35))

plotshape(markKumoTwist and kumoTwistUp, title = 'Twist+', style = shape.xcross, location = location.belowbar, size = size.tiny, color = color.new(effTwistUpCol, 0), offset = shift, text = 'Kt', textcolor = effTwistUpCol)
plotshape(markKumoTwist and kumoTwistDn, title = 'Twist−', style = shape.xcross, location = location.abovebar, size = size.tiny, color = color.new(effTwistDnCol, 0), offset = shift, text = 'Kt', textcolor = effTwistDnCol)

plotshape(markKumoBreak and kumoBullBO, title = 'Kumo↑', style = shape.diamond, location = location.belowbar, size = size.tiny, color = color.new(effKboBull, 0), text = 'Kb', textcolor = color.new(effKboBull, 0))
plotshape(markKumoBreak and kumoBearBO, title = 'Kumo↓', style = shape.diamond, location = location.abovebar, size = size.tiny, color = color.new(effKboBear, 0), text = 'Kb', textcolor = color.new(effKboBear, 0))

alertcondition(tkBullF, 'TK Cross Bullish', 'Ichimoku: Tenkan crossed above Kijun')
alertcondition(tkBearF, 'TK Cross Bearish', 'Ichimoku: Tenkan crossed below Kijun')
alertcondition(kumoBullBO, 'Kumo Breakout Up', 'Ichimoku: price closed above the Kumo')
alertcondition(kumoBearBO, 'Kumo Breakout Down', 'Ichimoku: price closed below the Kumo')
alertcondition(kumoTwistUp, 'Kumo Twist Bullish', 'Ichimoku: Senkou A crossed above Senkou B (future Kumo turns bullish)')
alertcondition(kumoTwistDn, 'Kumo Twist Bearish', 'Ichimoku: Senkou A crossed below Senkou B (future Kumo turns bearish)')

//=== 8) Bar colors ================================================================
tintKumo =
     not hasKumo ? neutralBarColor :
     aboveKumo ? color.new(cBull, 78) :
     belowKumo ? color.new(cBear, 78) : color.new(#787B86, 82)

tintTk =
     tkStackBull ? color.new(isBrand ? BRAND_GREEN : #2962FF, 78) :
     tkStackBear ? color.new(isBrand ? BRAND_RED : #B71C1C, 78) : neutralBarColor

tintBoth =
     hasKumo and aboveKumo and tkStackBull ? color.new(cBull, 78) :
     hasKumo and belowKumo and tkStackBear ? color.new(cBear, 78) :
     hasKumo and insideKumo ? color.new(#787B86, 82) :
     not hasKumo ? neutralBarColor :
     color.new(#5D6D7E, 80)

tintMixColored =
     barPalette == 'Kumo' ? tintKumo :
     barPalette == 'TK stack' ? tintTk :
     tintBoth

tintMixMono =
     barPalette == 'Kumo' ? tintKumoMono :
     barPalette == 'TK stack' ? tintTkMono :
     tintBothMono

tintMix = isMono ? tintMixMono : tintMixColored

showBarPaint = colorBars or isMono
barPaint = showBarPaint ? (isMono and not colorBars ? color.new(M_MID, 0) : tintMix) : na

barcolor(showBarPaint ? barPaint : na, title = 'Crypex candles')

bgcolor(
     shadeVsKumo ? (
     not hasKumo ? na :
     aboveKumo ? effShadeAbove :
     belowKumo ? effShadeBelow :
     effShadeIn) : na,
     title = 'Bg vs Kumo')

//=== 9a) Ichimoku analysis engine ================================================
// Future cloud: senkouA / senkouB at current bar are projected `shift` bars AHEAD.
// A > B now means the cloud that will appear in the future is bullish.
bool futureCloudBull = senkouA > senkouB
bool futureCloudBear = senkouA < senkouB

// Kijun flat: Donchian midpoint stays exactly the same when no new 26-bar H/L is made.
float kijunSlope5  = kijun - kijun[5]
bool  kijunFlat    = isFlat(kijun, flatBars + 1)
bool  kijunRising  = not kijunFlat and kijunSlope5 > 0
bool  kijunFalling = not kijunFlat and kijunSlope5 < 0

// Senkou B flat: same logic at the lagged (chart-drawn) position
bool  senkouBFlatNow = isFlat(senkouB, flatBars + 1)
bool  senkouAFlatNow = isFlat(senkouA, flatBars + 1)
float senkBLag     = senkouB[shift]
float senkBLag5    = senkouB[shift + 5]
bool  senkouBFlat  = not na(senkBLag) and senkouBFlatNow[shift]

// Tenkan distance from price (ATR-normalised) — high values signal overextension
float tenkanDistAtr = atr14 > 0 ? math.abs(close - tenkan) / atr14 : na

// Chikou reference candle (the one Chikou is drawn against) for scoring
float chRefHi    = high[shift]
float chRefLo    = low[shift]
float chikouScore = na(chRefHi) or na(chRefLo) ? 0.0 :
     close > chRefHi ? 1.0 :
     close < chRefLo ? -1.0 : 0.0

// Ichimoku composite score (theoretical range ≈ ±6.5)
// Price vs Kumo ±2 | TK stack ±1 | Price vs Kijun ±1 | Future cloud ±1 | Chikou ±1 | Kijun slope ±0.5
float ichiScore =
     (aboveKumo ? 2.0 : belowKumo ? -2.0 : 0.0) +
     (tkStackBull ? 1.0 : tkStackBear ? -1.0 : 0.0) +
     (close > kijun ? 1.0 : close < kijun ? -1.0 : 0.0) +
     (futureCloudBull ? 1.0 : futureCloudBear ? -1.0 : 0.0) +
     chikouScore +
     (kijunRising ? 0.5 : kijunFalling ? -0.5 : 0.0)

string ichiBias =
     ichiScore >= 5.5 ? 'Strong Bullish' :
     ichiScore >=  2.0 ? 'Bullish Bias' :
     ichiScore <= -5.5 ? 'Strong Bearish' :
     ichiScore <= -2.0 ? 'Bearish Bias' :
     'Neutral / Range'

//=== 9) Status table =============================================================
var table t = na

// Derived labels
string regime =
     not hasKumo ? 'Warm-up (no Kumo)' :
     aboveKumo   ? 'Above Kumo'        :
     belowKumo   ? 'Below Kumo'        : 'Inside Kumo'

string align =
     hasKumo and kumoBull and aboveKumo and tkStackBull       ? 'Strong bull stack'           :
     hasKumo and not kumoBull and belowKumo and tkStackBear   ? 'Strong bear stack'           :
     hasKumo and aboveKumo and not tkStackBull                ? 'Pullback risk (above cloud)' :
     hasKumo and belowKumo and not tkStackBear                ? 'Bounce risk (below cloud)'   :
     'Mixed / transit'

string _mtfTfLabel = showMtfCloud and mtfIsHigher ? ' (' + mtfTfStr + ')' : ''
string mtfReg = showMtfCloud and mtfTooFar ? 'HTF too far — pick a closer TF' : not showMtfCloud or not mtfIsHigher ? '—' : not mtfHas ? 'HTF warm-up' : mtfAbove ? 'Above Kumo' + _mtfTfLabel : mtfBelow ? 'Below Kumo' + _mtfTfLabel : 'In Kumo' + _mtfTfLabel
string mtfCloudTxt =
     not showMtfCloud or not mtfIsHigher or not mtfHas ? '—' :
     mtfCloudBull ? (isMono ? 'HTF light' : 'HTF green') :
     (isMono ? 'HTF dark' : 'HTF red')

string cloudLbl =
     hasKumo ? (kumoBull ? (isMono ? 'Light cloud (A>B)' : 'Green (A>B)') : (isMono ? 'Dark cloud (A<B)' : 'Red (A<B)')) : '—'

if showTable and barstate.islast
    if na(t)
        t := table.new(tablePos, 2, 19, border_width = 1, frame_color = color.new(color.gray, 40), border_color = color.new(color.gray, 55))
    table.clear(t, 0, 0, 1, 18)

    // ── semantic text colours ──────────────────────────────────────────────
    color tBull = isMono ? M_LIGHT  : cBull
    color tBear = isMono ? M_DARK   : cBear
    color tWarn = isMono ? M_ACCENT : color.new(#FF9800, 0)
    color tBase = color.white

    // ── alternating bg ─────────────────────────────────────────────────────
    color bg0 = color.new(color.black, 85)
    color bg1 = color.new(color.black, 90)

    // ── formatted strings ──────────────────────────────────────────────────
    float kumoThickAtr  = atr14 > 0 and hasKumo ? kumoThick / atr14 : na
    string thickRating  = na(kumoThickAtr) ? '' : kumoThickAtr > 1.5 ? ' · Thick' : kumoThickAtr < 0.5 ? ' · Thin' : ' · Normal'
    string thickStr     = not hasKumo ? '—' : str.tostring(kumoThick, format.mintick) + ' (' + str.tostring(math.round(thickTicks)) + ' t)' + thickRating
    string distStr      = not hasKumo or not showDistRows ? '—' : '↑ ' + str.tostring(math.round(dTopTicks)) + ' t / ↓ ' + str.tostring(math.round(dBotTicks)) + ' t'
    string nearStr      = not hasKumo or na(distNearestAtr) ? '—' : str.tostring(distNearest, format.mintick) + ' · ' + str.tostring(distNearestAtr, '#.##') + ' ATR'
    string tkDistStr    = na(tenkanDistAtr) ? '—' : str.tostring(tenkanDistAtr, '#.##') + ' ATR' + (tenkanDistAtr > 1.0 ? ' ⚠ overext' : tenkanDistAtr < 0.3 ? ' · near' : '')
    string kijSlopeStr  = kijunFlat ? 'Flat · equilibrium magnet' : kijunRising ? 'Rising' : kijunFalling ? 'Falling' : 'Sideways'
    string senkBStr     = na(senkBLag) ? '—' : senkouBFlat ? 'Flat S/R @ ' + str.tostring(senkBLag, format.mintick) : senkBLag > senkBLag5 ? 'Rising' : senkBLag < senkBLag5 ? 'Falling' : 'Sideways'
    string futureTxt    = futureCloudBull ? (isMono ? 'Light (A>B)' : 'Bullish (A>B)') : futureCloudBear ? (isMono ? 'Dark (A<B)' : 'Bearish (A<B)') : 'Flat / twisting'
    string chikTxt      = na(chRefHi) or na(chRefLo) ? 'Warm-up' : close > chRefHi ? 'Clear bullish' : close < chRefLo ? 'Clear bearish' : 'Trapped (neutral)'
    string scoreStr     = str.tostring(ichiScore, '#.#') + '  →  ' + ichiBias

    // ── per-row value colours ──────────────────────────────────────────────
    color regimeCol   = not hasKumo ? tBase : aboveKumo ? tBull : belowKumo ? tBear : tWarn
    color cloudCol    = not hasKumo ? tBase : kumoBull ? tBull : tBear
    color futureCol   = futureCloudBull ? tBull : futureCloudBear ? tBear : tBase
    color alignCol    = (aboveKumo and tkStackBull) ? tBull : (belowKumo and tkStackBear) ? tBear : tWarn
    color tkMomCol    = tkStackBull   ? tBull : tkStackBear   ? tBear : tBase
    color kijSlopeCol = kijunFlat     ? tWarn : kijunRising   ? tBull : kijunFalling ? tBear : tBase
    color senkBCol    = na(senkBLag) ? tBase : senkouBFlat ? tWarn : senkBLag > senkBLag5 ? tBull : senkBLag < senkBLag5 ? tBear : tBase
    color chikCol     = chikouScore > 0 ? tBull : chikouScore < 0 ? tBear : tWarn
    color tkDistCol   = na(tenkanDistAtr) ? tBase : tenkanDistAtr > 1.0 ? tWarn : tBase
    color biasCol     = ichiScore >= 2.0 ? tBull : ichiScore <= -2.0 ? tBear : tWarn

    // ── cells ──────────────────────────────────────────────────────────────
    table.cell(t, 0, 0,  'Crypex',           text_color = color.white, bgcolor = effTblHdrBg, text_size = tableSize)
    table.cell(t, 1, 0,  syminfo.ticker,      text_color = color.white, bgcolor = effTblHdrBg, text_size = tableSize)

    table.cell(t, 0, 1,  'Tenkan / Kijun',   text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 1,  str.tostring(tenkan, format.mintick) + ' / ' + str.tostring(kijun, format.mintick), text_color = tBase,      bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 2,  'Regime',            text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 2,  regime,              text_color = regimeCol,   bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 3,  'Cloud (now)',       text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 3,  cloudLbl,            text_color = cloudCol,    bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 4,  'Future cloud',      text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 4,  futureTxt,           text_color = futureCol,   bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 5,  'Alignment',         text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 5,  align,               text_color = alignCol,    bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 6,  'TK momentum',       text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 6,  tkStackBull ? 'Tenkan > Kijun' : tkStackBear ? 'Tenkan < Kijun' : 'Flat', text_color = tkMomCol, bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 7,  'Kijun slope',       text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 7,  kijSlopeStr,         text_color = kijSlopeCol, bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 8,  'Senkou B',          text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 8,  senkBStr,            text_color = senkBCol,    bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 9,  'Chikou',            text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 9,  chikTxt,             text_color = chikCol,     bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 10, 'Tenkan distance',   text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 10, tkDistStr,           text_color = tkDistCol,   bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 11, 'Kumo thickness',    text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 11, showDistRows ? thickStr : '—', text_color = tBase, bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 12, 'Distance (edge)',   text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 12, showDistRows ? distStr : '—', text_color = tBase, bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 13, 'Nearest · ATR',     text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 13, showDistRows ? nearStr : '—', text_color = tBase, bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 14, 'ATR(14)',           text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 14, showAtrRow ? str.tostring(atr14, format.mintick) : '—', text_color = tBase, bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 15, 'Score · Bias',      text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 15, scoreStr,            text_color = biasCol,     bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 16, 'HTF Kumo',          text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    table.cell(t, 1, 16, showMtfInTable ? mtfReg : '—', text_color = tBase, bgcolor = bg1, text_size = tableSize)

    table.cell(t, 0, 17, 'HTF cloud',         text_color = color.gray,  bgcolor = bg0, text_size = tableSize)
    table.cell(t, 1, 17, showMtfInTable ? mtfCloudTxt : '—', text_color = tBase, bgcolor = bg0, text_size = tableSize)

    table.cell(t, 0, 18, 'Shift · Params',    text_color = color.gray,  bgcolor = bg1, text_size = tableSize)
    string shiftTxt = (showMtfCloud and mtfIsHigher ? str.tostring(shift) + ' ch · ' + str.tostring(mtfForwardBars) + ' HTF shift' : str.tostring(shift) + ' ch') + '  |  ' + str.tostring(conversionPeriods) + '/' + str.tostring(basePeriods) + '/' + str.tostring(laggingSpanBPeriods)
    table.cell(t, 1, 18, shiftTxt,            text_color = tBase,       bgcolor = bg1, text_size = tableSize)

//=== 10) Ichimoku S/R levels =====================================================
// Short name for chart labels (strip star ratings)
srCleanNm(string raw) =>
    string r = raw
    r := str.replace_all(r, ' ★★★★', '')
    r := str.replace_all(r, ' ★★★', '')
    r := str.replace_all(r, ' ★★', '')
    r := str.replace_all(r, ' ★', '')
    r

// Strength tiers:
//   ★★★★ Flat Senkou B / Flat Kijun (historical equilibrium magnets)
//   ★★★  Kijun (dynamic)
//   ★★   Kumo edges (dynamic)
//
// Visuals:
//   Flat    → thick solid line, full colour
//   Dynamic → thin dashed line, 40% transparent colour
//   HTF     → dotted line, extra transparent, [H] tag
//   Cluster → shaded box when ≥2 levels within 0.25 ATR
var line[]  _srLines  = array.new<line>()
var label[] _srLabels = array.new<label>()
var box[]   _srBoxes  = array.new<box>()

if showSRLines and barstate.islast and hasKumo and atr14 > 0
    for _oldLine in _srLines
        line.delete(_oldLine)
    for _lb in _srLabels
        label.delete(_lb)
    for _bx in _srBoxes
        box.delete(_bx)
    array.clear(_srLines)
    array.clear(_srLabels)
    array.clear(_srBoxes)

    float _clThr = atr14 * 0.25
    // Cluster OFF: only merge levels at the same price, so nearby levels are no longer hidden.
    float _mergeThr = srCluster ? _clThr : syminfo.mintick

    // ── Raw level list ────────────────────────────────────────────────────
    // price | name | isFlat | strength(1-4) | isHTF
    float[]  _lp  = array.new_float()
    string[] _ln  = array.new_string()
    bool[]   _lf  = array.new_bool()
    int[]    _ls  = array.new_int()
    bool[]   _lh  = array.new_bool()

    // ── 1. Kijun — always the primary trendline S/R ───────────────────────
    array.push(_lp, kijun)
    array.push(_ln, kijunFlat ? 'Kijun ★★★★' : 'Kijun ★★★')
    array.push(_lf, kijunFlat)
    array.push(_ls, kijunFlat ? 4 : 3)
    array.push(_lh, false)

    // ── 2. Current cloud edges (each edge judged by the span that forms it) ──
    bool _botIsB   = senkouB[shift] <= senkouA[shift]
    bool _botFlat  = _botIsB ? senkouBFlat : senkouAFlatNow[shift]
    bool _topFlat  = _botIsB ? senkouAFlatNow[shift] : senkouBFlat
    array.push(_lp, kumoBottom)
    array.push(_ln, _botFlat ? 'Kumo bottom ★★★★' : 'Kumo bottom ★★')
    array.push(_lf, _botFlat)
    array.push(_ls, _botFlat ? 4 : 2)
    array.push(_lh, false)

    if kumoTop - kumoBottom > _clThr
        array.push(_lp, kumoTop)
        array.push(_ln, _topFlat ? 'Kumo top ★★★★' : 'Kumo top ★★')
        array.push(_lf, _topFlat)
        array.push(_ls, _topFlat ? 4 : 2)
        array.push(_lh, false)

    // ── 3. Historical flat Senkou B — the REAL historical S/R ──────────────
    // Senkou B stays EXACTLY the same while no new 52-bar H/L is made. Scan the past
    // 100 chart bars (step 5); a drawn Senkou B that was flat for flatBars bars there
    // is a lasting S/R magnet. Clustering merges duplicates.
    for _ni = 1 to 20
        int _n = _ni * 5
        float _sb = senkouB[shift + _n]
        if not na(_sb) and senkouBFlatNow[shift + _n]
            array.push(_lp, _sb)
            array.push(_ln, 'Old Kumo B ★★★★')
            array.push(_lf, true)
            array.push(_ls, 4)
            array.push(_lh, false)

    // ── 4. Historical flat Kijun levels ───────────────────────────────────
    for _ni = 1 to 12
        int _n = _ni * 5
        float _hkj = kijun[_n]
        if not na(_hkj) and kijunFlat[_n] and math.abs(_hkj - kijun) > _clThr
            array.push(_lp, _hkj)
            array.push(_ln, 'Old Kijun ★★★★')
            array.push(_lf, true)
            array.push(_ls, 4)
            array.push(_lh, false)

    // ── 5. HTF: current edges + historical flat Senkou B ──────────────────
    if showMtfCloud and mtfIsHigher and mtfHas
        array.push(_lp, mtfBot)
        array.push(_ln, mtfBotFlat ? 'HTF Kumo bottom ★★★★' : 'HTF Kumo bottom ★★')
        array.push(_lf, mtfBotFlat)
        array.push(_ls, mtfBotFlat ? 4 : 2)
        array.push(_lh, true)

        if mtfTop - mtfBot > _clThr
            array.push(_lp, mtfTop)
            array.push(_ln, mtfTopFlat ? 'HTF Kumo top ★★★★' : 'HTF Kumo top ★★')
            array.push(_lf, mtfTopFlat)
            array.push(_ls, mtfTopFlat ? 4 : 2)
            array.push(_lh, true)

        // Scan back in steps of ~5 HTF bars (flatness itself is measured on the HTF).
        int _hStep = math.max(5, math.round(5 * mtfRatio))
        for _ni = 1 to 20
            int _n = _ni * _hStep
            if _n > 4900
                break
            float _msb = mtfKumoB[_n]
            if not na(_msb) and mtfKumoBFlat[_n]
                array.push(_lp, _msb)
                array.push(_ln, 'Old HTF Kumo B ★★★★')
                array.push(_lf, true)
                array.push(_ls, 4)
                array.push(_lh, true)

    int   _sz  = array.size(_lp)
    int[] _idx = array.sort_indices(_lp, order.ascending)

    // ── Cluster detection ─────────────────────────────────────────────────
    float[]  _cMin = array.new_float()
    float[]  _cMax = array.new_float()
    string[] _cNm  = array.new_string()
    bool[]   _cFl  = array.new_bool()
    int[]    _cCt  = array.new_int()
    bool[]   _cH   = array.new_bool()

    bool[] _vis = array.new_bool(_sz, false)

    for _i = 0 to _sz - 1
        int _oi = array.get(_idx, _i)
        if not array.get(_vis, _oi)
            float  _pLo = array.get(_lp, _oi)
            float  _pHi = _pLo
            string _nm  = array.get(_ln, _oi)
            bool   _fl  = array.get(_lf, _oi)
            bool   _hh  = array.get(_lh, _oi)
            int    _ct  = 1
            array.set(_vis, _oi, true)

            int _j = _i + 1
            while _j < _sz
                int   _oj = array.get(_idx, _j)
                float _np = array.get(_lp, _oj)
                if _np - _pHi > _mergeThr
                    break
                if not array.get(_vis, _oj)
                    _pHi := _np
                    // Same-name duplicates (e.g. several Old Kumo B hits) are listed once.
                    string _add = array.get(_ln, _oj)
                    if not str.contains(_nm, _add)
                        _nm := _nm + ' + ' + _add
                    if array.get(_lf, _oj)
                        _fl := true
                    if not array.get(_lh, _oj)
                        _hh := false
                    _ct += 1
                    array.set(_vis, _oj, true)
                _j += 1

            array.push(_cMin, _pLo)
            array.push(_cMax, _pHi)
            array.push(_cNm,  _nm)
            array.push(_cFl,  _fl)
            array.push(_cCt,  _ct)
            array.push(_cH,   _hh)

    // ── Count supports (below price) for nearest-first numbering ──────────
    int _nCl = array.size(_cMin)
    int _nS  = 0
    for _k = 0 to _nCl - 1
        if (array.get(_cMin, _k) + array.get(_cMax, _k)) / 2 < close
            _nS += 1

    int _sCur = _nS
    int _rCur = 1

    // ── Draw ──────────────────────────────────────────────────────────────
    for _k = 0 to _nCl - 1
        float  _lo  = array.get(_cMin, _k)
        float  _hi  = array.get(_cMax, _k)
        float  _mid = (_lo + _hi) / 2
        string _nm  = array.get(_cNm, _k)
        bool   _fl  = array.get(_cFl, _k)
        int    _ct  = array.get(_cCt, _k)
        bool   _isH = array.get(_cH,  _k)
        bool   _isR = _mid >= close

        // Rank 1 = nearest to price on each side.
        int    _rank = _isR ? _rCur : _sCur
        color  _base = _isR ? (isMono ? M_DARK : cBear) : (isMono ? M_LIGHT : cBull)
        if _isR
            _rCur += 1
        else
            _sCur -= 1

        if _rank <= srMaxLevels
            string _rn  = (_isR ? 'R' : 'S') + str.tostring(_rank)
            string _ht  = _isH ? '[H]' : ''
            bool   _isZone = _ct > 1 and _hi > _lo
            string _rng = _isZone ? str.tostring(_lo, format.mintick) + ' – ' + str.tostring(_hi, format.mintick) : str.tostring(_lo, format.mintick)
            string _lbl = _rn + _ht + ' · ' + _rng + ' · ' + srCleanNm(_nm)

            int   _alpha  = _isH ? 55 : (_fl ? 0 : 40)
            color _col    = color.new(_base, _alpha)
            int   _lw     = _fl ? 2 : 1
            int   _x2     = bar_index + srExtend
            color _bgFlat = color.new(_base, _fl ? 78 : 88)

            if srCluster and _isZone
                array.push(_srBoxes, box.new(bar_index, _hi, _x2, _lo, border_color = _col, bgcolor = _bgFlat, border_width = _lw))
                array.push(_srLabels, label.new(_x2, _mid, _lbl, style = label.style_label_left, color = color.new(color.black, 100), textcolor = _col, size = tableSize))
            else
                string _lst = _isH ? line.style_dotted : (_fl ? line.style_solid : line.style_dashed)
                array.push(_srLines,  line.new(bar_index, _lo, _x2, _lo, color = _col, width = _lw, style = _lst))
                array.push(_srLabels, label.new(_x2, _lo, _lbl, style = label.style_label_left, color = color.new(color.black, 100), textcolor = _col, size = tableSize))

    // ── Kumo midpoint (outside the ranked system) ─────────────────────────
    if showKumoMid
        float  _mMid  = (kumoTop + kumoBottom) / 2
        string _mRole = _mMid >= close ? 'R' : 'S'
        array.push(_srLines,  line.new(bar_index, _mMid, bar_index + srExtend, _mMid, color = effKumoMidCol, width = 1, style = line.style_dotted))
        array.push(_srLabels, label.new(bar_index + srExtend, _mMid, _mRole + ' · ' + str.tostring(_mMid, format.mintick) + ' · Kumo mid', style = label.style_label_left, color = color.new(color.black, 100), textcolor = effKumoMidCol, size = tableSize))

//=== 11) Watermark ===============================================================
// "crypex" brand green · ".org" dark (white on dark charts).
// TradingView shows only one table per position, so all parts live in one table; text is aligned
// toward the neighbouring cell so the parts sit as close together as the cell padding allows.
var table wm = na
if showWatermark and barstate.islast
    if na(wm)
        wm := table.new(wmPos, 3, 1, border_width = 0, frame_width = 0)
    string wmSize = wmSizeStr == 'small' ? size.small : wmSizeStr == 'normal' ? size.normal : wmSizeStr == 'large' ? size.large : size.huge
    // Empty spacer cell keeps the text clear of the TradingView logo in the bottom-left corner.
    float spacerW = wmPos == position.bottom_left ? wmGap : 0.0
    bool bgLight = (color.r(chart.bg_color) * 299 + color.g(chart.bg_color) * 587 + color.b(chart.bg_color) * 114) / 1000 > 128
    color orgCol = bgLight ? BRAND_DARK : color.white
    table.cell(wm, 0, 0, '', width = spacerW, text_size = wmSize)
    table.cell(wm, 1, 0, 'crypex', text_color = color.new(BRAND_GREEN, wmTransp), text_size = wmSize, text_halign = text.align_right, text_valign = text.align_bottom, text_formatting = text.format_bold)
    table.cell(wm, 2, 0, '.org',   text_color = color.new(orgCol, wmTransp),      text_size = wmSize, text_halign = text.align_left, text_valign = text.align_bottom, text_formatting = text.format_bold)
ichimoku-mtf-kumo.mq5 · MQL5 · ۸۰۸ خط
//+------------------------------------------------------------------+
//| Crypex | Ichimoku Advanced (MT5)                                  |
//| HTF Kumo + ranked S/R levels + status panel + alerts            |
//| © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org |
//| License: personal, non-commercial use only. Redistribution or    |
//| resale without written permission is prohibited.                 |
//| مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون          |
//| اجازه‌ی کتبی ممنوع است.                                           |
//| Educational tool, not investment advice.                         |
//+------------------------------------------------------------------+
#property copyright "Crypex.org"
#property link      "https://crypex.org"
#property version   "1.00"
#property description "Ichimoku with higher-timeframe Kumo, S/R levels and status panel"
#property indicator_chart_window
#property indicator_buffers 13
#property indicator_plots   10

#property indicator_label1  "Tenkan-sen"
#property indicator_type1   DRAW_LINE
#property indicator_label2  "Kijun-sen"
#property indicator_type2   DRAW_LINE
#property indicator_label3  "Chikou Span"
#property indicator_type3   DRAW_LINE
#property indicator_label4  "Kumo"
#property indicator_type4   DRAW_FILLING
#property indicator_label5  "HTF Kumo"
#property indicator_type5   DRAW_FILLING
#property indicator_label6  "HTF Kijun"
#property indicator_type6   DRAW_LINE
#property indicator_label7  "TK Cross Up"
#property indicator_type7   DRAW_ARROW
#property indicator_label8  "TK Cross Down"
#property indicator_type8   DRAW_ARROW
#property indicator_label9  "Kumo Breakout Up"
#property indicator_type9   DRAW_ARROW
#property indicator_label10 "Kumo Breakout Down"
#property indicator_type10  DRAW_ARROW

enum ENUM_PANEL_POS { POS_TOP_RIGHT, POS_TOP_LEFT, POS_BOTTOM_RIGHT, POS_BOTTOM_LEFT };
enum ENUM_CAL_MODE  { CAL_AUTO, CAL_247, CAL_WEEKDAYS };

input group "Ichimoku"
input int    InpTenkan    = 9;      // Tenkan-sen
input int    InpKijun     = 26;     // Kijun-sen
input int    InpSenkouB   = 52;     // Senkou Span B
input int    InpDisp      = 26;     // Displacement (Senkou +, Chikou -)
input int    InpFlatBars  = 10;     // Min flat bars (Kijun / Senkou B)
input group "Lines and Kumo"
input bool   InpShowTenkan = true;
input bool   InpShowKijun  = true;
input bool   InpShowChikou = true;
input bool   InpShowKumo   = true;
input color  InpTenkanColor = C'11,163,28';
input color  InpKijunColor  = C'255,10,23';
input color  InpChikouColor = C'120,123,134';
input color  InpBullKumo    = C'129,199,132';
input color  InpBearKumo    = C'239,154,154';
input group "Higher timeframe Kumo"
input bool   InpShowHtf     = true;
input bool   InpHtfAuto     = true;            // Auto next timeframe
input ENUM_TIMEFRAMES InpHtfManual = PERIOD_H4; // Manual timeframe
input bool   InpShowHtfKijun = true;
input ENUM_CAL_MODE InpCalendar = CAL_AUTO;    // Market calendar (weekly/monthly HTF projection)
input double InpRatioOverride = 0.0;           // Chart bars per HTF bar (0 = auto)
input int    InpHtfBars     = 3000;            // HTF bars to load
input color  InpHtfBullKumo = C'178,223,180';
input color  InpHtfBearKumo = C'247,196,196';
input color  InpHtfKijunColor = C'178,34,34';
input group "Markers and alerts"
input bool   InpMarkTk      = true;            // TK cross arrows
input bool   InpMarkKumoBo  = false;           // Kumo breakout arrows
input bool   InpAlertPopup  = true;
input bool   InpAlertPush   = false;
input bool   InpAlertMail   = false;
input group "Status panel"
input bool   InpShowPanel   = true;
input ENUM_PANEL_POS InpPanelPos = POS_TOP_RIGHT;
input int    InpPanelFont   = 8;
input color  InpPanelBg     = C'21,26,32';
input group "Support / Resistance levels"
input bool   InpShowSR      = true;
input int    InpSrExtend    = 20;              // Extend (bars)
input bool   InpSrCluster   = true;            // Merge nearby levels into zones
input int    InpSrMax       = 10;              // Max levels per side
input color  InpSupportColor    = C'11,163,28';
input color  InpResistanceColor = C'255,10,23';
input group "Watermark"
input bool   InpShowWm      = true;            // Show crypex.org watermark
input int    InpWmSize      = 18;
input int    InpWmGap       = 60;              // Gap from left (px), clears the chart logo
input int    InpWmBottom    = 14;              // Gap from bottom (px)

const string PFX = "CRX_ICH_";
const color  BRAND_GREEN = C'11,163,28';

double BufTenkan[], BufKijun[], BufChikou[], BufSpanA[], BufSpanB[];
double BufHtfA[], BufHtfB[], BufHtfKijun[];
double BufTkUp[], BufTkDn[], BufKbUp[], BufKbDn[], BufAtr[];

// higher-timeframe store (chronological, index 0 = oldest)
double   hHi[], hLo[], hT[], hK[], hA[], hB[];
datetime hTm[];
int      jMap[];
int      hN = 0;
ENUM_TIMEFRAMES g_htf = PERIOD_CURRENT;
bool     g_htfOn = false;
bool     g_htfReady = false;
int      g_fwd = 0;
double   g_ratio = 1.0;
double   g_tick = 0.0;
datetime g_lastBar = 0;
datetime g_lastAlertBar = 0;
uint     g_lastPaint = 0;
uint     g_lastHtfTry = 0;
bool     g_firstDone = false;

//+------------------------------------------------------------------+
bool IsCryptoSymbol()
  {
   string s = _Symbol;
   string keys[] = {"BTC", "ETH", "LTC", "XRP", "SOL", "DOGE", "ADA", "BNB", "TRX", "DOT"};
   for(int i = 0; i < ArraySize(keys); i++)
      if(StringFind(s, keys[i]) >= 0) return true;
   return false;
  }

ENUM_TIMEFRAMES AutoNextTf()
  {
   int s = PeriodSeconds(_Period);
   if(s <= 60)    return PERIOD_H1;
   if(s <= 3600)  return PERIOD_H4;
   if(s <= 14400) return PERIOD_D1;
   if(s <= 86400) return PERIOD_W1;
   return PERIOD_MN1;
  }

//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,  BufTenkan,   INDICATOR_DATA);
   SetIndexBuffer(1,  BufKijun,    INDICATOR_DATA);
   SetIndexBuffer(2,  BufChikou,   INDICATOR_DATA);
   SetIndexBuffer(3,  BufSpanA,    INDICATOR_DATA);
   SetIndexBuffer(4,  BufSpanB,    INDICATOR_DATA);
   SetIndexBuffer(5,  BufHtfA,     INDICATOR_DATA);
   SetIndexBuffer(6,  BufHtfB,     INDICATOR_DATA);
   SetIndexBuffer(7,  BufHtfKijun, INDICATOR_DATA);
   SetIndexBuffer(8,  BufTkUp,     INDICATOR_DATA);
   SetIndexBuffer(9,  BufTkDn,     INDICATOR_DATA);
   SetIndexBuffer(10, BufKbUp,     INDICATOR_DATA);
   SetIndexBuffer(11, BufKbDn,     INDICATOR_DATA);
   SetIndexBuffer(12, BufAtr,      INDICATOR_CALCULATIONS);

   g_tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(g_tick <= 0) g_tick = _Point;

   // plots: 0 Tenkan, 1 Kijun, 2 Chikou, 3 Kumo, 4 HTF Kumo, 5 HTF Kijun, 6..9 arrows
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, InpTenkanColor);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, InpKijunColor);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, InpChikouColor);
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2);
   PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 1);
   PlotIndexSetInteger(2, PLOT_SHIFT, -InpDisp);
   PlotIndexSetInteger(3, PLOT_SHIFT, InpDisp);
   PlotIndexSetInteger(3, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, InpBullKumo);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 1, InpBearKumo);
   PlotIndexSetInteger(4, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, InpHtfBullKumo);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, InpHtfBearKumo);
   PlotIndexSetInteger(5, PLOT_LINE_COLOR, InpHtfKijunColor);
   PlotIndexSetInteger(5, PLOT_LINE_WIDTH, 1);
   PlotIndexSetInteger(5, PLOT_LINE_STYLE, STYLE_DASH);
   PlotIndexSetInteger(6, PLOT_ARROW, 233);
   PlotIndexSetInteger(7, PLOT_ARROW, 234);
   PlotIndexSetInteger(8, PLOT_ARROW, 159);
   PlotIndexSetInteger(9, PLOT_ARROW, 159);
   PlotIndexSetInteger(6, PLOT_LINE_COLOR, InpTenkanColor);
   PlotIndexSetInteger(7, PLOT_LINE_COLOR, InpKijunColor);
   PlotIndexSetInteger(8, PLOT_LINE_COLOR, InpTenkanColor);
   PlotIndexSetInteger(9, PLOT_LINE_COLOR, InpKijunColor);
   for(int p = 0; p < 10; p++) PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   if(!InpShowTenkan) PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
   if(!InpShowKijun)  PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
   if(!InpShowChikou) PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_NONE);
   if(!InpShowKumo)   PlotIndexSetInteger(3, PLOT_DRAW_TYPE, DRAW_NONE);

   // higher timeframe
   g_htf = InpHtfAuto ? AutoNextTf() : InpHtfManual;
   int chartSec = PeriodSeconds(_Period);
   int htfSec   = PeriodSeconds(g_htf);
   g_htfOn = InpShowHtf && htfSec > chartSec && chartSec > 0;
   bool weekdays = (InpCalendar == CAL_WEEKDAYS) || (InpCalendar == CAL_AUTO && !IsCryptoSymbol());
   double weekFactor = (weekdays && htfSec >= 604800 && chartSec < 604800) ? 5.0 / 7.0 : 1.0;
   g_ratio = g_htfOn ? (InpRatioOverride > 0 ? InpRatioOverride : (double)htfSec / chartSec * weekFactor) : 1.0;
   g_fwd = g_htfOn ? (int)MathMax(0, MathRound((InpDisp - 1) * g_ratio)) : 0;
   if(g_fwd > 4900) g_htfOn = false;   // too far ahead for a chart this small
   PlotIndexSetInteger(4, PLOT_SHIFT, g_fwd);
   if(!g_htfOn)
     {
      PlotIndexSetInteger(4, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(5, PLOT_DRAW_TYPE, DRAW_NONE);
     }
   else if(!InpShowHtfKijun)
      PlotIndexSetInteger(5, PLOT_DRAW_TYPE, DRAW_NONE);
   if(!InpMarkTk)
     {
      PlotIndexSetInteger(6, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(7, PLOT_DRAW_TYPE, DRAW_NONE);
     }
   if(!InpMarkKumoBo)
     {
      PlotIndexSetInteger(8, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(9, PLOT_DRAW_TYPE, DRAW_NONE);
     }
   IndicatorSetString(INDICATOR_SHORTNAME, "Crypex Ichimoku");
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PFX);
  }

//+------------------------------------------------------------------+
//| helpers                                                          |
//+------------------------------------------------------------------+
double Donchian(const double &hi[], const double &lo[], const int idx, const int len)
  {
   if(idx - len + 1 < 0) return EMPTY_VALUE;
   double h = hi[idx], l = lo[idx];
   for(int k = 1; k < len; k++)
     {
      if(hi[idx - k] > h) h = hi[idx - k];
      if(lo[idx - k] < l) l = lo[idx - k];
     }
   return (h + l) / 2.0;
  }

// value stayed within one tick over the whole window (len+1 bars)
bool IsFlat(const double &arr[], const int idx, const int len)
  {
   if(idx - len < 0) return false;
   double mx = -DBL_MAX, mn = DBL_MAX;
   for(int k = 0; k <= len; k++)
     {
      double v = arr[idx - k];
      if(v == EMPTY_VALUE) return false;
      if(v > mx) mx = v;
      if(v < mn) mn = v;
     }
   return (mx - mn) <= g_tick;
  }

// flatness of the upper (top=true) or lower edge of a Kumo made of spans a and b
bool IsFlatEdge(const double &a[], const double &b[], const int idx, const int len, const bool top)
  {
   if(idx - len < 0) return false;
   double mx = -DBL_MAX, mn = DBL_MAX;
   for(int k = 0; k <= len; k++)
     {
      double va = a[idx - k], vb = b[idx - k];
      if(va == EMPTY_VALUE || vb == EMPTY_VALUE) return false;
      double v = top ? MathMax(va, vb) : MathMin(va, vb);
      if(v > mx) mx = v;
      if(v < mn) mn = v;
     }
   return (mx - mn) <= g_tick;
  }

string Px(const double v)
  {
   return DoubleToString(v, _Digits);
  }

//+------------------------------------------------------------------+
//| Build the higher timeframe Ichimoku and map it to chart bars     |
//+------------------------------------------------------------------+
void BuildHtf(const int n, const datetime &time[])
  {
   g_htfReady = false;
   int want = MathMin(iBars(_Symbol, g_htf), InpHtfBars);
   int need = InpSenkouB + InpDisp + 5;
   if(want < need) return;
   ArrayResize(hHi, want); ArrayResize(hLo, want); ArrayResize(hTm, want);
   if(CopyHigh(_Symbol, g_htf, 0, want, hHi) != want) return;
   if(CopyLow(_Symbol, g_htf, 0, want, hLo) != want) return;
   if(CopyTime(_Symbol, g_htf, 0, want, hTm) != want) return;
   hN = want;
   ArrayResize(hT, hN); ArrayResize(hK, hN); ArrayResize(hA, hN); ArrayResize(hB, hN);
   for(int j = 0; j < hN; j++)
     {
      hT[j] = Donchian(hHi, hLo, j, InpTenkan);
      hK[j] = Donchian(hHi, hLo, j, InpKijun);
      hA[j] = (hT[j] != EMPTY_VALUE && hK[j] != EMPTY_VALUE) ? (hT[j] + hK[j]) / 2.0 : EMPTY_VALUE;
      hB[j] = Donchian(hHi, hLo, j, InpSenkouB);
     }
   ArrayResize(jMap, n);
   int j = 0;
   for(int i = 0; i < n; i++)
     {
      while(j + 1 < hN && hTm[j + 1] <= time[i]) j++;
      jMap[i] = (hTm[0] <= time[i]) ? j : -1;
      int c = jMap[i] - 1;               // last COMPLETED HTF bar: no repainting
      if(c >= 0 && hA[c] != EMPTY_VALUE && hB[c] != EMPTY_VALUE)
        {
         BufHtfA[i] = hA[c];
         BufHtfB[i] = hB[c];
         BufHtfKijun[i] = (hK[c] != EMPTY_VALUE) ? hK[c] : EMPTY_VALUE;
        }
      else
        {
         BufHtfA[i] = EMPTY_VALUE;
         BufHtfB[i] = EMPTY_VALUE;
         BufHtfKijun[i] = EMPTY_VALUE;
        }
     }
   g_htfReady = true;
  }

//+------------------------------------------------------------------+
//| Chart-TF Ichimoku, markers                                       |
//+------------------------------------------------------------------+
void ComputeChart(const int n, const int start, const double &high[], const double &low[], const double &close[])
  {
   for(int i = start; i < n; i++)
     {
      BufTenkan[i] = Donchian(high, low, i, InpTenkan);
      BufKijun[i]  = Donchian(high, low, i, InpKijun);
      BufSpanA[i]  = (BufTenkan[i] != EMPTY_VALUE && BufKijun[i] != EMPTY_VALUE) ? (BufTenkan[i] + BufKijun[i]) / 2.0 : EMPTY_VALUE;
      BufSpanB[i]  = Donchian(high, low, i, InpSenkouB);
      BufChikou[i] = close[i];
      double tr = high[i] - low[i];
      if(i == 0)
         BufAtr[i] = tr;
      else
        {
         tr = MathMax(tr, MathMax(MathAbs(high[i] - close[i - 1]), MathAbs(low[i] - close[i - 1])));
         BufAtr[i] = (BufAtr[i - 1] * 13.0 + tr) / 14.0;
        }
      BufTkUp[i] = EMPTY_VALUE; BufTkDn[i] = EMPTY_VALUE; BufKbUp[i] = EMPTY_VALUE; BufKbDn[i] = EMPTY_VALUE;
      if(i < 1) continue;
      if(BufTenkan[i] == EMPTY_VALUE || BufKijun[i] == EMPTY_VALUE || BufTenkan[i - 1] == EMPTY_VALUE || BufKijun[i - 1] == EMPTY_VALUE) continue;
      if(BufTenkan[i - 1] <= BufKijun[i - 1] && BufTenkan[i] > BufKijun[i]) BufTkUp[i] = low[i] - BufAtr[i] * 0.5;
      if(BufTenkan[i - 1] >= BufKijun[i - 1] && BufTenkan[i] < BufKijun[i]) BufTkDn[i] = high[i] + BufAtr[i] * 0.5;
      int c0 = i - InpDisp, c1 = i - 1 - InpDisp;
      if(c1 < 0 || BufSpanA[c0] == EMPTY_VALUE || BufSpanB[c0] == EMPTY_VALUE || BufSpanA[c1] == EMPTY_VALUE || BufSpanB[c1] == EMPTY_VALUE) continue;
      double top0 = MathMax(BufSpanA[c0], BufSpanB[c0]), bot0 = MathMin(BufSpanA[c0], BufSpanB[c0]);
      double top1 = MathMax(BufSpanA[c1], BufSpanB[c1]), bot1 = MathMin(BufSpanA[c1], BufSpanB[c1]);
      if(close[i - 1] <= top1 && close[i] > top0) BufKbUp[i] = low[i] - BufAtr[i] * 0.9;
      if(close[i - 1] >= bot1 && close[i] < bot0) BufKbDn[i] = high[i] + BufAtr[i] * 0.9;
     }
  }

//+------------------------------------------------------------------+
//| Alerts for the last closed bar                                   |
//+------------------------------------------------------------------+
void Notify(const string msg)
  {
   string full = "Crypex Ichimoku " + _Symbol + " " + EnumToString(_Period) + ": " + msg;
   if(InpAlertPopup) Alert(full);
   if(InpAlertPush)  SendNotification(full);
   if(InpAlertMail)  SendMail("Crypex Ichimoku", full);
  }

void CheckAlerts(const int n, const datetime &time[], const double &close[])
  {
   int b = n - 2;                       // last closed bar
   if(b < InpDisp + 3) return;
   if(time[b] == g_lastAlertBar) return;
   g_lastAlertBar = time[b];
   if(!g_firstDone) return;             // never alert for history on first load
   double t0 = BufTenkan[b], k0 = BufKijun[b], t1 = BufTenkan[b - 1], k1 = BufKijun[b - 1];
   if(t0 == EMPTY_VALUE || k0 == EMPTY_VALUE || t1 == EMPTY_VALUE || k1 == EMPTY_VALUE) return;
   if(t1 <= k1 && t0 > k0) Notify("Tenkan crossed above Kijun");
   if(t1 >= k1 && t0 < k0) Notify("Tenkan crossed below Kijun");
   int c0 = b - InpDisp, c1 = b - 1 - InpDisp;
   if(c1 >= 0 && BufSpanA[c0] != EMPTY_VALUE && BufSpanB[c0] != EMPTY_VALUE && BufSpanA[c1] != EMPTY_VALUE && BufSpanB[c1] != EMPTY_VALUE)
     {
      double top0 = MathMax(BufSpanA[c0], BufSpanB[c0]), bot0 = MathMin(BufSpanA[c0], BufSpanB[c0]);
      double top1 = MathMax(BufSpanA[c1], BufSpanB[c1]), bot1 = MathMin(BufSpanA[c1], BufSpanB[c1]);
      if(close[b - 1] <= top1 && close[b] > top0) Notify("Price closed above the Kumo");
      if(close[b - 1] >= bot1 && close[b] < bot0) Notify("Price closed below the Kumo");
     }
   if(BufSpanA[b] != EMPTY_VALUE && BufSpanB[b] != EMPTY_VALUE && BufSpanA[b - 1] != EMPTY_VALUE && BufSpanB[b - 1] != EMPTY_VALUE)
     {
      if(BufSpanA[b - 1] <= BufSpanB[b - 1] && BufSpanA[b] > BufSpanB[b]) Notify("Kumo twist: future Kumo turns bullish");
      if(BufSpanA[b - 1] >= BufSpanB[b - 1] && BufSpanA[b] < BufSpanB[b]) Notify("Kumo twist: future Kumo turns bearish");
     }
  }

//+------------------------------------------------------------------+
//| Text objects                                                     |
//+------------------------------------------------------------------+
void Label(const string name, const int corner, const int anchor, const int x, const int y,
           const string text, const color clr, const int size, const string font = "Arial")
  {
   if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
   ObjectSetString(0, name, OBJPROP_FONT, font);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 10);
  }

void PaintWatermark()
  {
   if(!InpShowWm)
     {
      ObjectDelete(0, PFX + "WM1");
      ObjectDelete(0, PFX + "WM2");
      return;
     }
   long bg = ChartGetInteger(0, CHART_BACKGROUND_COLOR);
   int r = (int)(bg & 0xFF), g = (int)((bg >> 8) & 0xFF), b = (int)((bg >> 16) & 0xFF);
   bool light = (r * 299 + g * 587 + b * 114) / 1000 > 128;
   color orgCol = light ? C'30,34,41' : clrWhite;
   int wCrypex = (int)(InpWmSize * 5.2);           // approximate pixel width of "crypex" in Arial Black
   Label(PFX + "WM1", CORNER_LEFT_LOWER, ANCHOR_LEFT_LOWER, InpWmGap, InpWmBottom, "crypex", BRAND_GREEN, InpWmSize, "Arial Black");
   Label(PFX + "WM2", CORNER_LEFT_LOWER, ANCHOR_LEFT_LOWER, InpWmGap + wCrypex, InpWmBottom, ".org", orgCol, InpWmSize, "Arial Black");
  }

//+------------------------------------------------------------------+
//| Status panel                                                     |
//+------------------------------------------------------------------+
string g_rowKey[20];
string g_rowVal[20];
color  g_rowClr[20];
int    g_rows = 0;

void Row(const string k, const string v, const color c)
  {
   if(g_rows >= 20) return;
   g_rowKey[g_rows] = k;
   g_rowVal[g_rows] = v;
   g_rowClr[g_rows] = c;
   g_rows++;
  }

void PaintPanel()
  {
   int fs = InpPanelFont;
   int lineH = fs * 2 + 2;
   int keyW = fs * 17, valW = fs * 27, pad = 8;
   bool right = (InpPanelPos == POS_TOP_RIGHT || InpPanelPos == POS_BOTTOM_RIGHT);
   bool lower = (InpPanelPos == POS_BOTTOM_RIGHT || InpPanelPos == POS_BOTTOM_LEFT);
   int corner = InpPanelPos == POS_TOP_RIGHT ? CORNER_RIGHT_UPPER : InpPanelPos == POS_TOP_LEFT ? CORNER_LEFT_UPPER : InpPanelPos == POS_BOTTOM_RIGHT ? CORNER_RIGHT_LOWER : CORNER_LEFT_LOWER;
   int totalH = g_rows * lineH + 10;

   string bgName = PFX + "PBG";
   if(ObjectFind(0, bgName) < 0) ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, bgName, OBJPROP_CORNER, corner);
   ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, pad);
   ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, pad);
   ObjectSetInteger(0, bgName, OBJPROP_XSIZE, keyW + valW + 18);
   ObjectSetInteger(0, bgName, OBJPROP_YSIZE, totalH);
   ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, InpPanelBg);
   ObjectSetInteger(0, bgName, OBJPROP_COLOR, C'70,76,86');
   ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, 0);
   ObjectSetInteger(0, bgName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, bgName, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, bgName, OBJPROP_ZORDER, 5);

   for(int i = 0; i < 20; i++)
     {
      string nk = PFX + "PK" + IntegerToString(i);
      string nv = PFX + "PV" + IntegerToString(i);
      if(i >= g_rows)
        {
         ObjectDelete(0, nk);
         ObjectDelete(0, nv);
         continue;
        }
      int y = pad + 6 + (lower ? (g_rows - 1 - i) : i) * lineH;
      int anchor = right ? (lower ? ANCHOR_RIGHT_LOWER : ANCHOR_RIGHT_UPPER) : (lower ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
      if(lower) y = pad + 6 + (g_rows - 1 - i) * lineH;
      int xVal = right ? pad + 9 : pad + 9 + keyW;
      int xKey = right ? pad + 9 + valW : pad + 9;
      Label(nk, corner, anchor, xKey, y, g_rowKey[i], i == 0 ? clrWhite : C'150,156,166', fs);
      Label(nv, corner, anchor, xVal, y, g_rowVal[i], g_rowClr[i], fs, i == 0 ? "Arial Bold" : "Arial");
     }
  }

//+------------------------------------------------------------------+
//| Support / resistance levels                                      |
//+------------------------------------------------------------------+
void AddLevel(double &lp[], string &ln[], bool &lf[], int &ls[], bool &lh[], int &cnt,
              const double p, const string name, const bool flat, const int str, const bool htf)
  {
   ArrayResize(lp, cnt + 1); ArrayResize(ln, cnt + 1); ArrayResize(lf, cnt + 1); ArrayResize(ls, cnt + 1); ArrayResize(lh, cnt + 1);
   lp[cnt] = p; ln[cnt] = name; lf[cnt] = flat; ls[cnt] = str; lh[cnt] = htf;
   cnt++;
  }

void DrawLevel(const string tag, const datetime t1, const datetime t2, const double lo, const double hi,
               const bool zone, const color clr, const bool flat, const bool htf, const string text)
  {
   string nl = PFX + "SRL" + tag, nt = PFX + "SRT" + tag;
   if(zone)
     {
      string nz = PFX + "SRZ" + tag;
      ObjectCreate(0, nz, OBJ_RECTANGLE, 0, t1, hi, t2, lo);
      ObjectSetInteger(0, nz, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, nz, OBJPROP_FILL, false);
      ObjectSetInteger(0, nz, OBJPROP_WIDTH, flat ? 2 : 1);
      ObjectSetInteger(0, nz, OBJPROP_STYLE, htf ? STYLE_DOT : (flat ? STYLE_SOLID : STYLE_DASH));
      ObjectSetInteger(0, nz, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nz, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectCreate(0, nl, OBJ_TREND, 0, t1, lo, t2, lo);
      ObjectSetInteger(0, nl, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, nl, OBJPROP_WIDTH, flat ? 2 : 1);
      ObjectSetInteger(0, nl, OBJPROP_STYLE, htf ? STYLE_DOT : (flat ? STYLE_SOLID : STYLE_DASH));
      ObjectSetInteger(0, nl, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, nl, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nl, OBJPROP_HIDDEN, true);
     }
   ObjectCreate(0, nt, OBJ_TEXT, 0, t2, (lo + hi) / 2.0);
   ObjectSetString(0, nt, OBJPROP_TEXT, " " + text);
   ObjectSetInteger(0, nt, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, nt, OBJPROP_FONTSIZE, InpPanelFont);
   ObjectSetInteger(0, nt, OBJPROP_ANCHOR, ANCHOR_LEFT);
   ObjectSetInteger(0, nt, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, nt, OBJPROP_HIDDEN, true);
  }

void PaintLevels(const int n, const datetime &time[], const double &close[], const double kijunNow, const bool kijunFlat,
                 const double kumoTop, const double kumoBot, const bool topFlat, const bool botFlat, const bool kumoOk,
                 const bool senkouBFlat, const int jc)
  {
   ObjectsDeleteAll(0, PFX + "SR");
   if(!InpShowSR || !kumoOk) return;
   const int L = n - 1;
   double atr = BufAtr[L];
   if(atr <= 0) return;
   double clThr = atr * 0.25;
   double mergeThr = InpSrCluster ? clThr : g_tick;

   double lp[]; string ln[]; bool lf[]; int ls[]; bool lh[];
   int cnt = 0;
   AddLevel(lp, ln, lf, ls, lh, cnt, kijunNow, "Kijun", kijunFlat, kijunFlat ? 4 : 3, false);
   AddLevel(lp, ln, lf, ls, lh, cnt, kumoBot, "Kumo bottom", botFlat, botFlat ? 4 : 2, false);
   if(kumoTop - kumoBot > clThr) AddLevel(lp, ln, lf, ls, lh, cnt, kumoTop, "Kumo top", topFlat, topFlat ? 4 : 2, false);

   // historical flat Senkou B and flat Kijun: lasting S/R magnets
   for(int ni = 1; ni <= 20; ni++)
     {
      int idx = L - InpDisp - ni * 5;
      if(idx >= InpFlatBars && BufSpanB[idx] != EMPTY_VALUE && IsFlat(BufSpanB, idx, InpFlatBars))
         AddLevel(lp, ln, lf, ls, lh, cnt, BufSpanB[idx], "Old Kumo B", true, 4, false);
     }
   for(int ni = 1; ni <= 12; ni++)
     {
      int idx = L - ni * 5;
      if(idx >= InpFlatBars && BufKijun[idx] != EMPTY_VALUE && IsFlat(BufKijun, idx, InpFlatBars) && MathAbs(BufKijun[idx] - kijunNow) > clThr)
         AddLevel(lp, ln, lf, ls, lh, cnt, BufKijun[idx], "Old Kijun", true, 4, false);
     }

   // higher timeframe levels
   if(g_htfReady && jc - InpDisp > InpFlatBars)
     {
      int hi0 = jc - InpDisp;
      if(hA[hi0] != EMPTY_VALUE && hB[hi0] != EMPTY_VALUE)
        {
         double mTop = MathMax(hA[hi0], hB[hi0]), mBot = MathMin(hA[hi0], hB[hi0]);
         bool mTopFlat = IsFlatEdge(hA, hB, hi0, InpFlatBars, true);
         bool mBotFlat = IsFlatEdge(hA, hB, hi0, InpFlatBars, false);
         AddLevel(lp, ln, lf, ls, lh, cnt, mBot, "HTF Kumo bottom", mBotFlat, mBotFlat ? 4 : 2, true);
         if(mTop - mBot > clThr) AddLevel(lp, ln, lf, ls, lh, cnt, mTop, "HTF Kumo top", mTopFlat, mTopFlat ? 4 : 2, true);
        }
      for(int ni = 1; ni <= 20; ni++)
        {
         int hidx = hi0 - ni * 5;
         if(hidx < InpFlatBars) break;
         if(hB[hidx] != EMPTY_VALUE && IsFlat(hB, hidx, InpFlatBars))
            AddLevel(lp, ln, lf, ls, lh, cnt, hB[hidx], "Old HTF Kumo B", true, 4, true);
        }
     }
   if(cnt == 0) return;

   // sort ascending by price (insertion sort on indices)
   int idxs[];
   ArrayResize(idxs, cnt);
   for(int i = 0; i < cnt; i++) idxs[i] = i;
   for(int i = 1; i < cnt; i++)
     {
      int key = idxs[i];
      int j = i - 1;
      while(j >= 0 && lp[idxs[j]] > lp[key])
        {
         idxs[j + 1] = idxs[j];
         j--;
        }
      idxs[j + 1] = key;
     }

   // cluster
   double cMin[], cMax[]; string cNm[]; bool cFl[], cH[]; int cCt[];
   int nCl = 0;
   bool used[];
   ArrayResize(used, cnt);
   for(int i = 0; i < cnt; i++) used[i] = false;
   for(int i = 0; i < cnt; i++)
     {
      int oi = idxs[i];
      if(used[oi]) continue;
      double pLo = lp[oi], pHi = lp[oi];
      string nm = ln[oi];
      bool fl = lf[oi], hh = lh[oi];
      int ct = 1;
      used[oi] = true;
      for(int j = i + 1; j < cnt; j++)
        {
         int oj = idxs[j];
         if(lp[oj] - pHi > mergeThr) break;
         if(used[oj]) continue;
         pHi = lp[oj];
         if(StringFind(nm, ln[oj]) < 0) nm = nm + " + " + ln[oj];
         if(lf[oj]) fl = true;
         if(!lh[oj]) hh = false;
         ct++;
         used[oj] = true;
        }
      ArrayResize(cMin, nCl + 1); ArrayResize(cMax, nCl + 1); ArrayResize(cNm, nCl + 1);
      ArrayResize(cFl, nCl + 1); ArrayResize(cH, nCl + 1); ArrayResize(cCt, nCl + 1);
      cMin[nCl] = pLo; cMax[nCl] = pHi; cNm[nCl] = nm; cFl[nCl] = fl; cH[nCl] = hh; cCt[nCl] = ct;
      nCl++;
     }

   int nS = 0;
   for(int k = 0; k < nCl; k++)
      if((cMin[k] + cMax[k]) / 2.0 < close[L]) nS++;
   int sCur = nS, rCur = 1;
   datetime t1 = time[L];
   datetime t2 = time[L] + (datetime)(InpSrExtend * PeriodSeconds(_Period));
   for(int k = 0; k < nCl; k++)
     {
      double mid = (cMin[k] + cMax[k]) / 2.0;
      bool isR = mid >= close[L];
      int rank = isR ? rCur : sCur;
      if(isR) rCur++; else sCur--;
      if(rank > InpSrMax) continue;
      bool zone = cCt[k] > 1 && cMax[k] > cMin[k] && InpSrCluster;
      string rng = (cCt[k] > 1 && cMax[k] > cMin[k]) ? Px(cMin[k]) + " - " + Px(cMax[k]) : Px(cMin[k]);
      string txt = (isR ? "R" : "S") + IntegerToString(rank) + (cH[k] ? "[H]" : "") + "  " + rng + "  " + cNm[k];
      DrawLevel(IntegerToString(k), t1, t2, cMin[k], cMax[k], zone, isR ? InpResistanceColor : InpSupportColor, cFl[k], cH[k], txt);
     }
  }

//+------------------------------------------------------------------+
//| Analysis of the last bar: status panel rows + S/R                |
//+------------------------------------------------------------------+
void Analyze(const int n, const datetime &time[], const double &high[], const double &low[], const double &close[])
  {
   const int L = n - 1;
   const int c0 = L - InpDisp;
   g_rows = 0;
   color tBull = C'8,153,129', tBear = C'242,54,69', tWarn = C'255,152,0', tBase = clrWhite;
   double atr = BufAtr[L];
   double tenkan = BufTenkan[L], kijun = BufKijun[L];
   bool kumoOk = (c0 >= 0 && BufSpanA[c0] != EMPTY_VALUE && BufSpanB[c0] != EMPTY_VALUE && tenkan != EMPTY_VALUE && kijun != EMPTY_VALUE && atr > 0);
   double kumoTop = 0, kumoBot = 0;
   bool kumoBull = false, above = false, below = false, inside = false;
   if(kumoOk)
     {
      kumoTop = MathMax(BufSpanA[c0], BufSpanB[c0]);
      kumoBot = MathMin(BufSpanA[c0], BufSpanB[c0]);
      kumoBull = BufSpanA[c0] > BufSpanB[c0];
      above = close[L] > kumoTop;
      below = close[L] < kumoBot;
      inside = !above && !below;
     }
   Row("Crypex", _Symbol, clrWhite);
   if(!kumoOk)
     {
      Row("Status", "Warm-up (not enough bars)", tBase);
      if(InpShowPanel) PaintPanel();
      PaintLevels(n, time, close, 0, false, 0, 0, false, false, false, false, 0);
      return;
     }

   bool tkBull = tenkan > kijun, tkBear = tenkan < kijun;
   bool futBull = BufSpanA[L] > BufSpanB[L], futBear = BufSpanA[L] < BufSpanB[L];
   bool kijunFlat = IsFlat(BufKijun, L, InpFlatBars);
   double slope5 = (L >= 5 && BufKijun[L - 5] != EMPTY_VALUE) ? kijun - BufKijun[L - 5] : 0;
   bool kijunRise = !kijunFlat && slope5 > 0, kijunFall = !kijunFlat && slope5 < 0;
   bool senkouBFlat = IsFlat(BufSpanB, c0, InpFlatBars);
   bool aFlatNow = IsFlat(BufSpanA, c0, InpFlatBars);
   double sbLag = BufSpanB[c0], sbLag5 = (c0 >= 5) ? BufSpanB[c0 - 5] : EMPTY_VALUE;
   double tenkanDist = MathAbs(close[L] - tenkan) / atr;
   double chHi = (L - InpDisp >= 0) ? high[L - InpDisp] : EMPTY_VALUE;
   double chLo = (L - InpDisp >= 0) ? low[L - InpDisp] : EMPTY_VALUE;
   double chScore = (chHi == EMPTY_VALUE) ? 0.0 : (close[L] > chHi ? 1.0 : (close[L] < chLo ? -1.0 : 0.0));

   double score = (above ? 2.0 : (below ? -2.0 : 0.0)) + (tkBull ? 1.0 : (tkBear ? -1.0 : 0.0))
                + (close[L] > kijun ? 1.0 : (close[L] < kijun ? -1.0 : 0.0)) + (futBull ? 1.0 : (futBear ? -1.0 : 0.0))
                + chScore + (kijunRise ? 0.5 : (kijunFall ? -0.5 : 0.0));
   string bias = score >= 5.5 ? "Strong Bullish" : score >= 2.0 ? "Bullish Bias" : score <= -5.5 ? "Strong Bearish" : score <= -2.0 ? "Bearish Bias" : "Neutral / Range";

   Row("Tenkan / Kijun", Px(tenkan) + " / " + Px(kijun), tBase);
   Row("Regime", above ? "Above Kumo" : (below ? "Below Kumo" : "Inside Kumo"), above ? tBull : (below ? tBear : tWarn));
   Row("Cloud (now)", kumoBull ? "Green (A>B)" : "Red (A<B)", kumoBull ? tBull : tBear);
   Row("Future cloud", futBull ? "Bullish (A>B)" : (futBear ? "Bearish (A<B)" : "Flat / twisting"), futBull ? tBull : (futBear ? tBear : tBase));
   string align = (kumoBull && above && tkBull) ? "Strong bull stack" : (!kumoBull && below && tkBear) ? "Strong bear stack" :
                  (above && !tkBull) ? "Pullback risk (above cloud)" : (below && !tkBear) ? "Bounce risk (below cloud)" : "Mixed / transit";
   Row("Alignment", align, (above && tkBull) ? tBull : ((below && tkBear) ? tBear : tWarn));
   Row("TK momentum", tkBull ? "Tenkan > Kijun" : (tkBear ? "Tenkan < Kijun" : "Flat"), tkBull ? tBull : (tkBear ? tBear : tBase));
   Row("Kijun slope", kijunFlat ? "Flat · equilibrium magnet" : (kijunRise ? "Rising" : (kijunFall ? "Falling" : "Sideways")), kijunFlat ? tWarn : (kijunRise ? tBull : (kijunFall ? tBear : tBase)));
   string sbTxt = senkouBFlat ? "Flat S/R @ " + Px(sbLag) : ((sbLag5 != EMPTY_VALUE && sbLag > sbLag5) ? "Rising" : ((sbLag5 != EMPTY_VALUE && sbLag < sbLag5) ? "Falling" : "Sideways"));
   Row("Senkou B", sbTxt, senkouBFlat ? tWarn : tBase);
   Row("Chikou", chHi == EMPTY_VALUE ? "Warm-up" : (chScore > 0 ? "Clear bullish" : (chScore < 0 ? "Clear bearish" : "Trapped (neutral)")), chScore > 0 ? tBull : (chScore < 0 ? tBear : tWarn));
   Row("Tenkan distance", DoubleToString(tenkanDist, 2) + " ATR" + (tenkanDist > 1.0 ? "  ! overext" : (tenkanDist < 0.3 ? "  · near" : "")), tenkanDist > 1.0 ? tWarn : tBase);
   double thick = kumoTop - kumoBot;
   double thickAtr = thick / atr;
   Row("Kumo thickness", Px(thick) + (thickAtr > 1.5 ? "  · Thick" : (thickAtr < 0.5 ? "  · Thin" : "  · Normal")), tBase);
   double dTop = kumoTop - close[L], dBot = close[L] - kumoBot;
   Row("Distance (edge)", "up " + DoubleToString(dTop / g_tick, 0) + " t / dn " + DoubleToString(dBot / g_tick, 0) + " t", tBase);
   double nearest = MathMin(MathAbs(dTop), MathAbs(dBot));
   Row("Nearest · ATR", Px(nearest) + " · " + DoubleToString(nearest / atr, 2) + " ATR", tBase);
   Row("ATR(14)", Px(atr), tBase);
   Row("Score · Bias", DoubleToString(score, 1) + "  ->  " + bias, score >= 2.0 ? tBull : (score <= -2.0 ? tBear : tWarn));

   int jc = -1;
   if(g_htfReady && ArraySize(jMap) == n) jc = jMap[L];
   if(g_htfReady && jc - InpDisp >= 0 && hA[jc - InpDisp] != EMPTY_VALUE && hB[jc - InpDisp] != EMPTY_VALUE)
     {
      double mTop = MathMax(hA[jc - InpDisp], hB[jc - InpDisp]), mBot = MathMin(hA[jc - InpDisp], hB[jc - InpDisp]);
      bool mBull = hA[jc - InpDisp] > hB[jc - InpDisp];
      string tfName = EnumToString(g_htf);
      StringReplace(tfName, "PERIOD_", "");
      Row("HTF Kumo", (close[L] > mTop ? "Above Kumo (" : (close[L] < mBot ? "Below Kumo (" : "In Kumo (")) + tfName + ")", close[L] > mTop ? tBull : (close[L] < mBot ? tBear : tWarn));
      Row("HTF cloud", mBull ? "HTF green" : "HTF red", mBull ? tBull : tBear);
     }
   else
     {
      Row("HTF Kumo", g_htfOn ? "HTF warm-up" : "-", tBase);
     }
   Row("Shift · Params", IntegerToString(InpDisp) + " ch" + (g_htfOn ? " · " + IntegerToString(g_fwd) + " HTF" : "") + "  |  " + IntegerToString(InpTenkan) + "/" + IntegerToString(InpKijun) + "/" + IntegerToString(InpSenkouB), tBase);

   if(InpShowPanel) PaintPanel();
   else ObjectsDeleteAll(0, PFX + "P");

   bool botIsB = BufSpanB[c0] <= BufSpanA[c0];
   bool botFlat = botIsB ? senkouBFlat : aFlatNow;
   bool topFlat = botIsB ? aFlatNow : senkouBFlat;
   PaintLevels(n, time, close, kijun, kijunFlat, kumoTop, kumoBot, topFlat, botFlat, true, senkouBFlat, jc);
  }

//+------------------------------------------------------------------+
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[])
  {
   const int n = rates_total;
   const int need = MathMax(InpSenkouB, MathMax(InpKijun, InpTenkan)) + InpDisp + 5;
   if(n < need) return 0;
   int start = (prev_calculated > 2) ? prev_calculated - 2 : 0;
   if(prev_calculated == 0)
      for(int i = 0; i < n; i++)
        {
         BufHtfA[i] = EMPTY_VALUE;
         BufHtfB[i] = EMPTY_VALUE;
         BufHtfKijun[i] = EMPTY_VALUE;
        }
   ComputeChart(n, start, high, low, close);

   uint now = GetTickCount();
   bool newBar = (time[n - 1] != g_lastBar) || prev_calculated == 0;   // full recalculation also rebuilds the HTF map
   if(newBar)
     {
      g_lastBar = time[n - 1];
      if(g_htfOn)
        {
         BuildHtf(n, time);
         g_lastHtfTry = now;
        }
      CheckAlerts(n, time, close);
     }
   else if(g_htfOn && !g_htfReady && now - g_lastHtfTry > 5000)
     {
      BuildHtf(n, time);           // HTF history was not loaded yet: retry every 5 s
      g_lastHtfTry = now;
     }

   if(newBar || now - g_lastPaint > 1500 || prev_calculated == 0)
     {
      g_lastPaint = now;
      Analyze(n, time, high, low, close);
      PaintWatermark();
      ChartRedraw();
     }
   g_firstDone = true;
   return rates_total;
  }
//+------------------------------------------------------------------+
ichimoku-mtf-kumo.mq4 · MQL4 · ۸۷۷ خط
//+------------------------------------------------------------------+
//| Crypex | Ichimoku Advanced (MT4)                                  |
//| HTF Kumo + ranked S/R levels + status panel + alerts            |
//| © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org |
//| License: personal, non-commercial use only. Redistribution or    |
//| resale without written permission is prohibited.                 |
//| مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون          |
//| اجازه‌ی کتبی ممنوع است.                                           |
//| Educational tool, not investment advice.                         |
//+------------------------------------------------------------------+
#property copyright "Crypex.org"
#property link      "https://crypex.org"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 8

enum ENUM_PANEL_POS { POS_TOP_RIGHT, POS_TOP_LEFT, POS_BOTTOM_RIGHT, POS_BOTTOM_LEFT };
enum ENUM_CAL_MODE  { CAL_AUTO, CAL_247, CAL_WEEKDAYS };

// ---- Ichimoku
input int    InpTenkan    = 9;      // Tenkan-sen
input int    InpKijun     = 26;     // Kijun-sen
input int    InpSenkouB   = 52;     // Senkou Span B
input int    InpDisp      = 26;     // Displacement (Senkou +, Chikou -)
input int    InpFlatBars  = 10;     // Min flat bars (Kijun / Senkou B)
// ---- Lines and Kumo
input bool   InpShowTenkan = true;
input bool   InpShowKijun  = true;
input bool   InpShowChikou = true;
input bool   InpShowKumo   = true;      // Senkou A/B lines
input bool   InpFillKumo   = true;      // Fill the Kumo (chart objects)
input color  InpTenkanColor = C'11,163,28';
input color  InpKijunColor  = C'255,10,23';
input color  InpChikouColor = C'120,123,134';
input color  InpSpanAColor  = C'102,187,106';
input color  InpSpanBColor  = C'229,115,115';
input color  InpBullKumo    = C'129,199,132';
input color  InpBearKumo    = C'239,154,154';
input int    InpCloudBars   = 300;      // Bars of Kumo fill (fewer = lighter chart)
// ---- Higher timeframe Kumo
input bool   InpShowHtf     = true;
input bool   InpHtfAuto     = true;            // Auto next timeframe
input ENUM_TIMEFRAMES InpHtfManual = PERIOD_H4; // Manual timeframe
input bool   InpShowHtfKijun = true;
input ENUM_CAL_MODE InpCalendar = CAL_AUTO;    // Market calendar (weekly/monthly HTF projection)
input double InpRatioOverride = 0.0;           // Chart bars per HTF bar (0 = auto)
input int    InpHtfBars     = 2000;            // HTF bars to load
input color  InpHtfBullKumo = C'178,223,180';
input color  InpHtfBearKumo = C'247,196,196';
input color  InpHtfKijunColor = C'178,34,34';
// ---- Markers and alerts
input bool   InpMarkTk      = true;            // TK cross arrows
input bool   InpMarkKumoBo  = false;           // Kumo breakout arrows
input bool   InpAlertPopup  = true;
input bool   InpAlertPush   = false;
input bool   InpAlertMail   = false;
// ---- Status panel
input bool   InpShowPanel   = true;
input ENUM_PANEL_POS InpPanelPos = POS_TOP_RIGHT;
input int    InpPanelFont   = 8;
input color  InpPanelBg     = C'21,26,32';
// ---- Support / Resistance levels
input bool   InpShowSR      = true;
input int    InpSrExtend    = 20;              // Extend (bars)
input bool   InpSrCluster   = true;            // Merge nearby levels into zones
input int    InpSrMax       = 10;              // Max levels per side
input color  InpSupportColor    = C'11,163,28';
input color  InpResistanceColor = C'255,10,23';
// ---- Watermark
input bool   InpShowWm      = true;            // Show crypex.org watermark
input int    InpWmSize      = 18;
input int    InpWmGap       = 60;              // Gap from left (px), clears the chart logo
input int    InpWmBottom    = 14;              // Gap from bottom (px)
// ---- Calculation
input int    InpMaxBars     = 3000;            // Bars used for calculation

const string PFX = "CRX_ICH_";
const color  BRAND_GREEN = C'11,163,28';

// plotted buffers (MT4 buffers are series: index 0 = newest bar)
double BufTenkan[], BufKijun[], BufChikou[], BufSpanA[], BufSpanB[], BufHtfKijun[], BufTkUp[], BufTkDn[];

// working arrays, chronological (index 0 = oldest of the used range)
double   cT[], cK[], cA[], cB[], cAtr[], cHi[], cLo[], cCl[];
double   cTkUp[], cTkDn[], cKbUp[], cKbDn[], cHtfA[], cHtfB[], cHtfK[];
datetime cTm[];
int      N = 0;

// higher-timeframe store (chronological)
double   hHi[], hLo[], hT[], hK[], hA[], hB[];
datetime hTm[];
int      jMap[];
int      hN = 0;
int      g_htf = 0;
bool     g_htfOn = false;
bool     g_htfReady = false;
int      g_fwd = 0;
double   g_ratio = 1.0;
double   g_tick = 0.0;
datetime g_lastBar = 0;
datetime g_lastAlertBar = 0;
uint     g_lastPaint = 0;
uint     g_lastHtfTry = 0;
bool     g_firstDone = false;

//+------------------------------------------------------------------+
bool IsCryptoSymbol()
  {
   string s = Symbol();
   string keys[] = {"BTC", "ETH", "LTC", "XRP", "SOL", "DOGE", "ADA", "BNB", "TRX", "DOT"};
   for(int i = 0; i < ArraySize(keys); i++)
      if(StringFind(s, keys[i]) >= 0) return true;
   return false;
  }

int AutoNextTf()
  {
   int s = PeriodSeconds(_Period);
   if(s <= 60)    return PERIOD_H1;
   if(s <= 3600)  return PERIOD_H4;
   if(s <= 14400) return PERIOD_D1;
   if(s <= 86400) return PERIOD_W1;
   return PERIOD_MN1;
  }

//+------------------------------------------------------------------+
int OnInit()
  {
   IndicatorBuffers(8);
   SetIndexBuffer(0, BufTenkan);
   SetIndexBuffer(1, BufKijun);
   SetIndexBuffer(2, BufChikou);
   SetIndexBuffer(3, BufSpanA);
   SetIndexBuffer(4, BufSpanB);
   SetIndexBuffer(5, BufHtfKijun);
   SetIndexBuffer(6, BufTkUp);
   SetIndexBuffer(7, BufTkDn);

   g_tick = MarketInfo(Symbol(), MODE_TICKSIZE);
   if(g_tick <= 0) g_tick = Point;

   SetIndexStyle(0, InpShowTenkan ? DRAW_LINE : DRAW_NONE, STYLE_SOLID, 2, InpTenkanColor);
   SetIndexStyle(1, InpShowKijun  ? DRAW_LINE : DRAW_NONE, STYLE_SOLID, 2, InpKijunColor);
   SetIndexStyle(2, InpShowChikou ? DRAW_LINE : DRAW_NONE, STYLE_SOLID, 1, InpChikouColor);
   SetIndexStyle(3, InpShowKumo   ? DRAW_LINE : DRAW_NONE, STYLE_SOLID, 1, InpSpanAColor);
   SetIndexStyle(4, InpShowKumo   ? DRAW_LINE : DRAW_NONE, STYLE_SOLID, 1, InpSpanBColor);
   SetIndexShift(2, -InpDisp);
   SetIndexShift(3, InpDisp);
   SetIndexShift(4, InpDisp);

   g_htf = InpHtfAuto ? AutoNextTf() : (int)InpHtfManual;
   int chartSec = PeriodSeconds(_Period);
   int htfSec   = PeriodSeconds(g_htf);
   g_htfOn = InpShowHtf && htfSec > chartSec && chartSec > 0;
   bool weekdays = (InpCalendar == CAL_WEEKDAYS) || (InpCalendar == CAL_AUTO && !IsCryptoSymbol());
   double weekFactor = (weekdays && htfSec >= 604800 && chartSec < 604800) ? 5.0 / 7.0 : 1.0;
   g_ratio = g_htfOn ? (InpRatioOverride > 0 ? InpRatioOverride : (double)htfSec / chartSec * weekFactor) : 1.0;
   g_fwd = g_htfOn ? (int)MathMax(0, MathRound((InpDisp - 1) * g_ratio)) : 0;
   if(g_fwd > 4900) g_htfOn = false;
   SetIndexStyle(5, (g_htfOn && InpShowHtfKijun) ? DRAW_LINE : DRAW_NONE, STYLE_DASH, 1, InpHtfKijunColor);

   SetIndexStyle(6, InpMarkTk ? DRAW_ARROW : DRAW_NONE, STYLE_SOLID, 1, InpTenkanColor);
   SetIndexStyle(7, InpMarkTk ? DRAW_ARROW : DRAW_NONE, STYLE_SOLID, 1, InpKijunColor);
   SetIndexArrow(6, 233);
   SetIndexArrow(7, 234);
   SetIndexLabel(0, "Tenkan-sen");
   SetIndexLabel(1, "Kijun-sen");
   SetIndexLabel(2, "Chikou Span");
   SetIndexLabel(3, "Senkou Span A");
   SetIndexLabel(4, "Senkou Span B");
   SetIndexLabel(5, "HTF Kijun");
   SetIndexLabel(6, "TK Cross Up");
   SetIndexLabel(7, "TK Cross Down");
   for(int p = 0; p < 8; p++) SetIndexEmptyValue(p, EMPTY_VALUE);
   IndicatorShortName("Crypex Ichimoku");
   IndicatorDigits(Digits);
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PFX);
  }

//+------------------------------------------------------------------+
//| helpers                                                          |
//+------------------------------------------------------------------+
double Donchian(const double &hi[], const double &lo[], const int idx, const int len)
  {
   if(idx - len + 1 < 0) return EMPTY_VALUE;
   double h = hi[idx], l = lo[idx];
   for(int k = 1; k < len; k++)
     {
      if(hi[idx - k] > h) h = hi[idx - k];
      if(lo[idx - k] < l) l = lo[idx - k];
     }
   return (h + l) / 2.0;
  }

// value stayed within one tick over the whole window (len+1 bars)
bool IsFlat(const double &arr[], const int idx, const int len)
  {
   if(idx - len < 0) return false;
   double mx = -DBL_MAX, mn = DBL_MAX;
   for(int k = 0; k <= len; k++)
     {
      double v = arr[idx - k];
      if(v == EMPTY_VALUE) return false;
      if(v > mx) mx = v;
      if(v < mn) mn = v;
     }
   return (mx - mn) <= g_tick;
  }

// flatness of the upper (top=true) or lower edge of a Kumo made of spans a and b
bool IsFlatEdge(const double &a[], const double &b[], const int idx, const int len, const bool top)
  {
   if(idx - len < 0) return false;
   double mx = -DBL_MAX, mn = DBL_MAX;
   for(int k = 0; k <= len; k++)
     {
      double va = a[idx - k], vb = b[idx - k];
      if(va == EMPTY_VALUE || vb == EMPTY_VALUE) return false;
      double v = top ? MathMax(va, vb) : MathMin(va, vb);
      if(v > mx) mx = v;
      if(v < mn) mn = v;
     }
   return (mx - mn) <= g_tick;
  }

string Px(const double v)
  {
   return DoubleToString(v, _Digits);
  }

//+------------------------------------------------------------------+
//| Build the higher timeframe Ichimoku and map it to chart bars     |
//+------------------------------------------------------------------+
void BuildHtf(const int n, const datetime &time[])
  {
   g_htfReady = false;
   ArrayResize(cHtfA, n); ArrayResize(cHtfB, n); ArrayResize(cHtfK, n);
   for(int i = 0; i < n; i++)
     {
      cHtfA[i] = EMPTY_VALUE;
      cHtfB[i] = EMPTY_VALUE;
      cHtfK[i] = EMPTY_VALUE;
     }
   int want = MathMin(iBars(Symbol(), g_htf), InpHtfBars);
   int need = InpSenkouB + InpDisp + 5;
   if(want < need) return;
   ArrayResize(hHi, want); ArrayResize(hLo, want); ArrayResize(hTm, want);
   for(int j = 0; j < want; j++)
     {
      int s = want - 1 - j;
      hHi[j] = iHigh(Symbol(), g_htf, s);
      hLo[j] = iLow(Symbol(), g_htf, s);
      hTm[j] = iTime(Symbol(), g_htf, s);
      if(hHi[j] <= 0 || hLo[j] <= 0 || hTm[j] <= 0) return;   // history not loaded yet
     }
   hN = want;
   ArrayResize(hT, hN); ArrayResize(hK, hN); ArrayResize(hA, hN); ArrayResize(hB, hN);
   for(int j = 0; j < hN; j++)
     {
      hT[j] = Donchian(hHi, hLo, j, InpTenkan);
      hK[j] = Donchian(hHi, hLo, j, InpKijun);
      hA[j] = (hT[j] != EMPTY_VALUE && hK[j] != EMPTY_VALUE) ? (hT[j] + hK[j]) / 2.0 : EMPTY_VALUE;
      hB[j] = Donchian(hHi, hLo, j, InpSenkouB);
     }
   ArrayResize(jMap, n);
   int j = 0;
   for(int i = 0; i < n; i++)
     {
      while(j + 1 < hN && hTm[j + 1] <= time[i]) j++;
      jMap[i] = (hTm[0] <= time[i]) ? j : -1;
      int c = jMap[i] - 1;               // last COMPLETED HTF bar: no repainting
      if(c >= 0 && hA[c] != EMPTY_VALUE && hB[c] != EMPTY_VALUE)
        {
         cHtfA[i] = hA[c];
         cHtfB[i] = hB[c];
         cHtfK[i] = hK[c];
        }
     }
   g_htfReady = true;
  }

//+------------------------------------------------------------------+
//| Chart-TF Ichimoku (chronological arrays), markers                |
//+------------------------------------------------------------------+
void ComputeChart(const int n, const double &high[], const double &low[], const double &close[])
  {
   ArrayResize(cT, n); ArrayResize(cK, n); ArrayResize(cA, n); ArrayResize(cB, n); ArrayResize(cAtr, n);
   ArrayResize(cTkUp, n); ArrayResize(cTkDn, n); ArrayResize(cKbUp, n); ArrayResize(cKbDn, n);
   for(int i = 0; i < n; i++)
     {
      cT[i] = Donchian(high, low, i, InpTenkan);
      cK[i] = Donchian(high, low, i, InpKijun);
      cA[i] = (cT[i] != EMPTY_VALUE && cK[i] != EMPTY_VALUE) ? (cT[i] + cK[i]) / 2.0 : EMPTY_VALUE;
      cB[i] = Donchian(high, low, i, InpSenkouB);
      double tr = high[i] - low[i];
      if(i == 0)
         cAtr[i] = tr;
      else
        {
         tr = MathMax(tr, MathMax(MathAbs(high[i] - close[i - 1]), MathAbs(low[i] - close[i - 1])));
         cAtr[i] = (cAtr[i - 1] * 13.0 + tr) / 14.0;
        }
      cTkUp[i] = EMPTY_VALUE; cTkDn[i] = EMPTY_VALUE; cKbUp[i] = EMPTY_VALUE; cKbDn[i] = EMPTY_VALUE;
      if(i < 1) continue;
      if(cT[i] == EMPTY_VALUE || cK[i] == EMPTY_VALUE || cT[i - 1] == EMPTY_VALUE || cK[i - 1] == EMPTY_VALUE) continue;
      if(cT[i - 1] <= cK[i - 1] && cT[i] > cK[i]) cTkUp[i] = low[i] - cAtr[i] * 0.5;
      if(cT[i - 1] >= cK[i - 1] && cT[i] < cK[i]) cTkDn[i] = high[i] + cAtr[i] * 0.5;
      int c0 = i - InpDisp, c1 = i - 1 - InpDisp;
      if(c1 < 0 || cA[c0] == EMPTY_VALUE || cB[c0] == EMPTY_VALUE || cA[c1] == EMPTY_VALUE || cB[c1] == EMPTY_VALUE) continue;
      double top0 = MathMax(cA[c0], cB[c0]), bot0 = MathMin(cA[c0], cB[c0]);
      double top1 = MathMax(cA[c1], cB[c1]), bot1 = MathMin(cA[c1], cB[c1]);
      if(close[i - 1] <= top1 && close[i] > top0) cKbUp[i] = low[i] - cAtr[i] * 0.9;
      if(close[i - 1] >= bot1 && close[i] < bot0) cKbDn[i] = high[i] + cAtr[i] * 0.9;
     }
  }

// copy chronological results into the series buffers (shift = n-1-i)
void PushBuffers(const int n, const bool all)
  {
   bool htfLine = g_htfOn && g_htfReady && InpShowHtfKijun && ArraySize(cHtfK) == n;
   for(int s = 0; s < n; s++)
     {
      int i = n - 1 - s;
      BufTenkan[s] = cT[i];
      BufKijun[s]  = cK[i];
      BufChikou[s] = cCl[i];
      BufSpanA[s]  = cA[i];
      BufSpanB[s]  = cB[i];
      BufHtfKijun[s] = htfLine ? cHtfK[i] : EMPTY_VALUE;
      BufTkUp[s]   = cTkUp[i];
      BufTkDn[s]   = cTkDn[i];
     }
   if(all)
      for(int s = n; s < Bars; s++)
        {
         BufTenkan[s] = EMPTY_VALUE; BufKijun[s] = EMPTY_VALUE; BufChikou[s] = EMPTY_VALUE;
         BufSpanA[s] = EMPTY_VALUE; BufSpanB[s] = EMPTY_VALUE; BufHtfKijun[s] = EMPTY_VALUE;
         BufTkUp[s] = EMPTY_VALUE; BufTkDn[s] = EMPTY_VALUE;
        }
  }

//+------------------------------------------------------------------+
//| Alerts for the last closed bar                                   |
//+------------------------------------------------------------------+
void Notify(const string msg)
  {
   string full = "Crypex Ichimoku " + _Symbol + " " + EnumToString(_Period) + ": " + msg;
   if(InpAlertPopup) Alert(full);
   if(InpAlertPush)  SendNotification(full);
   if(InpAlertMail)  SendMail("Crypex Ichimoku", full);
  }

void CheckAlerts(const int n, const datetime &time[], const double &close[])
  {
   int b = n - 2;                       // last closed bar
   if(b < InpDisp + 3) return;
   if(time[b] == g_lastAlertBar) return;
   g_lastAlertBar = time[b];
   if(!g_firstDone) return;             // never alert for history on first load
   double t0 = cT[b], k0 = cK[b], t1 = cT[b - 1], k1 = cK[b - 1];
   if(t0 == EMPTY_VALUE || k0 == EMPTY_VALUE || t1 == EMPTY_VALUE || k1 == EMPTY_VALUE) return;
   if(t1 <= k1 && t0 > k0) Notify("Tenkan crossed above Kijun");
   if(t1 >= k1 && t0 < k0) Notify("Tenkan crossed below Kijun");
   int c0 = b - InpDisp, c1 = b - 1 - InpDisp;
   if(c1 >= 0 && cA[c0] != EMPTY_VALUE && cB[c0] != EMPTY_VALUE && cA[c1] != EMPTY_VALUE && cB[c1] != EMPTY_VALUE)
     {
      double top0 = MathMax(cA[c0], cB[c0]), bot0 = MathMin(cA[c0], cB[c0]);
      double top1 = MathMax(cA[c1], cB[c1]), bot1 = MathMin(cA[c1], cB[c1]);
      if(close[b - 1] <= top1 && close[b] > top0) Notify("Price closed above the Kumo");
      if(close[b - 1] >= bot1 && close[b] < bot0) Notify("Price closed below the Kumo");
     }
   if(cA[b] != EMPTY_VALUE && cB[b] != EMPTY_VALUE && cA[b - 1] != EMPTY_VALUE && cB[b - 1] != EMPTY_VALUE)
     {
      if(cA[b - 1] <= cB[b - 1] && cA[b] > cB[b]) Notify("Kumo twist: future Kumo turns bullish");
      if(cA[b - 1] >= cB[b - 1] && cA[b] < cB[b]) Notify("Kumo twist: future Kumo turns bearish");
     }
  }

//+------------------------------------------------------------------+
//| Text objects                                                     |
//+------------------------------------------------------------------+
void Label(const string name, const int corner, const int anchor, const int x, const int y,
           const string text, const color clr, const int size, const string font = "Arial")
  {
   if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
   ObjectSetString(0, name, OBJPROP_FONT, font);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 10);
  }

void PaintWatermark()
  {
   if(!InpShowWm)
     {
      ObjectDelete(0, PFX + "WM1");
      ObjectDelete(0, PFX + "WM2");
      return;
     }
   long bg = ChartGetInteger(0, CHART_BACKGROUND_COLOR);
   int r = (int)(bg & 0xFF), g = (int)((bg >> 8) & 0xFF), b = (int)((bg >> 16) & 0xFF);
   bool light = (r * 299 + g * 587 + b * 114) / 1000 > 128;
   color orgCol = light ? C'30,34,41' : clrWhite;
   int wCrypex = (int)(InpWmSize * 5.2);           // approximate pixel width of "crypex" in Arial Black
   Label(PFX + "WM1", CORNER_LEFT_LOWER, ANCHOR_LEFT_LOWER, InpWmGap, InpWmBottom, "crypex", BRAND_GREEN, InpWmSize, "Arial Black");
   Label(PFX + "WM2", CORNER_LEFT_LOWER, ANCHOR_LEFT_LOWER, InpWmGap + wCrypex, InpWmBottom, ".org", orgCol, InpWmSize, "Arial Black");
  }

//+------------------------------------------------------------------+
//| Status panel                                                     |
//+------------------------------------------------------------------+
string g_rowKey[20];
string g_rowVal[20];
color  g_rowClr[20];
int    g_rows = 0;

void Row(const string k, const string v, const color c)
  {
   if(g_rows >= 20) return;
   g_rowKey[g_rows] = k;
   g_rowVal[g_rows] = v;
   g_rowClr[g_rows] = c;
   g_rows++;
  }

void PaintPanel()
  {
   int fs = InpPanelFont;
   int lineH = fs * 2 + 2;
   int keyW = fs * 17, valW = fs * 27, pad = 8;
   bool right = (InpPanelPos == POS_TOP_RIGHT || InpPanelPos == POS_BOTTOM_RIGHT);
   bool lower = (InpPanelPos == POS_BOTTOM_RIGHT || InpPanelPos == POS_BOTTOM_LEFT);
   int corner = InpPanelPos == POS_TOP_RIGHT ? CORNER_RIGHT_UPPER : InpPanelPos == POS_TOP_LEFT ? CORNER_LEFT_UPPER : InpPanelPos == POS_BOTTOM_RIGHT ? CORNER_RIGHT_LOWER : CORNER_LEFT_LOWER;
   int totalH = g_rows * lineH + 10;

   string bgName = PFX + "PBG";
   if(ObjectFind(0, bgName) < 0) ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, bgName, OBJPROP_CORNER, corner);
   ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, pad);
   ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, pad);
   ObjectSetInteger(0, bgName, OBJPROP_XSIZE, keyW + valW + 18);
   ObjectSetInteger(0, bgName, OBJPROP_YSIZE, totalH);
   ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, InpPanelBg);
   ObjectSetInteger(0, bgName, OBJPROP_COLOR, C'70,76,86');
   ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, 0);
   ObjectSetInteger(0, bgName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, bgName, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, bgName, OBJPROP_ZORDER, 5);

   for(int i = 0; i < 20; i++)
     {
      string nk = PFX + "PK" + IntegerToString(i);
      string nv = PFX + "PV" + IntegerToString(i);
      if(i >= g_rows)
        {
         ObjectDelete(0, nk);
         ObjectDelete(0, nv);
         continue;
        }
      int y = pad + 6 + (lower ? (g_rows - 1 - i) : i) * lineH;
      int anchor = right ? (lower ? ANCHOR_RIGHT_LOWER : ANCHOR_RIGHT_UPPER) : (lower ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
      if(lower) y = pad + 6 + (g_rows - 1 - i) * lineH;
      int xVal = right ? pad + 9 : pad + 9 + keyW;
      int xKey = right ? pad + 9 + valW : pad + 9;
      Label(nk, corner, anchor, xKey, y, g_rowKey[i], i == 0 ? clrWhite : C'150,156,166', fs);
      Label(nv, corner, anchor, xVal, y, g_rowVal[i], g_rowClr[i], fs, i == 0 ? "Arial Bold" : "Arial");
     }
  }

//+------------------------------------------------------------------+
//| Support / resistance levels                                      |
//+------------------------------------------------------------------+
void AddLevel(double &lp[], string &ln[], bool &lf[], int &ls[], bool &lh[], int &cnt,
              const double p, const string name, const bool flat, const int str, const bool htf)
  {
   ArrayResize(lp, cnt + 1); ArrayResize(ln, cnt + 1); ArrayResize(lf, cnt + 1); ArrayResize(ls, cnt + 1); ArrayResize(lh, cnt + 1);
   lp[cnt] = p; ln[cnt] = name; lf[cnt] = flat; ls[cnt] = str; lh[cnt] = htf;
   cnt++;
  }

void DrawLevel(const string tag, const datetime t1, const datetime t2, const double lo, const double hi,
               const bool zone, const color clr, const bool flat, const bool htf, const string text)
  {
   string nl = PFX + "SRL" + tag, nt = PFX + "SRT" + tag;
   if(zone)
     {
      string nz = PFX + "SRZ" + tag;
      ObjectCreate(0, nz, OBJ_RECTANGLE, 0, t1, hi, t2, lo);
      ObjectSetInteger(0, nz, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, nz, OBJPROP_FILL, false);
      ObjectSetInteger(0, nz, OBJPROP_WIDTH, flat ? 2 : 1);
      ObjectSetInteger(0, nz, OBJPROP_STYLE, htf ? STYLE_DOT : (flat ? STYLE_SOLID : STYLE_DASH));
      ObjectSetInteger(0, nz, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nz, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectCreate(0, nl, OBJ_TREND, 0, t1, lo, t2, lo);
      ObjectSetInteger(0, nl, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, nl, OBJPROP_WIDTH, flat ? 2 : 1);
      ObjectSetInteger(0, nl, OBJPROP_STYLE, htf ? STYLE_DOT : (flat ? STYLE_SOLID : STYLE_DASH));
      ObjectSetInteger(0, nl, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, nl, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nl, OBJPROP_HIDDEN, true);
     }
   ObjectCreate(0, nt, OBJ_TEXT, 0, t2, (lo + hi) / 2.0);
   ObjectSetString(0, nt, OBJPROP_TEXT, " " + text);
   ObjectSetInteger(0, nt, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, nt, OBJPROP_FONTSIZE, InpPanelFont);
   ObjectSetInteger(0, nt, OBJPROP_ANCHOR, ANCHOR_LEFT);
   ObjectSetInteger(0, nt, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, nt, OBJPROP_HIDDEN, true);
  }

void PaintLevels(const int n, const datetime &time[], const double &close[], const double kijunNow, const bool kijunFlat,
                 const double kumoTop, const double kumoBot, const bool topFlat, const bool botFlat, const bool kumoOk,
                 const bool senkouBFlat, const int jc)
  {
   ObjectsDeleteAll(0, PFX + "SR");
   if(!InpShowSR || !kumoOk) return;
   const int L = n - 1;
   double atr = cAtr[L];
   if(atr <= 0) return;
   double clThr = atr * 0.25;
   double mergeThr = InpSrCluster ? clThr : g_tick;

   double lp[]; string ln[]; bool lf[]; int ls[]; bool lh[];
   int cnt = 0;
   AddLevel(lp, ln, lf, ls, lh, cnt, kijunNow, "Kijun", kijunFlat, kijunFlat ? 4 : 3, false);
   AddLevel(lp, ln, lf, ls, lh, cnt, kumoBot, "Kumo bottom", botFlat, botFlat ? 4 : 2, false);
   if(kumoTop - kumoBot > clThr) AddLevel(lp, ln, lf, ls, lh, cnt, kumoTop, "Kumo top", topFlat, topFlat ? 4 : 2, false);

   // historical flat Senkou B and flat Kijun: lasting S/R magnets
   for(int ni = 1; ni <= 20; ni++)
     {
      int idx = L - InpDisp - ni * 5;
      if(idx >= InpFlatBars && cB[idx] != EMPTY_VALUE && IsFlat(cB, idx, InpFlatBars))
         AddLevel(lp, ln, lf, ls, lh, cnt, cB[idx], "Old Kumo B", true, 4, false);
     }
   for(int ni = 1; ni <= 12; ni++)
     {
      int idx = L - ni * 5;
      if(idx >= InpFlatBars && cK[idx] != EMPTY_VALUE && IsFlat(cK, idx, InpFlatBars) && MathAbs(cK[idx] - kijunNow) > clThr)
         AddLevel(lp, ln, lf, ls, lh, cnt, cK[idx], "Old Kijun", true, 4, false);
     }

   // higher timeframe levels
   if(g_htfReady && jc - InpDisp > InpFlatBars)
     {
      int hi0 = jc - InpDisp;
      if(hA[hi0] != EMPTY_VALUE && hB[hi0] != EMPTY_VALUE)
        {
         double mTop = MathMax(hA[hi0], hB[hi0]), mBot = MathMin(hA[hi0], hB[hi0]);
         bool mTopFlat = IsFlatEdge(hA, hB, hi0, InpFlatBars, true);
         bool mBotFlat = IsFlatEdge(hA, hB, hi0, InpFlatBars, false);
         AddLevel(lp, ln, lf, ls, lh, cnt, mBot, "HTF Kumo bottom", mBotFlat, mBotFlat ? 4 : 2, true);
         if(mTop - mBot > clThr) AddLevel(lp, ln, lf, ls, lh, cnt, mTop, "HTF Kumo top", mTopFlat, mTopFlat ? 4 : 2, true);
        }
      for(int ni = 1; ni <= 20; ni++)
        {
         int hidx = hi0 - ni * 5;
         if(hidx < InpFlatBars) break;
         if(hB[hidx] != EMPTY_VALUE && IsFlat(hB, hidx, InpFlatBars))
            AddLevel(lp, ln, lf, ls, lh, cnt, hB[hidx], "Old HTF Kumo B", true, 4, true);
        }
     }
   if(cnt == 0) return;

   // sort ascending by price (insertion sort on indices)
   int idxs[];
   ArrayResize(idxs, cnt);
   for(int i = 0; i < cnt; i++) idxs[i] = i;
   for(int i = 1; i < cnt; i++)
     {
      int key = idxs[i];
      int j = i - 1;
      while(j >= 0 && lp[idxs[j]] > lp[key])
        {
         idxs[j + 1] = idxs[j];
         j--;
        }
      idxs[j + 1] = key;
     }

   // cluster
   double cMin[], cMax[]; string cNm[]; bool cFl[], cH[]; int cCt[];
   int nCl = 0;
   bool used[];
   ArrayResize(used, cnt);
   for(int i = 0; i < cnt; i++) used[i] = false;
   for(int i = 0; i < cnt; i++)
     {
      int oi = idxs[i];
      if(used[oi]) continue;
      double pLo = lp[oi], pHi = lp[oi];
      string nm = ln[oi];
      bool fl = lf[oi], hh = lh[oi];
      int ct = 1;
      used[oi] = true;
      for(int j = i + 1; j < cnt; j++)
        {
         int oj = idxs[j];
         if(lp[oj] - pHi > mergeThr) break;
         if(used[oj]) continue;
         pHi = lp[oj];
         if(StringFind(nm, ln[oj]) < 0) nm = nm + " + " + ln[oj];
         if(lf[oj]) fl = true;
         if(!lh[oj]) hh = false;
         ct++;
         used[oj] = true;
        }
      ArrayResize(cMin, nCl + 1); ArrayResize(cMax, nCl + 1); ArrayResize(cNm, nCl + 1);
      ArrayResize(cFl, nCl + 1); ArrayResize(cH, nCl + 1); ArrayResize(cCt, nCl + 1);
      cMin[nCl] = pLo; cMax[nCl] = pHi; cNm[nCl] = nm; cFl[nCl] = fl; cH[nCl] = hh; cCt[nCl] = ct;
      nCl++;
     }

   int nS = 0;
   for(int k = 0; k < nCl; k++)
      if((cMin[k] + cMax[k]) / 2.0 < close[L]) nS++;
   int sCur = nS, rCur = 1;
   datetime t1 = time[L];
   datetime t2 = time[L] + (datetime)(InpSrExtend * PeriodSeconds(_Period));
   for(int k = 0; k < nCl; k++)
     {
      double mid = (cMin[k] + cMax[k]) / 2.0;
      bool isR = mid >= close[L];
      int rank = isR ? rCur : sCur;
      if(isR) rCur++; else sCur--;
      if(rank > InpSrMax) continue;
      bool zone = cCt[k] > 1 && cMax[k] > cMin[k] && InpSrCluster;
      string rng = (cCt[k] > 1 && cMax[k] > cMin[k]) ? Px(cMin[k]) + " - " + Px(cMax[k]) : Px(cMin[k]);
      string txt = (isR ? "R" : "S") + IntegerToString(rank) + (cH[k] ? "[H]" : "") + "  " + rng + "  " + cNm[k];
      DrawLevel(IntegerToString(k), t1, t2, cMin[k], cMax[k], zone, isR ? InpResistanceColor : InpSupportColor, cFl[k], cH[k], txt);
     }
  }

//+------------------------------------------------------------------+
//| Analysis of the last bar: status panel rows + S/R                |
//+------------------------------------------------------------------+
void Analyze(const int n, const datetime &time[], const double &high[], const double &low[], const double &close[])
  {
   const int L = n - 1;
   const int c0 = L - InpDisp;
   g_rows = 0;
   color tBull = C'8,153,129', tBear = C'242,54,69', tWarn = C'255,152,0', tBase = clrWhite;
   double atr = cAtr[L];
   double tenkan = cT[L], kijun = cK[L];
   bool kumoOk = (c0 >= 0 && cA[c0] != EMPTY_VALUE && cB[c0] != EMPTY_VALUE && tenkan != EMPTY_VALUE && kijun != EMPTY_VALUE && atr > 0);
   double kumoTop = 0, kumoBot = 0;
   bool kumoBull = false, above = false, below = false, inside = false;
   if(kumoOk)
     {
      kumoTop = MathMax(cA[c0], cB[c0]);
      kumoBot = MathMin(cA[c0], cB[c0]);
      kumoBull = cA[c0] > cB[c0];
      above = close[L] > kumoTop;
      below = close[L] < kumoBot;
      inside = !above && !below;
     }
   Row("Crypex", _Symbol, clrWhite);
   if(!kumoOk)
     {
      Row("Status", "Warm-up (not enough bars)", tBase);
      if(InpShowPanel) PaintPanel();
      PaintLevels(n, time, close, 0, false, 0, 0, false, false, false, false, 0);
      return;
     }

   bool tkBull = tenkan > kijun, tkBear = tenkan < kijun;
   bool futBull = cA[L] > cB[L], futBear = cA[L] < cB[L];
   bool kijunFlat = IsFlat(cK, L, InpFlatBars);
   double slope5 = (L >= 5 && cK[L - 5] != EMPTY_VALUE) ? kijun - cK[L - 5] : 0;
   bool kijunRise = !kijunFlat && slope5 > 0, kijunFall = !kijunFlat && slope5 < 0;
   bool senkouBFlat = IsFlat(cB, c0, InpFlatBars);
   bool aFlatNow = IsFlat(cA, c0, InpFlatBars);
   double sbLag = cB[c0], sbLag5 = (c0 >= 5) ? cB[c0 - 5] : EMPTY_VALUE;
   double tenkanDist = MathAbs(close[L] - tenkan) / atr;
   double chHi = (L - InpDisp >= 0) ? high[L - InpDisp] : EMPTY_VALUE;
   double chLo = (L - InpDisp >= 0) ? low[L - InpDisp] : EMPTY_VALUE;
   double chScore = (chHi == EMPTY_VALUE) ? 0.0 : (close[L] > chHi ? 1.0 : (close[L] < chLo ? -1.0 : 0.0));

   double score = (above ? 2.0 : (below ? -2.0 : 0.0)) + (tkBull ? 1.0 : (tkBear ? -1.0 : 0.0))
                + (close[L] > kijun ? 1.0 : (close[L] < kijun ? -1.0 : 0.0)) + (futBull ? 1.0 : (futBear ? -1.0 : 0.0))
                + chScore + (kijunRise ? 0.5 : (kijunFall ? -0.5 : 0.0));
   string bias = score >= 5.5 ? "Strong Bullish" : score >= 2.0 ? "Bullish Bias" : score <= -5.5 ? "Strong Bearish" : score <= -2.0 ? "Bearish Bias" : "Neutral / Range";

   Row("Tenkan / Kijun", Px(tenkan) + " / " + Px(kijun), tBase);
   Row("Regime", above ? "Above Kumo" : (below ? "Below Kumo" : "Inside Kumo"), above ? tBull : (below ? tBear : tWarn));
   Row("Cloud (now)", kumoBull ? "Green (A>B)" : "Red (A<B)", kumoBull ? tBull : tBear);
   Row("Future cloud", futBull ? "Bullish (A>B)" : (futBear ? "Bearish (A<B)" : "Flat / twisting"), futBull ? tBull : (futBear ? tBear : tBase));
   string align = (kumoBull && above && tkBull) ? "Strong bull stack" : (!kumoBull && below && tkBear) ? "Strong bear stack" :
                  (above && !tkBull) ? "Pullback risk (above cloud)" : (below && !tkBear) ? "Bounce risk (below cloud)" : "Mixed / transit";
   Row("Alignment", align, (above && tkBull) ? tBull : ((below && tkBear) ? tBear : tWarn));
   Row("TK momentum", tkBull ? "Tenkan > Kijun" : (tkBear ? "Tenkan < Kijun" : "Flat"), tkBull ? tBull : (tkBear ? tBear : tBase));
   Row("Kijun slope", kijunFlat ? "Flat · equilibrium magnet" : (kijunRise ? "Rising" : (kijunFall ? "Falling" : "Sideways")), kijunFlat ? tWarn : (kijunRise ? tBull : (kijunFall ? tBear : tBase)));
   string sbTxt = senkouBFlat ? "Flat S/R @ " + Px(sbLag) : ((sbLag5 != EMPTY_VALUE && sbLag > sbLag5) ? "Rising" : ((sbLag5 != EMPTY_VALUE && sbLag < sbLag5) ? "Falling" : "Sideways"));
   Row("Senkou B", sbTxt, senkouBFlat ? tWarn : tBase);
   Row("Chikou", chHi == EMPTY_VALUE ? "Warm-up" : (chScore > 0 ? "Clear bullish" : (chScore < 0 ? "Clear bearish" : "Trapped (neutral)")), chScore > 0 ? tBull : (chScore < 0 ? tBear : tWarn));
   Row("Tenkan distance", DoubleToString(tenkanDist, 2) + " ATR" + (tenkanDist > 1.0 ? "  ! overext" : (tenkanDist < 0.3 ? "  · near" : "")), tenkanDist > 1.0 ? tWarn : tBase);
   double thick = kumoTop - kumoBot;
   double thickAtr = thick / atr;
   Row("Kumo thickness", Px(thick) + (thickAtr > 1.5 ? "  · Thick" : (thickAtr < 0.5 ? "  · Thin" : "  · Normal")), tBase);
   double dTop = kumoTop - close[L], dBot = close[L] - kumoBot;
   Row("Distance (edge)", "up " + DoubleToString(dTop / g_tick, 0) + " t / dn " + DoubleToString(dBot / g_tick, 0) + " t", tBase);
   double nearest = MathMin(MathAbs(dTop), MathAbs(dBot));
   Row("Nearest · ATR", Px(nearest) + " · " + DoubleToString(nearest / atr, 2) + " ATR", tBase);
   Row("ATR(14)", Px(atr), tBase);
   Row("Score · Bias", DoubleToString(score, 1) + "  ->  " + bias, score >= 2.0 ? tBull : (score <= -2.0 ? tBear : tWarn));

   int jc = -1;
   if(g_htfReady && ArraySize(jMap) == n) jc = jMap[L];
   if(g_htfReady && jc - InpDisp >= 0 && hA[jc - InpDisp] != EMPTY_VALUE && hB[jc - InpDisp] != EMPTY_VALUE)
     {
      double mTop = MathMax(hA[jc - InpDisp], hB[jc - InpDisp]), mBot = MathMin(hA[jc - InpDisp], hB[jc - InpDisp]);
      bool mBull = hA[jc - InpDisp] > hB[jc - InpDisp];
      string tfName = EnumToString((ENUM_TIMEFRAMES)g_htf);
      StringReplace(tfName, "PERIOD_", "");
      Row("HTF Kumo", (close[L] > mTop ? "Above Kumo (" : (close[L] < mBot ? "Below Kumo (" : "In Kumo (")) + tfName + ")", close[L] > mTop ? tBull : (close[L] < mBot ? tBear : tWarn));
      Row("HTF cloud", mBull ? "HTF green" : "HTF red", mBull ? tBull : tBear);
     }
   else
     {
      Row("HTF Kumo", g_htfOn ? "HTF warm-up" : "-", tBase);
     }
   Row("Shift · Params", IntegerToString(InpDisp) + " ch" + (g_htfOn ? " · " + IntegerToString(g_fwd) + " HTF" : "") + "  |  " + IntegerToString(InpTenkan) + "/" + IntegerToString(InpKijun) + "/" + IntegerToString(InpSenkouB), tBase);

   if(InpShowPanel) PaintPanel();
   else ObjectsDeleteAll(0, PFX + "P");

   bool botIsB = cB[c0] <= cA[c0];
   bool botFlat = botIsB ? senkouBFlat : aFlatNow;
   bool topFlat = botIsB ? aFlatNow : senkouBFlat;
   PaintLevels(n, time, close, kijun, kijunFlat, kumoTop, kumoBot, topFlat, botFlat, true, senkouBFlat, jc);
  }

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Kumo fill (rectangles) and breakout arrows, as chart objects     |
//+------------------------------------------------------------------+
datetime TimeAt(const int k, const int n)
  {
   if(k < 0) return cTm[0];
   if(k <= n - 1) return cTm[k];
   return cTm[n - 1] + (datetime)((k - (n - 1)) * PeriodSeconds(_Period));
  }

void Rect(const string name, const datetime t1, const datetime t2, const double top, const double bottom, const color clr)
  {
   ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, top, t2, bottom);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
  }

void PaintCloud(const int n)
  {
   ObjectsDeleteAll(0, PFX + "K");
   int first = MathMax(0, n - InpCloudBars);
   if(InpFillKumo)
     {
      // chart-TF Kumo: one rectangle per bar (cloud at displayed bar i is made from bar i - disp)
      for(int i = MathMax(first, InpDisp); i <= n - 1 + InpDisp; i++)
        {
         int r = i - InpDisp;
         if(r < 0 || r > n - 1 || cA[r] == EMPTY_VALUE || cB[r] == EMPTY_VALUE) continue;
         Rect(PFX + "KC" + IntegerToString(i), TimeAt(i, n), TimeAt(i + 1, n), MathMax(cA[r], cB[r]), MathMin(cA[r], cB[r]), cA[r] > cB[r] ? InpBullKumo : InpBearKumo);
        }
      // higher-timeframe Kumo: one rectangle per HTF step, projected g_fwd bars ahead
      if(g_htfOn && g_htfReady && ArraySize(cHtfA) == n)
        {
         int runStart = -1;
         for(int i = first; i <= n; i++)
           {
            bool ok = (i < n) && cHtfA[i] != EMPTY_VALUE && cHtfB[i] != EMPTY_VALUE;
            bool same = ok && runStart >= 0 && cHtfA[i] == cHtfA[runStart] && cHtfB[i] == cHtfB[runStart];
            if(runStart >= 0 && !same)
              {
               Rect(PFX + "KH" + IntegerToString(runStart), TimeAt(runStart + g_fwd, n), TimeAt(i + g_fwd, n),
                    MathMax(cHtfA[runStart], cHtfB[runStart]), MathMin(cHtfA[runStart], cHtfB[runStart]),
                    cHtfA[runStart] > cHtfB[runStart] ? InpHtfBullKumo : InpHtfBearKumo);
               runStart = -1;
              }
            if(ok && runStart < 0) runStart = i;
           }
        }
     }
   if(InpMarkKumoBo)
      for(int i = first; i < n; i++)
        {
         if(cKbUp[i] != EMPTY_VALUE)
           {
            string a = PFX + "KBU" + IntegerToString(i);
            ObjectCreate(0, a, OBJ_ARROW, 0, cTm[i], cKbUp[i]);
            ObjectSetInteger(0, a, OBJPROP_ARROWCODE, 159);
            ObjectSetInteger(0, a, OBJPROP_COLOR, InpTenkanColor);
            ObjectSetInteger(0, a, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, a, OBJPROP_HIDDEN, true);
           }
         if(cKbDn[i] != EMPTY_VALUE)
           {
            string a = PFX + "KBD" + IntegerToString(i);
            ObjectCreate(0, a, OBJ_ARROW, 0, cTm[i], cKbDn[i]);
            ObjectSetInteger(0, a, OBJPROP_ARROWCODE, 159);
            ObjectSetInteger(0, a, OBJPROP_COLOR, InpKijunColor);
            ObjectSetInteger(0, a, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, a, OBJPROP_HIDDEN, true);
           }
        }
  }

//+------------------------------------------------------------------+
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[])
  {
   int total = MathMin(rates_total, InpMaxBars);
   int need = MathMax(InpSenkouB, MathMax(InpKijun, InpTenkan)) + InpDisp + 5;
   if(total < need) return 0;
   uint now = GetTickCount();
   bool newBar = (Time[0] != g_lastBar) || prev_calculated == 0;
   if(!newBar && now - g_lastPaint < 1500) return rates_total;

   N = total;
   ArrayResize(cHi, N); ArrayResize(cLo, N); ArrayResize(cCl, N); ArrayResize(cTm, N);
   for(int i = 0; i < N; i++)
     {
      int s = N - 1 - i;
      cHi[i] = High[s];
      cLo[i] = Low[s];
      cCl[i] = Close[s];
      cTm[i] = Time[s];
     }
   ComputeChart(N, cHi, cLo, cCl);

   if(newBar)
     {
      g_lastBar = Time[0];
      if(g_htfOn)
        {
         BuildHtf(N, cTm);
         g_lastHtfTry = now;
        }
      CheckAlerts(N, cTm, cCl);
     }
   else if(g_htfOn && !g_htfReady && now - g_lastHtfTry > 5000)
     {
      BuildHtf(N, cTm);            // HTF history was not loaded yet: retry every 5 s
      g_lastHtfTry = now;
     }

   PushBuffers(N, prev_calculated == 0);
   if(newBar) PaintCloud(N);
   g_lastPaint = now;
   Analyze(N, cTm, cHi, cLo, cCl);
   PaintWatermark();
   ChartRedraw();
   g_firstDone = true;
   return rates_total;
  }
//+------------------------------------------------------------------+

منطق کار

  • خطوط و ابر طبق قرارداد TradingView: تنکان و کیجون (میانگین سقف و کف دونچین)، سنکو A و B به‌اندازه‌ی ۲۵ کندل جلوتر و چیکو اسپن ۲۵ کندل عقب‌تر.
  • ابر تایم‌فریم بالاتر: یک تایم‌فریم بالاتر (به‌صورت خودکار یا دستی) با همان پارامترها روی چارت می‌آید. فقط کندل‌های تأییدشده خوانده می‌شود، پس روی کندل زنده تغییر نمی‌کند و ریپینت ندارد.
  • حمایت و مقاومت: کیجون، لبه‌های ابر و سنکو B تخت (flat) گذشته با ستاره‌ی قدرت رتبه‌بندی می‌شوند. سطوح نزدیک به هم (کمتر از ۰٫۲۵ ATR) به یک ناحیه ادغام می‌شوند و نزدیک‌ترین‌ها S1 و R1 نام می‌گیرند. سطوح تایم‌فریم بالاتر با برچسب [H] مشخص‌اند.
  • امتیاز ترکیبی (حداکثر ±۶٫۵): جایگاه قیمت نسبت به ابر (±۲)، ترتیب تنکان و کیجون (±۱)، قیمت نسبت به کیجون (±۱)، رنگ ابر آینده (±۱)، چیکو اسپن (±۱) و شیب کیجون (±۰٫۵). امتیاز ۲ به بالا «تمایل صعودی» و ۵٫۵ به بالا «صعودی قوی» است (برای نزولی برعکس).
  • نشانگرها (اختیاری): تقاطع تنکان و کیجون، تقاطع قیمت با کیجون یا تنکان، تویست ابر و شکست ابر. آلارم برای تقاطع TK، شکست ابر و تویست ابر آماده است.
  • جدول وضعیت: جایگاه نسبت به ابر، رنگ ابر حال و آینده، شیب کیجون، وضعیت سنکو B، چیکو، ضخامت و فاصله‌ی ابر بر حسب تیک و ATR، و وضعیت ابر تایم‌فریم بالاتر.
  • نسخه‌ی متاتریدر ۵ و ۴: همه‌ی بخش‌های اصلی (ابر تایم‌فریم بالاتر، سطوح S/R، پنل وضعیت، نشانگرها، آلارم و واترمارک) را دارد. تفاوت‌ها: ابر در MT5 با رسم پرشده‌ی خود پلتفرم و در MT4 با آبجکت‌های مستطیلی (به‌طور پیش‌فرض ۳۰۰ کندل آخر) رسم می‌شود؛ حالت‌های رنگ‌آمیزی کندل و پس‌زمینه‌ی TradingView را ندارد؛ جابه‌جایی ابر طبق قرارداد کلاسیک متاتریدر ۲۶ کندل جلوتر است (در TradingView ۲۵).

تنظیمات مهم

Tenkan / Kijun / Senkou B
دوره‌های ایچیموکو؛ پیش‌فرض ۹ / ۲۶ / ۵۲
Displacement
جابه‌جایی ابر و چیکو؛ پیش‌فرض ۲۶ (طبق قرارداد TradingView ابر ۲۵ کندل جلو می‌رود)
Show HTF Kumo / Auto next TF
نمایش ابر تایم‌فریم بالاتر و انتخاب خودکار تایم‌فریم (۱ ساعته به ۴ ساعته، ۴ ساعته به روزانه، روزانه به هفتگی)
Market calendar
کریپتو ۲۴/۷ است و فارکس، طلا و سهام فقط ۵ روز در هفته. این گزینه موقعیت ابر هفتگی و ماهانه را درست جلو می‌برد (پیش‌فرض: Auto)
Min flat bars
حداقل کندل‌های متوالی برای اینکه کیجون یا سنکو B «تخت» حساب شود
S/R levels / Cluster zones / Max levels
نمایش سطوح، ادغام سطوح نزدیک و حداکثر تعداد سطح در هر سمت
Style
سه حالت ظاهر: Crypex، Colored و Monochrome
Show crypex.org watermark
واترمارک قابل خاموش‌کردن
MT4/MT5: Kumo fill, Bars of Kumo fill
(فقط MT4) پر کردن ابر و تعداد کندل‌هایی که ابر برایشان رسم می‌شود؛ کمتر یعنی چارت سبک‌تر
MT4/MT5: Alerts
هشدار پاپ‌آپ، پوش و ایمیل برای تقاطع TK، شکست و تویست ابر

کجا بهتر کار می‌کند

چارت‌های ۱ ساعته تا روزانه روی طلا، جفت‌ارزهای اصلی و ارزهای دیجیتال پرنقدینگی؛ برای هم‌راستایی تایم‌فریم (مثلاً ورود در ۱ ساعته در جهت ابر ۴ ساعته). ابر هفتگی و ماهانه روی کریپتو دقیق است و روی فارکس و طلا با تنظیم تقویم بازار تقریباً درست جلو می‌رود.

نقطه‌ضعف

این یک اندیکاتور تحلیلی است، نه سیگنال معاملاتی؛ امتیاز و برچسب‌ها فقط وضعیت ایچیموکو را خلاصه می‌کنند. در بازار رنج، تقاطع‌ها و تویست‌ها زیاد و کاذب می‌شوند. روی سهام با جلسه‌ی کوتاه (غیر ۲۴ ساعته) برای تایم‌فریم‌های درون‌روزی باید «تعداد کندل هر کندل HTF» را دستی وارد کنید. کد متاتریدر در MetaEditor کامپایل نشده و فقط با شبیه‌ساز اجرا بررسی شده است؛ قبل از استفاده آن را کامپایل کنید و روی حساب دمو و چند تایم‌فریم تست کنید.

این کد آموزشی است و توصیه‌ی سرمایه‌گذاری نیست. قبل از استفاده روی حساب واقعی، کامپایل و رفتار آن را روی حساب دمو و در بک‌تست بررسی کنید؛ نتیجه‌ی گذشته تضمین‌کننده‌ی آینده نیست.

← همه‌ی اسکریپت‌ها