0% found this document useful (0 votes)
1 views

volume

The document is a Pine Script code for a TradingView indicator called 'Volume Profile, Pivot Anchored'. It calculates and visualizes trading activity and potential price reversals based on volume profiles and pivot points. The script includes various functions for drawing lines, boxes, and labels, as well as inputs for customizing the indicator's appearance and behavior.

Uploaded by

Ali Thaer Mutlaq
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views

volume

The document is a Pine Script code for a TradingView indicator called 'Volume Profile, Pivot Anchored'. It calculates and visualizes trading activity and potential price reversals based on volume profiles and pivot points. The script includes various functions for drawing lines, boxes, and labels, as well as inputs for customizing the indicator's appearance and behavior.

Uploaded by

Ali Thaer Mutlaq
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 23

//@version=5

//
═══════════════════════════════════════════════════════════════════════════════════
═══════════════ //
//# *
═══════════════════════════════════════════════════════════════════════════════════
═══════════
//# *
//# * Study : Volume Profile, Pivot Anchored
//# * Author : © dgtrd
//# *
//# * Revision History
//# * Release : Jun 06, 2022 : Initial Release
//# * Update : Sep 19, 2022 : Alerts addition, Improved labels, Naked PoC
option, Bug correction
//# *
//# *
═══════════════════════════════════════════════════════════════════════════════════
═══════════
//
═══════════════════════════════════════════════════════════════════════════════════
═══════════════ //

//
-----------------------------------------------------------------------------------
----------- //
// Functions
-----------------------------------------------------------------------------------
//

f_drawOnlyLineX(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)

f_drawLineX(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
var id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)
line.set_xy1(id, _x1, _y1)
line.set_xy2(id, _x2, _y2)
line.set_color(id, _color)
id

f_drawOnlyBoxX(_left, _top, _right, _bottom, _border_color, _border_width,


_border_style) =>
box.new(_left, _top, _right, _bottom, _border_color, _border_width,
_border_style, bgcolor=_border_color)

f_drawOnlyLabelX(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size,


_textalign, _tooltip) =>
label.new(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size,
_textalign, _tooltip)

f_drawLabelX(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor, _size,


_textalign, _tooltip) =>
var id = label.new(_x, _y, _text, _xloc, _yloc, _color, _style, _textcolor,
_size, _textalign, _tooltip)
label.set_xy(id, _x, _y)
label.set_text(id, _text)
label.set_tooltip(id, _tooltip)

f_getHighLow(_len, _calc, _offset) =>


if _calc
htf_l = low [_offset]
htf_h = high[_offset]
vol = 0.

for x = 0 to _len - 1
htf_l := math.min(low [_offset + x], htf_l)
htf_h := math.max(high[_offset + x], htf_h)
vol += volume[_offset + x]

htf_l := math.min(low [_offset + _len], htf_l)


htf_h := math.max(high[_offset + _len], htf_h)

[htf_h, htf_l, vol]

// check for box breaches - code snippet from pine user guide
f_checkBreaches(arrayOfBoxes, extend) =>
int qtyOfBoxes = array.size(arrayOfBoxes)
for boxNo = 0 to (qtyOfBoxes > 0 ? qtyOfBoxes - 1 : na)
if boxNo < array.size(arrayOfBoxes)
box currentBox = array.get(arrayOfBoxes, boxNo)
float boxMidLevel = math.avg(box.get_bottom(currentBox),
box.get_top(currentBox))
bool boxWasCrossed = math.sign(close[1] - boxMidLevel) !=
math.sign(close - boxMidLevel)
bool boxWasTouched = math.sign(close[1] - boxMidLevel) !=
math.sign(low - boxMidLevel) or math.sign(close[1] - boxMidLevel) != math.sign(high
- boxMidLevel)

if boxWasCrossed and extend == 'Until Bar Cross'


array.remove(arrayOfBoxes, boxNo)
int(na)
else if boxWasTouched and extend == 'Until Bar Touch'
array.remove(arrayOfBoxes, boxNo)
int(na)
else
box.set_right(currentBox, bar_index)
int(na)

// Functions
-----------------------------------------------------------------------------------
//
//
-----------------------------------------------------------------------------------
----------- //

indicator("Volume Profile long and short ", "", true, max_bars_back = 5000,
max_boxes_count = 500)//, max_lines_count = 100, max_labels_count = 100)

//
-----------------------------------------------------------------------------------
----------- //
// Inputs
-----------------------------------------------------------------------------------
---- //

group_volume_profile = 'Pivot Points Volume Profile'


tooltip_pvt = 'The Pivot Points High Low indicator is used to determine and
anticipate potential changes in market price and reversals\n' +
'\'Volume Profile, Pivot Points Anchored\' Custom indicator
addtionally calculates the trading activity between two Pivot Points'
pvtLength = input.int(20, "Pivot Points Left/Right Length", minval=1, group
= group_volume_profile, tooltip = tooltip_pvt)

tooltip_vp = 'Common Interest Profile (Total Volume) - displays total


