Structure Compass
Reads market structure on two scales: direction, break strength, the level that would flip it, and where price sits in the range.
Which way is the market pointing, how solid is that, which level would turn it around, and where is price within the last major range? Structure Compass is built to answer those questions at a glance.
How it works
Swing highs and lows are found by volatility rather than a fixed number of bars: a high is confirmed once price has come back from it by a set multiple of the average bar range (ATR). Two scales run side by side. The major scale reads the main direction; the minor scale reads the waves within it.
A close beyond the last confirmed swing is a break:
- BOS (break of structure): price breaks the last swing in the direction of the trend, so the trend continues.
- CHoCH (change of character): price breaks the last swing against the trend, so the trend turns.
- CHoCH+ (strong change of character): a recent turn failed to hold, and the trend now turns back again.
Every break is graded weak, firm or strong by how far the close went past the level and by the volume of the breaking bar. Weak breaks are drawn faint.
Reading the chart
- Green: rising structure. Upward breaks and order zones below price are drawn in green.
- Red: falling structure. Downward breaks and order zones above price are drawn in red.
- Amber line: the flip level, the confirmed major swing whose break would turn the major direction. It ends just after the last bar; the further price is from it, the more room the current direction has.
- Grey dashed line: the middle of the last major range. Price above it is in the premium half, below it in the discount half.
- Flip tag ("Flip · 82940.80 · 1.77% away"): the price of the flip level and how far the last close is from it. Watch this distance shrink: a close beyond the amber line turns the major direction.
- BOS and CHoCH tags: a break of structure (the trend continues) or a change of character (the trend turns), written at the broken level. The three dots grade the break: one filled dot is weak, two firm, three strong (how far the close went past the level and how heavy the volume was).
- "▼ 1.52% since" after a tag: the furthest price has travelled beyond the broken level since the break. A small figure on an old break means the move did not follow through.
- Order zone label ("2.45K traded · buy 90% · sell 10% · 0.9% away"): the volume traded inside the zone since it formed, the estimated share of buyers and sellers in that volume, and the distance from price to the zone. When price is inside the zone, "inside" replaces the distance, and "no trades yet" means price has not come back to it.
- "Major ·" before a zone label: the zone was left by a break on the major scale; a label without it comes from the minor scale. Major zones mark levels that mattered to the main direction.
- Layers inside a zone: the shading grows darker toward the last bar, and the thick edge line is the side price would meet first. The green bar at the top and the red bar at the bottom of the right end show the buy and sell shares at a glance.
- Range dial (ten slices to the right of the last bar): the last major range from its low to its high; the lower five slices are green (discount), the upper five red (premium), deeper toward the ends. The lit slice holds the current price and its box ("54%") gives the position of price in the range, 0% at the low and 100% at the high.
- Small triangles: stop hunts, bars whose wick ran past the highs or lows of recent bars and then closed back. They often mark where stops were taken before price turned.
- EQH and EQL: equal highs and equal lows, two minor swings that stopped at nearly the same price. Stops tend to gather beyond them, so a break of that line is often sharp.
- Board, rows "Major" and "Minor": the direction of each scale and how many bars it has lasted. When both point the same way, the structure is aligned.
- Board, row "Flip level": the distance from price to the flip level and whether the level is above or below price. A small distance means the major direction is close to turning.
- Board, row "In range": where price sits in the last major range, with "premium" above 50% and "discount" below it. It tells you whether price is relatively high or low within the range.
- Board, row "Held 12 bars": for each grade (weak, firm, strong), the share of past breaks on this symbol that held for 12 bars without a close back behind the level. Until enough breaks have finished, it shows a count such as "0/5" instead of a rate.
- Board, row "Trend 1/5 up": on five timeframes (5 min, 15 min, 1 h, 4 h and daily), how many show the 20-bar average above the 50-bar one, with an arrow for each. The more arrows agree with the chart, the stronger the backdrop.
Settings
- Major reversal (× ATR)
- 6
- How far price must come back for a major swing to be confirmed. Smaller values flip the main direction more often.
- Minor reversal (× ATR)
- 2.5
- The same for the minor scale.
- Lookback (bars)
- 1000
- How many recent bars the structure is read from; about five months on 4h.
- Bars a break must hold
- 12
- Bars that must pass without a close back behind the level for a break to count as held.
- Zones per side
- 4
- The maximum number of active order zones kept above and below price.
- Hunt lookback (bars)
- 15
- How many earlier highs or lows a wick must run past to count as a stop hunt.
- Show chart patterns
- off
- Turn on to draw double tops and bottoms, head and shoulders, triangles and wedges.
- Breaks before showing a rate
- 5
- Breaks needed before the board shows a hold rate.
Limitations
A swing is only confirmed once price has moved far enough away from it, so the structure always trails price by a few bars.
Hold rates are counted from this symbol's own history and describe past behaviour. They are hidden until there are enough breaks, and even then they cannot say how the next break will turn out.
Piyasa hangi yöne bakıyor, bu yön ne kadar sağlam, hangi seviye kırılırsa yön döner ve fiyat son büyük aralığın neresinde? Structure Compass bu soruları tek bakışta cevaplamak için yazıldı.
Nasıl çalışır
Tepe ve dipler sabit bir bar sayısıyla değil, oynaklıkla bulunur: fiyat bir tepeden ortalama bar aralığının (ATR) belli bir katı kadar geri gelince o tepe kesinleşir. Yan yana iki ölçek çalışır: büyük ölçek ana yönü, küçük ölçek o yönün içindeki dalgaları okur.
Son kesinleşmiş tepe ya da dibin ötesinde bir kapanış kırılımdır:
- BOS (yapı kırılımı): fiyat, eğilimle aynı yönde son tepeyi ya da dibi kırıyor; eğilim sürüyor.
- CHoCH (yön değişimi): fiyat, eğilimin tersine son tepeyi ya da dibi kırıyor; eğilim dönüyor.
- CHoCH+ (güçlü yön değişimi): yakın zamanda yaşanan bir dönüş tutmamıştı; yön şimdi yeniden çevriliyor.
Her kırılım, kapanışın seviyeyi ne kadar geçtiğine ve kırılım barının hacmine göre zayıf, sağlam ya da güçlü diye derecelendirilir. Zayıf kırılımlar soluk çizilir.
Nasıl okunur
- Yeşil: yükselen yapı. Yukarı kırılımlar ve fiyatın altındaki emir bölgeleri yeşille çizilir.
- Kırmızı: düşen yapı. Aşağı kırılımlar ve fiyatın üstündeki emir bölgeleri kırmızıyla çizilir.
- Sarı çizgi: dönüş seviyesi; kırılırsa büyük ölçekteki yönü çevirecek kesinleşmiş tepe ya da dip. Son barın hemen sağında biter; fiyat ondan ne kadar uzaksa mevcut yönün o kadar alanı vardır.
- Gri kesikli çizgi: son büyük aralığın ortası. Fiyat bu çizginin üstündeyse pahalı yarıda, altındaysa ucuz yarıdadır.
- Dönüş etiketi ("Dönüş · 82940,80 · %1,77 uzakta"): dönüş seviyesinin fiyatı ve son kapanışın ona uzaklığı. Bu uzaklığın daraldığını izleyebilirsiniz; sarı çizginin ötesinde bir kapanış büyük ölçekteki yönü çevirir.
- BOS ve CHoCH etiketleri: kırılan seviyeye yazılan yapı kırılımı (eğilim sürüyor) ya da yön değişimi (eğilim dönüyor). Üç nokta kırılımın derecesidir: bir dolu nokta zayıf, iki sağlam, üç güçlü (kapanışın seviyeyi ne kadar geçtiği ve hacmin ağırlığı).
- Etiketin yanındaki "▼ %1,52 kırılımdan beri": kırılımdan bu yana fiyatın kırılan seviyenin ötesine en çok ne kadar gittiği. Eski bir kırılımda küçük bir oran, hareketin devam etmediğini gösterir.
- Emir bölgesi etiketi ("2,45K işlem gördü · alış %90 · satış %10 · %0,9 uzakta"): bölge oluştuğundan beri içinde işlem gören hacim, bu hacimde alıcı ve satıcı payının tahmini ve fiyatın bölgeye uzaklığı. Fiyat bölgenin içindeyse uzaklık yerine "içinde" yazar; "henüz işlem yok" ise fiyatın bölgeye geri dönmediğini gösterir.
- Etiketin başındaki "Büyük ölçek ·": bölge büyük ölçekteki bir kırılımdan kalmıştır; öneksiz etiket küçük ölçekten gelir. Büyük ölçek bölgeleri ana yön için önemli olmuş seviyelerdir.
- Bölgenin içindeki katmanlar: dolgu son bara doğru koyulaşır; kalın kenar çizgisi fiyatın ilk karşılaşacağı taraftır. Sağ uçtaki üstte yeşil ve altta kırmızı çubuk, alış ve satış payını tek bakışta gösterir.
- Aralık kadranı (son barın sağındaki on dilim): son büyük aralık, dibinden tepesine. Alttaki beş dilim yeşil (ucuz), üstteki beş dilim kırmızıdır (pahalı); uçlara doğru renk koyulaşır. Yanan dilim fiyatın bulunduğu dilimdir; içindeki kutu ("%54") fiyatın aralıktaki yerini verir: dipte %0, tepede %100.
- Küçük üçgenler: stop avları; fitili yakın barların tepesini ya da dibini geçip geri kapanan barlar. Çoğu zaman fiyat dönmeden önce stopların alındığı yeri gösterirler.
- EQH ve EQL: eşit tepeler ve eşit dipler; neredeyse aynı fiyatta duran iki küçük tepe ya da dip. Ötelerinde stoplar birikir; bu çizginin kırılması çoğu zaman sert olur.
- Pano, "Büyük ölçek" ve "Küçük ölçek" satırları: iki ölçeğin yönü ve bu yönün kaç bardır sürdüğü. İkisi aynı yönü gösteriyorsa yapı uyumludur.
- Pano, "Dönüş seviyesi" satırı: fiyatın dönüş seviyesine uzaklığı ve seviyenin fiyatın üstünde mi altında mı olduğu. Uzaklık küçükse büyük ölçekteki yön dönmeye yakındır.
- Pano, "Aralıkta" satırı: fiyatın son büyük aralıktaki yeri; %50'nin üstü "pahalı bölge", altı "ucuz bölge". Fiyatın aralık içinde görece yüksekte mi alçakta mı olduğunu gösterir.
- Pano, "12 bar tutunma" satırı: her derece (zayıf, sağlam, güçlü) için bu sembolde geçmiş kırılımların 12 bar boyunca seviyenin gerisine kapanış gelmeden tutma oranı. Yeterli kırılım tamamlanana kadar oran yerine "0/5" gibi bir sayı yazar.
- Pano, "Trend 1/5 yükselişte" satırı: beş periyotta (5 dakika, 15 dakika, 1 saat, 4 saat ve günlük) 20 barlık ortalamanın 50 barlık ortalamanın üstünde olduğu periyot sayısı; her periyot için bir ok. Oklar grafikle ne kadar aynı yöndeyse arka plan o kadar güçlüdür.
Ayarlar
- Major reversal (× ATR)
- 6
- Büyük ölçekte bir tepenin ya da dibin kesinleşmesi için gereken geri dönüş. Küçük değer ana yönü daha sık çevirir.
- Minor reversal (× ATR)
- 2,5
- Aynısı küçük ölçek için.
- Lookback (bars)
- 1000
- Yapının kaç barlık geçmişten okunduğu. 4 saatlikte yaklaşık beş ay.
- Bars a break must hold
- 12
- Bir kırılımın tutmuş sayılması için seviyenin gerisine kapanış gelmeden geçmesi gereken bar sayısı.
- Zones per side
- 4
- Fiyatın üstünde ve altında tutulan en çok emir bölgesi.
- Hunt lookback (bars)
- 15
- Bir fitilin stop avı sayılması için kaç önceki barın tepesini ya da dibini geçmesi gerektiği.
- Show chart patterns
- kapalı
- Açılırsa ikili tepe ve dip, omuz baş omuz, üçgen ve kama çizilir.
- Breaks before showing a rate
- 5
- Panoda tutma oranı gösterilmeden önce gereken en az kırılım sayısı.
Sınırlar
Tepe ve dip ancak fiyat ondan yeterince uzaklaşınca kesinleşir; bu yüzden yapı her zaman birkaç bar geriden gelir.
Tutma oranları bu sembolün geçmişinden sayılır ve geçmiş davranışı gösterir. Az sayıda kırılımla oran gösterilmez; çok sayıda kırılımla bile bir sonraki kırılımın aynı biçimde sonuçlanacağı söylenemez.
indicator("Structure Compass", "Compass", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500) // ── Palette (terminal design language, dark theme) ──var TONE_RISE = "#33d69f"var TONE_FALL = "#fb5b6b"var TONE_WARN = "#e3b341"var TONE_INFO = "#5d87c5"var TONE_SOFT = "#818692" var G_SWINGS = "Swings"var G_STRENGTH = "Strength"var G_LEVELS = "Levels"var G_ORDERS = "Resting orders"var G_HUNTS = "Hunts and gaps"
// © Markuda// Reads market structure on two scales and shows where price sits in the last major range. // ═══════════════════════════════════════════════════════════════════════════════// STRUCTURE COMPASS//// Which way is the market pointing, how firm is that, which single level would// turn it around, where are orders waiting, and where in the range is price now?//// Swings are found by volatility, not by a fixed bar window: a high is confirmed// once price has come back from it by a set multiple of ATR (and a low the same// way). Two scales run side by side — major for the main direction, minor for the// waves inside it. A close beyond the last confirmed swing is a break:// BOS the break goes the way the scale already points// CHoCH the break turns the scale around// CHoCH+ a turn that undoes a previous turn which failed within the hold window// Every break is graded by how far the close went past the level (in ATR) and by// the volume of the break bar: weak, firm or strong. Weak breaks are drawn faint.//// The flip level is the one confirmed major swing whose break would turn the// major direction. The board shows its distance, the position of price in the// last major range (0% = range low, 100% = range high) and how often breaks of// each grade held on this symbol: a break holds when no opposite turn arrives// within the hold window.//// To the right of the last bar the dial shows the last major range in ten// slices, green (discount) at the bottom, red (premium) at the top; the slice// price is in is lit and says where in the range price sits.//// Other layers, each with its own switch:// order zones the last opposite candle before a break, on both scales (zones// from major breaks are marked Major), with the buy/sell share// of the volume that traded inside it since. The two nearest on// each side of price are drawn in full, the others faint// stop hunts a bar whose wick runs past recent highs or lows and closes back// gaps a three-bar range with no trading in it, shrinking as it fills// patterns double tops/bottoms, head and shoulders, triangles, wedges// trend board five timeframes: is the 20-bar average above the 50-bar one.// Each timeframe is read from its own bars, on the last bar only// yesterday the previous day's high and low//// Words used// ATR the average bar range over the last 21 bars. Every distance setting is// measured in it, so the same value fits any price.//// Only the most recent bars are studied ("Lookback (bars)", 1,000 by default:// on 4h that is about five months, enough for 5-8 major breaks; more history// mostly adds faded marks that leave the chart anyway). Before that a bar costs// one comparison, so a long chart takes about as long as a short one.//// Default values were chosen by measurement on BTCUSDT and ETHUSDT, 1h and 4h,// 5,000 bars each (October 2026):// major reversal 6 ATR: 5-8 major breaks per 1,000 bars (3 ATR gave 18-23,// too many to call "main direction")// minor reversal 2.5 ATR: 26-30 minor breaks per 1,000 bars (1.25 gave// 76-86 and buried the chart in tags; 2 gave 35-43)// hold window 12 bars, grades from the break-bar close and volume:// weak breaks held 9-21%, firm 22-30%, strong 50-61% of the time.// The old rule (no opposite turn) held 90-100% for every grade and told// nothing, so a break now fails when a close comes back behind the level.// grade limits: weak = close less than 0.2 ATR past the level on volume under// 1.4× its 34-bar average; strong = 0.6 ATR or more on 1.4× or more.//// This is an indicator: it computes past price and does not know the future.// Nothing it draws is a forecast or investment advice; it does not trade and// places no orders.// ═══════════════════════════════════════════════════════════════════════════════ indicator("Structure Compass", "Compass", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500) // ── Palette (terminal design language, dark theme) ──var TONE_RISE = "#33d69f"var TONE_FALL = "#fb5b6b"var TONE_WARN = "#e3b341"var TONE_INFO = "#5d87c5"var TONE_SOFT = "#818692" var G_SWINGS = "Swings"var G_STRENGTH = "Strength"var G_LEVELS = "Levels"var G_ORDERS = "Resting orders"var G_HUNTS = "Hunts and gaps"var G_PATTERNS = "Patterns"var G_BOARD = "Board"var G_CANDLES = "Candles"var G_COLORS = "Colors" // ── Inputs ──var majorTurn = inputFloat(6.0, "Major reversal (× ATR)", min = 1.0, max = 12.0, step = 0.25, group = G_SWINGS, tooltip = "A major swing is confirmed once price comes back from it by this many average bar ranges.")var minorTurn = inputFloat(2.5, "Minor reversal (× ATR)", min = 0.25, max = 6.0, step = 0.25, group = G_SWINGS, tooltip = "The same for the minor scale. It should be smaller than the major reversal.")var historyBars = inputInt(1000, "Lookback (bars)", min = 300, max = 5000, step = 100, group = G_SWINGS, tooltip = "Structure is read from the most recent bars only. A larger value draws more history and runs slower.")var majorBreaksOn = inputBool(true, "Show major breaks", group = G_SWINGS, tooltip = "Draws BOS and CHoCH of the major scale as solid lines.")var minorBreaksOn = inputBool(true, "Show minor breaks", group = G_SWINGS, tooltip = "Draws BOS and CHoCH of the minor scale as dashed lines.")var plusOn = inputBool(true, "Mark a second reversal (CHoCH+)", group = G_SWINGS, tooltip = "Marks a turn as CHoCH+ when it undoes a previous turn that failed within the hold window.")var keepBreaks = inputInt(8, "Breaks kept per scale", min = 1, max = 50, step = 1, group = G_SWINGS, tooltip = "Only the most recent breaks of each scale stay on the chart. The newest is drawn bright, older ones fade.")var tagSizeName = inputString("Small", "Break tag size", group = G_SWINGS, tooltip = "Text size of the BOS / CHoCH tags.", options = ["Tiny", "Small", "Normal"])var gradeOn = inputBool(true, "Grade breaks by strength", group = G_STRENGTH, tooltip = "Draws weak breaks faint and writes the grade in the tag's tooltip.")var holdBars = inputInt(12, "Bars a break must hold", min = 2, max = 200, step = 1, group = G_STRENGTH, tooltip = "A break counts as held when no close comes back behind the broken level within this many bars.")var flipOn = inputBool(true, "Show flip level", group = G_LEVELS, tooltip = "The confirmed major swing whose break would turn the major direction.")var midOn = inputBool(true, "Show range midline", group = G_LEVELS, tooltip = "The middle of the last major range. Above it is premium, below it discount.")var dialOn = inputBool(true, "Show range dial", group = G_LEVELS, tooltip = "Ten slices to the right of the last bar that show where price sits in the last major range. Needs the range midline.")var dayOn = inputBool(false, "Show yesterday's high/low", group = G_LEVELS, tooltip = "Two lines at the previous day's high and low.")var shelvesOn = inputBool(true, "Draw order zones", group = G_ORDERS, tooltip = "Marks the last opposite candle before a break, on both scales. Zones from major breaks are marked Major.")var zonesPerSide = inputInt(4, "Zones per side", min = 1, max = 10, step = 1, group = G_ORDERS, tooltip = "The most live order zones kept above and below price. The two nearest on each side are drawn in full, the others as a thin frame.")var shareOn = inputBool(true, "Show buy/sell share", group = G_ORDERS, tooltip = "Writes inside each zone how the volume traded there since it formed splits between buyers and sellers.")var keepSpent = inputBool(false, "Keep spent zones", group = G_ORDERS, tooltip = "When on, a zone that price closed through stays on the chart, faded.")var huntsOn = inputBool(true, "Show stop hunts", group = G_HUNTS, tooltip = "Marks a bar whose wick runs past recent highs or lows and closes back.")var huntsKept = inputInt(8, "Stop hunts kept", min = 1, max = 50, step = 1, group = G_HUNTS, tooltip = "Only the most recent stop hunts stay on the chart.")var huntLookback = inputInt(15, "Hunt lookback (bars)", min = 3, max = 100, step = 1, group = G_HUNTS, tooltip = "How many earlier bars the wick has to run past.")var equalOn = inputBool(true, "Mark equal highs and lows", group = G_HUNTS, tooltip = "Joins two minor swings that stopped at nearly the same price: stops tend to rest just beyond them.")var equalTol = inputFloat(0.25, "Equal within (× ATR)", min = 0.05, max = 1.0, step = 0.05, group = G_HUNTS, tooltip = "Two swings count as equal when they are closer than this many average bar ranges.")var gapsOn = inputBool(true, "Draw untraded gaps", group = G_HUNTS, tooltip = "Marks a three-bar range with no trading in it and shrinks it as price fills it.")var tinyGap = inputFloat(0.3, "Skip gaps smaller than (× ATR)", min = 0.0, max = 3.0, step = 0.05, group = G_HUNTS, tooltip = "Gaps smaller than this many average bar ranges are not drawn.")var shapesOn = inputBool(false, "Show chart patterns", group = G_PATTERNS, tooltip = "Reads double tops and bottoms, head and shoulders, triangles and wedges from the major swings.")var boardOn = inputBool(true, "Show structure board", group = G_BOARD, tooltip = "Turns the board on or off.")var boardCorner = inputString("Auto", "Board corner", group = G_BOARD, tooltip = "Which corner of the chart the board sits in. Auto keeps it top right and moves it to an empty corner only when, top right, it would cover the dial or the bars.", options = ["Auto", "Top right", "Top left", "Bottom left", "Bottom right"])var textSizeName = inputString("Small", "Text size", group = G_BOARD, tooltip = "Text size of the board.", options = ["Tiny", "Small", "Normal", "Large"])var boardLook = inputString("Dark", "Board colors", group = G_BOARD, tooltip = "Dark suits the dark chart theme, Light the light one.", options = ["Dark", "Light"])var minBreaks = inputInt(5, "Breaks before showing a rate", min = 1, max = 50, step = 1, group = G_BOARD, tooltip = "A hold rate is shown only after this many finished breaks of that grade.")var tfA = inputString("5", "Timeframe 1", group = G_BOARD, tooltip = "First timeframe of the trend rows.", options = ["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W"])var tfB = inputString("15", "Timeframe 2", group = G_BOARD, tooltip = "Second timeframe of the trend rows.", options = ["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W"])var tfC = inputString("60", "Timeframe 3", group = G_BOARD, tooltip = "Third timeframe of the trend rows.", options = ["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W"])var tfD = inputString("240", "Timeframe 4", group = G_BOARD, tooltip = "Fourth timeframe of the trend rows.", options = ["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W"])var tfE = inputString("D", "Timeframe 5", group = G_BOARD, tooltip = "Fifth timeframe of the trend rows.", options = ["1", "3", "5", "15", "30", "60", "120", "240", "360", "720", "D", "W"])var tintOn = inputBool(false, "Tint candles by minor direction", group = G_CANDLES, tooltip = "Colors candles by the direction of the minor scale.")var riseTone = inputColor(TONE_RISE, "Rising", group = G_COLORS)var fallTone = inputColor(TONE_FALL, "Falling", group = G_COLORS)var flipTone = inputColor(TONE_WARN, "Flip level", group = G_COLORS)var midTone = inputColor(TONE_SOFT, "Midline", group = G_COLORS) // Board colors follow the chosen look (the chart theme cannot be read from code).var lightBoard = boardLook == "Light"var TONE_BASE = lightBoard ? "#fcfdff" : "#161920"var TONE_INK = lightBoard ? "#131722" : "#eceef2"var TONE_DEEP = "#11141a" // ── Types ──// One swing-finding scale. leg: 1 while tracking a rise (looking for a top),// -1 while tracking a fall. course: the scale's direction after its last break.type Scale { float turn = 1.0 bool major = false int swingDir = 0 float peak = na int peakBar = 0 float hiLvl = na int hiBar = 0 bool hiUsed = true float loLvl = na int loBar = 0 bool loUsed = true int course = 0 int courseSince = 0 int lastTurnBar = -100000 int lastTurnTo = 0} // A break waiting to be judged: did price stay beyond the level for `holdBars`?type Verdict { int bornBar = 0 int towards = 0 int grade = 0 float lvl = na} // An order zone and its drawing.type Shelf { float upper = na float lower = na int side = 0 bool major = false int fromBar = 0 float buyVol = 0.0 float sellVol = 0.0 bool spent = false bool wasInside = false box frame = na array parts = na array rails = na} // A gap. The newest of each scale is drawn bright, older ones fade.type Mark { line ln = na label tag = na int towards = 0 int grade = 0 bool major = false float lvl = na int fromBar = 0 int atBar = 0 string word = ""} // A gap and its drawing.type Hole { float upper = na float lower = na int side = 0 box frame = na} // ── Helpers ──fn sizeOf(choice) => choice == "Tiny" ? size.tiny : choice == "Normal" ? size.normal : choice == "Large" ? size.large : size.smallvar tagSize = sizeOf(textSizeName)var breakTag = sizeOf(tagSizeName)fn cornerOf(choice) => choice == "Top left" ? position.top_left : choice == "Bottom right" ? position.bottom_right : choice == "Bottom left" ? position.bottom_left : position.top_rightfn halfUp(x) => floor(x + 0.5)fn percentText(part, whole) => whole > 0 ? toString(halfUp(part * 100 / whole)) + "%" : "-"fn oneDp(x) => toString(halfUp(x * 10) / 10, "0.0")fn twoDp(x) => toString(halfUp(x * 100) / 100, "0.00")// Price with as many decimals as its size needs (fixed, so 317.00 does not read as 317).fn priceText(x) => toString(x, abs(x) >= 100 ? "0.00" : abs(x) >= 1 ? "0.0000" : "0.00000000")fn shortVol(v) => v >= 1000000 ? twoDp(v / 1000000) + "M" : v >= 1000 ? twoDp(v / 1000) + "K" : twoDp(v)fn gradeName(g) => g == 3 ? "strong" : g == 2 ? "firm" : "weak"fn pips(g) => g == 3 ? "⬤⬤⬤" : g == 2 ? "⬤⬤◯" : "⬤◯◯"fn arrow(d) => d > 0 ? "▲ Up" : d < 0 ? "▼ Down" : "· Flat" // A board cell on a solid ground (the chart paints cell grounds a little see-through).fn cellAt(t, top, col, row, txt, tone) => tableCell(t, col, row + top, txt, tone, bgcolor = TONE_BASE, text_size = tagSize) // The nearest live zone on each side is drawn clear, the others faint.// The nearest one also darkens from its first bar to now in four steps, gets a// bright edge with a glow on the side facing price, and two rails along its edges:// the upper rail as long as the buy share, the lower one the sell share.fn paintShelf(sh, near) { tone = sh.side > 0 ? riseTone : fallTone if arraySize(sh.parts) > 0 { for k = 0 to arraySize(sh.parts) - 1 { boxDelete(arrayGet(sh.parts, k)) } arrayClear(sh.parts) } if arraySize(sh.rails) > 0 { for k = 0 to arraySize(sh.rails) - 1 { lineDelete(arrayGet(sh.rails, k)) } arrayClear(sh.rails) } boxSetBgColor(sh.frame, colorNew(tone, near ? 100 : 91)) boxSetBorderColor(sh.frame, colorNew(tone, 100)) boxSetTextColor(sh.frame, colorNew(near ? TONE_INK : tone, near ? 0 : 45)) // Zones end just after the last bar, so the dial to their right stays clear. boxSetExtend(sh.frame, extend.none) boxSetRight(sh.frame, barIndex + 1) if near { endAt = barIndex + 1 len = endAt - sh.fromBar for k = 0 to 3 { arrayPush(sh.parts, boxNew(sh.fromBar + halfUp(len * k / 4), sh.upper, sh.fromBar + halfUp(len * (k + 1) / 4), sh.lower, bgcolor = colorNew(tone, 88 - k * 7), border_color = colorNew(tone, 100), border_width = 0)) } tot = sh.buyVol + sh.sellVol buyShare = tot > 0 ? sh.buyVol / tot : 0.5 rail = min(48, max(8, len)) h = (sh.upper - sh.lower) * 0.22 if tot > 0 { arrayPush(sh.parts, boxNew(endAt - max(1, halfUp(rail * buyShare)), sh.upper, endAt, sh.upper - h, bgcolor = colorNew(riseTone, 8), border_color = colorNew(riseTone, 100), border_width = 0)) arrayPush(sh.parts, boxNew(endAt - max(1, halfUp(rail * (1 - buyShare))), sh.lower + h, endAt, sh.lower, bgcolor = colorNew(fallTone, 8), border_color = colorNew(fallTone, 100), border_width = 0)) } edge = sh.side > 0 ? sh.upper : sh.lower arrayPush(sh.rails, lineNew(sh.fromBar, edge, endAt, edge, colorNew(tone, 82), line.style_solid, 9)) arrayPush(sh.rails, lineNew(sh.fromBar, edge, endAt, edge, colorNew(tone, 0), line.style_solid, 2)) }} // Newest break of a scale bright with a soft glow, older ones fade step by step.fn restyleMarks(marks) { n = arraySize(marks) if n > 0 { for j = 0 to n - 1 { m = arrayGet(marks, j) age = n - 1 - j tone = m.towards > 0 ? riseTone : fallTone gradeFade = gradeOn and m.grade == 1 ? 18 : gradeOn and m.grade == 2 ? 8 : 0 fade = age == 0 ? gradeFade : min(88, 52 + age * 5 + gradeFade) lineSetColor(m.ln, colorNew(tone, fade)) lineSetWidth(m.ln, age == 0 and m.major ? 2 : 1) labelSetTextcolor(m.tag, age == 0 ? TONE_DEEP : colorNew(tone, fade)) labelSetColor(m.tag, age == 0 ? colorNew(tone, gradeFade) : colorNew(TONE_BASE, 100)) // The newest major break also says how far price has run past it since. if age == 0 and m.major { since = max(1, barIndex - m.atBar + 1) reach = m.towards > 0 ? max(highest(high, since), m.lvl) : min(lowest(low, since), m.lvl) labelSetText(m.tag, (gradeOn ? m.word + " " + pips(m.grade) : m.word) + " " + (m.towards > 0 ? "▲ " : "▼ ") + twoDp(abs(reach - m.lvl) / m.lvl * 100) + "% since") } } }} // Trend of a timeframe: 1 when the 20-bar average is above the 50-bar one, else -1.fn trendOf() => sma(close, 20) > sma(close, 50) ? 1 : -1 // ── Measures ──// Work happens only in the last `historyBars` bars, plus a short warm-up for the// averages. Before that a bar costs one comparison.var warmStart = lastBarIndex - historyBars - 150var windowStart = lastBarIndex - historyBarsvar float rangeUnit = navar float volBase = na var Scale big = Scale.new(turn = majorTurn, major = true)var Scale small = Scale.new(turn = minorTurn, major = false)var pending = arrayNew()var heldCount = arrayNewInt(3, 0)var doneCount = arrayNewInt(3, 0)var shelves = arrayNew()var holes = arrayNew()var topsSeen = arrayNewFloat()var topBarsSeen = arrayNewInt()var floorsSeen = arrayNewFloat()var floorBarsSeen = arrayNewInt()var swingCount = 0var majorMarks = arrayNew()var minorMarks = arrayNew()var equalMarks = arrayNew() var majorTurnedNow = falsevar minorTurnedNow = falsevar strongNow = false // Grade of a break: how far the close went past the level and how loud the bar was.fn gradeOf(beyondAtr, loudness) { g = 2 if beyondAtr < 0.2 and loudness < 1.4 { g := 1 } else if beyondAtr >= 0.6 and loudness >= 1.4 { g := 3 } return g} // Draws one break: a line from the swing to the break bar, a tag at the break end.fn drawBreak(s, lvl, fromBar, towards, word, g) { showIt = s.major ? majorBreaksOn : minorBreaksOn if showIt { baseTone = towards > 0 ? riseTone : fallTone dim = gradeOn and g == 1 ? 62 : gradeOn and g == 2 ? 26 : 0 tone = colorNew(baseTone, dim) ln = lineNew(fromBar, lvl, barIndex, lvl, tone, s.major ? line.style_solid : line.style_dashed, s.major ? 2 : 1) tip = word + (s.major ? " · major" : " · minor") + (gradeOn ? " · " + gradeName(g) + " break" : "") tg = labelNew(lvl, gradeOn ? word + " " + pips(g) : word, barIndex, color = colorNew(TONE_BASE, 100), size = breakTag, textcolor = tone, style = towards > 0 ? label.style_label_down : label.style_label_up, tooltip = tip) marks = s.major ? majorMarks : minorMarks arrayPush(marks, Mark.new(ln = ln, tag = tg, towards = towards, grade = g, major = s.major, lvl = lvl, fromBar = fromBar, atBar = barIndex, word = word)) if arraySize(marks) > keepBreaks { old = arrayShift(marks) lineDelete(old.ln) labelDelete(old.tag) } }} // Remembers a confirmed major swing for the pattern layer (latest six of each kind).fn rememberTop(lvl, atBar) { swingCount := swingCount + 1 arrayUnshift(topsSeen, lvl) arrayUnshift(topBarsSeen, atBar) if arraySize(topsSeen) > 6 { arrayPop(topsSeen) arrayPop(topBarsSeen) }}fn rememberFloor(lvl, atBar) { swingCount := swingCount + 1 arrayUnshift(floorsSeen, lvl) arrayUnshift(floorBarsSeen, atBar) if arraySize(floorsSeen) > 6 { arrayPop(floorsSeen) arrayPop(floorBarsSeen) }} // Two minor swings at nearly the same price: a dotted join and a small tag.fn markEqual(barA, lvlA, barB, lvlB, side) { lvl = side > 0 ? max(lvlA, lvlB) : min(lvlA, lvlB) tone = side > 0 ? fallTone : riseTone arrayPush(equalMarks, lineNew(barA, lvl, barB, lvl, colorNew(tone, 45), line.style_dotted, 1)) arrayPush(equalMarks, labelNew(lvl, side > 0 ? "EQH" : "EQL", barB, color = colorNew(TONE_BASE, 100), size = size.tiny, textcolor = colorNew(tone, 35), style = side > 0 ? label.style_label_down : label.style_label_up, tooltip = side > 0 ? "Equal highs: two minor tops at nearly the same price" : "Equal lows: two minor bottoms at nearly the same price")) if arraySize(equalMarks) > 8 { lineDelete(arrayShift(equalMarks)) labelDelete(arrayShift(equalMarks)) }} // Order zone on a break: the last opposite candle between the swing and now.fn addShelf(towards, fromBar, isMajor) { back = min(barIndex - fromBar, 300) found = -1 if back > 0 { for i = 0 to back { opposite = towards > 0 ? close[i] < open[i] : close[i] > open[i] if found < 0 and opposite { found := i } } } // Both scales can break on the same bar and pick the same candle: one zone, marked Major if either break was major. same = -1 if found >= 0 and arraySize(shelves) > 0 { for j = 0 to arraySize(shelves) - 1 { q = arrayGet(shelves, j) if q.side == towards and q.fromBar == barIndex - found and not q.spent { same := j } } } if same >= 0 { if isMajor { q2 = arrayGet(shelves, same) q2.major := true } } else if found >= 0 { sh = Shelf.new(upper = high[found], lower = low[found], side = towards, major = isMajor, fromBar = barIndex - found, parts = arrayNew(), rails = arrayNew()) tone = towards > 0 ? riseTone : fallTone sh.frame := boxNew(sh.fromBar, sh.upper, barIndex, sh.lower, border_color = colorNew(tone, 100), border_width = 0, bgcolor = colorNew(tone, 91), text = "", text_color = colorNew(tone, 45), text_halign = "right", text_size = tagSize, extend = extend.right) arrayPush(shelves, sh) live = 0 oldest = -1 for j = 0 to arraySize(shelves) - 1 { q = arrayGet(shelves, j) if q.side == towards and not q.spent { live := live + 1 if oldest < 0 { oldest := j } } } if live > zonesPerSide and oldest >= 0 { gone = arrayGet(shelves, oldest) paintShelf(gone, false) boxDelete(gone.frame) arrayRemove(shelves, oldest) } }} // One step of a scale: track the leg, confirm swings, detect breaks.fn stepScale(s) { if s.swingDir == 0 { s.swingDir := 1 s.peak := high s.peakBar := barIndex } else if s.swingDir == 1 { if high >= s.peak { s.peak := high s.peakBar := barIndex } else if s.peak - low >= s.turn * rangeUnit { if not s.major and equalOn and not na(s.hiLvl) and equalTol * rangeUnit >= abs(s.peak - s.hiLvl) { markEqual(s.hiBar, s.hiLvl, s.peakBar, s.peak, 1) } s.hiLvl := s.peak s.hiBar := s.peakBar s.hiUsed := false if s.major { rememberTop(s.hiLvl, s.hiBar) } s.swingDir := -1 s.peak := low s.peakBar := barIndex } } else { if low <= s.peak { s.peak := low s.peakBar := barIndex } else if high - s.peak >= s.turn * rangeUnit { if not s.major and equalOn and not na(s.loLvl) and equalTol * rangeUnit >= abs(s.peak - s.loLvl) { markEqual(s.loBar, s.loLvl, s.peakBar, s.peak, -1) } s.loLvl := s.peak s.loBar := s.peakBar s.loUsed := false if s.major { rememberFloor(s.loLvl, s.loBar) } s.swingDir := 1 s.peak := high s.peakBar := barIndex } } loud = volBase > 0 ? volume / volBase : 1.0 if not s.hiUsed and close > s.hiLvl { g = gradeOf((close - s.hiLvl) / rangeUnit, loud) turning = s.course != 1 word = "BOS" if turning { quickUndo = s.lastTurnTo == -1 and holdBars >= barIndex - s.lastTurnBar word := plusOn and quickUndo ? "CHoCH+" : "CHoCH" } drawBreak(s, s.hiLvl, s.hiBar, 1, word, g) if shelvesOn { addShelf(1, s.hiBar, s.major) } arrayPush(pending, Verdict.new(bornBar = barIndex, towards = 1, grade = g, lvl = s.hiLvl)) s.hiUsed := true if turning { s.lastTurnBar := barIndex s.lastTurnTo := 1 s.course := 1 s.courseSince := barIndex } strongNow := strongNow or g == 3 if turning and s.major { majorTurnedNow := true } if turning and not s.major { minorTurnedNow := true } } if not s.loUsed and close < s.loLvl { g = gradeOf((s.loLvl - close) / rangeUnit, loud) turning = s.course != -1 word = "BOS" if turning { quickUndo = s.lastTurnTo == 1 and holdBars >= barIndex - s.lastTurnBar word := plusOn and quickUndo ? "CHoCH+" : "CHoCH" } drawBreak(s, s.loLvl, s.loBar, -1, word, g) if shelvesOn { addShelf(-1, s.loBar, s.major) } arrayPush(pending, Verdict.new(bornBar = barIndex, towards = -1, grade = g, lvl = s.loLvl)) s.loUsed := true if turning { s.lastTurnBar := barIndex s.lastTurnTo := -1 s.course := -1 s.courseSince := barIndex } strongNow := strongNow or g == 3 if turning and s.major { majorTurnedNow := true } if turning and not s.major { minorTurnedNow := true } }} // Drawings kept from one bar to the next.var huntMarks = arrayNew()var label patternTag = navar line patternLineA = navar line patternLineB = navar line dayTopLine = navar line dayFloorLine = navar float trendA = navar float trendB = navar float trendC = navar float trendD = navar float trendE = navar line flipLine = navar line flipHaloIn = navar line flipHaloOut = navar label flipTag = navar line midLine = navar dial = arrayNew()var table board = na // ── Everything below runs only from the warm-up start on ──if barIndex >= warmStart {rangeUnit := atr(21)volBase := sma(volume, 34)majorTurnedNow := falseminorTurnedNow := falsestrongNow := falseinWindow = barIndex >= windowStart // ── Run the two scales ──if inWindow and not na(rangeUnit) { stepScale(big) stepScale(small)} // ── Judge pending breaks: held if no close came back behind the level in time ──if inWindow and arraySize(pending) > 0 { k = arraySize(pending) - 1 while k >= 0 { v = arrayGet(pending, k) undone = barIndex > v.bornBar and (v.towards > 0 ? close < v.lvl : close > v.lvl) aged = barIndex - v.bornBar >= holdBars if undone or aged { gi = v.grade - 1 arraySet(doneCount, gi, arrayGet(doneCount, gi) + 1) if not undone { arraySet(heldCount, gi, arrayGet(heldCount, gi) + 1) } arrayRemove(pending, k) } k := k - 1 }} // ── Order zones: share, spend, entry ──zoneEntryNow = falseif inWindow and arraySize(shelves) > 0 { z = arraySize(shelves) - 1 while z >= 0 { sh = arrayGet(shelves, z) if not sh.spent { overlaps = high >= sh.lower and low <= sh.upper if overlaps and barIndex > sh.fromBar { rng = high - low upShare = rng > 0 ? (close - low) / rng : 0.5 sh.buyVol := sh.buyVol + volume * upShare sh.sellVol := sh.sellVol + volume * (1 - upShare) } if overlaps and not sh.wasInside and barIndex > sh.fromBar + 1 { zoneEntryNow := true } sh.wasInside := overlaps through = sh.side > 0 ? close < sh.lower : close > sh.upper if through { sh.spent := true paintShelf(sh, false) if keepSpent { boxSetExtend(sh.frame, extend.none) boxSetRight(sh.frame, barIndex) boxSetBgColor(sh.frame, colorNew(TONE_SOFT, 90)) } else { boxDelete(sh.frame) arrayRemove(shelves, z) } } } z := z - 1 }} // ── Stop hunts ──priorTop = highest(high, huntLookback)[1]priorFloor = lowest(low, huntLookback)[1]barRange = high - lowhuntUp = inWindow and huntsOn and high > priorTop and close < priorTop and barRange > 0 and (high - max(open, close)) >= 0.5 * barRangehuntDown = inWindow and huntsOn and low < priorFloor and close > priorFloor and barRange > 0 and (min(open, close) - low) >= 0.5 * barRangeif huntUp or huntDown { arrayPush(huntMarks, labelNew(huntUp ? high : low, huntUp ? "▼" : "▲", barIndex, color = colorNew(TONE_BASE, 100), size = size.tiny, textcolor = colorNew(huntUp ? fallTone : riseTone, 18), style = huntUp ? label.style_label_down : label.style_label_up, tooltip = huntUp ? "Stop hunt above: the wick ran past the highs of the last " + toString(huntLookback) + " bars and the bar closed back below" : "Stop hunt below: the wick ran past the lows of the last " + toString(huntLookback) + " bars and the bar closed back above")) if arraySize(huntMarks) > huntsKept { labelDelete(arrayShift(huntMarks)) }} // ── Gaps ──newGapNow = falseif inWindow and gapsOn and not na(rangeUnit) { if low > high[2] and low - high[2] >= tinyGap * rangeUnit { hu = Hole.new(upper = low, lower = high[2], side = 1) hu.frame := boxNew(barIndex - 1, hu.upper, barIndex, hu.lower, border_color = colorNew(TONE_INFO, 100), border_width = 0, bgcolor = colorNew(TONE_INFO, 88), extend = extend.right) arrayPush(holes, hu) newGapNow := true } if high < low[2] and low[2] - high >= tinyGap * rangeUnit { hd = Hole.new(upper = low[2], lower = high, side = -1) hd.frame := boxNew(barIndex - 1, hd.upper, barIndex, hd.lower, border_color = colorNew(TONE_INFO, 100), border_width = 0, bgcolor = colorNew(TONE_INFO, 88), extend = extend.right) arrayPush(holes, hd) newGapNow := true }}if inWindow and arraySize(holes) > 0 { h = arraySize(holes) - 1 while h >= 0 { gp = arrayGet(holes, h) if gp.side > 0 and low < gp.upper { gp.upper := max(low, gp.lower) boxSetTop(gp.frame, gp.upper) } if 0 > gp.side and high > gp.lower { gp.lower := min(high, gp.upper) boxSetBottom(gp.frame, gp.lower) } filled = 0 >= gp.upper - gp.lower if filled or arraySize(holes) > 32 and h == 0 { boxDelete(gp.frame) arrayRemove(holes, h) } h := h - 1 }} // ── Patterns (read when a new major swing is confirmed) ──newSwing = swingCount != nz(swingCount[1], 0)if shapesOn and newSwing and arraySize(topsSeen) >= 3 and arraySize(floorsSeen) >= 3 and not na(rangeUnit) { t0 = arrayGet(topsSeen, 0) t1 = arrayGet(topsSeen, 1) t2 = arrayGet(topsSeen, 2) f0 = arrayGet(floorsSeen, 0) f1 = arrayGet(floorsSeen, 1) f2 = arrayGet(floorsSeen, 2) tb0 = arrayGet(topBarsSeen, 0) tb1 = arrayGet(topBarsSeen, 1) tb2 = arrayGet(topBarsSeen, 2) fb0 = arrayGet(floorBarsSeen, 0) fb1 = arrayGet(floorBarsSeen, 1) fb2 = arrayGet(floorBarsSeen, 2) near = 0.5 * rangeUnit // Slopes per bar, compared against a limit that scales with ATR and spacing. upperSlope = tb0 != tb1 ? (t0 - t1) / (tb0 - tb1) : 0.0 lowerSlope = fb0 != fb1 ? (f0 - f1) / (fb0 - fb1) : 0.0 flatLimit = rangeUnit * 0.6 / max(abs(tb0 - tb1), 1) name = "" isTopPattern = true if abs(t0 - t1) <= near and fb0 > tb1 and fb0 < tb0 and f0 < min(t0, t1) - 2 * near { name := "Double top" } else if abs(f0 - f1) <= near and tb0 > fb1 and tb0 < fb0 and t0 > max(f0, f1) + 2 * near { name := "Double bottom" isTopPattern := false } else if t1 > t0 + near and t1 > t2 + near and abs(t0 - t2) <= 2 * near { name := "Head and shoulders" } else if f1 < f0 - near and f1 < f2 - near and abs(f0 - f2) <= 2 * near { name := "Inverse head and shoulders" isTopPattern := false } else if upperSlope < -flatLimit and lowerSlope > flatLimit { name := "Triangle" } else if upperSlope > flatLimit and lowerSlope > flatLimit and lowerSlope > upperSlope { name := "Rising wedge" } else if upperSlope < -flatLimit and lowerSlope < -flatLimit and upperSlope < lowerSlope { name := "Falling wedge" isTopPattern := false } if name != "" { if not na(patternTag) { labelDelete(patternTag) lineDelete(patternLineA) lineDelete(patternLineB) } pt = colorNew(TONE_INFO, 15) patternLineA := lineNew(tb1, t1, tb0, t0, pt, line.style_solid, 1) patternLineB := lineNew(fb1, f1, fb0, f0, pt, line.style_solid, 1) patternTag := labelNew(isTopPattern ? max(t0, t1) : min(f0, f1), name, max(tb0, fb0), color = colorNew(TONE_BASE, 100), size = tagSize, textcolor = pt, style = isTopPattern ? label.style_label_down : label.style_label_up, tooltip = name + " read from the last major swings") }} // ── Yesterday's high and low ──dayTop = security(syminfo.tickerid, "D", high[1], ignore_invalid_symbol = true)dayFloor = security(syminfo.tickerid, "D", low[1], ignore_invalid_symbol = true)if dayOn and barstate.islast and not na(dayTop) { if na(dayTopLine) { dayTopLine := lineNew(barIndex - 48, dayTop, barIndex, dayTop, colorNew(TONE_SOFT, 32), line.style_dotted, 1, extend.right) dayFloorLine := lineNew(barIndex - 48, dayFloor, barIndex, dayFloor, colorNew(TONE_SOFT, 32), line.style_dotted, 1, extend.right) }} // ── Trend rows ──// Read with securityLast(): the latest value of each timeframe from its own bars,// lower timeframes than the chart's included. When a timeframe's data is not// loaded it returns empty and the row shows "-", instead of stopping the indicator.// The chart's own timeframe is read from the chart's bars.trendSign = trendOf() if barstate.islast { trendA := tfA == timeframe.period ? trendSign : securityLast(syminfo.tickerid, tfA, trendOf(), ignore_invalid_symbol = true) trendB := tfB == timeframe.period ? trendSign : securityLast(syminfo.tickerid, tfB, trendOf(), ignore_invalid_symbol = true) trendC := tfC == timeframe.period ? trendSign : securityLast(syminfo.tickerid, tfC, trendOf(), ignore_invalid_symbol = true) trendD := tfD == timeframe.period ? trendSign : securityLast(syminfo.tickerid, tfD, trendOf(), ignore_invalid_symbol = true) trendE := tfE == timeframe.period ? trendSign : securityLast(syminfo.tickerid, tfE, trendOf(), ignore_invalid_symbol = true)} // ── Flip level, midline and the board (last bar only) ──flipPrice = big.course > 0 ? big.loLvl : 0 > big.course ? big.hiLvl : naflipFrom = big.course > 0 ? big.loBar : big.hiBarflipCrossNow = not na(flipPrice) and (big.course > 0 ? crossunder(close, flipPrice) : crossover(close, flipPrice))if barstate.islast { if flipOn and not na(flipPrice) { gapPct = abs(close - flipPrice) / close * 100 // The glow: a clear 2-pixel core, two wider fainter layers around it. Like the other levels it ends just after the // last bar, with its tag at the end. if na(flipLine) { flipHaloOut := lineNew(flipFrom, flipPrice, barIndex + 1, flipPrice, colorNew(flipTone, 90), line.style_solid, 11) flipHaloIn := lineNew(flipFrom, flipPrice, barIndex + 1, flipPrice, colorNew(flipTone, 74), line.style_solid, 5) flipLine := lineNew(flipFrom, flipPrice, barIndex + 1, flipPrice, colorNew(flipTone, 0), line.style_solid, 2) flipTag := labelNew(flipPrice, "", barIndex, color = colorNew(flipTone, 0), size = tagSize, textcolor = TONE_DEEP, style = 0 > big.course ? label.style_label_down : label.style_label_up, tooltip = "The confirmed major swing whose break would turn the major direction") } lineSetXy1(flipHaloOut, flipFrom, flipPrice) lineSetXy2(flipHaloOut, barIndex + 1, flipPrice) lineSetXy1(flipHaloIn, flipFrom, flipPrice) lineSetXy2(flipHaloIn, barIndex + 1, flipPrice) lineSetXy1(flipLine, flipFrom, flipPrice) lineSetXy2(flipLine, barIndex + 1, flipPrice) labelSetXy(flipTag, barIndex, flipPrice) labelSetText(flipTag, "Flip · " + priceText(flipPrice) + " · " + twoDp(gapPct) + "% away") } if midOn and not na(big.hiLvl) and not na(big.loLvl) { midPrice = (big.hiLvl + big.loLvl) / 2 fromAt = min(big.hiBar, big.loBar) if na(midLine) { midLine := lineNew(fromAt, midPrice, barIndex, midPrice, colorNew(midTone, 45), line.style_dashed, 1) } lineSetXy1(midLine, fromAt, midPrice) lineSetXy2(midLine, barIndex, midPrice) } restyleMarks(majorMarks) restyleMarks(minorMarks) // Gaps also end just after the last bar, clear of the dial. if arraySize(holes) > 0 { for k = 0 to arraySize(holes) - 1 { gp = arrayGet(holes, k) boxSetExtend(gp.frame, extend.none) boxSetRight(gp.frame, barIndex + 1) } } // The dial: the last major range as ten slices just right of the last bar, // green (discount) at the bottom to red (premium) at the top, stronger toward // the ends. The slice price is in is lit, with its share of the range written in it. if arraySize(dial) > 0 { for k = 0 to arraySize(dial) - 1 { boxDelete(arrayGet(dial, k)) } arrayClear(dial) } if midOn and dialOn and not na(big.hiLvl) and not na(big.loLvl) and big.hiLvl > big.loLvl { step = (big.hiLvl - big.loLvl) / 10 at = min(9, max(0, floor((close - big.loLvl) / step))) inside = close >= big.loLvl and close <= big.hiLvl for k = 0 to 9 { tone = k >= 5 ? fallTone : riseTone lit = inside and k == at bot = big.loLvl + k * step + step * 0.08 topP = big.loLvl + (k + 1) * step - step * 0.08 fade = halfUp(86 - (abs(k - 4.5) - 0.5) * 11) if lit { arrayPush(dial, boxNew(barIndex + 1, topP + step * 0.2, barIndex + 9, bot - step * 0.2, bgcolor = colorNew(tone, 84), border_color = colorNew(tone, 100), border_width = 0)) } arrayPush(dial, boxNew(barIndex + (lit ? 2 : 3), topP, barIndex + (lit ? 8 : 7), bot, bgcolor = colorNew(tone, lit ? 0 : fade + 4), border_color = colorNew(tone, 100), border_width = 0, text = lit ? toString(halfUp((close - big.loLvl) / (big.hiLvl - big.loLvl) * 100)) + "%" : "", text_color = TONE_DEEP, text_size = size.small)) } // End caps: the flip end is amber. capHi = 0 > big.course and flipOn ? flipTone : fallTone capLo = big.course > 0 and flipOn ? flipTone : riseTone arrayPush(dial, boxNew(barIndex + 2, big.hiLvl + step * 0.06, barIndex + 8, big.hiLvl - step * 0.06, bgcolor = colorNew(capHi, 0), border_color = colorNew(capHi, 100), border_width = 0)) arrayPush(dial, boxNew(barIndex + 2, big.loLvl + step * 0.06, barIndex + 8, big.loLvl - step * 0.06, bgcolor = colorNew(capLo, 0), border_color = colorNew(capLo, 100), border_width = 0)) } // Highest text of the zones drawn in full: it sits mid-zone at the right end, where the board would. textTop = 0.0 if arraySize(shelves) > 0 { // Rank of each live zone by distance on its own side: 0 and 1 are drawn in full. // A zone that overlaps a closer one on the same side stays faint, so two full // zones are never drawn over each other. ranks = arrayNewInt(arraySize(shelves), 99) for j = 0 to arraySize(shelves) - 1 { sh = arrayGet(shelves, j) if not sh.spent { d = abs((sh.upper + sh.lower) / 2 - close) closer = 0 covered = false for q = 0 to arraySize(shelves) - 1 { o = arrayGet(shelves, q) if q != j and not o.spent and o.side == sh.side and d > abs((o.upper + o.lower) / 2 - close) { closer := closer + 1 covered := covered or (o.upper >= sh.lower and sh.upper >= o.lower) } } arraySet(ranks, j, covered ? 99 : closer) } } for j = 0 to arraySize(shelves) - 1 { sh = arrayGet(shelves, j) tot = sh.buyVol + sh.sellVol away = close > sh.upper ? (close - sh.upper) / close * 100 : sh.lower > close ? (sh.lower - close) / close * 100 : 0.0 boxSetText(sh.frame, (sh.major ? "Major · " : "") + (shareOn and tot > 0 ? shortVol(tot) + " traded · buy " + percentText(sh.buyVol, tot) + " · sell " + percentText(sh.sellVol, tot) : "no trades yet") + " · " + (away > 0 ? oneDp(away) + "% away" : "inside")) if not sh.spent { paintShelf(sh, 2 > arrayGet(ranks, j)) if 2 > arrayGet(ranks, j) { textTop := max(textTop, (sh.upper + sh.lower) / 2) } } } } if boardOn { // Six columns, seven rows: the board stays small enough not to cover the chart. // Auto: the last 120 bars stand in for what is on screen. A corner is free when // the bars under its third of the width stay out of its quarter of the height; top right // also needs the zone texts out of its top 13% (the board's own height) // (measured on a 1600 x 1000 window: the board covers about a quarter of each). place = boardCorner if boardCorner == "Auto" { span = min(119, barIndex) third = max(1, floor((span + 1) / 3)) hi = high lo = low for k = 1 to span { hi := max(hi, high[k]) lo := min(lo, low[k]) } // The dial runs the full range, so it widens the price scale; the board sits left of it. hi := midOn and not na(big.hiLvl) ? max(hi, big.hiLvl) : hi cut = (hi - lo) * 0.25 rightTop = highest(high, third) leftTop = high[span] leftLow = low[span] for k = span - third + 1 to span { leftTop := max(leftTop, high[k]) leftLow := min(leftLow, low[k]) } place := hi - cut > rightTop and hi - (hi - lo) * 0.13 > textTop ? "Top right" : hi - cut > leftTop ? "Top left" : leftLow > lo + cut ? "Bottom left" : "Top right" } // Top right the board starts one empty row lower, clear of the bar's price line. // Top left the chart itself starts it below the order buttons and the indicator // names, however many there are. top = place == "Top right" ? 1 : 0 if na(board) { // The board sits in its corner, flush with the price scale. Each cell has its own ground; the table itself is // see-through. board := tableNew(cornerOf(place), 6, 7 + top, bgcolor = colorNew(TONE_BASE, 100), border_width = 0, frame_width = 0) if top > 0 { for k = 0 to top - 1 { tableCell(board, 0, k, " ", text_size = size.normal) } } } cellAt(board, top, 0, 0, "Structure", TONE_INK) tableMergeCells(board, 0, top, 5, top) cellAt(board, top, 0, 1, "Major", TONE_SOFT) cellAt(board, top, 1, 1, arrow(big.course), big.course > 0 ? riseTone : 0 > big.course ? fallTone : TONE_SOFT) tableMergeCells(board, 1, 1 + top, 2, 1 + top) cellAt(board, top, 3, 1, big.course != 0 ? toString(barIndex - big.courseSince) + " bars" : "-", TONE_INK) tableMergeCells(board, 3, 1 + top, 5, 1 + top) cellAt(board, top, 0, 2, "Minor", TONE_SOFT) cellAt(board, top, 1, 2, arrow(small.course), small.course > 0 ? riseTone : 0 > small.course ? fallTone : TONE_SOFT) tableMergeCells(board, 1, 2 + top, 2, 2 + top) cellAt(board, top, 3, 2, small.course != 0 ? toString(barIndex - small.courseSince) + " bars" : "-", TONE_INK) tableMergeCells(board, 3, 2 + top, 5, 2 + top) cellAt(board, top, 0, 3, "Flip level", TONE_SOFT) cellAt(board, top, 1, 3, na(flipPrice) ? "-" : twoDp(abs(close - flipPrice) / close * 100) + "%", flipTone) tableMergeCells(board, 1, 3 + top, 2, 3 + top) cellAt(board, top, 3, 3, na(flipPrice) ? "" : (close > flipPrice ? "below price" : "above price"), TONE_INK) tableMergeCells(board, 3, 3 + top, 5, 3 + top) hasRange = not na(big.hiLvl) and not na(big.loLvl) and big.hiLvl > big.loLvl place = hasRange ? (close - big.loLvl) / (big.hiLvl - big.loLvl) * 100 : na cellAt(board, top, 0, 4, "In range", TONE_SOFT) cellAt(board, top, 1, 4, na(place) ? "-" : toString(halfUp(place)) + "%", TONE_INK) tableMergeCells(board, 1, 4 + top, 2, 4 + top) cellAt(board, top, 3, 4, na(place) ? "" : place >= 50 ? "premium" : "discount", na(place) ? TONE_SOFT : place >= 50 ? fallTone : riseTone) tableMergeCells(board, 3, 4 + top, 5, 4 + top) cellAt(board, top, 0, 5, "Held " + toString(holdBars) + " bars", TONE_SOFT) for g = 0 to 2 { n = arrayGet(doneCount, g) cellText = n >= minBreaks ? percentText(arrayGet(heldCount, g), n) : toString(n) + "/" + toString(minBreaks) cellAt(board, top, 1 + g * 2, 5, gradeName(g + 1) + " " + cellText, n >= minBreaks ? TONE_INK : TONE_SOFT) tableMergeCells(board, 1 + g * 2, 5 + top, min(5, 2 + g * 2), 5 + top) } tfs = arrayFrom(tfA, tfB, tfC, tfD, tfE) vals = arrayFrom(trendA, trendB, trendC, trendD, trendE) ups = 0 reads = 0 for r = 0 to 4 { frameName = arrayGet(tfs, r) v = arrayGet(vals, r) ok = not na(v) if ok { reads := reads + 1 ups := ups + (v > 0 ? 1 : 0) } cellAt(board, top, 1 + r, 6, frameName + (ok ? (v > 0 ? " ▲" : " ▼") : " -"), ok ? (v > 0 ? riseTone : fallTone) : TONE_SOFT) } cellAt(board, top, 0, 6, reads > 0 ? "Trend " + toString(ups) + "/" + toString(reads) + " up" : "Trend", ups * 2 > reads ? riseTone : ups * 2 < reads ? fallTone : TONE_SOFT) }} // ── Candles ──barcolor(tintOn ? (small.course > 0 ? riseTone : 0 > small.course ? fallTone : na) : na) // ── Alerts ──if inWindow { if majorTurnedNow { alert("Major direction turned (CHoCH) at " + toString(close), alert.freq_once_per_bar) } if minorTurnedNow { alert("Minor direction turned (CHoCH) at " + toString(close), alert.freq_once_per_bar) } if strongNow { alert("Strong break: the close went well past the level on high volume", alert.freq_once_per_bar) } if flipCrossNow { alert("Price closed through the flip level at " + toString(flipPrice), alert.freq_once_per_bar) } if shelvesOn and zoneEntryNow { alert("Price entered an order zone", alert.freq_once_per_bar) } if newGapNow { alert("A new untraded gap formed", alert.freq_once_per_bar) } if huntUp or huntDown { alert(huntUp ? "Stop hunt above: the bar ran past recent highs and closed back" : "Stop hunt below: the bar ran past recent lows and closed back", alert.freq_once_per_bar) }}}
Sürüm 119 Eki 2026Son sürüm
İlk yayın
Bu göstergeyle ilgili henüz bir gönderi bulunmuyor. Bu gösterge hakkındaki ilk gönderiyi siz yazabilirsiniz.


