// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.
0
at [Link]
// © fluxchart
//@version=5
const bool DEBUG = false
const int maxBoxesCount = 500
const float overlapThresholdPercentage = 0.0
int maxDistanceToLastBar = 1250 // Affects Running Time
const int maxSDZones = 30
const int minZoneSize = 10
const int RETEST_COOLDOWN = 5
const int minDistanceBetweenZones = 5
const float maxZoneSizeATR = 1.5
indicator(title = 'Supply & Demand (MTF) | Flux Charts', shorttitle = "Supply and
Demand (MTF) | Flux Charts", overlay = true, max_boxes_count = maxBoxesCount,
max_labels_count = maxBoxesCount, max_lines_count = maxBoxesCount, max_bars_back =
2000, dynamic_requests = true)
maxDistanceString = [Link]("Normal", "Max Distance To Last Bar", options =
["High", "Normal", "Low"], group = "General Configuration", display =
[Link])
sdEndMethod = [Link]("Close", "Zone Invalidation", options = ["Wick",
"Close"], group = "General Configuration", display = [Link])
combineSDs = DEBUG ? [Link](true, "Combine Zones", group = "General
Configuration", display = [Link]) : true
momentumBodyMult = DEBUG ? [Link](0.5, "Momentum Body Mult", step = 0.1, group
= "General Configuration") : 0.5
momentumCount = DEBUG ? [Link](4,"Momentum Count", group = "General
Configuration") : 4
momentumSpan = DEBUG ? [Link](4, "Momentum Span", group = "General
Configuration") : 4
//zoneCount = [Link]("High", 'Zone Count', options = ["High", "Medium",
"Low", "One"], tooltip = "Number of S&D Zones to be rendered. Higher options will
result in older S&Ds shown.", group = "General Configuration", display =
[Link])
zoneCount = "High"
retestsEnabled = [Link](true, "Retests", inline = "rb", group = "General
Configuration", display = [Link])
breaksEnabled = [Link](false, "Breaks", inline = "rb", group = "General
Configuration", display = [Link])
showInvalidated = [Link](true, "Show Historic Zones", group = "General
Configuration", display = [Link])
bullSDZoneColor = input(#08998180, 'Demand', inline = 'sdColor', group = 'General
Configuration', display = [Link])
bearSDZoneColor = input(#f2364680, 'Supply', inline = 'sdColor', group = 'General
Configuration', display = [Link])
demandZones = zoneCount == "One" ? 1 : zoneCount == "Low" ? 3 : zoneCount ==
"Medium" ? 5 : 30
supplyZones = zoneCount == "One" ? 1 : zoneCount == "Low" ? 3 : zoneCount ==
"Medium" ? 5 : 30
timeframe1Enabled = [Link](true, title = "", group = "Timeframes", inline =
"timeframe1", display = [Link])
timeframe1 = [Link]("", title = "", group = "Timeframes", inline =
"timeframe1", display = [Link])
timeframe2Enabled = [Link](false, title = "", group = "Timeframes", inline =
"timeframe2", display = [Link])
timeframe2 = [Link]("15", title = "", group = "Timeframes", inline =
"timeframe2", display = [Link])
timeframe3Enabled = [Link](false, title = "", group = "Timeframes", inline =
"timeframe3", display = [Link])
timeframe3 = [Link]("30", title = "", group = "Timeframes", inline =
"timeframe3", display = [Link])
textColor = [Link](#ffffffcc, "Text Color", group = "Style")
labelsAtSameLevel = DEBUG ? [Link](true, "[DBG] Place Labels At Same Level",
group = "Style") : true
labelsAtSameLevelBreak = false
atr = [Link](20)
averageBodySize = [Link]([Link](close - open), 20)
maxDistanceToLastBar := maxDistanceString == "Low" ? 150 : maxDistanceString ==
"Normal" ? 500 : 1250
type sdZoneInfo
float top
float bottom
string sdType
int startTime
int breakTime
int guid
string timeframeStr
bool disabled = false
string combinedTimeframesStr = na
bool combined = false
type sdZone
sdZoneInfo info
bool isRendered = false
box sdBox = na
line sdBoxLineTop = na
line sdBoxLineMiddle = na
line sdBoxLineBottom = na
//
box sdBoxText = na
type retestLabelContainer
int guid
array<label> labels
createSDZone (sdZoneInfo sdZoneInfoF) =>
sdZone newSDZone = [Link](sdZoneInfoF)
newSDZone
safeDeleteSDZone (sdZone sdZoneF) =>
[Link] := false
[Link]([Link])
[Link]([Link])
[Link]([Link])
[Link]([Link])
[Link]([Link])
type timeframeInfo
int index = na
string timeframeStr = na
bool isEnabled = false
sdZoneInfo[] demandZonesList = na
sdZoneInfo[] supplyZonesList = na
newTimeframeInfo (index, timeframeStr, isEnabled) =>
newTFInfo = [Link]()
[Link] := index
[Link] := isEnabled
[Link] := timeframeStr
newTFInfo
// ____ TYPES END ____
var timeframeInfo[] timeframeInfos = [Link](newTimeframeInfo(1, timeframe1,
timeframe1Enabled), newTimeframeInfo(2, timeframe2, timeframe2Enabled),
newTimeframeInfo(3, timeframe3, timeframe3Enabled))
var demandZonesList = [Link]<sdZoneInfo>(0)
var supplyZonesList = [Link]<sdZoneInfo>(0)
var breakLabels = [Link]<int, label>()
var retestLabels = [Link]<int, retestLabelContainer>()
var int oldestBarTime = na
if bar_index == last_bar_index - maxDistanceToLastBar
oldestBarTime := time
var allSDZonesList = [Link]<sdZone>(0)
moveLine(_line, _x, _y, _x2) =>
line.set_xy1(_line, _x, _y)
line.set_xy2(_line, _x2, _y)
moveBox (_box, _topLeftX, _topLeftY, _bottomRightX, _bottomRightY) =>
box.set_lefttop(_box, _topLeftX, _topLeftY)
box.set_rightbottom(_box, _bottomRightX, _bottomRightY)
isTimeframeLower (timeframe1F, timeframe2F) =>
timeframe.in_seconds(timeframe1F) < timeframe.in_seconds(timeframe2F)
getMinTimeframe (timeframe1F, timeframe2F) =>
if isTimeframeLower(timeframe1F, timeframe2F)
timeframe1F
else
timeframe2F
getMaxTimeframe (timeframe1F, timeframe2F) =>
if isTimeframeLower(timeframe1F, timeframe2F)
timeframe2F
else
timeframe1F
formatTimeframeString (formatTimeframe) =>
timeframeF = formatTimeframe == "" ? [Link] : formatTimeframe
if [Link](timeframeF, "D") or [Link](timeframeF, "W") or
[Link](timeframeF, "S") or [Link](timeframeF, "M")
timeframeF
else
seconds = timeframe.in_seconds(timeframeF)
if seconds >= 3600
hourCount = int(seconds / 3600)
[Link](hourCount) + " Hour" + (hourCount > 1 ? "s" : "")
else
timeframeF + " Min"
colorWithTransparency (colorF, transparencyX) =>
[Link](colorF, color.t(colorF) * transparencyX)
createSDBox (boxColor, transparencyX = 1.0, xlocType = xloc.bar_time) =>
[Link](na, na, na, na, xloc = xlocType, extend = [Link], bgcolor =
colorWithTransparency(boxColor, transparencyX), text_color = textColor, text_halign
= text.align_right, text_valign = text.align_bottom, text_size = [Link],
border_color = boxColor)
renderSDZone (sdZone sd) =>
sdZoneInfo info = [Link]
[Link] := true
sdColor = [Link] == "Demand" ? bullSDZoneColor : bearSDZoneColor
int zoneSize = na
if na([Link])
zoneSize := (time + 1) - [Link]
else
zoneSize := ([Link] - [Link])
render = true
if zoneSize < timeframe.in_seconds([Link]) * minZoneSize * 1000
render := false
if [Link] < nz(oldestBarTime, time)
render := false
if render and (showInvalidated or (na([Link])))
[Link] := createSDBox(sdColor, 1.5)
if [Link]
[Link].set_bgcolor(colorWithTransparency(sdColor, 1.1))
startX = [Link]
maxEndX = [Link] + zoneSize / 2
float middlePoint = ([Link] + [Link]) / 2
moveBox([Link], [Link], [Link], [Link] + zoneSize,
[Link])
[Link] := [Link]([Link], middlePoint, [Link]
+ zoneSize, middlePoint, xloc = xloc.bar_time, color = textColor, style =
line.style_dashed)
[Link] := createSDBox([Link]([Link], 100))
moveBox([Link], maxEndX, middlePoint, [Link] + zoneSize,
[Link])
SDText = (na([Link]) ?
formatTimeframeString([Link]) : [Link]) + " "
+ [Link]
//box.set_text([Link], SDText)
boxText = na([Link]) ?
formatTimeframeString([Link]) : [Link]
if DEBUG
boxText += " | " + [Link]([Link])
box.set_text([Link], boxText)
areaOfSD (sdZoneInfo SDInfoF) =>
float XA1 = [Link]
float XA2 = na([Link]) ? time + 1 : [Link]
float YA1 = [Link]
float YA2 = [Link]
float edge1 = [Link]((XA2 - XA1) * (XA2 - XA1) + (YA2 - YA2) * (YA2 - YA2))
float edge2 = [Link]((XA2 - XA2) * (XA2 - XA2) + (YA2 - YA1) * (YA2 - YA1))
float totalArea = edge1 * edge2
totalArea
doSDsTouch (sdZoneInfo SDInfo1, sdZoneInfo SDInfo2) =>
float XA1 = [Link]
float XA2 = na([Link]) ? (time + 1) : [Link]
float YA1 = [Link] + atr / 100
float YA2 = [Link] - atr / 100
float XB1 = [Link]
float XB2 = na([Link]) ? (time + 1) : [Link]
float YB1 = [Link] + atr / 100
float YB2 = [Link] - atr / 100
float intersectionArea = [Link](0, [Link](XA2, XB2) - [Link](XA1, XB1)) *
[Link](0, [Link](YA1, YB1) - [Link](YA2, YB2))
float unionArea = areaOfSD(SDInfo1) + areaOfSD(SDInfo2) - intersectionArea
float overlapPercentage = (intersectionArea / unionArea) * 100.0
if overlapPercentage > overlapThresholdPercentage
true
else
false
isSDValid (sdZoneInfo SDInfo) =>
valid = true
if [Link]
valid := false
valid
clampSDZone (sdZoneInfo sdZoneF) =>
sdZoneSize = [Link] - [Link]
if sdZoneSize > atr * maxZoneSizeATR
diff = sdZoneSize - (atr * maxZoneSizeATR)
[Link] -= diff / 2
[Link] += diff / 2
combineSDsFunc () =>
if [Link]() > 0
lastCombinations = 999
while lastCombinations > 0
lastCombinations := 0
for i = 0 to [Link]() - 1
curSD1 = [Link](i)
for j = 0 to [Link]() - 1
curSD2 = [Link](j)
if i == j
continue
if not isSDValid([Link]) or not isSDValid([Link])
continue
if [Link] != [Link]
continue
if doSDsTouch([Link], [Link])
[Link] := true
[Link] := true
sdZone newSD =
createSDZone([Link]([Link]([Link], [Link]),
[Link]([Link], [Link]), [Link]))
[Link] := [Link]([Link],
[Link])
[Link] := [Link](nz([Link]),
nz([Link]))
[Link] := [Link] == 0 ? na :
[Link]
[Link] := [Link]
[Link] := [Link]
clampSDZone([Link])
[Link] := true
if timeframe.in_seconds([Link]) !=
timeframe.in_seconds([Link])
[Link] :=
(na([Link]) ?
formatTimeframeString([Link]) :
[Link]) + " & " + (na([Link])
? formatTimeframeString([Link]) :
[Link])
[Link](newSD)
lastCombinations += 1
reqSeq (timeframeStr) =>
if timeframe.in_seconds(timeframeStr) == timeframe.in_seconds()
[demandZonesList, supplyZonesList]
else
[demandZonesListF, supplyZonesListF] = [Link]([Link],
timeframeStr, [demandZonesList, supplyZonesList])
[demandZonesListF, supplyZonesListF]
getTFData (timeframeInfo timeframeInfoF, timeframeStr) =>
if [Link]
[demandZonesListF, supplyZonesListF] = reqSeq(timeframeStr)
[demandZonesListF, supplyZonesListF]
else
[na, na]
handleTimeframeInfo (timeframeInfo timeframeInfoF, demandZonesListF,
supplyZonesListF) =>
if [Link]
[Link] := demandZonesListF
[Link] := supplyZonesListF
handleSDZonesFinal () =>
if DEBUG
[Link]("Demand Count " + [Link]([Link]()))
[Link]("Supply Count " + [Link]([Link]()))
[Link]("All " + [Link]([Link]()))
[Link]("Max " + [Link](demandZones))
if [Link]() > 0
for i = 0 to [Link]() - 1
safeDeleteSDZone([Link](i))
[Link]()
for i = 0 to [Link]() - 1
curTimeframe = [Link](i)
if not [Link]
continue
if [Link]() > 0
for j = 0 to [Link]([Link]() - 1,
demandZones - 1)
sdZoneInfoF = [Link](j)
[Link] := [Link]
[Link](createSDZone([Link](sdZoneInfoF)))
if [Link]() > 0
for j = 0 to [Link]([Link]() - 1,
supplyZones - 1)
sdZoneInfoF = [Link](j)
[Link] := [Link]
[Link](createSDZone([Link](sdZoneInfoF)))
if combineSDs
combineSDsFunc()
if [Link]() > 0
for i = 0 to [Link]() - 1
curSD = [Link](i)
if isSDValid([Link])
renderSDZone(curSD)
bodySize = [Link](close - open)
getMomentumCandleCount (lastBars, reqMult) =>
bearishCnt = 0
bullishCnt = 0
for i = 0 to lastBars - 1
if bodySize[i] >= averageBodySize * reqMult
if close[i] > open[i]
bullishCnt += 1
else
bearishCnt += 1
[bullishCnt, bearishCnt]
[bullishMomentum, bearishMomentum] = getMomentumCandleCount(momentumSpan,
momentumBodyMult)
var int lastDemandZone = 0
var int lastSupplyZone = 0
// Find Supply & Demand
if bar_index > last_bar_index - maxDistanceToLastBar
if bullishMomentum >= momentumCount and bar_index - lastDemandZone >
minDistanceBetweenZones
lastDemandZone := bar_index
newSDZone = [Link](high[momentumSpan + 1], low[momentumSpan + 1],
"Demand", time[momentumSpan + 1], na, time[momentumSpan + 1])
clampSDZone(newSDZone)
[Link](newSDZone)
if [Link]() > maxSDZones
[Link]()
if bearishMomentum >= momentumCount and bar_index - lastSupplyZone >
minDistanceBetweenZones
lastSupplyZone := bar_index
newSDZone = [Link](high[momentumSpan + 1], low[momentumSpan + 1],
"Supply", time[momentumSpan + 1], na, time[momentumSpan + 1])
clampSDZone(newSDZone)
[Link](newSDZone)
if [Link]() > maxSDZones
[Link]()
// Invalidation
if [Link]() > 0
for i = [Link]() - 1 to 0
currentSD = [Link](i)
if na([Link])
if (sdEndMethod == "Wick" ? low : [Link](open, close)) <
[Link]
[Link] := time
if [Link]() > 0
for i = [Link]() - 1 to 0
currentSD = [Link](i)
if na([Link])
if (sdEndMethod == "Wick" ? high : [Link](open, close)) >
[Link]
[Link] := time
[demandZonesListTimeframe1, supplyZonesListTimeframe1] =
getTFData([Link](0), timeframe1)
[demandZonesListTimeframe2, supplyZonesListTimeframe2] =
getTFData([Link](1), timeframe2)
[demandZonesListTimeframe3, supplyZonesListTimeframe3] =
getTFData([Link](2), timeframe3)
var lastRetestIndexSupply = 0
var lastRetestIndexDemand = 0
float renderRetestLabelBuyside = na
int renderRetestLabelBuysideGUID = na
float renderRetestLabelSellside = na
int renderRetestLabelSellsideGUID = na
float renderBreakLabelBuyside = na
int renderBreakLabelBuysideGUID = na
float renderBreakLabelSellside = na
int renderBreakLabelSellsideGUID = na
var disabledDuplicateTF = false
// Disable Duplicate Timeframes
if not disabledDuplicateTF
disabledDuplicateTF := true
for i = 0 to [Link]() - 1
for j = 0 to [Link]() - 1
if i == j
continue
timeframeInfo1 = [Link](i)
timeframeInfo2 = [Link](j)
if [Link] and [Link] and
timeframe.in_seconds([Link]) ==
timeframe.in_seconds([Link])
[Link] := false
if [Link] and bar_index > last_bar_index - maxDistanceToLastBar
handleTimeframeInfo([Link](0), demandZonesListTimeframe1,
supplyZonesListTimeframe1)
handleTimeframeInfo([Link](1), demandZonesListTimeframe2,
supplyZonesListTimeframe2)
handleTimeframeInfo([Link](2), demandZonesListTimeframe3,
supplyZonesListTimeframe3)
handleSDZonesFinal()
// Breaks
if [Link]() > 0
for i = 0 to [Link]() - 1
curZone = [Link](i)
if [Link]
continue
if not showInvalidated and not na([Link])
continue
if na([Link])
continue
if time - [Link] < minZoneSize *
timeframe.in_seconds([Link]) * 1000
continue
if [Link] < nz(oldestBarTime, time)
continue
if time == [Link]
if [Link] == "Supply"
if [Link] - [Link] >
minZoneSize * timeframe.in_seconds() * 1000
renderBreakLabelBuyside := [Link]
renderBreakLabelBuysideGUID := [Link]
else
if [Link] - [Link] >
minZoneSize * timeframe.in_seconds() * 1000
renderBreakLabelSellside := [Link]
renderBreakLabelSellsideGUID := [Link]
// Retests
if [Link]() > 0
for i = 0 to [Link]() - 1
curZone = [Link](i)
if [Link]
continue
if not showInvalidated and not na([Link])
continue
if not na([Link])
continue
if time - [Link] < minZoneSize *
timeframe.in_seconds([Link]) * 1000
continue
if [Link] < nz(oldestBarTime, time)
continue
middleLine = ([Link] + [Link]) / 2.0
if [Link] == "Supply" and bar_index -
lastRetestIndexSupply > RETEST_COOLDOWN
if high > [Link]
renderRetestLabelBuyside := [Link]
renderRetestLabelBuysideGUID := [Link]
lastRetestIndexSupply := bar_index
else if [Link] == "Demand" and bar_index -
lastRetestIndexDemand > RETEST_COOLDOWN
if low < [Link]
renderRetestLabelSellside := [Link]
renderRetestLabelSellsideGUID := [Link]
lastRetestIndexDemand := bar_index
//plotshape(not na(renderRetestLabelBuyside) and retestsEnabled ?
renderRetestLabelBuyside : na, "", [Link], color = bearSDZoneColor, text =
"R", location = labelsAtSameLevel ? [Link] : [Link],
textcolor = [Link], size = [Link])
//plotshape(not na(renderRetestLabelSellside) and retestsEnabled ?
renderRetestLabelSellside : na, "", [Link], color = bullSDZoneColor, text =
"R", location = labelsAtSameLevel ? [Link] : [Link],
textcolor = [Link], size = [Link])
// Retests
if not na(renderRetestLabelBuyside) and retestsEnabled
newLabel = [Link](bar_index, renderRetestLabelBuyside, style =
label.style_label_down, color = bearSDZoneColor, text = "R", textcolor =
[Link], size = [Link])
//[Link](bar_index, renderRetestLabelSellside, style = label.style_label_up,
color = bullSDZoneColor, text = "R", textcolor = [Link], size = [Link])
if na([Link](renderRetestLabelBuysideGUID))
newContainer = [Link](renderRetestLabelBuysideGUID)
[Link] := [Link]<label>()
[Link](newLabel)
[Link](renderRetestLabelBuysideGUID, newContainer)
else
[Link](renderRetestLabelBuysideGUID).[Link](newLabel)
if not na(renderRetestLabelSellside) and retestsEnabled
newLabel = [Link](bar_index, renderRetestLabelSellside, style =
label.style_label_up, color = bullSDZoneColor, text = "R", textcolor = [Link],
size = [Link])
if na([Link](renderRetestLabelSellsideGUID))
newContainer = [Link](renderRetestLabelSellsideGUID)
[Link] := [Link]<label>()
[Link](newLabel)
[Link](renderRetestLabelSellsideGUID, newContainer)
else
[Link](renderRetestLabelSellsideGUID).[Link](newLabel)
if [Link]().size() > 0
for i = 0 to [Link]().size() - 1
curKey = [Link]().get(i)
foundKey = false
if [Link]() > 0
for j = 0 to [Link]() - 1
if [Link](j).[Link] == curKey
if [Link](j).[Link]
continue
if not showInvalidated and not
na([Link](j).[Link])
continue
if time - [Link](j).[Link] < minZoneSize *
timeframe.in_seconds([Link](j).[Link]) * 1000
continue
if [Link](j).[Link] < nz(oldestBarTime,
time)
continue
foundKey := true
break
if not foundKey
for j = 0 to [Link](curKey).[Link]() - 1
[Link]([Link](curKey).[Link](j))
// Breaks
if not na(renderBreakLabelBuyside) and breaksEnabled
[Link](renderBreakLabelBuysideGUID, [Link](bar_index,
renderBreakLabelBuyside, style = label.style_label_up, color = [Link], text =
"B", textcolor = [Link], size = [Link]))
if not na(renderBreakLabelSellside) and breaksEnabled
[Link](renderBreakLabelSellsideGUID, [Link](bar_index,
renderBreakLabelSellside, style = label.style_label_down, color = [Link], text
= "B", textcolor = [Link], size = [Link]))
if [Link]().size() > 0
for i = 0 to [Link]().size() - 1
curKey = [Link]().get(i)
foundKey = false
if [Link]() > 0
for j = 0 to [Link]() - 1
if [Link](j).[Link] == curKey
if [Link](j).[Link]
continue
foundKey := true
break
if not foundKey
[Link]([Link](curKey))
alertcondition(not na(renderRetestLabelBuyside) and [Link], "Supply
Zone Retest @ {{ticker}}", "Supply Zone Retest @ {{ticker}}")
alertcondition(not na(renderRetestLabelSellside) and [Link], "Demand
Zone Retest @ {{ticker}}", "Demand Zone Retest @ {{ticker}}")
alertcondition(not na(renderBreakLabelBuyside) and [Link], "Supply
Zone Break @ {{ticker}}", "Supply Zone Break @ {{ticker}}")
alertcondition(not na(renderBreakLabelSellside) and [Link], "Demand
Zone Break @ {{ticker}}", "Demand Zone Break @ {{ticker}}")