// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ChartPrime
//@version=5
indicator("Ghost Tangent Crossings [ChartPrime]", overlay = true, max_lines_count = 100, max_polylines_count = 100, max_labels_count = 500, max_bars_back = 1000)
type pivot
float current = na
int current_idx = na
float previous = na
int previous_idx = na
pivothigh(float source, int back, int forward)=>
var pivot pivot_high = pivot.new(na, na, na, na)
bool ph = not na(ta.pivothigh(source, back, forward))
if ph
pivot_high := pivot.new(source[forward], bar_index - forward, pivot_high.current, pivot_high.current_idx)
[pivot_high, ph]
pivotlow(float source, int back, int forward)=>
var pivot pivot_low = pivot.new(na, na, na, na)
bool pl = not na(ta.pivotlow(source, back, forward))
if pl
pivot_low := pivot.new(source[forward], bar_index - forward, pivot_low.current, pivot_low.current_idx)
[pivot_low, pl]
generate_ellipse(int start_x, int end_x, float start_y, float end_y)=>
chart.point[] points = array.new()
float a = end_x - start_x
float b = end_y - start_y
int x = na
if a > 1
for i = 0 to 90
int new_x = int(a * math.cos(math.toradians(i)))
float y = b * math.sin(math.toradians(i))
if x != new_x
points.push(chart.point.new(na, start_x + x, start_y + y))
x := new_x
points.push(chart.point.new(na, start_x, end_y))
else
points.unshift(chart.point.new(na, end_x, start_y))
points.push(chart.point.new(na, start_x, end_y))
points
ellipse_slope(int start_x, int end_x, float start_y, float end_y, chart.point[] ellipse_points)=>
if ellipse_points.size() > 2
float[] dy = array.new()
float[] centers = array.new()
int[] idx = array.new()
for i = 0 to ellipse_points.size() - 2
float delta = math.abs(ellipse_points.get(i + 1).price - ellipse_points.get(i).price)
dy.push(delta)
for i = 1 to dy.size() - 1
idx.push(i)
float left = dy.copy().slice(0, i + 1).sum()
float right = dy.copy().slice(i - 1, dy.size()).sum()
centers.push(math.abs(left - right))
int mid = idx.get(centers.indexof(centers.min()))
chart.point tangent = ellipse_points.get(mid)
float a = end_x - start_x
float b = end_y - start_y
float x = tangent.index - start_x
float y = tangent.price - start_y
float slope = -(math.pow(b, 2) * x) / (math.pow(a, 2) * y)
[tangent, slope]
else
[ellipse_points.first(), -(ellipse_points.last().price - ellipse_points.first().price)]
check_b(int start_x, float start_y, int forward_length, float slope, bool polarity)=>
int i = forward_length - 1
bool found = false
int start_idx = bar_index - start_x
int current_idx = start_idx
float end_price = na
while not found and current_idx >= 0
i += 1
float check_price = start_y + slope * i
current_idx := start_idx - i
if current_idx < 0
break
if polarity
end_price := high[current_idx]
if end_price < check_price
found := true
else
end_price := low[current_idx]
if end_price > check_price
found := true
if found
[found, end_price, bar_index - current_idx]
else
[found, float(na), int(na)]
generate_zig_zag(
int start_x
, int end_x
, float start_y
, float end_y
, bool show_elliptical_zig
, string equipoint_style
, bool show_break
, bool extend_line
, color up_color
, color down_color
, color text_color
, label[] zig_zag_points
, polyline[] zig_zags
, line[] equipoints
, label[] break_labels
, bool polarity
, bool ghost = false)=>
color bullish_color = polarity ? up_color : down_color
color bearish_color = polarity ? down_color : up_color
chart.point[] ellipse_points = generate_ellipse(start_x, end_x, start_y, end_y)
[tangent, slope] = ellipse_slope(start_x, end_x, start_y, end_y, ellipse_points)
int length = end_x - start_x
int back_length = tangent.index - start_x
int forward_length = length - back_length
if show_elliptical_zig
zig_zag_points.push(label.new(ellipse_points.last(), na, color = bullish_color, style = label.style_circle, size = size.auto))
zig_zags.push(polyline.new(ellipse_points, false, false, xloc.bar_index, bullish_color, line_width = 2))
if equipoint_style == "Directional"
if extend_line
equipoints.push(line.new(tangent.index, tangent.price, tangent.index + 1, tangent.price + slope, xloc.bar_index, extend.both, bullish_color, line.style_dashed))
else
equipoints.push(line.new(tangent.index - back_length, tangent.price - slope * back_length, tangent.index + back_length, tangent.price + slope * back_length, xloc.bar_index, extend.none, bullish_color, line.style_dashed))
if equipoint_style == "Horizontal"
if extend_line
equipoints.push(line.new(tangent.index, tangent.price, tangent.index + 1, tangent.price, xloc.bar_index, extend.right, bullish_color, line.style_dashed))
else
equipoints.push(line.new(tangent.index, tangent.price, tangent.index + length * 2, tangent.price, xloc.bar_index, extend.none, bullish_color, line.style_dashed))
if show_break and not ghost
[found, found_price, found_idx] = check_b(tangent.index, tangent.price, forward_length, slope, polarity)
if found
break_labels.push(label.new(found_idx, found_price, "B", xloc.bar_index, polarity ? yloc.abovebar : yloc.belowbar, polarity ? down_color :up_color, polarity ? label.style_label_down : label.style_label_up, text_color))
else
break_labels.push(label.new(na, na, na))
dump_ghost(polyline[] ghost_zig_zags, label[] ghost_zig_zag_points, line[] ghost_equipoints)=>
if ghost_zig_zags.size() > 0
for i = ghost_zig_zags.size() - 1 to 0
ghost_zig_zags.pop().delete()
if ghost_zig_zag_points.size() > 0
for i = ghost_zig_zag_points.size() - 1 to 0
ghost_zig_zag_points.pop().delete()
if ghost_equipoints.size() > 0
for i = ghost_equipoints.size() - 1 to 0
ghost_equipoints.pop().delete()
const string settings = "Settings"
string pivot_type = input.string("Wick", "Pivot Style", ["Wick", "Body"], group = settings)
int pivot_forward = input.int(25, "Pivot Lookforward", minval = 1, group = settings)
const string visual = "Visuals"
bool show_elliptical_zig = input.bool(true, "Show Elliptical Zig-Zag", group = visual)
bool show_ghosts = input.bool(true, "Show Ghost Elliptical Zig-Zag", group = visual)
bool show_break = input.bool(true, "Show Break", group = visual)
int max_zig = input.int(10, "Max Zig-Zags", minval = 0, maxval = 100, group = visual)
string equipoint_style = input.string("Directional", "Equipoint Style", ["Directional", "Horizontal", "None"], group = visual)
bool extend_line = input.bool(false, "Extend Lines", group = visual)
color up_color = input.color(#1bcf66, "Up Color", group = visual)
color down_color = input.color(#ee2d2d, "Down Color", group = visual)
color ghost_up_color = input.color(#1bcf663a, "Ghost Up Color", group = visual)
color ghost_down_color = input.color(#ee2d2d3a, "Ghost Down Color", group = visual)
color text_color = input.color(#EEEEEE, "Text Color", group = visual)
var polyline[] zig_zags = array.new()
var label[] zig_zag_points = array.new()
var line[] equipoints = array.new()
var label[] break_labels = array.new()
var polyline[] ghost_zig_zags = array.new()
var label[] ghost_zig_zag_points = array.new()
var line[] ghost_equipoints = array.new()
float high_source = pivot_type == "Wick" ? high : math.max(open, close)
float low_source = pivot_type == "Wick" ? low : math.min(open, close)
var int ph_back = pivot_forward
var int pl_back = pivot_forward
[ph, new_ph] = pivothigh(high_source, ph_back, pivot_forward)
[pl, new_pl] = pivotlow(low_source, pl_back, pivot_forward)
bool polarity_up = ph.current_idx > pl.current_idx
bool polarity_down = ph.current_idx < pl.current_idx
var int last_up_start = na
var int last_up_end = na
var int last_down_start = na
var int last_down_end = na
var float last_high = na
var float last_low = na
var bool polarity = na
bool up_wait = not polarity
bool down_wait = polarity
if new_ph and polarity_up and (last_up_start < pl.current_idx or na(last_up_start)) and up_wait and barstate.isconfirmed
dump_ghost(ghost_zig_zags, ghost_zig_zag_points, ghost_equipoints)
bool connect = not na(last_down_end) ? pl.current_idx == last_down_end : true
int start_x = connect ? pl.current_idx : last_down_end
int end_x = ph.current_idx
float start_y = ph.current
float end_y = connect ? pl.current : last_low
last_up_start := start_x
last_up_end := end_x
last_high := start_y
polarity := true
generate_zig_zag(start_x, end_x, start_y, end_y, show_elliptical_zig, equipoint_style, show_break, extend_line, up_color, down_color, text_color, zig_zag_points, zig_zags, equipoints, break_labels, true)
if new_pl and polarity_down and (last_down_start < ph.current_idx or na(last_down_start)) and down_wait and barstate.isconfirmed
dump_ghost(ghost_zig_zags, ghost_zig_zag_points, ghost_equipoints)
bool connect = not na(last_up_end) ? ph.current_idx == last_up_end : true
int start_x = connect ? ph.current_idx : last_up_end
int end_x = pl.current_idx
float start_y = pl.current
float end_y = connect ? ph.current : last_high
last_down_start := start_x
last_down_end := end_x
last_low := start_y
polarity := false
generate_zig_zag(start_x, end_x, start_y, end_y, show_elliptical_zig, equipoint_style, show_break, extend_line, up_color, down_color, text_color, zig_zag_points, zig_zags, equipoints, break_labels, false)
bool ghost_up_connect = not na(last_down_end) ? pl.current_idx == last_down_end : true
int ghost_up_start_x = ghost_up_connect ? pl.current_idx : last_down_end
int since_pl = ta.barssince(new_pl)
int ghost_up_range = bar_index - ghost_up_start_x
float[] ghost_max = array.new()
if ghost_up_range >= 0
for i = 0 to ghost_up_range - since_pl
ghost_max.push(high_source[i])
float ghost_up_start_y = ghost_max.max()
float ghost_up_end_y = ghost_up_connect ? pl.current : last_low
int ghost_up_since = ghost_max.size() > 0 ? ghost_max.indexof(ghost_up_start_y) : 0
int ghost_up_end_x = bar_index - ghost_up_since
if up_wait and not new_ph and show_ghosts
dump_ghost(ghost_zig_zags, ghost_zig_zag_points, ghost_equipoints)
generate_zig_zag(ghost_up_start_x, ghost_up_end_x, ghost_up_start_y, ghost_up_end_y, show_elliptical_zig, equipoint_style, show_break, extend_line, ghost_up_color, ghost_down_color, text_color, ghost_zig_zag_points, ghost_zig_zags, ghost_equipoints, break_labels, true, true)
bool ghost_down_connect = not na(last_up_end) ? ph.current_idx == last_up_end : true
int ghost_down_start_x = ghost_down_connect ? ph.current_idx : last_up_end
int since_ph = ta.barssince(new_ph)
int ghost_down_range = bar_index - ghost_down_start_x
float[] ghost_min = array.new()
if ghost_down_range >= 0
for i = 0 to ghost_down_range - since_ph
ghost_min.push(low_source[i])
float ghost_down_start_y = ghost_min.min()
float ghost_down_end_y = ghost_down_connect ? ph.current : last_high
int ghost_down_since = ghost_min.size() > 0 ? ghost_min.indexof(ghost_down_start_y) : 0
int ghost_down_end_x = bar_index - ghost_down_since
if down_wait and not new_pl and show_ghosts
dump_ghost(ghost_zig_zags, ghost_zig_zag_points, ghost_equipoints)
generate_zig_zag(ghost_down_start_x, ghost_down_end_x, ghost_down_start_y, ghost_down_end_y, show_elliptical_zig, equipoint_style, show_break, extend_line, ghost_up_color, ghost_down_color, text_color, ghost_zig_zag_points, ghost_zig_zags, ghost_equipoints, break_labels, false, true)
if zig_zags.size() > max_zig
zig_zags.shift().delete()
zig_zag_points.shift().delete()
if equipoints.size() > max_zig
equipoints.shift().delete()
if break_labels.size() > max_zig
break_labels.shift().delete()
ph_back := math.min(math.max(nz(bar_index - last_down_end - pl_back + 1, 5), 0), 500)
pl_back := math.min(math.max(nz(bar_index - last_up_end - ph_back + 1, 5), 0), 500)