twopirllc / pandas-ta

Technical Analysis Indicators - Pandas TA is an easy to use Python 3 Pandas Extension with 150+ Indicators
https://twopirllc.github.io/pandas-ta/
MIT License
5.21k stars 1.02k forks source link

KVO Vs TradingView KVO #734

Closed RaimondodelBalzo closed 8 months ago

RaimondodelBalzo commented 10 months ago

Hello there, Thanks a lot for your project and all the features of this library. I use it every day! I am currently using many of the momentum and volume indicators without problems. Only one "fails" compared to TradingView: KVO I am sure that it has to do with some parameters or some average applied when I recall the function.

Could please let me know the parameters to have the same results of TradingView?

Thanks.

Best, Raimondo

Groni3000 commented 10 months ago

I took most used TradingView KVO indicator by HPotter. Here is his code:

study(title="Klinger Volume Oscillator (KVO)", shorttitle="KVO")
TrigLen = input(13, minval=1)
FastX = input(34, minval=1)
SlowX = input(55, minval=1)
xTrend = iff(hlc3 > hlc3[1], volume * 100, -volume * 100) // <--- Here is what can cause the problem for you
xFast = ema(xTrend, FastX)
xSlow = ema(xTrend, SlowX)
xKVO = xFast - xSlow
xTrigger = ema(xKVO, TrigLen)
plot(xKVO, color=blue, title="KVO")
plot(xTrigger, color=red, title="Trigger")

So the only thing you need to do is simply multiplying volume by 100:

import pandas_ta as pdt

pdt.kvo(h, l, c, 100 * v)

Still, you may not have the same result, because of different data sources. For example, I downloaded QQQ from yfinance and volumes was slightly "not the same". But, as I can see, pandas_ta makes the same logic as TradingView by default, like ewm with span and first value calculated as simple mean...

RaimondodelBalzo commented 10 months ago

Thanks for your reply and help. And sorry for getting back to you after a couple of days.

Screenshot TradingView

Input.csv Output.csv

I have attached a small dataset (one stock) and I applied this command:

data2.ta.kvo(high=data2["high"], low=data2["low"], close=data2['close'], volume=data2["volume"]*100, append=True)

The two results are different. What I am missing? Thanks again.

Groni3000 commented 10 months ago

@RaimondodelBalzo Sorry for my late appearance too) As I told, different sources, I guess. For example, first row (07-07-2023) in Input.csv: volume = 63600 And in TradingView volume = 63639, you can see it in this screen: image

The difference is small, but in ewm next value depends on previous and this indicator calculates several such ewms. Can you clarify your data source? If you got it from TradingView - it is strange because I even plot volume and it says 63639... image

If you manage to download exactly TradingView data and test it - that would be perfect, but as I know, it is not free [̲̅$̲̅(̲̅ ͡° ͜ʖ ͡°̲̅)̲̅$̲̅]

Or, maybe, I didn't understand you and Input.csv is TradingView source, but that would be strange, because TV calculations takes volume that I plot.

Groni3000 commented 10 months ago