trading activity over a specified time period at specific price levels'
volumeProfile = input.bool(true, 'Volume Profile (Common Interest)'
, inline='BB3', group = group_volume_profile, tooltip = tooltip_vp)
totalVolumeColor = input.color(color.new(#fbc02d, 35), ''
, inline='BB3', group = group_volume_profile)
vaVolumeColor = input.color(color.new(#434651, 35), ''
, inline='BB3', group = group_volume_profile)

tooltip_va = 'Value Area (VA) – The range of price levels in which a


specified percentage of all volume was traded during the time period'
isValueArea = input.float(68, "Value Area Volume %", minval = 0, maxval = 100
, group = group_volume_profile, tooltip = tooltip_va) / 100

tooltip_poc = 'Point of Control (POC) - The price level for the time period
with the highest traded volume'
pointOfControl = input.bool(true, 'Point of Control (PoC)'
, inline='PoC', group = group_volume_profile, tooltip = tooltip_poc)
pocColor = input.color(color.new(#ff0000, 0), ''
, inline='PoC', group = group_volume_profile)
pocExtend = input.string('None', 'Extend Point of Control (PoC)',
options=['Until Last Bar', 'Until Bar Cross', 'Until Bar Touch', 'None'], group =
group_volume_profile)

tooltip_vah = 'Value Area High (VAH) - The highest price level within the
value area'
valueAreaHigh = input.bool(true, 'Value Area High (VAH)'
, inline='VAH', group = group_volume_profile, tooltip = tooltip_vah)
vahColor = input.color(color.new(#2962ff, 0), ''
, inline='VAH', group = group_volume_profile)

tooltip_val = 'Value Area Low (VAL) - The lowest price level within the value
area'
valueAreaLow = input.bool(true, 'Value Area Low (VAL) '
, inline='VAL', group = group_volume_profile, tooltip = tooltip_val)
valColor = input.color(color.new(#2962ff, 0), ''
, inline='VAL', group = group_volume_profile)

vaBackground = input.bool(true, 'Background Fill of Value Area (VA)'


, inline='vBG', group = group_volume_profile)
vaBackgroundColor = input.color(color.new(#2962ff, 89), ''
, inline='vBG', group = group_volume_profile)

levels = input.string('Pivot Points', 'Level Labels', options = ['Pivot


Points', 'Profile High/Low', 'Value Area High/Low'], group = group_volume_profile)
pvtPrice = input(true, "Price", inline = 'Levels',
group=group_volume_profile)
pvtChange = input(true, "Price Change", inline = 'Levels',
group=group_volume_profile)
pvtVolume = input(true, "Cumulative Volume", inline = 'Levels',
group=group_volume_profile)
profileLevels = input.int(25, 'Number of Rows' , minval = 10, maxval = 100 ,
step = 1 , group = group_volume_profile)
profilePlacement = input.string('Left', 'Placment', options = ['Right', 'Left']
, group = group_volume_profile)
profileWidth = input.int(30, 'Profile Width %', minval = 0, maxval = 100
, group = group_volume_profile) / 100
backgroundFill = input.bool(true, 'Background Fill of Profile Range'
, inline ='BG', group = group_volume_profile)
backgroundColor = input.color(color.new(#2962ff, 95), ''
, inline ='BG', group = group_volume_profile)

tooltip_vwcb = 'Colors bars based on the bar\'s volume relative to volume


moving average'
group_vwcb = 'Volume Weighted Colored Bars'
vwcb = input.bool(true, 'Volume Weighted Colored Bars',
group=group_vwcb, tooltip = tooltip_vwcb)

//
-----------------------------------------------------------------------------------
----------- //
// Definitions
----------------------------------------------------------------------------------
//

barPriceLow = low
barPriceHigh = high
bullCandle = close > open
nzVolume = nz(volume)

volumeStorageT = array.new_float(profileLevels + 1, 0.)

var a_poc = array.new_box()

var x1 = 0
var x2 = 0
var levelAbovePoc = 0
var levelBelowPoc = 0
var pvtHigh1 = 0.
var pvtLow1 = 0.
var pvtLast = ''

// Definitions
----------------------------------------------------------------------------------
//
//
-----------------------------------------------------------------------------------
----------- //
// Calculations
--------------------------------------------------------------------------------- /
/

pvtHigh = ta.pivothigh(pvtLength, pvtLength)


pvtLow = ta.pivotlow (pvtLength, pvtLength)
proceed = not na(pvtHigh) or not na(pvtLow)

if proceed
x1 := x2
x2 := bar_index
if not na(pvtHigh)
pvtHigh1 := pvtHigh
pvtLast := 'H'

if not na(pvtLow)
pvtLow1 := pvtLow
pvtLast := 'L'

profileLength = x2 - x1

[priceHighest, priceLowest, tradedVolume] = f_getHighLow(profileLength, proceed,


pvtLength)
priceStep = (priceHighest - priceLowest) / profileLevels

if proceed and nzVolume and priceStep > 0 and bar_index > profileLength and
profileLength > 0

for barIndexx = 1 to profileLength


level = 0
barIndex = barIndexx + pvtLength

for priceLevel = priceLowest to priceHighest by priceStep


if barPriceHigh[barIndex] >= priceLevel and barPriceLow[barIndex] <
priceLevel + priceStep
array.set(volumeStorageT, level, array.get(volumeStorageT, level) +
nzVolume[barIndex] * ((barPriceHigh[barIndex] - barPriceLow[barIndex]) == 0 ? 1 :
priceStep / (barPriceHigh[barIndex] - barPriceLow[barIndex])) )
level += 1

pocLevel = array.indexof(volumeStorageT, array.max(volumeStorageT))


totalVolumeTraded = array.sum(volumeStorageT) * isValueArea
valueArea = array.get(volumeStorageT, pocLevel)
levelAbovePoc := pocLevel
levelBelowPoc := pocLevel

while valueArea < totalVolumeTraded


if levelBelowPoc == 0 and levelAbovePoc == profileLevels - 1
break

volumeAbovePoc = 0.
if levelAbovePoc < profileLevels - 1
volumeAbovePoc := array.get(volumeStorageT, levelAbovePoc + 1)

volumeBelowPoc = 0.
if levelBelowPoc > 0
volumeBelowPoc := array.get(volumeStorageT, levelBelowPoc - 1)

if volumeBelowPoc == 0 and volumeAbovePoc == 0


break

if volumeAbovePoc >= volumeBelowPoc


valueArea += volumeAbovePoc
levelAbovePoc += 1
else
valueArea += volumeBelowPoc
levelBelowPoc -= 1

for level = 0 to profileLevels - 1


if volumeProfile
startBoxIndex = profilePlacement == 'Right' ? bar_index -
int(array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength *
profileWidth) : bar_index - profileLength
endBoxIndex = profilePlacement == 'Right' ? bar_index :
startBoxIndex + int( array.get(volumeStorageT, level) / array.max(volumeStorageT) *
profileLength * profileWidth)
f_drawOnlyBoxX(startBoxIndex - pvtLength, priceLowest + (level + 0.1) *
priceStep, endBoxIndex - pvtLength, priceLowest + (level + 0.9) * priceStep, level
>= levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor : vaVolumeColor, 1,
line.style_solid)

if backgroundFill
f_drawOnlyBoxX(bar_index[pvtLength] - profileLength, priceHighest,
bar_index[pvtLength], priceLowest, backgroundColor, 1, line.style_dotted)

if pointOfControl
array.push(a_poc, box.new(bar_index[pvtLength] - profileLength, priceLowest
+ (array.indexof(volumeStorageT, array.max(volumeStorageT)) + .40) * priceStep,
bar_index[pvtLength], priceLowest + (array.indexof(volumeStorageT,
array.max(volumeStorageT)) + .60) * priceStep, pocColor, bgcolor = pocColor ))

vah = f_drawOnlyLineX(bar_index[pvtLength] - profileLength, priceLowest +


(levelAbovePoc + 1.00) * priceStep, bar_index[pvtLength], priceLowest +
(levelAbovePoc + 1.00) * priceStep, xloc.bar_index, extend.none, valueAreaHigh ?
vahColor : #00000000, line.style_solid, 2)
val = f_drawOnlyLineX(bar_index[pvtLength] - profileLength, priceLowest +
(levelBelowPoc + 0.00) * priceStep, bar_index[pvtLength], priceLowest +
(levelBelowPoc + 0.00) * priceStep, xloc.bar_index, extend.none, valueAreaLow ?
valColor : #00000000, line.style_solid, 2)

if vaBackground
linefill.new(vah, val, vaBackgroundColor)

statTip = '\n -Traded Volume : ' + str.tostring(tradedVolume, format.volume) +


' (' + str.tostring(profileLength - 1) + ' bars)' +
'\n *Average Volume/Bar : ' + str.tostring(tradedVolume /
(profileLength - 1), format.volume) +
'\n\nProfile High : ' + str.tostring(priceHighest,
format.mintick) + ' ↑ %' + str.tostring((priceHighest - priceLowest) / priceLowest
* 100, '#.##') +
'\nProfile Low : ' + str.tostring(priceLowest, format.mintick)
+ ' ↓ %' + str.tostring((priceHighest - priceLowest) / priceHighest * 100, '#.##')
+
'\n -Point Of Control : ' + str.tostring(priceLowest +
(array.indexof(volumeStorageT, array.max(volumeStorageT)) + .50) * priceStep,
format.mintick) +
'\n\nValue Area High : ' + str.tostring(priceLowest +
(levelAbovePoc + 1.00) * priceStep, format.mintick) +
'\nValue Area Low : ' + str.tostring(priceLowest +
(levelBelowPoc + 0.00) * priceStep, format.mintick) +
'\n -Value Area Width : %' + str.tostring(((priceLowest +
(levelAbovePoc + 1.00) * priceStep) - (priceLowest + (levelBelowPoc + 0.00) *
priceStep)) / (priceHighest - priceLowest) * 100, '#.##') +
'\n\nNumber of Bars (Profile) : ' + str.tostring(profileLength)

if levels != 'Pivot Points'


upperPriceLevel = levels == 'Value Area High/Low' ? priceLowest +
(levelAbovePoc + 1.00) * priceStep : priceHighest
lowerPriceLevel = levels == 'Value Area High/Low' ? priceLowest +
(levelBelowPoc + 0.00) * priceStep : priceLowest

upperText = (pvtPrice ? str.tostring(upperPriceLevel, format.mintick) : '')


+ (not na(pvtHigh) ? (pvtChange ? (pvtPrice ? ' ↑ %' : '↑ %') +
str.tostring((pvtHigh - pvtLow1) * 100 / pvtLow1 , '#.##') : '') + (pvtVolume ?
(pvtPrice or pvtChange ? '\n' : '') + str.tostring(tradedVolume, format.volume) :
'') : '')
lowerText = (pvtPrice ? str.tostring(lowerPriceLevel, format.mintick) : '')
+ (not na(pvtLow) ? (pvtChange ? (pvtPrice ? ' ↓ %' : '↓ %') +
str.tostring((pvtHigh1 - pvtLow) * 100 / pvtHigh1, '#.##') : '') + (pvtVolume ?
(pvtPrice or pvtChange ? '\n' : '') + str.tostring(tradedVolume, format.volume) :
'') : '')

f_drawOnlyLabelX(bar_index[pvtLength] - profileLength / 2, upperPriceLevel,


upperText, xloc.bar_index, yloc.price, (upperText != '' ? chart.fg_color :
#00000000), label.style_label_down, chart.bg_color, size.normal, text.align_center,
'Profile High : ' + str.tostring(priceHighest, format.mintick) + '\n %' +
str.tostring((priceHighest - priceLowest) / priceLowest * 100, '#.##') + ' higher
than the Profile Low' + statTip )
f_drawOnlyLabelX(bar_index[pvtLength] - profileLength / 2, lowerPriceLevel,
lowerText, xloc.bar_index, yloc.price, (lowerText != '' ? chart.fg_color :
#00000000), label.style_label_up , chart.bg_color , size.normal,
text.align_center, 'Profile Low : ' + str.tostring(priceLowest, format.mintick) +
'\n %' + str.tostring((priceHighest - priceLowest) / priceHighest * 100, '#.##') +
' lower than the Profile High' + statTip )
else
if not na(pvtHigh)
f_drawOnlyLabelX(bar_index[pvtLength], pvtHigh, (pvtPrice ?
str.tostring(pvtHigh, format.mintick) : '') + (pvtChange ? (pvtPrice ? ' ↑ %' : '↑
%') + str.tostring((pvtHigh - pvtLow1) * 100 / pvtLow1 , '#.##') : '') + (pvtVolume
? (pvtPrice or pvtChange ? '\n' : '') + str.tostring(tradedVolume, format.volume) :
''), xloc.bar_index, yloc.price, chart.fg_color, label.style_label_down,
chart.bg_color, (not pvtPrice and not pvtChange and not pvtVolume ? size.tiny :
size.normal), text.align_center, 'Pivot High : ' + str.tostring(pvtHigh,
format.mintick) + '\n -Price Change : %' + str.tostring((pvtHigh - pvtLow1) * 100 /
pvtLow1 , '#.##') + statTip)
if not na(pvtLow)
f_drawOnlyLabelX(bar_index[pvtLength], pvtLow , (pvtPrice ?
str.tostring(pvtLow , format.mintick) : '') + (pvtChange ? (pvtPrice ? ' ↓ %' : '↓
%') + str.tostring((pvtHigh1 - pvtLow) * 100 / pvtHigh1, '#.##') : '') + (pvtVolume
? (pvtPrice or pvtChange ? '\n' : '') + str.tostring(tradedVolume, format.volume) :
''), xloc.bar_index, yloc.price, chart.fg_color, label.style_label_up ,
chart.bg_color, (not pvtPrice and not pvtChange and not pvtVolume ? size.tiny :
size.normal), text.align_center, 'Pivot Low : ' + str.tostring(pvtLow,
format.mintick) + '\n -Price Change : %' + str.tostring((pvtHigh1 - pvtLow) * 100 /
pvtHigh1, '#.##') + statTip)

if pointOfControl and pocExtend != 'None'


f_checkBreaches(a_poc, pocExtend)

var a_profileD = array.new_box()


profileLength := barstate.islast ? last_bar_index - x2 + pvtLength : 1
priceHighest := ta.highest(high, profileLength > 0 ? profileLength + 1 : 1)
priceLowest := ta.lowest (low , profileLength > 0 ? profileLength + 1 : 1)
priceStep := (priceHighest - priceLowest) / profileLevels
var pocLevel = 0

[_, _, tradedVolume1] = f_getHighLow(profileLength, true, 0)


if barstate.islast and nzVolume and profileLength > 0 and priceStep > 0

if array.size(a_profileD) > 0
for i = 0 to array.size(a_profileD) - 1
box.delete(array.shift(a_profileD))

for barIndex = 1 to profileLength


level = 0

for priceLevel = priceLowest to priceHighest by priceStep


if barPriceHigh[barIndex] >= priceLevel and barPriceLow[barIndex] <
priceLevel + priceStep
array.set(volumeStorageT, level, array.get(volumeStorageT, level) +
nzVolume[barIndex] * ((barPriceHigh[barIndex] - barPriceLow[barIndex]) == 0 ? 1 :
priceStep / (barPriceHigh[barIndex] - barPriceLow[barIndex])) )
level += 1

pocLevel := array.indexof(volumeStorageT, array.max(volumeStorageT))


totalVolumeTraded = array.sum(volumeStorageT) * isValueArea
valueArea = array.get(volumeStorageT, pocLevel)
levelAbovePoc := pocLevel
levelBelowPoc := pocLevel

while valueArea < totalVolumeTraded


if levelBelowPoc == 0 and levelAbovePoc == profileLevels - 1
break

volumeAbovePoc = 0.
if levelAbovePoc < profileLevels - 1
volumeAbovePoc := array.get(volumeStorageT, levelAbovePoc + 1)

volumeBelowPoc = 0.
if levelBelowPoc > 0
volumeBelowPoc := array.get(volumeStorageT, levelBelowPoc - 1)

if volumeBelowPoc == 0 and volumeAbovePoc == 0


break

if volumeAbovePoc >= volumeBelowPoc


valueArea += volumeAbovePoc
levelAbovePoc += 1
else
valueArea += volumeBelowPoc
levelBelowPoc -= 1

for level = 0 to profileLevels - 1


if volumeProfile
startBoxIndex = profilePlacement == 'Right' ? bar_index -
int(array.get(volumeStorageT, level) / array.max(volumeStorageT) * profileLength *
profileWidth) : bar_index - profileLength
endBoxIndex = profilePlacement == 'Right' ? bar_index :
startBoxIndex + int( array.get(volumeStorageT, level) / array.max(volumeStorageT) *
profileLength * profileWidth)
array.push(a_profileD, box.new(startBoxIndex, priceLowest + (level +
0.1) * priceStep, endBoxIndex, priceLowest + (level + 0.9) * priceStep, level >=
levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor : vaVolumeColor,
bgcolor = level >= levelBelowPoc and level <= levelAbovePoc ? totalVolumeColor :
vaVolumeColor ))

if backgroundFill
array.push(a_profileD, box.new(bar_index - profileLength, priceHighest,
bar_index, priceLowest, backgroundColor, bgcolor = backgroundColor ))

if pointOfControl
array.push(a_profileD, box.new(bar_index - profileLength, priceLowest +
(pocLevel + .40) * priceStep, bar_index, priceLowest + (pocLevel + .60) *
priceStep, pocColor, bgcolor = pocColor ))

vah = f_drawLineX(bar_index - profileLength, priceLowest + (levelAbovePoc +


1.00) * priceStep, bar_index, priceLowest + (levelAbovePoc + 1.00) * priceStep,
xloc.bar_index, extend.none, valueAreaHigh ? vahColor : #00000000,
line.style_solid, 2)
val = f_drawLineX(bar_index - profileLength, priceLowest + (levelBelowPoc +
0.00) * priceStep, bar_index, priceLowest + (levelBelowPoc + 0.00) * priceStep,
xloc.bar_index, extend.none, valueAreaLow ? valColor : #00000000,
line.style_solid, 2)

if vaBackground
linefill.new(vah, val, vaBackgroundColor)

if levels != 'Pivot Points'


statTip = '\n -Traded Volume : ' + str.tostring(tradedVolume1,
format.volume) + ' (' + str.tostring(profileLength - 1) + ' bars)' +
'\n *Average Volume/Bar : ' + str.tostring(tradedVolume1 /
(profileLength - 1), format.volume) +
'\n\nProfile High : ' + str.tostring(priceHighest,
format.mintick) + ' ↑ %' + str.tostring((priceHighest - priceLowest) / priceLowest
* 100, '#.##') +
'\nProfile Low : ' + str.tostring(priceLowest, format.mintick)
+ ' ↓ %' + str.tostring((priceHighest - priceLowest) / priceHighest * 100, '#.##')
+
'\n -Point Of Control : ' + str.tostring(priceLowest +
(array.indexof(volumeStorageT, array.max(volumeStorageT)) + .50) * priceStep,
format.mintick) +
'\n\nValue Area High : ' + str.tostring(priceLowest +
(levelAbovePoc + 1.00) * priceStep, format.mintick) +
'\nValue Area Low : ' + str.tostring(priceLowest +
(levelBelowPoc + 0.00) * priceStep, format.mintick) +
'\n -Value Area Width : %' + str.tostring(((priceLowest +
(levelAbovePoc + 1.00) * priceStep) - (priceLowest + (levelBelowPoc + 0.00) *
priceStep)) / (priceHighest - priceLowest) * 100, '#.##') +
'\n\nNumber of Bars (Profile) : ' + str.tostring(profileLength)
+
(pvtChange ? '\n\n*price change caculated based on last pivot
high/low and last price' : '')

upperPriceLevel = levels == 'Value Area High/Low' ? priceLowest +


(levelAbovePoc + 1.00) * priceStep : priceHighest
lowerPriceLevel = levels == 'Value Area High/Low' ? priceLowest +
(levelBelowPoc + 0.00) * priceStep : priceLowest

upperText = (pvtPrice ? str.tostring(upperPriceLevel, format.mintick) : '')


+ (pvtLast == 'L' ? (pvtChange ? (pvtPrice ? ' ↑ %' : '↑ %') + str.tostring((close
- pvtLow1) * 100 / pvtLow1 , '#.##') + '*' : '') + (pvtVolume ? (pvtPrice or
pvtChange ? '\n' : '') + str.tostring(tradedVolume1, format.volume) : '') : '')
lowerText = (pvtPrice ? str.tostring(lowerPriceLevel, format.mintick) : '')
+ (pvtLast == 'H' ? (pvtChange ? (pvtPrice ? ' ↓ %' : '↓ %') +
str.tostring((pvtHigh1 - close) * 100 / pvtHigh1, '#.##') + '*' : '') +
(pvtVolume ? (pvtPrice or pvtChange ? '\n' : '') + str.tostring(tradedVolume1,
format.volume) : '') : '')

f_drawLabelX(bar_index - profileLength / 2, upperPriceLevel, upperText,


xloc.bar_index, yloc.price, (upperText != '' ? chart.fg_color : #00000000),
label.style_label_down, chart.bg_color, size.normal, text.align_center, 'Profile
High : ' + str.tostring(priceHighest, format.mintick) + '\n %' +
str.tostring((priceHighest - priceLowest) / priceLowest * 100, '#.##') + ' higher
than the Profile Low' + statTip )
f_drawLabelX(bar_index - profileLength / 2, lowerPriceLevel, lowerText,
xloc.bar_index, yloc.price, (lowerText != '' ? chart.fg_color : #00000000),
label.style_label_up , chart.bg_color, size.normal, text.align_center, 'Profile
Low : ' + str.tostring(priceLowest, format.mintick) + '\n %' +
str.tostring((priceHighest - priceLowest) / priceHighest * 100, '#.##') + ' lower
than the Profile High' + statTip )

//plot(barstate.islast ? priceLowest + (array.indexof(volumeStorageT,


array.max(volumeStorageT)) + .50) * priceStep : na, 'Developing PoC', pocColor,
display=display.none)
//plot(barstate.islast ? priceLowest + (levelAbovePoc + 1.00) * priceStep : na,
'Developing VAH', vahColor, display=display.none)
//plot(barstate.islast ? priceLowest + (levelBelowPoc + 0.00) * priceStep : na,
'Developing VAL', valColor, display=display.none)
//
-----------------------------------------------------------------------------------
----------- //
// Volume Weighted Colored Bars
----------------------------------------------------------------- //

group_volume_weighted_colored_bars = 'Volume Weighted Colored Bars'


vSMA = ta.sma(nzVolume, input.int(89, 'Volume Moving Average Length',
group=group_volume_weighted_colored_bars))
upThesh = input.float(1.618, 'Bold Bars avbove Volume Average * ', minval=1.,
step=.1, group=group_volume_weighted_colored_bars)
barcolor(vwcb and nzVolume ? nzVolume > vSMA * upThesh ? open < close ? #006400 :
#910000 : nzVolume < vSMA * input.float(0.618, 'Light Bars below Volume Average *
', minval=.1, step=.1, group=group_volume_weighted_colored_bars) ? open < close ?
#7FFFD4 : #FF9800 : open < close ? color.green : color.red : na, title='Volume
Weighted Colored Bars', editable = false)

// Volume Weighted Colored Bars


----------------------------------------------------------------- //
//
-----------------------------------------------------------------------------------
----------- //
// Alerts
-----------------------------------------------------------------------------------
---- //

priceTxt = str.tostring(close, format.mintick)


tickerTxt = syminfo.ticker

if ta.cross(close, priceLowest + (pocLevel + .50) * priceStep) and pointOfControl


alert(tickerTxt + ' : Pivots Volume Profile : Price touches/crosses Point Of
Control Line, price ' + priceTxt)

if ta.cross(close, priceLowest + (levelAbovePoc + 1.00) * priceStep) and


valueAreaHigh
alert(tickerTxt + ' : Pivots Volume Profile : Price touches/crosses Value Area
High Line, price ' + priceTxt)

if ta.cross(close, priceLowest + (levelBelowPoc + 0.00) * priceStep) and


valueAreaLow
alert(tickerTxt + ' : Pivots Volume Profile : Price touches/crosses Value Area
Low Line, price ' + priceTxt)

if nzVolume > vSMA * upThesh and vwcb


alert(tickerTxt + ' high volume, price ' + priceTxt)

// Alerts
-----------------------------------------------------------------------------------
---- //
//
-----------------------------------------------------------------------------------
----------- //

var table logo = table.new(position.bottom_right, 1, 1)


if barstate.islast
table.cell(logo, 0, 0, ' ', text_size=size.normal, text_color=color.teal)

/////////////////////////////
//////////////////

///

// This source code is subject to the terms of the Mozilla Public License 2.0
at https://mozilla.org/MPL/2.0/

import jdehorty/MLExtensions/2 as ml
import jdehorty/KernelFunctions/2 as kernels

// ====================
// ==== Background ====
// ====================

// When using Machine Learning algorithms like K-Nearest Neighbors, choosing an


// appropriate distance metric is essential. Euclidean Distance is often used as
// the default distance metric, but it may not always be the best choice. This is
// because market data is often significantly impacted by proximity to significant
// world events such as FOMC Meetings and Black Swan events. These major economic
// events can contribute to a warping effect analogous a massive object's
// gravitational warping of Space-Time. In financial markets, this warping effect
// operates on a continuum, which can analogously be referred to as "Price-Time".

// To help to better account for this warping effect, Lorentzian Distance can be
// used as an alternative distance metric to Euclidean Distance. The geometry of
// Lorentzian Space can be difficult to visualize at first, and one of the best
// ways to intuitively understand it is through an example involving 2 feature
// dimensions (z=2). For purposes of this example, let's assume these two features
// are Relative Strength Index (RSI) and the Average Directional Index (ADX). In
// reality, the optimal number of features is in the range of 3-8, but for the sake
// of simplicity, we will use only 2 features in this example.
// Fundamental Assumptions:
// (1) We can calculate RSI and ADX for a given chart.
// (2) For simplicity, values for RSI and ADX are assumed to adhere to a Gaussian
// distribution in the range of 0 to 100.
// (3) The most recent RSI and ADX value can be considered the origin of a
coordinate
// system with ADX on the x-axis and RSI on the y-axis.

// Distances in Euclidean Space:


// Measuring the Euclidean Distances of historical values with the most recent
point
// at the origin will yield a distribution that resembles Figure 1 (below).

// [RSI]
// |
// |
// |
// ...:::....
// .:.:::••••••:::•::..
// .:•:.:•••::::••::••....::.
// ....:••••:••••••••::••:...:•.
// ...:.::::::•••:::•••:•••::.:•..
// ::•:.:•:•••••••:.:•::::::...:..
// |--------.:•••..•••••••:••:...:::•:•:..:..----------[ADX]
// 0 :•:....:•••••::.:::•••::••:.....
// ::....:.:••••••••:•••::••::..:.
// .:...:••:::••••••••::•••....:
// ::....:.....:•::•••:::::..
// ..:..::••..::::..:•:..
// .::..:::.....:
// |
// |
// |
// |
// _|_ 0
//
// Figure 1: Neighborhood in Euclidean Space

// Distances in Lorentzian Space:


// However, the same set of historical values measured using Lorentzian Distance
will
// yield a different distribution that resembles Figure 2 (below).

//
// [RSI]
// ::.. | ..:::
// ..... | ......
// .••••::. | :••••••.
// .:•••••:. | :::••••••.
// .•••••:... | .::.••••••.
// .::•••••::.. | :..••••••..
// .:•••••••::.........::••••••:..
// ..::::••••.•••••••.•••••••:.
// ...:•••••••.•••••••••::.
// .:..••.••••••.••••..
// |---------------.:•••••••••••••••••.---------------[ADX]
// 0 .:•:•••.••••••.•••••••.
// .••••••••••••••••••••••••:.
// .:••••••••••::..::.::••••••••:.
// .::••••••::. | .::•••:::.
// .:••••••.. | :••••••••.
// .:••••:... | ..•••••••:.
// ..:••::.. | :.•••••••.
// .:•.... | ...::.:••.
// ...:.. | :...:••.
// :::. | ..::
// _|_ 0
//
// Figure 2: Neighborhood in Lorentzian Space

// Observations:
// (1) In Lorentzian Space, the shortest distance between two points is not
// necessarily a straight line, but rather, a geodesic curve.
// (2) The warping effect of Lorentzian distance reduces the overall influence
// of outliers and noise.
// (3) Lorentzian Distance becomes increasingly different from Euclidean Distance
// as the number of nearest neighbors used for comparison increases.

// ======================
// ==== Custom Types ====
// ======================

// This section uses PineScript's new Type syntax to define important data
structures
// used throughout the script.

type Settings
float source
int neighborsCount
int maxBarsBack
int featureCount
int colorCompression
bool showExits
bool useDynamicExits

type Label
int long
int short
int neutral

type FeatureArrays
array<float> f1
array<float> f2
array<float> f3
array<float> f4
array<float> f5

type FeatureSeries
float f1
float f2
float f3
float f4
float f5

type MLModel
int firstBarIndex
array<int> trainingLabels
int loopSize
float lastDistance
array<float> distancesArray
array<int> predictionsArray
int prediction

type FilterSettings
bool useVolatilityFilter
bool useRegimeFilter
bool useAdxFilter
float regimeThreshold
int adxThreshold

type Filter
bool volatility
bool regime
bool adx

// ==========================
// ==== Helper Functions ====
// ==========================

series_from(feature_string, _close, _high, _low, _hlc3, f_paramA, f_paramB) =>


switch feature_string
"RSI" => ml.n_rsi(_close, f_paramA, f_paramB)
"WT" => ml.n_wt(_hlc3, f_paramA, f_paramB)
"CCI" => ml.n_cci(_close, f_paramA, f_paramB)
"ADX" => ml.n_adx(_high, _low, _close, f_paramA)

get_lorentzian_distance(int i, int featureCount, FeatureSeries featureSeries,


FeatureArrays featureArrays) =>
switch featureCount
5 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i))) +
math.log(1+math.abs(featureSeries.f4 - array.get(featureArrays.f4,
i))) +
math.log(1+math.abs(featureSeries.f5 - array.get(featureArrays.f5,
i)))
4 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i))) +
math.log(1+math.abs(featureSeries.f4 - array.get(featureArrays.f4,
i)))
3 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i))) +
math.log(1+math.abs(featureSeries.f3 - array.get(featureArrays.f3,
i)))
2 => math.log(1+math.abs(featureSeries.f1 - array.get(featureArrays.f1,
i))) +
math.log(1+math.abs(featureSeries.f2 - array.get(featureArrays.f2,
i)))

// ================
// ==== Inputs ====
// ================

// Settings Object: General User-Defined Inputs


Settings settings =
Settings.new(
input.source(title='Source', defval=close, group="General Settings",
tooltip="Source of the input data"),
input.int(title='Neighbors Count', defval=8, group="General Settings", minval=1,
maxval=100, step=1, tooltip="Number of neighbors to consider"),
input.int(title="Max Bars Back", defval=2000, group="General Settings"),
input.int(title="Feature Count", defval=5, group="Feature Engineering",
minval=2, maxval=5, tooltip="Number of features to use for ML predictions."),
input.int(title="Color Compression", defval=1, group="General Settings",
minval=1, maxval=10, tooltip="Compression factor for adjusting the intensity of the
color scale."),
input.bool(title="Show Default Exits", defval=false, group="General Settings",
tooltip="Default exits occur exactly 4 bars after an entry signal. This corresponds
to the predefined length of a trade during the model's training process.",
inline="exits"),
input.bool(title="Use Dynamic Exits", defval=false, group="General Settings",
tooltip="Dynamic exits attempt to let profits ride by dynamically adjusting the
exit threshold based on kernel regression logic.", inline="exits")
)

// Trade Stats Settings


// Note: The trade stats section is NOT intended to be used as a replacement for
proper backtesting. It is intended to be used for calibration purposes only.
showTradeStats = input.bool(true, 'Show Trade Stats', tooltip='Displays the trade
stats for a given configuration. Useful for optimizing the settings in the Feature
Engineering section. This should NOT replace backtesting and should be used for
calibration purposes only. Early Signal Flips represent instances where the model
changes signals before 4 bars elapses; high values can indicate choppy (ranging)
market conditions.', group="General Settings")
useWorstCase = input.bool(false, "Use Worst Case Estimates", tooltip="Whether to
use the worst case scenario for backtesting. This option can be useful for creating
a conservative estimate that is based on close prices only, thus avoiding the
effects of intrabar repainting. This option assumes that the user does not enter
when the signal first appears and instead waits for the bar to close as
confirmation. On larger timeframes, this can mean entering after a large move has
already occurred. Leaving this option disabled is generally better for those that
use this indicator as a source of confluence and prefer estimates that demonstrate
discretionary mid-bar entries. Leaving this option enabled may be more consistent
with traditional backtesting results.", group="General Settings")

// Settings object for user-defined settings


FilterSettings filterSettings =
FilterSettings.new(
input.bool(title="Use Volatility Filter", defval=true, tooltip="Whether to use
the volatility filter.", group="Filters"),
input.bool(title="Use Regime Filter", defval=true, group="Filters",
inline="regime"),
input.bool(title="Use ADX Filter", defval=false, group="Filters", inline="adx"),
input.float(title="Threshold", defval=-0.1, minval=-10, maxval=10, step=0.1,
tooltip="Whether to use the trend detection filter. Threshold for detecting
Trending/Ranging markets.", group="Filters", inline="regime"),
input.int(title="Threshold", defval=20, minval=0, maxval=100, step=1,
tooltip="Whether to use the ADX filter. Threshold for detecting Trending/Ranging
markets.", group="Filters", inline="adx")
)

// Filter object for filtering the ML predictions


Filter filter =
Filter.new(
ml.filter_volatility(1, 10, filterSettings.useVolatilityFilter),
ml.regime_filter(ohlc4, filterSettings.regimeThreshold,
filterSettings.useRegimeFilter),
ml.filter_adx(settings.source, 14, filterSettings.adxThreshold,
filterSettings.useAdxFilter)
)

// Feature Variables: User-Defined Inputs for calculating Feature Series.


f1_string = input.string(title=" #", options=["RSI", "WT", "CCI", "ADX"],
defval="RSI", inline = "01", tooltip="", group="")
f1_paramA = input.int(title=" #", tooltip="The primary parameter of feature 1.",
defval=14, inline = "02", group="")
f1_paramB = input.int(title=" #", tooltip="The secondary parameter of feature 2 (if
applicable).", defval=1, inline = "02", group="")
f2_string = input.string(title=" #", options=["RSI", "WT", "CCI", "ADX"],
defval="WT", inline = "03", tooltip="", group="")
f2_paramA = input.int(title=" #", tooltip="The primary parameter of feature 2.",
defval=10, inline = "04", group="")
f2_paramB = input.int(title=" #", tooltip="The secondary parameter of feature 2 (if
applicable).", defval=11, inline = "04", group="")
f3_string = input.string(title=" #", options=["RSI", "WT", "CCI", "ADX"],
defval="CCI", inline = "05", tooltip="", group="")
f3_paramA = input.int(title=" #", tooltip="The primary parameter of feature 3.",
defval=20, inline = "06", group="")
f3_paramB = input.int(title=" #", tooltip="The secondary parameter of feature 3 (if
applicable).", defval=1, inline = "06", group="")
f4_string = input.string(title=" #", options=["RSI", "WT", "CCI", "ADX"],
defval="ADX", inline = "07", tooltip="", group="")
f4_paramA = input.int(title=" #", tooltip="The primary parameter of feature 4.",
defval=20, inline = "08", group="")
f4_paramB = input.int(title=" #", tooltip="The secondary parameter of feature 4 (if
applicable).", defval=2, inline = "08", group="")
f5_string = input.string(title=" #", options=["RSI", "WT", "CCI", "ADX"],
defval="RSI", inline = "09", tooltip="", group="")
f5_paramA = input.int(title=" #", tooltip="The primary parameter of feature 5.",
defval=9, inline = "10", group="")
f5_paramB = input.int(title=" #", tooltip=".", defval=1, inline = "10", group="")

// FeatureSeries Object: Calculated Feature Series based on Feature Variables


featureSeries =
FeatureSeries.new(
series_from(f1_string, close, high, low, hlc3, f1_paramA, f1_paramB), // f1
series_from(f2_string, close, high, low, hlc3, f2_paramA, f2_paramB), // f2
series_from(f3_string, close, high, low, hlc3, f3_paramA, f3_paramB), // f3
series_from(f4_string, close, high, low, hlc3, f4_paramA, f4_paramB), // f4
series_from(f5_string, close, high, low, hlc3, f5_paramA, f5_paramB) // f5
)

// FeatureArrays Variables: Storage of Feature Series as Feature Arrays Optimized


for ML
// Note: These arrays cannot be dynamically created within the FeatureArrays Object
Initialization and thus must be set-up in advance.
var f1Array = array.new_float()
var f2Array = array.new_float()
var f3Array = array.new_float()
var f4Array = array.new_float()
var f5Array = array.new_float()
array.push(f1Array, featureSeries.f1)
array.push(f2Array, featureSeries.f2)
array.push(f3Array, featureSeries.f3)
array.push(f4Array, featureSeries.f4)
array.push(f5Array, featureSeries.f5)

// FeatureArrays Object: Storage of the calculated FeatureArrays into a single


object
featureArrays =
FeatureArrays.new(
f1Array, // f1
f2Array, // f2
f3Array, // f3
f4Array, // f4
f5Array // f5
)

// Label Object: Used for classifying historical data as training data for the ML
Model
Label direction =
Label.new(
long=1,
short=-1,
neutral=0
)

// Derived from General Settings


maxBarsBackIndex = last_bar_index >= settings.maxBarsBack ? last_bar_index -
settings.maxBarsBack : 0

// EMA Settings
useEmaFilter = input.bool(title="@@", defval=false, group="@@", inline="@@")
emaPeriod = input.int(title="@@", defval=200, minval=1, step=1, group="@@",
inline="@@", tooltip="@@")
isEmaUptrend = useEmaFilter ? close > ta.ema(close, emaPeriod) : true
isEmaDowntrend = useEmaFilter ? close < ta.ema(close, emaPeriod) : true
useSmaFilter = input.bool(title="@@", defval=false, group="@@", inline="@@")
smaPeriod = input.int(title="@@", defval=200, minval=1, step=1, group="@@",
inline="@@", tooltip="@@")
isSmaUptrend = useSmaFilter ? close > ta.sma(close, smaPeriod) : true
isSmaDowntrend = useSmaFilter ? close < ta.sma(close, smaPeriod) : true

// Nadaraya-Watson Kernel Regression Settings


useKernelFilter = input.bool(true, "@@", group="@@", inline="@@")
showKernelEstimate = input.bool(true, "@@", group="@@", inline="@@")
useKernelSmoothing = input.bool(false, "Enhance Kernel Smoothing", tooltip="##",
inline='1', group='##')
h = input.int(8, '@@', minval=3, tooltip='##', group="##", inline="##")
r = input.float(8., '##', step=0.25, tooltip='@@', group="@@", inline="@@")
x = input.int(25, "##", tooltip='@@', group="@@", inline="@@")
lag = input.int(2, "Lag", tooltip="@@", inline='1', group='@@')
// Display Settings
showBarColors = input.bool(true, "@@", tooltip="@@", group="@@")
showBarPredictions = input.bool(defval = true, title = "@@", tooltip = "@@.",
group="@@")
useAtrOffset = input.bool(defval = false, title = "@@", tooltip = "@@.",
group="@@")
barPredictionsOffset = input.float(0, "@@", minval=0, tooltip="@@", group="@@")

// =================================
// ==== Next Bar Classification ====
// =================================

// This model specializes specifically in predicting the direction of price action


over the course of the next 4 bars.
// To avoid complications with the ML model, this value is hardcoded to 4 bars but
support for other training lengths may be added in the future.
src = settings.source
y_train_series = src[4] < src[0] ? direction.short : src[4] > src[0] ?
direction.long : direction.neutral
var y_train_array = array.new_int(0)

// Variables used for ML Logic


var predictions = array.new_float(0)
var prediction = 0.
var signal = direction.neutral
var distances = array.new_float(0)

array.push(y_train_array, y_train_series)

// =========================
// ==== Core ML Logic ====
// =========================

// Approximate Nearest Neighbors Search with Lorentzian Distance:


// A novel variation of the Nearest Neighbors (NN) search algorithm that ensures a
chronologically uniform distribution of neighbors.

// In a traditional KNN-based approach, we would iterate through the entire dataset


and calculate the distance between the current bar
// and every other bar in the dataset and then sort the distances in ascending
order. We would then take the first k bars and use their
// labels to determine the label of the current bar.

// There are several problems with this traditional KNN approach in the context of
real-time calculations involving time series data:
// - It is computationally expensive to iterate through the entire dataset and
calculate the distance between every historical bar and
// the current bar.
// - Market time series data is often non-stationary, meaning that the statistical
properties of the data change slightly over time.
// - It is possible that the nearest neighbors are not the most informative ones,
and the KNN algorithm may return poor results if the
// nearest neighbors are not representative of the majority of the data.

// Previously, the user @capissimo attempted to address some of these issues in


several of his PineScript-based KNN implementations by:
// - Using a modified KNN algorithm based on consecutive furthest neighbors to find
a set of approximate "nearest" neighbors.
// - Using a sliding window approach to only calculate the distance between the
current bar and the most recent n bars in the dataset.

// Of these two approaches, the latter is inherently limited by the fact that it
only considers the most recent bars in the overall dataset.

// The former approach has more potential to leverage historical price action, but
is limited by:
// - The possibility of a sudden "max" value throwing off the estimation
// - The possibility of selecting a set of approximate neighbors that are not
representative of the majority of the data by oversampling
// values that are not chronologically distinct enough from one another
// - The possibility of selecting too many "far" neighbors, which may result in a
poor estimation of price action

// To address these issues, a novel Approximate Nearest Neighbors (ANN) algorithm


is used in this indicator.

// In the below ANN algorithm:


// 1. The algorithm iterates through the dataset in chronological order, using the
modulo operator to only perform calculations every 4 bars.
// This serves the dual purpose of reducing the computational overhead of the
algorithm and ensuring a minimum chronological spacing
// between the neighbors of at least 4 bars.
// 2. A list of the k-similar neighbors is simultaneously maintained in both a
predictions array and corresponding distances array.
// 3. When the size of the predictions array exceeds the desired number of nearest
neighbors specified in settings.neighborsCount,
// the algorithm removes the first neighbor from the predictions array and the
corresponding distance array.
// 4. The lastDistance variable is overriden to be a distance in the lower 25% of
the array. This step helps to boost overall accuracy
// by ensuring subsequent newly added distance values increase at a slower rate.
// 5. Lorentzian distance is used as a distance metric in order to minimize the
effect of outliers and take into account the warping of
// "price-time" due to proximity to significant economic events.

lastDistance = -1.0
size = math.min(settings.maxBarsBack-1, array.size(y_train_array)-1)
sizeLoop = math.min(settings.maxBarsBack-1, size)

if bar_index >= maxBarsBackIndex //{


for i = 0 to sizeLoop //{
d = get_lorentzian_distance(i, settings.featureCount, featureSeries,
featureArrays)
if d >= lastDistance and i%4 //{
lastDistance := d
array.push(distances, d)
array.push(predictions, math.round(array.get(y_train_array, i)))
if array.size(predictions) > settings.neighborsCount //{
lastDistance := array.get(distances,
math.round(settings.neighborsCount*3/4))
array.shift(distances)
array.shift(predictions)
//}
//}
//}
prediction := array.sum(predictions)
//}
// ============================
// ==== Prediction Filters ====
// ============================

// User Defined Filters: Used for adjusting the frequency of the ML Model's
predictions
filter_all = filter.volatility and filter.regime and filter.adx

// Filtered Signal: The model's prediction of future price movement direction with
user-defined filters applied
signal := prediction > 0 and filter_all ? direction.long : prediction < 0 and
filter_all ? direction.short : nz(signal[1])

// Bar-Count Filters: Represents strict filters based on a pre-defined holding


period of 4 bars
var int barsHeld = 0
barsHeld := ta.change(signal) ? 0 : barsHeld + 1
isHeldFourBars = barsHeld == 4
isHeldLessThanFourBars = 0 < barsHeld and barsHeld < 4

// Fractal Filters: Derived from relative appearances of signals in a given time


series fractal/segment with a default length of 4 bars
isDifferentSignalType = ta.change(signal)
isEarlySignalFlip = ta.change(signal) and (ta.change(signal[1]) or
ta.change(signal[2]) or ta.change(signal[3]))
isBuySignal = signal == direction.long and isEmaUptrend and isSmaUptrend
isSellSignal = signal == direction.short and isEmaDowntrend and isSmaDowntrend
isLastSignalBuy = signal[4] == direction.long and isEmaUptrend[4] and
isSmaUptrend[4]
isLastSignalSell = signal[4] == direction.short and isEmaDowntrend[4] and
isSmaDowntrend[4]
isNewBuySignal = isBuySignal and isDifferentSignalType
isNewSellSignal = isSellSignal and isDifferentSignalType

// Kernel Regression Filters: Filters based on Nadaraya-Watson Kernel Regression


using the Rational Quadratic Kernel
// For more information on this technique refer to my other open source indicator
located here:
// https://www.tradingview.com/script/AWNvbPRM-Nadaraya-Watson-Rational-Quadratic-
Kernel-Non-Repainting/
c_green = color.new(#009988, 20)
c_red = color.new(#CC3311, 20)
transparent = color.new(#000000, 100)
yhat1 = kernels.rationalQuadratic(settings.source, h, r, x)
yhat2 = kernels.gaussian(settings.source, h-lag, x)
kernelEstimate = yhat1
// Kernel Rates of Change
bool wasBearishRate = yhat1[2] > yhat1[1]
bool wasBullishRate = yhat1[2] < yhat1[1]
bool isBearishRate = yhat1[1] > yhat1
bool isBullishRate = yhat1[1] < yhat1
isBearishChange = isBearishRate and wasBullishRate
isBullishChange = isBullishRate and wasBearishRate
// Kernel Crossovers
bool isBullishCrossAlert = ta.crossover(yhat2, yhat1)
bool isBearishCrossAlert = ta.crossunder(yhat2, yhat1)
bool isBullishSmooth = yhat2 >= yhat1
bool isBearishSmooth = yhat2 <= yhat1
// Kernel Colors
color colorByCross = isBullishSmooth ? c_green : c_red
color colorByRate = isBullishRate ? c_green : c_red
color plotColor = showKernelEstimate ? (useKernelSmoothing ? colorByCross :
colorByRate) : transparent
plot(kernelEstimate, color=plotColor, linewidth=2, title="@@")
// Alert Variables
bool alertBullish = useKernelSmoothing ? isBullishCrossAlert : isBullishChange
bool alertBearish = useKernelSmoothing ? isBearishCrossAlert : isBearishChange
// Bullish and Bearish Filters based on Kernel
isBullish = useKernelFilter ? (useKernelSmoothing ? isBullishSmooth :
isBullishRate) : true
isBearish = useKernelFilter ? (useKernelSmoothing ? isBearishSmooth :
isBearishRate) : true

// ===========================
// ==== Entries and Exits ====
// ===========================

// Entry Conditions: Booleans for ML Model Position Entries


startLongTrade = isNewBuySignal and isBullish and isEmaUptrend and isSmaUptrend
startShortTrade = isNewSellSignal and isBearish and isEmaDowntrend and
isSmaDowntrend

// Dynamic Exit Conditions: Booleans for ML Model Position Exits based on Fractal
Filters and Kernel Regression Filters
lastSignalWasBullish = ta.barssince(startLongTrade) < ta.barssince(startShortTrade)
lastSignalWasBearish = ta.barssince(startShortTrade) < ta.barssince(startLongTrade)
barsSinceRedEntry = ta.barssince(startShortTrade)
barsSinceRedExit = ta.barssince(alertBullish)
barsSinceGreenEntry = ta.barssince(startLongTrade)
barsSinceGreenExit = ta.barssince(alertBearish)
isValidShortExit = barsSinceRedExit > barsSinceRedEntry
isValidLongExit = barsSinceGreenExit > barsSinceGreenEntry
endLongTradeDynamic = (isBearishChange and isValidLongExit[1])
endShortTradeDynamic = (isBullishChange and isValidShortExit[1])

// Fixed Exit Conditions: Booleans for ML Model Position Exits based on a Bar-Count
Filters
endLongTradeStrict = ((isHeldFourBars and isLastSignalBuy) or
(isHeldLessThanFourBars and isNewSellSignal and isLastSignalBuy)) and
startLongTrade[4]
endShortTradeStrict = ((isHeldFourBars and isLastSignalSell) or
(isHeldLessThanFourBars and isNewBuySignal and isLastSignalSell)) and
startShortTrade[4]
isDynamicExitValid = not useEmaFilter and not useSmaFilter and not
useKernelSmoothing
endLongTrade = settings.useDynamicExits and isDynamicExitValid ?
endLongTradeDynamic : endLongTradeStrict
endShortTrade = settings.useDynamicExits and isDynamicExitValid ?
endShortTradeDynamic : endShortTradeStrict

// =========================
// ==== Plotting Labels ====
// =========================

// Note: These will not repaint once the most recent bar has fully closed. By
default, signals appear over the last closed bar; to override this behavior set
offset=0.
plotshape(startLongTrade ? low : na, '##', shape.labelup, location.belowbar,
color=ml.color_green(prediction), size=size.small, offset=0)
plotshape(startShortTrade ? high : na, '###', shape.labeldown, location.abovebar,
ml.color_red(-prediction), size=size.small, offset=0)
plotshape(endLongTrade and settings.showExits ? high : na, '##', shape.xcross,
location.absolute, color=#3AFF17, size=size.tiny, offset=0)
plotshape(endShortTrade and settings.showExits ? low : na, '##', shape.xcross,
location.absolute, color=#FD1707, size=size.tiny, offset=0)

// ================
// ==== Alerts ====
// ================

// Separate Alerts for Entries and Exits


alertcondition(startLongTrade, title='## ▲', message='# ▲ ')
alertcondition(endLongTrade, title=' ▲', message='')
alertcondition(startShortTrade, title=' ▼', message='')
alertcondition(endShortTrade, title=' ▼', message='')

// Combined Alerts for Entries and Exits


alertcondition(startShortTrade or startLongTrade, title='O', message='')
alertcondition(endShortTrade or endLongTrade, title='C', message='')

// Kernel Estimate Alerts


alertcondition(condition=alertBullish, title='', message='')
alertcondition(condition=alertBearish, title='', message='')

// =========================
// ==== Display Signals ====
// =========================

atrSpaced = useAtrOffset ? ta.atr(1) : na


compressionFactor = settings.neighborsCount / settings.colorCompression
c_pred = prediction > 0 ? color.from_gradient(prediction, 0, compressionFactor,
#787b86, #009988) : prediction <= 0 ? color.from_gradient(prediction, -
compressionFactor, 0, #CC3311, #787b86) : na
c_label = showBarPredictions ? c_pred : na
c_bars = showBarColors ? color.new(c_pred, 50) : na
x_val = bar_index
y_val = useAtrOffset ? prediction > 0 ? high + atrSpaced: low - atrSpaced :
prediction > 0 ? high + hl2*barPredictionsOffset/20 : low -
hl2*barPredictionsOffset/30
//label.new(x_val, y_val, str.tostring(prediction), xloc.bar_index, yloc.price,
color.new(color.white, 100), label.style_label_up, c_label, size.normal,
text.align_left)
barcolor(showBarColors ? color.new(c_pred, 50) : na)

// =====================
// ==== Backtesting ====
// =====================

// The following can be used to stream signals to a backtest adapter


backTestStream = switch
startLongTrade => 1
endLongTrade => 2
startShortTrade => -1
endShortTrade => -2
plot(backTestStream, "@@@", display=display.none)

// The following can be used to display real-time trade stats. This can be a useful
mechanism for obtaining real-time feedback during Feature Engineering. This does
NOT replace the need to properly backtest.
// Note: In this context, a "Stop-Loss" is defined instances where the ML Signal
prematurely flips directions before an exit signal can be generated.
[totalWins, totalLosses, totalEarlySignalFlips, totalTrades, tradeStatsHeader,
winLossRatio, winRate] = ml.backtest(high, low, open, startLongTrade, endLongTrade,
startShortTrade, endShortTrade, isEarlySignalFlip, maxBarsBackIndex, bar_index,
settings.source, useWorstCase)

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy