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

//@version=5

// @description a library for everything related to price action, starting off with displacements
library("lib_priceaction", overlay = true)

// @function calculate if there is a displacement and how strong it is
// @param len The amount of candles to consider for the deviation
// @param min_strength The minimum displacement strength to trigger a signal
// @param o The source series on which calculations are based
// @param c The source series on which calculations are based
// @returns a tuple of (bool signal, float displacement_strength)
export displacement(int len = 20, float min_strength = 2, float o = open, float c = close) =>
	float bar_diff = c-o
	float bar_size = math.abs(bar_diff)
	float bar_dir = math.sign(bar_diff)
	float stddev = ta.stdev(bar_size, len)
	float strength = bar_size / stddev

	bool is_displacement = strength >= min_strength
	
	[is_displacement, strength * bar_dir]


// @function calculate a ratio how far a price has retraced compared to a move between two reference levels
// @param price_start The start price level of the reference move
// @param price_end The end price level of the reference move
// @param price_retracement The retraced price level
// @returns the retracement ratio (float, always positive) or na if one of the inputs was na
export retracement_ratio(float price_start, float price_end, float price_retracement) =>
	na(price_start) or na(price_end) or na(price_retracement) ? na : math.abs(price_end - price_retracement)/math.abs(price_start - price_end)


// @function calculate a ratio how far a price has retraced compared to a move between two reference levels
// @param price_start The start price level of the reference move
// @param price_end The end price level of the reference move
// @param target_ratio The target retracement ratio (must be positive)
// @returns the retracement target ratio price level (float) or na if one of the inputs was na
export target_ratio_price(float price_start, float price_end, float target_ratio) =>
	na(price_start) or na(price_end) or na(target_ratio) ? na : price_end + target_ratio * (price_start - price_end)


// @function check if a price is in a certain range, e.g. to check if price reached a target zone
// @param x the price variable
// @param a one range limit (sign agnostic)
// @param b other range limit (sign agnostic)
// @returns true if x is between a and b (inclusive)
export in_range(float x, float a, float b) =>
    na(x) or na(a) or na(b) ? false : x >= math.min(a,b) and x <= math.max(a,b)

// @function check if two price ranges overlap, e.g. to check if a target is price is possible that fits both target zones
// @param x the price variable
// @param a1 one limit of range 1
// @param b1 other limit of range 1
// @param a2 one limit of range 2
// @param b2 other limit of range 2
// @returns true if x is between a and b (inclusive
export range_overlap(float a1, float b1, float a2, float b2) =>
    na(a1) or na(b1) or na(a2) or na(b2) ? false : math.min(a1, b1) <= math.max(a2,b2) and math.max(a1,b1) >= math.min(a2,b2)


var g_displace = "Displacement"
stddev_len = input.int(10, minval = 1, title = "Length", tooltip = "Lookback for bar size stddev", group = g_displace)
stddev_min = input.float(3, minval = 0, step = 0.1, title = "Min Displacement Strength", group = g_displace)
disp_color = input.color(color.yellow, "Signal Marker", group = g_displace)

[signal, strength] = displacement(stddev_len, stddev_min)

dir = math.sign(close - open)

bgcolor(signal ? disp_color : na)
plotshape(signal and dir == 1, 'Signal Up', shape.triangleup, location.belowbar, color.green, size = size.small)
plotshape(signal and dir == -1, 'Signal Down', shape.triangledown, location.abovebar, color.red, size = size.small)
if signal and dir == 1
	label.new(bar_index, na, str.tostring(strength), yloc = yloc.belowbar, color = #00000000, style = label.style_label_up, textcolor = color.black)
if signal and dir == -1
	label.new(bar_index, na, str.tostring(-strength), yloc = yloc.abovebar, color = #00000000, style = label.style_label_down, textcolor = color.black)