@RaimondodelBalzo, sorry for my misleading and bad answer =( Here is an update

pandas_ta version of signed_series in kvo uses hardcoded initial (first) value of 1. tradingview version uses -1. So you will never have the same output. You can see it here: ---------------pandas_ta---------------: image

---------------tradingview---------------: image

Other things should be exactly the same.

AND!!!! Very important!!! This indicator is REALLY sensative! You won't get the same values even if you exclude only one first point. Here is example for your Input.csv: image

So, if you use only part of your data - there is no reason to expect the same output as tradingview indicator. Moreover, sadly to say, you will not have the same output even if you use entire quotes from tradingview (I mean with correct volumes) cuz it has different sign for the first value in sv.

@twopirllc so, I would suggest to make first value of -1 (if you want to make indicator like in TradingView) or left it NaN (-1 is debatable, bu 1 seems to be really wrong, there is no way first value is bigger then non-existing value, so I don't see a reason to make it 1).

RaimondodelBalzo commented 10 months ago

@Groni3000 Thanks for the followup. It would be enough to have an almost-similar value. With the signal following the same logic. But it is really hard to see if this is true. So what I gather is that it would be better to do my own KVO calculation in python without using this package. Am I correct?

Groni3000 commented 10 months ago

@RaimondodelBalzo Well, this is, maybe, is the best option. It's not that hard to implement, I guess. It's hard to check correctness. I had to waste ~15 mins to write all volume values by hand to compare computation results xDD Programmer ahahhaha. Here is my funcs as inspiration to you if you need them:

def my_typ_price(h, l, c):
    return (h + l + c) / 3

def my_signed_series(s, init=-1):
    # init = -1 for TradingView
    # init = 1 for pandas_ta
    res = s.diff(1)
    res[res > 0] = 1
    res[res < 0] = -1
    res.iloc[0] = init
    return res

def my_ewm(ser:pd.Series, window:int):
    # Here I didn't investigate how TradingView ewm behaives with np.NaN values,
    # perhaps they make something like this:
    #
    # nan_vals_idx = np.where(ser.isna())[0]
    # first_non_nan_idx = 0 if nan_vals_idx.size == 0 else nan_vals_idx[-1] + 1      #<--- That's not exactly correct, they implement it differently (if prev value is NaN ===> simple moving avarage or something like this), but with correct series (only first N values COULD be NaNs) it should be fine 
    # nth = ser.iloc[first_non_nan_idx:first_non_nan_idx+window].mean()
    # ser = ser.copy()
    # ser.iloc[:first_non_nan_idx + window] = np.NaN
    # ser.iloc[:first_non_nan_idx + window - 1] = nth
    #
    # I checked it with only non NaN values and this ewm is exactly the same as TradingVeiw version

    ser = ser.copy()
    nth = ser.iloc[:window].mean() 
    ser.iloc[:window] = np.NaN
    ser.iloc[window-1] = nth

    return ser.ewm(span=window, adjust=False).mean()

def my_kvo(h, l, c, v, w_1=34, w_2=55, w_3=13):
    tp = my_typ_price(h, l, c)
    sv = v * my_signed_series(tp)
    kvo = my_ewm(sv, w_1) - my_ewm(sv, w_2)
    kvo_signal = my_ewm(kvo, w_3)

    return kvo, kvo_signal

About "It would be enough to have an almost-similar value"... If you use part of data (even one less candle) - result may differ a lot. If you use all data => hard to say... Every next value depends on previous and if first value with different sign ... With smaller windows I guess it's cruicial. With big windows ... well... Hard to say =)

twopirllc commented 9 months ago

Hello @RaimondodelBalzo & @Groni3000

Thanks for using Pandas TA!

Apologies for the slow reply and hashing out the details in the mean time with good analysis and solutions. It helps make reviewing indicators easier. 😎

I don't remember exactly if the 1 in signed_volume = volume * signed_series(hlc3(high, low, close), 1) was by design or just a mistake. 🤦🏼‍♂️

TradingView also has Klinger Oscillator:

//@version=5
indicator(title="Klinger Oscillator", format=format.volume, timeframe="", timeframe_gaps=true)
var cumVol = 0.
cumVol += nz(volume)
if barstate.islast and cumVol == 0
    runtime.error("No volume is provided by the data vendor.")
sv = ta.change(hlc3) >= 0 ? volume : -volume
kvo = ta.ema(sv, 34) - ta.ema(sv, 55)
sig = ta.ema(kvo, 13)

But if I only need to replace the initial signed_volume() to -1, then even better if that is the consensus. 😎

Kind Regards, KJ

twopirllc commented 8 months ago

Hello @RaimondodelBalzo & @Groni3000

I assume by no response that the solution provided was sufficient. Thus I will be closing this issue in a few days.

Kind Regards, KJ