Price Correction to fix data manipulation and mispricing
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Techotomy

//@version=5
indicator(title="Price Correction to fix data manipulation and mispricing", shorttitle='Price Correction', 
 overlay=true)

preset_tooltip = "Select this option to automatically correct mispricing issues specific to the SPY and NYA"
intraday_tooltip = "Use intraday OHLC data to build daily OHLC candles"
timeframe_tooltip = "Daily Price Correction relies on intraday data, which is limited by TradingView.  
 Increasing the timeframe increases the number of historical daily bars that can be corrected, but may hinder 
 price correction accuracy for symbols that have intraday mispricing as well (e.g., NYA)"
start_tooltip = "Select this option to see the start date of Price Correction.  If more historical price bars 
 need to be corrected, increase the intraday time frame."
deviation_tooltip = "Select this option to show daily OHLC bars that differ from intraday data by the specified 
 percent difference. Bars or candles that deviate are painted with the specified discrepancy color"

use_presets = input.bool(defval=true, title='Use Presets', group='Price Correction Settings', 
 tooltip=preset_tooltip)
use_intraday_opens = input.bool(defval=false, title='Use Intraday Open', group='Price Correction Settings', 
 tooltip=intraday_tooltip)
use_intraday_highs = input.bool(defval=false, title='Use Intraday High', group='Price Correction Settings', 
 tooltip=intraday_tooltip)
use_intraday_lows = input.bool(defval=false, title='Use Intraday Low', group='Price Correction Settings', 
 tooltip=intraday_tooltip)
use_intraday_closes = input.bool(defval=false, title='Use Intraday Close', group='Price Correction Settings', 
 tooltip=intraday_tooltip)
show_start_date = input.bool(defval=false, title='Show Start Date', group='Price Correction Settings', 
 tooltip=start_tooltip)
ltf = input.int(defval=1, title='Intraday Time Frame', minval=1, group='Price Correction Settings', 
 tooltip=timeframe_tooltip)
lower_tf = str.tostring(ltf)

chart_type = input.string(defval='Candle', title='Chart Type', options=['Candle', 'Bar'], 
 group='Chart Settings')
body_color_up = input.color(defval=#26a69a, inline='body', title='Body Color       Up', 
 group='Chart Settings')
body_color_dn = input.color(defval=#ef5350, inline='body', title='Down', group='Chart Settings')
wick_color_up = input.color(defval=#26a69a, inline='wick', title='Wick Color       Up', 
 group='Chart Settings')
wick_color_dn = input.color(defval=#ef5350, inline='wick', title='Down', group='Chart Settings')
border_color_up = input.color(defval=#26a69a, inline='border', title='Border Color     Up', 
 group='Chart Settings')
border_color_dn = input.color(defval=#ef5350, inline='border', title='Down', group='Chart Settings')
close_based = input.bool(defval=false, title='Color by previous close', group='Chart Settings')

use_dev = input.bool(defval=false, title='Show Discrepancies (On/Off)', group='Discrepancy Reporting', 
 tooltip=deviation_tooltip)
thresh_dev = input.float(defval=0.1, title='Percent Discrepancy', group='Discrepancy Reporting', 
 tooltip=deviation_tooltip)
color_dev = input.color(defval=color.blue, title='Discrepancy Color', group='Discrepancy Reporting', 
 tooltip=deviation_tooltip)

updn_color(is_deviation, open_0, close_0, close_1, color_up, color_dn, color_dev, close_based) =>
    _color = color_dn
    if is_deviation
        _color := color_dev
    else if (close_0 >= close_1 and close_based) or (close_0 >= open_0 and not close_based)
        _color := color_up
    _color

percent_difference(v0, v1) =>
    100 * math.abs(v0 - v1) / v0

[intraday_opens, intraday_highs, intraday_lows, intraday_closes] = request.security_lower_tf(
     syminfo.ticker, lower_tf, [open, high, low, close])
[daily_open, daily_high, daily_low, daily_close] = request.security(
     syminfo.ticker, "D", [open, high, low, close])

_open = open
_high = high
_low = low
_close = close

var start_correction = false
if array.size(intraday_opens) > 0 and not start_correction and show_start_date
    label.new(bar_index, high, "Start\nPrice\nCorrection", color=color_dev)
    start_correction := true

is_deviation = false
if timeframe.isdaily and array.size(intraday_opens) > 0

    intraday_open = array.get(intraday_opens, 0)
    intraday_high = array.max(intraday_highs)
    intraday_low = array.min(intraday_lows)
    intraday_close = array.get(intraday_closes, array.size(intraday_closes)-1)

    if use_dev and syminfo.ticker != "NYA"
        if percent_difference(open, intraday_open) >= thresh_dev or 
         percent_difference(high, intraday_high) >= thresh_dev or 
         percent_difference(low, intraday_low) >= thresh_dev or 
         percent_difference(close, intraday_close) >= thresh_dev
            is_deviation := true

    if use_intraday_opens
        _open := intraday_open
    if use_intraday_highs
        _high := intraday_high
    if use_intraday_lows
        _low := intraday_low
    if use_intraday_closes
        _close := intraday_close

    if use_presets
        if syminfo.ticker == "SPY"
            _low := intraday_low
            _high := intraday_high
        else if syminfo.ticker == "NYA"
            if math.round(daily_close[1]) == math.round(array.get(intraday_opens, 0))
                array.remove(intraday_opens, 0)
                array.remove(intraday_lows, 0)
                array.remove(intraday_highs, 0)
            _open := array.get(intraday_opens, 0)
            _low := array.min(intraday_lows)
            _high := array.max(intraday_highs)

            if use_dev
                if percent_difference(open, array.get(intraday_opens, 0)) >= thresh_dev or 
                 percent_difference(high, array.max(intraday_highs)) >= thresh_dev or 
                 percent_difference(low, array.min(intraday_lows)) >= thresh_dev or 
                 percent_difference(close, intraday_close) >= thresh_dev
                    is_deviation := true

if timeframe.isintraday
    if use_presets
        if syminfo.ticker == "NYA"
            t = time("D", session=session.regular)
            if not na(t) and (na(t[1]) or t > t[1])
                if math.round(daily_close[0]) == math.round(open)
                    _open := _close
                    _high := _close
                    _low := _close

plotcandle(chart_type == "Candle" ? _open : na, _high, _low, _close, 
 color=updn_color(
  is_deviation, _open[0], _close[0], _close[1], body_color_up, body_color_dn, color_dev, close_based), 
 bordercolor=updn_color(
  is_deviation, _open[0], _close[0], _close[1], border_color_up, border_color_dn, color_dev, close_based),
 wickcolor=updn_color(
  is_deviation, _open[0], _close[0], _close[1], wick_color_up, wick_color_dn, color_dev, close_based),
 display=display.all - display.price_scale, editable=false)

plotbar(chart_type == "Bar" ? _open : na, _high, _low, _close, 
 color=updn_color(
  is_deviation, _open[0], _close[0], _close[1], body_color_up, body_color_dn, color_dev, close_based),
 display=display.all - display.price_scale, editable=false)