Repository navigation
Expand file tree
/
Copy pathFitMom_Strategy.pine
More file actions
536 lines (473 loc) · 25.7 KB
/
Copy pathFitMom_Strategy.pine
File metadata and controls
536 lines (473 loc) · 25.7 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
//@version=6
indicator("FitMom Strategy", shorttitle = "FitMom Strategy", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)
// ─────────────────────────────────────────────────────────────────────────────
// INPUTS
// ─────────────────────────────────────────────────────────────────────────────
grpS = "Sessions"
tzIn = input.string("America/Chicago", "Timezone", group = grpS, tooltip = "America/Chicago handles CST/CDT automatically.")
nySessIn = input.session("0830-1500", "NY session (profile built here)", group = grpS)
nightSessIn = input.session("1700-0100", "Level display window", group = grpS)
rangeSessIn = input.session("1800-1900", "High/Low window", group = grpS)
grpP = "Volume Profile"
rowsIn = input.int(1000, "Profile rows", minval = 10, maxval = 1000, group = grpP, tooltip = "More rows = finer price resolution for POC/VAH/VAL.")
vaPctIn = input.float(70, "Value area %", minval = 10, maxval = 100, step = 1, group = grpP) / 100
grpL = "Value Area Levels"
showPOC = input.bool(true, "POC", inline = "poc", group = grpL)
pocCol = input.color(color.orange, "", inline = "poc", group = grpL)
showVAH = input.bool(true, "VAH", inline = "vah", group = grpL)
vahCol = input.color(color.teal, "", inline = "vah", group = grpL)
showVAL = input.bool(true, "VAL", inline = "val", group = grpL)
valCol = input.color(color.teal, "", inline = "val", group = grpL)
lvlWidthIn = input.int(2, "Width", minval = 1, maxval = 5, group = grpL)
lvlStyleIn = input.string("Solid", "Style", options = ["Solid", "Dashed", "Dotted"], group = grpL)
showVAFill = input.bool(false, "Shade value area", inline = "fill", group = grpL)
vaFillCol = input.color(color.new(color.teal, 90), "", inline = "fill", group = grpL)
grpR = "High/Low Window"
showRH = input.bool(true, "High", inline = "rh", group = grpR)
rhCol = input.color(color.lime, "", inline = "rh", group = grpR)
showRL = input.bool(true, "Low", inline = "rl", group = grpR)
rlCol = input.color(color.red, "", inline = "rl", group = grpR)
rWidthIn = input.int(1, "Width", minval = 1, maxval = 5, group = grpR)
rStyleIn = input.string("Dashed", "Style", options = ["Solid", "Dashed", "Dotted"], group = grpR)
rangeName = input.string("6-7pm", "Label prefix", group = grpR)
grpV = "Trading Start Line"
showVLine = input.bool(true, "Show vertical line", group = grpV)
vHourIn = input.int(19, "Hour", minval = 0, maxval = 23, inline = "vt", group = grpV)
vMinIn = input.int(0, "Minute", minval = 0, maxval = 59, inline = "vt", group = grpV)
vTextIn = input.string("trading begins", "Text", group = grpV)
vCol = input.color(color.gray, "Color", group = grpV)
vWidthIn = input.int(1, "Width", minval = 1, maxval = 5, group = grpV)
vStyleIn = input.string("Dotted", "Style", options = ["Solid", "Dashed", "Dotted"], group = grpV)
grpLb = "Labels"
showLabels = input.bool(true, "Show labels", group = grpLb)
showDates = input.bool(true, "Include date in label", group = grpLb)
labelSizeIn = input.string("Small", "Label size", options = ["Tiny", "Small", "Normal"], group = grpLb)
grpF = "FVG Continuation Engine"
useFvgSig = input.bool(true, "Enable FVG continuation signals", group = grpF)
fvgOnVA = input.bool(true, "Use NY POC / VAH / VAL", group = grpF)
fvgOnRng = input.bool(true, "Use 6-7pm High / Low", group = grpF,
tooltip = "The 6-7pm levels only become tradable after the window closes. A high that is still forming touches itself on every new high.")
fvgAfterStart = input.bool(false, "Only arm after the trading start time", group = grpF,
tooltip = "When on, level touches only count between the 'trading begins' time and the end of the overnight window.")
atrLen = input.int(14, "ATR length", minval = 1, group = grpF)
fTolMode = input.string("ATR", "Touch tolerance", options = ["ATR", "Ticks", "None"], group = grpF)
fTolAtr = input.float(0.10, " ATR multiple", step = 0.05, minval = 0, group = grpF)
fTolTick = input.int(4, " Ticks", minval = 0, group = grpF)
fCool = input.int(10, "Bars before the same level can re-arm", minval = 1, group = grpF)
minLag = input.int(1, "Min bars from touch to FVG", minval = 0, group = grpF)
maxLag = input.int(4, "Max bars from touch to FVG", minval = 1, group = grpF)
minGap = input.float(0.10, "Min gap size (x ATR)", step = 0.05, minval = 0, group = grpF)
minDisp = input.float(0.60, "Min displacement body (x ATR)", step = 0.10, minval = 0, group = grpF)
needRej = input.bool(true, "FVG must reject the level (close clear of it)", group = grpF)
fillMode = input.string("Full (far edge)", "Fill requirement",
options = ["Touch (near edge)", "50% (CE)", "Full (far edge)"], group = grpF)
maxFill = input.int(30, "Max bars to fill the gap", minval = 1, group = grpF)
confMode = input.string("Close back through gap", "Continuation confirmation",
options = ["Close back through gap", "Close beyond FVG bar extreme"], group = grpF)
maxConf = input.int(15, "Max bars to confirm after the fill", minval = 1, group = grpF)
invBuf = input.float(0.15, "Invalidation buffer beyond gap (x ATR)", step = 0.05, minval = 0, group = grpF)
oneADay = input.bool(false, "Only one signal per level per day", group = grpF)
showFvg = input.bool(true, "Draw FVG boxes", group = grpF)
cFvgUp = input.color(#26A69A, "Signal colour long", inline = "fvgC", group = grpF)
cFvgDn = input.color(#EF5350, "short", inline = "fvgC", group = grpF)
fvgTr = input.int(75, " FVG box transparency %", minval = 0, maxval = 100, group = grpF)
showSigLb = input.bool(true, "Signal labels", group = grpF)
grpT = "Line Touch Alerts"
touchTicks = input.int(0, "Touch tolerance (ticks)", minval = 0, group = grpT,
tooltip = "0 = price must actually trade through the line. Higher values count a near miss as a touch.")
touchAfterStart = input.bool(false, "Only alert after the trading start time", group = grpT)
if timeframe.isdwm or timeframe.in_seconds() > 3600
runtime.error("Use an intraday chart of 1 hour or lower (1-5 min recommended for an accurate profile).")
// ─────────────────────────────────────────────────────────────────────────────
// HELPERS
// ─────────────────────────────────────────────────────────────────────────────
f_style(string s) =>
s == "Dashed" ? line.style_dashed : s == "Dotted" ? line.style_dotted : line.style_solid
f_size(string s) =>
s == "Tiny" ? size.tiny : s == "Normal" ? size.normal : size.small
// Make sessions apply to all 7 days unless the user specified days
f_sess(string s) =>
str.contains(s, ":") ? s : s + ":1234567"
// Minutes after midnight from "HHMM" starting at position pos of a session string
f_clock(string s, int pos) =>
int h = int(str.tonumber(str.substring(s, pos, pos + 2)))
int m = int(str.tonumber(str.substring(s, pos + 2, pos + 4)))
h * 60 + m
f_text(string name, int dateTs) =>
showDates ? name + " " + str.format_time(dateTs, "MMM d", tzIn) : name
// Builds a volume profile from stored bar highs/lows/volumes and returns [POC, VAH, VAL]
f_valueArea(array<float> hs, array<float> ls, array<float> vs, int rows, float vaPct) =>
float poc = na
float vah = na
float val = na
int n = hs.size()
if n > 0
float hi = hs.max()
float lo = ls.min()
if hi > lo
float step = (hi - lo) / rows
bool noVol = vs.sum() <= 0 // fall back to time-at-price if symbol has no volume
array<float> hist = array.new<float>(rows, 0.0)
for i = 0 to n - 1
float bH = hs.get(i)
float bL = ls.get(i)
float w = noVol ? 1.0 : vs.get(i)
float rng = bH - bL
int r0 = math.max(0, math.min(rows - 1, math.floor((bL - lo) / step)))
int r1 = math.max(0, math.min(rows - 1, math.floor((bH - lo) / step)))
if rng <= 0
hist.set(r0, hist.get(r0) + w)
else
// spread the bar's volume across the rows it covers, proportional to overlap
for r = r0 to r1
float rowLo = lo + r * step
float ov = math.min(bH, rowLo + step) - math.max(bL, rowLo)
if ov > 0
hist.set(r, hist.get(r) + w * ov / rng)
int pocRow = hist.indexof(hist.max())
float target = hist.sum() * vaPct
float acc = hist.get(pocRow)
int up = pocRow
int dn = pocRow
// expand value area outward from POC toward the heavier side
while acc < target and (up < rows - 1 or dn > 0)
float vUp = up < rows - 1 ? hist.get(up + 1) : -1.0
float vDn = dn > 0 ? hist.get(dn - 1) : -1.0
if vUp >= vDn
up += 1
acc += vUp
else
dn -= 1
acc += vDn
poc := math.round_to_mintick(lo + (pocRow + 0.5) * step)
vah := math.round_to_mintick(lo + (up + 1) * step)
val := math.round_to_mintick(lo + dn * step)
else
poc := hi
vah := hi
val := hi
[poc, vah, val]
f_drawLevel(bool show, float price, int x1, int x2, color col, string name, int dateTs) =>
line ln = na
if show and not na(price)
ln := line.new(x1, price, x2, price, xloc.bar_time, extend.none, col, f_style(lvlStyleIn), lvlWidthIn)
if showLabels
label.new(x2, price, f_text(name, dateTs), xloc.bar_time, yloc.price, color(na), label.style_label_left, col, f_size(labelSizeIn))
ln
// ─────────────────────────────────────────────────────────────────────────────
// SESSION STATE
// ─────────────────────────────────────────────────────────────────────────────
string nySess = f_sess(nySessIn)
string nightSess = f_sess(nightSessIn)
string rangeSess = f_sess(rangeSessIn)
bool inNY = not na(time(timeframe.period, nySess, tzIn))
bool inNight = not na(time(timeframe.period, nightSess, tzIn))
bool inRange = not na(time(timeframe.period, rangeSess, tzIn))
bool newDay = dayofmonth(time, tzIn) != dayofmonth(time[1], tzIn)
var array<float> hArr = array.new<float>()
var array<float> lArr = array.new<float>()
var array<float> vArr = array.new<float>()
var bool nyActive = false
var bool pending = false
var int nyDate = na
var int pDate = na
var float pPOC = na
var float pVAH = na
var float pVAL = na
var int nightEndTs = na
var float rHi = na
var float rLo = na
var line rHiLine = na
var line rLoLine = na
var label rHiLbl = na
var label rLoLbl = na
var int rEndTs = na
var bool rDone = false
// Levels currently available to the FVG engine
var float aPOC = na
var float aVAH = na
var float aVAL = na
var int vaEndTs = na
// 1) NY session finished -> compute that day's POC / VAH / VAL
if nyActive and (not inNY or newDay)
[vaP, vaH, vaL] = f_valueArea(hArr, lArr, vArr, rowsIn, vaPctIn)
pPOC := vaP
pVAH := vaH
pVAL := vaL
pDate := nyDate
pending := not na(vaP)
nyActive := false
// 2) Collect bars during the NY session
if inNY and not nyActive
hArr.clear()
lArr.clear()
vArr.clear()
nyDate := time
nyActive := true
if inNY
hArr.push(high)
lArr.push(low)
vArr.push(nz(volume))
// 3) Overnight window opens (5pm) -> draw the most recent session's levels until 5am
// Friday's levels appear at Sunday's 5pm open, since that's the next overnight session.
if inNight and not inNight[1]
int sMin = f_clock(nightSessIn, 0)
int eMin = f_clock(nightSessIn, 5)
int dur = eMin - sMin <= 0 ? eMin - sMin + 1440 : eMin - sMin
int startTs = timestamp(tzIn, year(time, tzIn), month(time, tzIn), dayofmonth(time, tzIn), math.floor(sMin / 60), sMin % 60)
if startTs > time
startTs -= 86400000
nightEndTs := startTs + dur * 60000
if pending
line vahLn = f_drawLevel(showVAH, pVAH, startTs, nightEndTs, vahCol, "NY VAH", pDate)
line valLn = f_drawLevel(showVAL, pVAL, startTs, nightEndTs, valCol, "NY VAL", pDate)
f_drawLevel(showPOC, pPOC, startTs, nightEndTs, pocCol, "NY POC", pDate)
if showVAFill and not na(vahLn) and not na(valLn)
linefill.new(vahLn, valLn, vaFillCol)
aPOC := showPOC ? pPOC : na
aVAH := showVAH ? pVAH : na
aVAL := showVAL ? pVAL : na
vaEndTs := nightEndTs
pending := false
// 4) 6pm-7pm high/low, extended to the end of the overnight window (5am)
if inRange and not inRange[1]
rHi := high
rLo := low
int x1 = time
int x2 = not na(nightEndTs) and nightEndTs > time ? nightEndTs : time + 11 * 3600000
rEndTs := x2
rDone := false
string rs = f_style(rStyleIn)
rHiLine := showRH ? line.new(x1, rHi, x2, rHi, xloc.bar_time, extend.none, rhCol, rs, rWidthIn) : na
rLoLine := showRL ? line.new(x1, rLo, x2, rLo, xloc.bar_time, extend.none, rlCol, rs, rWidthIn) : na
rHiLbl := showRH and showLabels ? label.new(x2, rHi, f_text(rangeName + " High", time), xloc.bar_time, yloc.price, color(na), label.style_label_left, rhCol, f_size(labelSizeIn)) : na
rLoLbl := showRL and showLabels ? label.new(x2, rLo, f_text(rangeName + " Low", time), xloc.bar_time, yloc.price, color(na), label.style_label_left, rlCol, f_size(labelSizeIn)) : na
else if inRange
rHi := math.max(rHi, high)
rLo := math.min(rLo, low)
if not na(rHiLine)
rHiLine.set_y1(rHi)
rHiLine.set_y2(rHi)
if not na(rLoLine)
rLoLine.set_y1(rLo)
rLoLine.set_y2(rLo)
if not na(rHiLbl)
rHiLbl.set_y(rHi)
if not na(rLoLbl)
rLoLbl.set_y(rLo)
if not inRange and inRange[1]
rDone := true
// 5) Vertical "trading begins" line at 19:00 CT each day
int vTs = timestamp(tzIn, year(time, tzIn), month(time, tzIn), dayofmonth(time, tzIn), vHourIn, vMinIn)
var int tradeStartTs = na
if time >= vTs and time[1] < vTs
tradeStartTs := vTs
if showVLine
float yTop = high > low ? high : high + syminfo.mintick
line.new(vTs, low, vTs, yTop, xloc.bar_time, extend.both, vCol, f_style(vStyleIn), vWidthIn)
label.new(vTs, high, vTextIn, xloc.bar_time, yloc.abovebar, color(na), label.style_label_down, vCol, f_size(labelSizeIn))
// ─────────────────────────────────────────────────────────────────────────────
// 6) FVG CONTINUATION ENGINE (from TO Bounce Engine v13, engine B only)
// 1 price touches one of this indicator's levels
// 2 within N bars an FVG prints in the rejection direction
// 3 price returns and fills that gap
// 4 price closes back through the gap -> signal
// ─────────────────────────────────────────────────────────────────────────────
atrv = ta.atr(atrLen)
var int dayCount = 0
if timeframe.change("D")
dayCount += 1
cFvgBoxB = color.new(cFvgUp, fvgTr)
cFvgBoxS = color.new(cFvgDn, fvgTr)
// A level is only live while its line is on the chart
bool vaLive = not na(vaEndTs) and time < vaEndTs
bool rngLive = rDone and not na(rEndTs) and time < rEndTs
bool tradeLive = not na(tradeStartTs) and not na(nightEndTs) and time >= tradeStartTs and time < nightEndTs
bool armOK = not fvgAfterStart or tradeLive
type Touch
string nm
float top
float bot
int bar
type Setup
string nm
int dir
float gTop
float gBot
float confP
int fvgBar
int fillBar
int st
box bx
var map<string, int> lastTouchBar = map.new<string, int>()
var map<string, int> lastSigDay = map.new<string, int>()
var array<Touch> touches = array.new<Touch>()
var array<Setup> setups = array.new<Setup>()
addLvl(array<string> ns, array<float> ts, array<float> bs, bool use, string nm, float t, float b) =>
if use and not na(t) and not na(b)
ns.push(nm)
ts.push(t)
bs.push(b)
0
bool lffcBull = false
bool lffcBear = false
string lffcName = ""
if useFvgSig and barstate.isconfirmed and not na(atrv)
names = array.new<string>()
tops = array.new<float>()
bots = array.new<float>()
addLvl(names, tops, bots, fvgOnVA and vaLive, "NY POC", aPOC, aPOC)
addLvl(names, tops, bots, fvgOnVA and vaLive, "NY VAH", aVAH, aVAH)
addLvl(names, tops, bots, fvgOnVA and vaLive, "NY VAL", aVAL, aVAL)
addLvl(names, tops, bots, fvgOnRng and rngLive and showRH, rangeName + " High", rHi, rHi)
addLvl(names, tops, bots, fvgOnRng and rngLive and showRL, rangeName + " Low", rLo, rLo)
// ── STAGE 1: register touches ──
float ftol = fTolMode == "ATR" ? atrv * fTolAtr : fTolMode == "Ticks" ? syminfo.mintick * fTolTick : 0.0
if armOK and names.size() > 0
for i = 0 to names.size() - 1
nm = names.get(i)
tp = tops.get(i)
bt = bots.get(i)
if low - ftol <= tp and high + ftol >= bt
prev = lastTouchBar.contains(nm) ? lastTouchBar.get(nm) : -999999
if bar_index - prev >= fCool
lastTouchBar.put(nm, bar_index)
touches.push(Touch.new(nm, tp, bt, bar_index))
if touches.size() > 0
for i = touches.size() - 1 to 0
if bar_index - touches.get(i).bar > maxLag
touches.remove(i)
// ── STAGE 2: FVG on the 3-bar window ending here ──
disp = math.abs(close[1] - open[1])
bull = low > high[2] and (low - high[2]) >= atrv * minGap and disp >= atrv * minDisp and close[1] > open[1]
bear = high < low[2] and (low[2] - high) >= atrv * minGap and disp >= atrv * minDisp and close[1] < open[1]
if (bull or bear) and touches.size() > 0
int dir = bull ? 1 : -1
float gBot = bull ? high[2] : high
float gTop = bull ? low : low[2]
int best = -1
for i = touches.size() - 1 to 0
tch = touches.get(i)
lag = bar_index - tch.bar
okLag = lag >= minLag and lag <= maxLag
okRej = not needRej or (dir == 1 ? close > tch.top : close < tch.bot)
if okLag and okRej and best == -1
best := i
if best >= 0
tch = touches.get(best)
float cp = na
if confMode == "Close back through gap"
cp := dir == 1 ? gTop : gBot
else
cp := dir == 1 ? high : low
box bx = na
if showFvg
bx := box.new(bar_index - 2, gTop, bar_index, gBot,
border_width = 0, bgcolor = dir == 1 ? cFvgBoxB : cFvgBoxS)
setups.push(Setup.new(tch.nm, dir, gTop, gBot, cp, bar_index, 0, 0, bx))
touches.remove(best)
// ── STAGES 3 & 4: manage open setups ──
float invB = atrv * invBuf
if setups.size() > 0
for i = setups.size() - 1 to 0
su = setups.get(i)
mid = (su.gTop + su.gBot) / 2
bool dead = false
bool fired = false
float fillP = na
if fillMode == "Full (far edge)"
fillP := su.dir == 1 ? su.gBot : su.gTop
else if fillMode == "50% (CE)"
fillP := mid
else
fillP := su.dir == 1 ? su.gTop : su.gBot
blown = su.dir == 1 ? close < su.gBot - invB : close > su.gTop + invB
if su.st == 0
filled = su.dir == 1 ? low <= fillP : high >= fillP
if filled
su.st := 1
su.fillBar := bar_index
else if blown or bar_index - su.fvgBar > maxFill
dead := true
if su.st == 1 and not dead
conf = su.dir == 1 ? close > su.confP : close < su.confP
ok = not oneADay or not lastSigDay.contains(su.nm) or lastSigDay.get(su.nm) != dayCount
if conf and ok
fired := true
dead := true
lastSigDay.put(su.nm, dayCount)
if su.dir == 1
lffcBull := true
else
lffcBear := true
lffcName := su.nm
else if conf and not ok
dead := true
else if blown or bar_index - su.fillBar > maxConf
dead := true
if not na(su.bx) and not dead
su.bx.set_right(bar_index + 1)
if dead
if not na(su.bx)
if fired
su.bx.set_bgcolor(color.new(su.dir == 1 ? cFvgUp : cFvgDn, math.max(fvgTr - 20, 0)))
su.bx.set_right(bar_index)
else
box.delete(su.bx)
setups.remove(i)
// ─────────────────────────────────────────────────────────────────────────────
// 7) SIGNAL MARKERS & ALERTS
// ─────────────────────────────────────────────────────────────────────────────
plotshape(lffcBull, "FVG continuation long", shape.arrowup, location.belowbar, cFvgUp, size = size.small)
plotshape(lffcBear, "FVG continuation short", shape.arrowdown, location.abovebar, cFvgDn, size = size.small)
string sigNote = lffcBull or lffcBear ? "FVG continuation " + (lffcBull ? "long" : "short") + " off " + lffcName : ""
if showSigLb and (lffcBull or lffcBear)
label.new(bar_index, lffcBull ? low : high, "FVG" + (lffcBull ? "▲" : "▼"),
style = lffcBull ? label.style_label_up : label.style_label_down,
color = color.new(lffcBull ? color.green : color.red, 25),
textcolor = color.white, size = size.tiny, tooltip = sigNote)
alertcondition(lffcBull, "FVG continuation long", "Bullish level → FVG → fill → continuation confirmed")
alertcondition(lffcBear, "FVG continuation short", "Bearish level → FVG → fill → continuation confirmed")
alertcondition(lffcBull or lffcBear, "Any FVG continuation", "Level → FVG → fill → continuation confirmed")
if lffcBull or lffcBear
alert(syminfo.ticker + " " + timeframe.period + " " + sigNote, alert.freq_once_per_bar)
// ─────────────────────────────────────────────────────────────────────────────
// 8) LINE TOUCH ALERTS
// Fires on the first bar that touches a line after a bar that didn't,
// so a candle chopping around a level doesn't alert on every bar.
// Only lines currently on the chart count (VA 5pm-5am, 6-7pm range after 7pm).
// ─────────────────────────────────────────────────────────────────────────────
float tTol = syminfo.mintick * touchTicks
bool touchOK = not touchAfterStart or tradeLive
f_touch(float lvl) =>
not na(lvl) and low - tTol <= lvl and high + tTol >= lvl
bool onPOC = touchOK and vaLive and f_touch(aPOC)
bool onVAH = touchOK and vaLive and f_touch(aVAH)
bool onVAL = touchOK and vaLive and f_touch(aVAL)
bool onRH = touchOK and rngLive and showRH and f_touch(rHi)
bool onRL = touchOK and rngLive and showRL and f_touch(rLo)
bool touchPOC = onPOC and not onPOC[1]
bool touchVAH = onVAH and not onVAH[1]
bool touchVAL = onVAL and not onVAL[1]
bool touchRH = onRH and not onRH[1]
bool touchRL = onRL and not onRL[1]
bool touchAny = touchPOC or touchVAH or touchVAL or touchRH or touchRL
alertcondition(touchPOC, "Touch: NY POC", "{{ticker}} touched NY POC")
alertcondition(touchVAH, "Touch: NY VAH", "{{ticker}} touched NY VAH")
alertcondition(touchVAL, "Touch: NY VAL", "{{ticker}} touched NY VAL")
alertcondition(touchRH, "Touch: 6-7pm High", "{{ticker}} touched the 6-7pm high")
alertcondition(touchRL, "Touch: 6-7pm Low", "{{ticker}} touched the 6-7pm low")
alertcondition(touchAny, "Touch: any line", "{{ticker}} touched a FitMom line")
if touchAny
string tMsg = ""
if touchPOC
tMsg += "NY POC " + str.tostring(aPOC, format.mintick) + " "
if touchVAH
tMsg += "NY VAH " + str.tostring(aVAH, format.mintick) + " "
if touchVAL
tMsg += "NY VAL " + str.tostring(aVAL, format.mintick) + " "
if touchRH
tMsg += rangeName + " High " + str.tostring(rHi, format.mintick) + " "
if touchRL
tMsg += rangeName + " Low " + str.tostring(rLo, format.mintick) + " "
alert(syminfo.ticker + " " + timeframe.period + " touched: " + tMsg, alert.freq_once_per_bar)