-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtrend-compass-daily.pine
More file actions
838 lines (709 loc) · 41.4 KB
/
trend-compass-daily.pine
File metadata and controls
838 lines (709 loc) · 41.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
// ============================================================================
// Trend Compass — Daily
// Timeframe: Daily (primary), compatible with any TF
// Version: 1.0.0
// Author: Chris Thomas
// ============================================================================
// Strategic trend assessment overlay for equities, ETFs, and indices.
// Answers: What is the trend? How strong? Accelerating or exhausting?
// Reversing? Where are we structurally?
//
// No signal generation. Pure trend context, strength assessment, and
// structural positioning. Ticker-agnostic — works on NVDA, AAPL, GOOGL,
// SPY, QQQ, IWM, or any liquid instrument.
//
// Components:
// - Multi-EMA ribbon (10/21/50) + 200 EMA anchor
// - HTF context: Weekly 50 EMA, prior week/month H/L/C
// - Trend phase classifier (6 states)
// - Composite weighted trend score (-11 to +11)
// - RSI + MACD divergence detection
// - OBV trend confirmation
// - ATR regime + BB width percentile
// - Optional Ichimoku Cloud overlay
// - Optional Fibonacci retracement levels
// - 52-week high/low context
// - 17-row heads-up dashboard
// - 12 alert conditions
// ============================================================================
// ============================================================================
// TradingView Pine Script v6
// ============================================================================
//@version=6
indicator(
title = "Trend Compass Daily",
shorttitle = "TC-D",
overlay = true,
max_labels_count = 500,
max_lines_count = 500,
max_boxes_count = 100,
max_bars_back = 5000
)
// ============================================================================
// CONSTANTS
// ============================================================================
var string GP_EMA = "EMA Ribbon"
var string GP_ANCHOR = "Anchor EMA (200)"
var string GP_HTF = "Higher Timeframe"
var string GP_TREND = "Trend Strength (ADX)"
var string GP_RSI = "RSI"
var string GP_MACD = "MACD"
var string GP_VOL = "Volume"
var string GP_ATR = "Volatility (ATR)"
var string GP_BB = "Bollinger Band Width"
var string GP_LEVELS = "Key Levels"
var string GP_FIB = "Fibonacci Retracement"
var string GP_ICH = "Ichimoku Cloud"
var string GP_SCORE = "Scoring Engine"
var string GP_DASH = "Dashboard"
// Colors — dark-theme optimized, consistent with existing suite
var color C_BULL = #00E676
var color C_BEAR = #FF1744
var color C_NEUTRAL = #B0BEC5
var color C_YELLOW = #FFD600
var color C_ORANGE = #FF9100
var color C_CYAN = #00BCD4
var color C_PURPLE = #7C4DFF
var color C_WHITE = #FFFFFF
var color C_GRAY = #78909C
var color C_DARK_GRAY = #455A64
var color C_DASH_BG = color.new(#1A1A2E, 5)
var color C_DASH_TEXT = #E0E0E0
var color C_DASH_HEADER = #7C4DFF
var color C_CLOUD_BULL = color.new(#00E676, 85)
var color C_CLOUD_BEAR = color.new(#FF1744, 85)
var color C_CLOUD_MIX = color.new(#FFD600, 90)
var color C_200_EMA = #FF6D00
var color C_HTF_EMA = #E040FB
var color C_WEEK_LVL = #42A5F5
var color C_MONTH_LVL = #AB47BC
var color C_52W_LVL = #FF8A65
var color C_FIB_382 = #80CBC4
var color C_FIB_500 = #FFD54F
var color C_FIB_618 = #FF8A65
var color C_FIB_ZONE = color.new(#FFD54F, 92)
var color C_ICH_BULL = color.new(#00E676, 88)
var color C_ICH_BEAR = color.new(#FF1744, 88)
var color C_ICH_TENKAN = #2979FF
var color C_ICH_KIJUN = #FF6D00
// ============================================================================
// INPUTS
// ============================================================================
// --- EMA Ribbon ---
int i_emaFastLen = input.int(10, "Fast EMA", minval=2, group=GP_EMA)
int i_emaMidLen = input.int(21, "Mid EMA", minval=2, group=GP_EMA)
int i_emaSlowLen = input.int(50, "Slow EMA", minval=2, group=GP_EMA)
bool i_showEma = input.bool(true, "Show EMA Ribbon", group=GP_EMA)
bool i_showCloud = input.bool(true, "Show Cloud Fill", group=GP_EMA)
// --- Anchor EMA (200) ---
int i_anchorLen = input.int(200, "Anchor EMA Length", minval=50, group=GP_ANCHOR)
bool i_showAnchor = input.bool(true, "Show Anchor EMA", group=GP_ANCHOR)
// --- Higher Timeframe ---
bool i_useHTF = input.bool(true, "Enable HTF Confirmation", group=GP_HTF)
int i_htfEmaLen = input.int(50, "HTF Slow EMA Length", minval=5, group=GP_HTF)
string i_htfTF = input.string("W", "HTF Timeframe", options=["W", "M"], group=GP_HTF)
bool i_showHTFEma = input.bool(true, "Show HTF EMA on Chart", group=GP_HTF)
// --- Trend Strength (ADX) ---
int i_adxLen = input.int(14, "ADX Length", minval=5, group=GP_TREND)
int i_adxSlopeLB = input.int(5, "ADX Slope Lookback", minval=2, maxval=20, group=GP_TREND)
int i_emaSlopeLB = input.int(5, "EMA Slope Lookback", minval=2, maxval=20, group=GP_TREND)
float i_adxSlopeThr = input.float(0.3, "ADX Slope Threshold", minval=0.05, step=0.05, group=GP_TREND)
// --- RSI ---
int i_rsiLen = input.int(14, "RSI Length", minval=2, group=GP_RSI)
bool i_rsiDiv = input.bool(true, "Enable RSI Divergence", group=GP_RSI)
int i_rsiPivotL = input.int(5, "Divergence Pivot Left Bars", minval=2, maxval=20, group=GP_RSI)
int i_rsiPivotR = input.int(3, "Divergence Pivot Right Bars", minval=1, maxval=10, group=GP_RSI)
// --- MACD ---
int i_macdFast = input.int(12, "MACD Fast", minval=2, group=GP_MACD)
int i_macdSlow = input.int(26, "MACD Slow", minval=2, group=GP_MACD)
int i_macdSignal = input.int(9, "MACD Signal", minval=2, group=GP_MACD)
bool i_macdDiv = input.bool(true, "Enable MACD Divergence", group=GP_MACD)
int i_macdPivotL = input.int(5, "MACD Div Pivot Left", minval=2, maxval=20, group=GP_MACD)
int i_macdPivotR = input.int(3, "MACD Div Pivot Right", minval=1, maxval=10, group=GP_MACD)
// --- Volume ---
bool i_useOBV = input.bool(true, "Enable OBV Analysis", group=GP_VOL)
int i_obvEmaLen = input.int(21, "OBV EMA Length", minval=5, group=GP_VOL)
int i_relVolLen = input.int(20, "Relative Volume Lookback", minval=5, group=GP_VOL)
// --- ATR / Volatility ---
int i_atrLen = input.int(14, "ATR Length", minval=2, group=GP_ATR)
int i_atrPctlLB = input.int(100, "ATR Percentile Lookback", minval=20, maxval=500, group=GP_ATR)
// --- BB Width ---
int i_bbLen = input.int(20, "BB Length", minval=5, group=GP_BB)
float i_bbMult = input.float(2.0, "BB Multiplier", minval=0.5, step=0.1, group=GP_BB)
int i_bbwPctlLB = input.int(100, "BB Width Percentile Lookback", minval=20, maxval=500, group=GP_BB)
// --- Key Levels ---
bool i_show52w = input.bool(true, "Show 52-Week High/Low", group=GP_LEVELS)
bool i_showPrWk = input.bool(true, "Show Prior Week H/L/C", group=GP_LEVELS)
bool i_showPrMo = input.bool(true, "Show Prior Month H/L/C", group=GP_LEVELS)
// --- Fibonacci Retracement ---
bool i_showFib = input.bool(false, "Show Fibonacci Levels", group=GP_FIB, tooltip="Auto-calculated from the highest high and lowest low over the lookback period. Default OFF.")
int i_fibLB = input.int(50, "Fibonacci Lookback Bars", minval=10, maxval=200, group=GP_FIB)
bool i_showFibFill = input.bool(true, "Show Fib Zone Fill", group=GP_FIB)
// --- Ichimoku Cloud ---
bool i_showIchi = input.bool(false, "Show Ichimoku Cloud", group=GP_ICH, tooltip="Traditional Ichimoku Kinko Hyo overlay. Default OFF.")
int i_ichiTenkan = input.int(9, "Tenkan-sen (Conversion)", minval=2, group=GP_ICH)
int i_ichiKijun = input.int(26, "Kijun-sen (Base)", minval=2, group=GP_ICH)
int i_ichiSenkouB = input.int(52, "Senkou Span B (Leading B)", minval=2, group=GP_ICH)
int i_ichiDisp = input.int(26, "Displacement", minval=1, group=GP_ICH)
// --- Scoring Engine ---
bool i_useWeighted = input.bool(true, "Use Weighted Scoring", group=GP_SCORE, tooltip="Structural conditions score 2x, confirmation 1x. Disable for equal weighting.")
// --- Dashboard ---
bool i_showDash = input.bool(true, "Show Dashboard", group=GP_DASH)
string i_dashPos = input.string("Bottom Right", "Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=GP_DASH)
string i_dashSize = input.string("Small", "Size", options=["Tiny", "Small", "Normal", "Large"], group=GP_DASH)
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
f1(float v) => str.tostring(v, "#.#")
f2(float v) => str.tostring(v, "#.##")
f0(float v) => str.tostring(v, "#")
posnegColor(float v) => v > 0 ? C_BULL : v < 0 ? C_BEAR : C_NEUTRAL
biasColor(int score, int maxS) =>
float pct = maxS != 0 ? math.abs(score) / maxS : 0
if score > 0
pct > 0.6 ? C_BULL : pct > 0.3 ? color.new(C_BULL, 30) : C_NEUTRAL
else if score < 0
pct > 0.6 ? C_BEAR : pct > 0.3 ? color.new(C_BEAR, 30) : C_NEUTRAL
else
C_NEUTRAL
biasLabel(int score, int maxS) =>
float pct = maxS != 0 ? math.abs(score) / maxS : 0
if math.abs(score) == 0
"NEUTRAL"
else if pct > 0.6
score > 0 ? "STRONG BULL" : "STRONG BEAR"
else if pct > 0.3
score > 0 ? "LEAN BULL" : "LEAN BEAR"
else
score > 0 ? "WEAK BULL" : "WEAK BEAR"
// ============================================================================
// EMA CALCULATIONS
// ============================================================================
float emaFast = ta.ema(close, i_emaFastLen)
float emaMid = ta.ema(close, i_emaMidLen)
float emaSlow = ta.ema(close, i_emaSlowLen)
float emaAnchor = ta.ema(close, i_anchorLen)
bool emaBullish = emaFast > emaMid and emaMid > emaSlow
bool emaBearish = emaFast < emaMid and emaMid < emaSlow
// Full stack includes anchor
bool fullStackBull = emaBullish and emaSlow > emaAnchor
bool fullStackBear = emaBearish and emaSlow < emaAnchor
// EMA spread — normalized to percentage for ticker-agnostic comparison
float emaSpread = emaSlow != 0 ? math.abs(emaFast - emaSlow) / emaSlow * 100 : 0.0
bool emaCompressed = emaSpread < 0.5
// EMA stack label
string emaStackStr = fullStackBull ? "FULL BULL" : fullStackBear ? "FULL BEAR" : emaBullish ? "PARTIAL BULL" : emaBearish ? "PARTIAL BEAR" : emaCompressed ? "COMPRESSED" : "MIXED"
color emaStackClr = fullStackBull ? C_BULL : fullStackBear ? C_BEAR : emaBullish ? color.new(C_BULL, 40) : emaBearish ? color.new(C_BEAR, 40) : emaCompressed ? C_YELLOW : C_NEUTRAL
// ============================================================================
// HIGHER TIMEFRAME DATA
// ============================================================================
float htfEma = request.security(syminfo.tickerid, i_htfTF, ta.ema(close, i_htfEmaLen), lookahead=barmerge.lookahead_off)
// Prior week high / low / close
[pwHigh, pwLow, pwClose] = request.security(syminfo.tickerid, "W", [high[1], low[1], close[1]], lookahead=barmerge.lookahead_on)
// Prior month high / low / close
[pmHigh, pmLow, pmClose] = request.security(syminfo.tickerid, "M", [high[1], low[1], close[1]], lookahead=barmerge.lookahead_on)
// HTF trend assessment
bool htfBullish = not na(htfEma) and close > htfEma
bool htfBearish = not na(htfEma) and close < htfEma
// ============================================================================
// ADX / DMI
// ============================================================================
[diPlus, diMinus, adxVal] = ta.dmi(i_adxLen, i_adxLen)
// ADX slope (rate of change over lookback)
float adxSlope = adxVal - nz(adxVal[i_adxSlopeLB])
// ADX tier classification
string adxTier = adxVal > 45 ? "EXTREME" : adxVal > 30 ? "STRONG" : adxVal > 20 ? "MODERATE" : "WEAK"
color adxClr = adxVal > 45 ? C_ORANGE : adxVal > 30 ? C_BULL : adxVal > 20 ? C_CYAN : C_GRAY
// ADX slope label
string adxSlopeStr = adxSlope > i_adxSlopeThr ? "RISING" : adxSlope < -i_adxSlopeThr ? "FALLING" : "FLAT"
color adxSlopeClr = adxSlope > i_adxSlopeThr ? C_BULL : adxSlope < -i_adxSlopeThr ? C_BEAR : C_NEUTRAL
// ============================================================================
// 50 EMA SLOPE ANALYSIS
// ============================================================================
float emaSlopeRaw = emaSlow - nz(emaSlow[i_emaSlopeLB])
float emaSlopeNorm = emaSlow != 0 ? emaSlopeRaw / emaSlow * 100 : 0.0
bool emaSlopePos = emaSlopeNorm > 0.01
bool emaSlopeNeg = emaSlopeNorm < -0.01
// Slope acceleration (second derivative)
float prevSlopeNorm = nz(emaSlow[i_emaSlopeLB]) != 0 ? (nz(emaSlow[i_emaSlopeLB]) - nz(emaSlow[i_emaSlopeLB * 2])) / nz(emaSlow[i_emaSlopeLB]) * 100 : 0.0
float slopeAccel = emaSlopeNorm - prevSlopeNorm
// ============================================================================
// TREND PHASE CLASSIFICATION
// ============================================================================
// Detect recent EMA crossovers
bool fastCrossMid = ta.cross(emaFast, emaMid)
bool midCrossSlow = ta.cross(emaMid, emaSlow)
int barsSinceFXM = ta.barssince(fastCrossMid)
int barsSinceMXS = ta.barssince(midCrossSlow)
bool recentCross = (not na(barsSinceFXM) and barsSinceFXM <= 3) or (not na(barsSinceMXS) and barsSinceMXS <= 5)
// Phase determination (priority order matters)
string trendPhase = "CONSOLIDATING"
color phaseClr = C_NEUTRAL
if recentCross
trendPhase := "REVERSING"
phaseClr := C_ORANGE
else if adxVal > 30 and adxSlope < -i_adxSlopeThr
trendPhase := "EXHAUSTING"
phaseClr := C_YELLOW
else if adxVal > 30 and math.abs(adxSlope) <= i_adxSlopeThr
trendPhase := "MATURE"
phaseClr := C_CYAN
else if adxVal > 20 and adxSlope > i_adxSlopeThr
trendPhase := "ACCELERATING"
phaseClr := C_BULL
else if adxVal <= 25 and adxSlope > i_adxSlopeThr and not emaCompressed
trendPhase := "EMERGING"
phaseClr := color.new(C_BULL, 30)
else if adxVal < 20 and emaCompressed
trendPhase := "CONSOLIDATING"
phaseClr := C_GRAY
else
trendPhase := "CONSOLIDATING"
phaseClr := C_GRAY
// ============================================================================
// RSI
// ============================================================================
float rsiVal = ta.rsi(close, i_rsiLen)
string rsiLbl = rsiVal > 70 ? "OVERBOUGHT" : rsiVal > 55 ? "BULLISH" : rsiVal < 30 ? "OVERSOLD" : rsiVal < 45 ? "BEARISH" : "NEUTRAL"
color rsiClr = rsiVal > 70 ? C_ORANGE : rsiVal > 55 ? C_BULL : rsiVal < 30 ? C_ORANGE : rsiVal < 45 ? C_BEAR : C_NEUTRAL
// ============================================================================
// RSI DIVERGENCE DETECTION
// ============================================================================
// Track pivots on RSI and price to detect divergences
var float prevRsiPL = na
var float prevPricePL = na
var int prevPivLowBar = na
var float prevRsiPH = na
var float prevPricePH = na
var int prevPivHiBar = na
// Current pivot detection (returned N bars ago where N = right bars)
float rsiPivotLow = i_rsiDiv ? ta.pivotlow(rsiVal, i_rsiPivotL, i_rsiPivotR) : na
float pricePivLow = i_rsiDiv ? ta.pivotlow(low, i_rsiPivotL, i_rsiPivotR) : na
float rsiPivotHigh = i_rsiDiv ? ta.pivothigh(rsiVal, i_rsiPivotL, i_rsiPivotR) : na
float pricePivHigh = i_rsiDiv ? ta.pivothigh(high, i_rsiPivotL, i_rsiPivotR) : na
var string rsiDivState = "NONE"
var color rsiDivClr = C_NEUTRAL
var int rsiDivBar = na
// Bullish divergence: price lower low, RSI higher low
if not na(rsiPivotLow) and not na(pricePivLow)
int curPivBar = bar_index - i_rsiPivotR
if not na(prevRsiPL) and not na(prevPricePL) and not na(prevPivLowBar)
if pricePivLow < prevPricePL and rsiPivotLow > prevRsiPL
rsiDivState := "BULL DIV"
rsiDivClr := C_BULL
rsiDivBar := bar_index
// Draw divergence line on price
line.new(prevPivLowBar, prevPricePL, curPivBar, pricePivLow, color=C_BULL, style=line.style_dashed, width=2)
else if pricePivLow > prevPricePL and rsiPivotLow < prevRsiPL
rsiDivState := "HIDDEN BULL"
rsiDivClr := color.new(C_BULL, 40)
rsiDivBar := bar_index
prevRsiPL := rsiPivotLow
prevPricePL := pricePivLow
prevPivLowBar := curPivBar
// Bearish divergence: price higher high, RSI lower high
if not na(rsiPivotHigh) and not na(pricePivHigh)
int curPivBar = bar_index - i_rsiPivotR
if not na(prevRsiPH) and not na(prevPricePH) and not na(prevPivHiBar)
if pricePivHigh > prevPricePH and rsiPivotHigh < prevRsiPH
rsiDivState := "BEAR DIV"
rsiDivClr := C_BEAR
rsiDivBar := bar_index
line.new(prevPivHiBar, prevPricePH, curPivBar, pricePivHigh, color=C_BEAR, style=line.style_dashed, width=2)
else if pricePivHigh < prevPricePH and rsiPivotHigh > prevRsiPH
rsiDivState := "HIDDEN BEAR"
rsiDivClr := color.new(C_BEAR, 40)
rsiDivBar := bar_index
prevRsiPH := rsiPivotHigh
prevPricePH := pricePivHigh
prevPivHiBar := curPivBar
// Decay divergence state after N bars
if not na(rsiDivBar) and bar_index - rsiDivBar > 15
rsiDivState := "NONE"
rsiDivClr := C_NEUTRAL
// ============================================================================
// MACD
// ============================================================================
[macdLine, macdSignalLine, macdHist] = ta.macd(close, i_macdFast, i_macdSlow, i_macdSignal)
string macdStr = macdHist > 0 ? "BULLISH" : macdHist < 0 ? "BEARISH" : "NEUTRAL"
color macdClr = macdHist > 0 ? C_BULL : macdHist < 0 ? C_BEAR : C_NEUTRAL
bool macdAboveSignal = macdLine > macdSignalLine
string macdXStr = macdAboveSignal ? "ABOVE SIG" : "BELOW SIG"
// ============================================================================
// MACD DIVERGENCE DETECTION
// ============================================================================
var float prevMacdPL = na
var float prevMacdPrPL = na
var int prevMacdPLBar = na
var float prevMacdPH = na
var float prevMacdPrPH = na
var int prevMacdPHBar = na
float macdPivotLow = i_macdDiv ? ta.pivotlow(macdHist, i_macdPivotL, i_macdPivotR) : na
float macdPricePL = i_macdDiv ? ta.pivotlow(low, i_macdPivotL, i_macdPivotR) : na
float macdPivotHigh = i_macdDiv ? ta.pivothigh(macdHist, i_macdPivotL, i_macdPivotR) : na
float macdPricePH = i_macdDiv ? ta.pivothigh(high, i_macdPivotL, i_macdPivotR) : na
var string macdDivState = "NONE"
var color macdDivClr = C_NEUTRAL
var int macdDivBar = na
// MACD Bullish divergence
if not na(macdPivotLow) and not na(macdPricePL)
int curBar = bar_index - i_macdPivotR
if not na(prevMacdPL) and not na(prevMacdPrPL) and not na(prevMacdPLBar)
if macdPricePL < prevMacdPrPL and macdPivotLow > prevMacdPL
macdDivState := "BULL DIV"
macdDivClr := C_BULL
macdDivBar := bar_index
line.new(prevMacdPLBar, prevMacdPrPL, curBar, macdPricePL, color=C_CYAN, style=line.style_dotted, width=2)
else if macdPricePL > prevMacdPrPL and macdPivotLow < prevMacdPL
macdDivState := "HIDDEN BULL"
macdDivClr := color.new(C_CYAN, 40)
macdDivBar := bar_index
prevMacdPL := macdPivotLow
prevMacdPrPL := macdPricePL
prevMacdPLBar := curBar
// MACD Bearish divergence
if not na(macdPivotHigh) and not na(macdPricePH)
int curBar = bar_index - i_macdPivotR
if not na(prevMacdPH) and not na(prevMacdPrPH) and not na(prevMacdPHBar)
if macdPricePH > prevMacdPrPH and macdPivotHigh < prevMacdPH
macdDivState := "BEAR DIV"
macdDivClr := C_BEAR
macdDivBar := bar_index
line.new(prevMacdPHBar, prevMacdPrPH, curBar, macdPricePH, color=C_PURPLE, style=line.style_dotted, width=2)
else if macdPricePH < prevMacdPrPH and macdPivotHigh > prevMacdPH
macdDivState := "HIDDEN BEAR"
macdDivClr := color.new(C_PURPLE, 40)
macdDivBar := bar_index
prevMacdPH := macdPivotHigh
prevMacdPrPH := macdPricePH
prevMacdPHBar := curBar
// Decay
if not na(macdDivBar) and bar_index - macdDivBar > 15
macdDivState := "NONE"
macdDivClr := C_NEUTRAL
// ============================================================================
// OBV (On-Balance Volume)
// ============================================================================
float obvVal = ta.obv
float obvEma = ta.ema(obvVal, i_obvEmaLen)
float obvSlope = obvEma - nz(obvEma[5])
// OBV confirms price trend?
bool priceUp = emaSlopePos
bool obvUp = obvSlope > 0
bool priceDown = emaSlopeNeg
bool obvDown = obvSlope < 0
string obvStr = "NEUTRAL"
color obvClr = C_NEUTRAL
if i_useOBV
if (priceUp and obvUp) or (priceDown and obvDown)
obvStr := "CONFIRMING"
obvClr := C_BULL
else if (priceUp and obvDown) or (priceDown and obvUp)
obvStr := "DIVERGING"
obvClr := C_ORANGE
else
obvStr := "NEUTRAL"
obvClr := C_NEUTRAL
// ============================================================================
// RELATIVE VOLUME
// ============================================================================
float avgVol = ta.sma(volume, i_relVolLen)
float relVol = avgVol != 0 ? volume / avgVol : 0.0
string relVolStr = relVol > 2.0 ? "SPIKE" : relVol > 1.2 ? "ABOVE" : relVol > 0.8 ? "NORMAL" : "DRY"
color relVolClr = relVol > 2.0 ? C_ORANGE : relVol > 1.2 ? C_BULL : relVol > 0.8 ? C_NEUTRAL : C_GRAY
// ============================================================================
// ATR + PERCENTILE RANKING
// ============================================================================
float atrVal = ta.atr(i_atrLen)
// Percentile rank over lookback
int atrRank = 0
for i = 1 to i_atrPctlLB
if atrVal > nz(atrVal[i])
atrRank += 1
float atrPctl = i_atrPctlLB > 0 ? atrRank / i_atrPctlLB * 100 : 50.0
string atrRegime = atrPctl >= 90 ? "EXTREME" : atrPctl >= 70 ? "HIGH" : atrPctl >= 30 ? "NORMAL" : "LOW"
color atrClr = atrPctl >= 90 ? C_ORANGE : atrPctl >= 70 ? C_YELLOW : atrPctl >= 30 ? C_NEUTRAL : C_CYAN
// ============================================================================
// BOLLINGER BAND WIDTH PERCENTILE
// ============================================================================
[bbMid, bbUp, bbLow] = ta.bb(close, i_bbLen, i_bbMult)
float bbWidth = bbMid != 0 ? (bbUp - bbLow) / bbMid * 100 : 0.0
// Percentile rank of BB width
int bbwRank = 0
for i = 1 to i_bbwPctlLB
if bbWidth > nz(bbWidth[i])
bbwRank += 1
float bbwPctl = i_bbwPctlLB > 0 ? bbwRank / i_bbwPctlLB * 100 : 50.0
string bbwStr = bbwPctl < 10 ? "COMPRESSED" : bbwPctl < 30 ? "NARROW" : bbwPctl > 90 ? "EXPANDED" : bbwPctl > 70 ? "WIDE" : "NORMAL"
color bbwClr = bbwPctl < 10 ? C_CYAN : bbwPctl < 30 ? color.new(C_CYAN, 40) : bbwPctl > 90 ? C_ORANGE : bbwPctl > 70 ? C_YELLOW : C_NEUTRAL
// ============================================================================
// 52-WEEK HIGH / LOW
// ============================================================================
float high52w = ta.highest(high, 252)
float low52w = ta.lowest(low, 252)
float range52 = high52w - low52w
float pos52 = range52 != 0 ? (close - low52w) / range52 * 100 : 50.0
// ============================================================================
// FIBONACCI RETRACEMENT (optional)
// ============================================================================
float fibHigh = ta.highest(high, i_fibLB)
float fibLow = ta.lowest(low, i_fibLB)
float fibRange = fibHigh - fibLow
float fib382 = fibHigh - fibRange * 0.382
float fib500 = fibHigh - fibRange * 0.500
float fib618 = fibHigh - fibRange * 0.618
// Draw fib levels as horizontal lines on last bar
var line fibLine382 = na
var line fibLine500 = na
var line fibLine618 = na
var line fibLineHi = na
var line fibLineLo = na
var label fibLbl382 = na
var label fibLbl500 = na
var label fibLbl618 = na
if i_showFib and barstate.islast
// Clean up previous
line.delete(fibLine382)
line.delete(fibLine500)
line.delete(fibLine618)
line.delete(fibLineHi)
line.delete(fibLineLo)
label.delete(fibLbl382)
label.delete(fibLbl500)
label.delete(fibLbl618)
int x1 = bar_index - i_fibLB
int x2 = bar_index + 10
fibLineHi := line.new(x1, fibHigh, x2, fibHigh, color=C_GRAY, style=line.style_dotted, width=1)
fibLineLo := line.new(x1, fibLow, x2, fibLow, color=C_GRAY, style=line.style_dotted, width=1)
fibLine382 := line.new(x1, fib382, x2, fib382, color=C_FIB_382, style=line.style_dashed, width=1)
fibLine500 := line.new(x1, fib500, x2, fib500, color=C_FIB_500, style=line.style_dashed, width=1)
fibLine618 := line.new(x1, fib618, x2, fib618, color=C_FIB_618, style=line.style_dashed, width=1)
fibLbl382 := label.new(x2, fib382, "38.2%", style=label.style_none, textcolor=C_FIB_382, size=size.tiny)
fibLbl500 := label.new(x2, fib500, "50.0%", style=label.style_none, textcolor=C_FIB_500, size=size.tiny)
fibLbl618 := label.new(x2, fib618, "61.8%", style=label.style_none, textcolor=C_FIB_618, size=size.tiny)
// ============================================================================
// ICHIMOKU CLOUD (optional)
// ============================================================================
float tenkan = i_showIchi ? (ta.highest(high, i_ichiTenkan) + ta.lowest(low, i_ichiTenkan)) / 2 : na
float kijun = i_showIchi ? (ta.highest(high, i_ichiKijun) + ta.lowest(low, i_ichiKijun)) / 2 : na
float senkouA = i_showIchi ? (tenkan + kijun) / 2 : na
float senkouB = i_showIchi ? (ta.highest(high, i_ichiSenkouB) + ta.lowest(low, i_ichiSenkouB)) / 2 : na
float chikou = i_showIchi ? close : na
// ============================================================================
// COMPOSITE TREND SCORE
// ============================================================================
int wStructural = i_useWeighted ? 2 : 1
int wConfirm = 1
int trendScore = 0
// --- Structural conditions (2x weight in weighted mode) ---
// 1. EMA Stack alignment (fast > mid > slow)
trendScore += emaBullish ? wStructural : emaBearish ? -wStructural : 0
// 2. Price vs 200 EMA (anchor)
if not na(emaAnchor)
trendScore += close > emaAnchor ? wStructural : close < emaAnchor ? -wStructural : 0
// 3. Price vs HTF Slow EMA (Weekly 50)
if i_useHTF and not na(htfEma)
trendScore += close > htfEma ? wStructural : close < htfEma ? -wStructural : 0
// --- Confirmation conditions (1x weight) ---
// 4. ADX + DI direction
if adxVal > 20
trendScore += diPlus > diMinus ? wConfirm : diMinus > diPlus ? -wConfirm : 0
// 5. RSI position
trendScore += rsiVal > 55 ? wConfirm : rsiVal < 45 ? -wConfirm : 0
// 6. MACD above/below signal
trendScore += macdAboveSignal ? wConfirm : -wConfirm
// 7. OBV trend confirmation
if i_useOBV
trendScore += obvSlope > 0 ? wConfirm : obvSlope < 0 ? -wConfirm : 0
// 8. 50 EMA slope direction
trendScore += emaSlopePos ? wConfirm : emaSlopeNeg ? -wConfirm : 0
// --- Maximum possible score ---
int maxScore = 3 * wStructural + 5 * wConfirm
// --- Trend score delta tracking ---
float scoreAvg5 = ta.sma(trendScore, 5)
string scoreTrend = trendScore > scoreAvg5 + 0.5 ? "IMPROVING" : trendScore < scoreAvg5 - 0.5 ? "FADING" : "STABLE"
color scoreTrendClr = scoreTrend == "IMPROVING" ? C_BULL : scoreTrend == "FADING" ? C_BEAR : C_NEUTRAL
// ============================================================================
// DISTANCE FROM 200 EMA
// ============================================================================
float dist200 = not na(emaAnchor) and emaAnchor != 0 ? (close - emaAnchor) / emaAnchor * 100 : 0.0
color dist200Clr = dist200 > 5 ? C_BULL : dist200 > 0 ? color.new(C_BULL, 40) : dist200 < -5 ? C_BEAR : dist200 < 0 ? color.new(C_BEAR, 40) : C_NEUTRAL
// ============================================================================
// PLOTS — EMA Ribbon
// ============================================================================
plot(i_showEma ? emaFast : na, "Fast EMA", color=C_BULL, linewidth=1)
plot(i_showEma ? emaMid : na, "Mid EMA", color=C_YELLOW, linewidth=1)
plot(i_showEma ? emaSlow : na, "Slow EMA", color=C_BEAR, linewidth=1)
plot(i_showAnchor ? emaAnchor : na, "200 EMA", color=C_200_EMA, linewidth=2, style=plot.style_line)
// EMA cloud fill
p_fast = plot(i_showEma and i_showCloud ? emaFast : na, "Fast (fill)", color=color(na), display=display.none)
p_slow = plot(i_showEma and i_showCloud ? emaSlow : na, "Slow (fill)", color=color(na), display=display.none)
fill(p_fast, p_slow, color=emaBullish ? C_CLOUD_BULL : emaBearish ? C_CLOUD_BEAR : C_CLOUD_MIX, title="EMA Cloud")
// HTF EMA
plot(i_useHTF and i_showHTFEma ? htfEma : na, "HTF EMA", color=C_HTF_EMA, linewidth=2, style=plot.style_stepline)
// ============================================================================
// PLOTS — Ichimoku Cloud (optional)
// ============================================================================
plot(i_showIchi ? tenkan : na, "Tenkan-sen", color=C_ICH_TENKAN, linewidth=1)
plot(i_showIchi ? kijun : na, "Kijun-sen", color=C_ICH_KIJUN, linewidth=1)
plot(i_showIchi ? chikou : na, "Chikou Span", color=C_PURPLE, linewidth=1, offset=-i_ichiDisp)
p_senkouA = plot(i_showIchi ? senkouA : na, "Senkou A", color=C_BULL, linewidth=1, offset=i_ichiDisp, display=display.pane)
p_senkouB = plot(i_showIchi ? senkouB : na, "Senkou B", color=C_BEAR, linewidth=1, offset=i_ichiDisp, display=display.pane)
fill(p_senkouA, p_senkouB, color=senkouA > senkouB ? C_ICH_BULL : C_ICH_BEAR, title="Kumo Cloud")
// ============================================================================
// KEY LEVELS — drawn as horizontal lines on last bar
// ============================================================================
// --- Prior Week H/L/C ---
var line lwH = na, var line lwL = na, var line lwC = na
if i_showPrWk and barstate.islast and not na(pwHigh)
line.delete(lwH), line.delete(lwL), line.delete(lwC)
lwH := line.new(bar_index - 20, pwHigh, bar_index + 5, pwHigh, color=C_WEEK_LVL, style=line.style_dashed, width=1)
lwL := line.new(bar_index - 20, pwLow, bar_index + 5, pwLow, color=C_WEEK_LVL, style=line.style_dashed, width=1)
lwC := line.new(bar_index - 20, pwClose, bar_index + 5, pwClose, color=color.new(C_WEEK_LVL, 40), style=line.style_dotted, width=1)
// --- Prior Month H/L/C ---
var line lmH = na, var line lmL = na, var line lmC = na
if i_showPrMo and barstate.islast and not na(pmHigh)
line.delete(lmH), line.delete(lmL), line.delete(lmC)
lmH := line.new(bar_index - 40, pmHigh, bar_index + 5, pmHigh, color=C_MONTH_LVL, style=line.style_dashed, width=1)
lmL := line.new(bar_index - 40, pmLow, bar_index + 5, pmLow, color=C_MONTH_LVL, style=line.style_dashed, width=1)
lmC := line.new(bar_index - 40, pmClose, bar_index + 5, pmClose, color=color.new(C_MONTH_LVL, 40), style=line.style_dotted, width=1)
// --- 52-Week H/L ---
var line l52H = na, var line l52L = na
if i_show52w and barstate.islast
line.delete(l52H), line.delete(l52L)
l52H := line.new(bar_index - 60, high52w, bar_index + 5, high52w, color=C_52W_LVL, style=line.style_solid, width=1)
l52L := line.new(bar_index - 60, low52w, bar_index + 5, low52w, color=C_52W_LVL, style=line.style_solid, width=1)
// ============================================================================
// DASHBOARD
// ============================================================================
if i_showDash and barstate.islast
// --- Position mapping ---
string tblPos = switch i_dashPos
"Top Right" => position.top_right
"Top Left" => position.top_left
"Bottom Right" => position.bottom_right
"Bottom Left" => position.bottom_left
=> position.top_right
string cellSize = switch i_dashSize
"Tiny" => size.tiny
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.small
// 3 columns, 17 rows
var table dash = table.new(tblPos, 3, 17,
bgcolor = C_DASH_BG,
border_width = 0,
border_color = color.new(#FFFFFF, 85),
frame_width = 1,
frame_color = color.new(#FFFFFF, 80))
// === Row 0: Header ===
table.cell(dash, 0, 0, " Trend Compass - Daily ", text_color=#FFFFFF,
bgcolor=C_DASH_HEADER, text_size=cellSize, text_halign=text.align_center)
table.cell(dash, 1, 0, "", bgcolor=C_DASH_HEADER, text_size=cellSize)
table.cell(dash, 2, 0, "", bgcolor=C_DASH_HEADER, text_size=cellSize)
table.merge_cells(dash, 0, 0, 2, 0)
// === Row 1: Trend Phase ===
table.cell(dash, 0, 1, "Phase", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 1, trendPhase, text_color=phaseClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 1, "", text_size=cellSize)
// === Row 2: Trend Score ===
string tsStr = (trendScore > 0 ? "+" : "") + str.tostring(trendScore) + "/" + str.tostring(maxScore)
table.cell(dash, 0, 2, "Score", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 2, tsStr, text_color=biasColor(trendScore, maxScore), text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 2, biasLabel(trendScore, maxScore), text_color=biasColor(trendScore, maxScore), text_size=cellSize, text_halign=text.align_right)
// === Row 3: Score Trend ===
table.cell(dash, 0, 3, "Momentum", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 3, scoreTrend, text_color=scoreTrendClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 3, i_useWeighted ? "WTD" : "EQL", text_color=C_GRAY, text_size=cellSize, text_halign=text.align_right)
// === Row 4: EMA Stack ===
table.cell(dash, 0, 4, "EMA Stack", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 4, emaStackStr, text_color=emaStackClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 4, str.tostring(i_emaFastLen) + "/" + str.tostring(i_emaMidLen) + "/" + str.tostring(i_emaSlowLen),
text_color=C_GRAY, text_size=cellSize, text_halign=text.align_right)
// === Row 5: ADX ===
table.cell(dash, 0, 5, "ADX", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 5, f1(adxVal), text_color=adxClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 5, adxTier, text_color=adxClr, text_size=cellSize, text_halign=text.align_right)
// === Row 6: ADX Slope ===
table.cell(dash, 0, 6, "ADX Slope", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 6, adxSlopeStr, text_color=adxSlopeClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 6, f1(adxSlope), text_color=adxSlopeClr, text_size=cellSize, text_halign=text.align_right)
// === Row 7: RSI ===
table.cell(dash, 0, 7, "RSI", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 7, f1(rsiVal), text_color=rsiClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 7, rsiLbl, text_color=rsiClr, text_size=cellSize, text_halign=text.align_right)
// === Row 8: MACD ===
table.cell(dash, 0, 8, "MACD", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 8, macdStr, text_color=macdClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 8, macdXStr, text_color=macdClr, text_size=cellSize, text_halign=text.align_right)
// === Row 9: RSI Divergence ===
table.cell(dash, 0, 9, "RSI Div", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 9, rsiDivState, text_color=rsiDivClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 9, "", text_size=cellSize)
// === Row 10: MACD Divergence ===
table.cell(dash, 0, 10, "MACD Div", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 10, macdDivState, text_color=macdDivClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 10, "", text_size=cellSize)
// === Row 11: OBV Trend ===
table.cell(dash, 0, 11, "OBV", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 11, obvStr, text_color=obvClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 11, "", text_size=cellSize)
// === Row 12: Relative Volume ===
table.cell(dash, 0, 12, "Rel Vol", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 12, f1(relVol) + "x", text_color=relVolClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 12, relVolStr, text_color=relVolClr, text_size=cellSize, text_halign=text.align_right)
// === Row 13: ATR Regime ===
table.cell(dash, 0, 13, "ATR", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 13, f2(atrVal), text_color=atrClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 13, atrRegime + " (" + f0(atrPctl) + "%)", text_color=atrClr, text_size=cellSize, text_halign=text.align_right)
// === Row 14: BB Width Percentile ===
table.cell(dash, 0, 14, "BB Width", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 14, f0(bbwPctl) + "%", text_color=bbwClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 14, bbwStr, text_color=bbwClr, text_size=cellSize, text_halign=text.align_right)
// === Row 15: HTF Trend ===
string htfStr = not na(htfEma) ? (htfBullish ? "BULLISH" : htfBearish ? "BEARISH" : "NEUTRAL") : "N/A"
color htfClr = htfBullish ? C_BULL : htfBearish ? C_BEAR : C_NEUTRAL
table.cell(dash, 0, 15, i_htfTF + " Trend", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 15, htfStr, text_color=htfClr, text_size=cellSize, text_halign=text.align_right)
table.cell(dash, 2, 15, "", text_size=cellSize)
// === Row 16: Distance from 200 EMA ===
string d200Str = (dist200 >= 0 ? "+" : "") + f1(dist200) + "%"
table.cell(dash, 0, 16, "200 EMA Δ", text_color=C_DASH_TEXT, text_size=cellSize, text_halign=text.align_left)
table.cell(dash, 1, 16, d200Str, text_color=dist200Clr, text_size=cellSize, text_halign=text.align_right)
// 52-week position
if i_show52w
table.cell(dash, 2, 16, "52w: " + f0(pos52) + "%", text_color=pos52 > 80 ? C_BULL : pos52 < 20 ? C_BEAR : C_NEUTRAL, text_size=cellSize, text_halign=text.align_right)
else
table.cell(dash, 2, 16, "", text_size=cellSize)
// ============================================================================
// ALERTS
// ============================================================================
// --- Trend phase transitions ---
string prevPhase = na(trendPhase[1]) ? "CONSOLIDATING" : trendPhase[1]
bool phaseChanged = trendPhase != prevPhase
alertcondition(phaseChanged, "Trend Phase Changed", "{{ticker}} trend phase changed on {{interval}}")
// --- Score thresholds ---
int strongThresh = i_useWeighted ? 7 : 5
alertcondition(trendScore >= strongThresh, "Strong Bullish Trend", "{{ticker}} strong bullish trend score on {{interval}}")
alertcondition(trendScore <= -strongThresh, "Strong Bearish Trend", "{{ticker}} strong bearish trend score on {{interval}}")
alertcondition(ta.cross(trendScore, 0), "Score Crossed Neutral", "{{ticker}} trend score crossed zero on {{interval}}")
// --- Divergence alerts ---
alertcondition(rsiDivState == "BULL DIV" and rsiDivState[1] != "BULL DIV", "RSI Bullish Divergence", "{{ticker}} RSI bullish divergence detected on {{interval}}")
alertcondition(rsiDivState == "BEAR DIV" and rsiDivState[1] != "BEAR DIV", "RSI Bearish Divergence", "{{ticker}} RSI bearish divergence detected on {{interval}}")
alertcondition(macdDivState == "BULL DIV" and macdDivState[1] != "BULL DIV", "MACD Bullish Divergence", "{{ticker}} MACD bullish divergence detected on {{interval}}")
alertcondition(macdDivState == "BEAR DIV" and macdDivState[1] != "BEAR DIV", "MACD Bearish Divergence", "{{ticker}} MACD bearish divergence detected on {{interval}}")
// --- EMA cross alerts ---
bool goldenCross = ta.crossover(emaSlow, emaAnchor)
bool deathCross = ta.crossunder(emaSlow, emaAnchor)
alertcondition(goldenCross, "Golden Cross (50/200)", "{{ticker}} 50 EMA crossed above 200 EMA on {{interval}}")
alertcondition(deathCross, "Death Cross (50/200)", "{{ticker}} 50 EMA crossed below 200 EMA on {{interval}}")
// --- Volatility regime change ---
string prevAtrRegime = na(atrRegime[1]) ? "NORMAL" : atrRegime[1]
bool atrRegimeChange = atrRegime != prevAtrRegime
alertcondition(atrRegimeChange, "ATR Regime Change", "{{ticker}} ATR regime changed on {{interval}}")
// --- BB extreme compression ---
alertcondition(bbwPctl < 5, "BB Extreme Compression", "{{ticker}} BB width is in extreme compression (<5th percentile) on {{interval}}")
// ============================================================================
// END
// ============================================================================