TA-Lib / ta-lib-python

Python wrapper for TA-Lib (http://ta-lib.org/).
http://ta-lib.github.io/ta-lib-python
Other
9.78k stars 1.77k forks source link

Incorrect outputs for RSI, BBANDS, BOP, MFI, CMO, STOCHRSI #594

Closed Elyasnz closed 1 year ago

Elyasnz commented 1 year ago

first of all, thanks for the great library

im having trouble calculating StochasticRsi, rsi, mfi, cmo values for some symbols using TA-Lib version 0.4.26 but the values are ok in websites like tradingview or binance

i think there is a problem when numbers are very small but note that the functions work with higher time-frames like Daily or above

CODE


import requests
from pandas import DataFrame, to_datetime
from talib import RSI, STOCHRSI, MFI, CMO
from mplfinance import plot

# symbol='ATABTC'
# symbol='CELRBTC'
# symbol='CHRBTC'
# symbol='DOGEBTC'
# symbol='GALABTC'
# symbol='GRTBTC'
symbol='HBARBTC'
# symbol='IOTABTC'
# symbol='TRXBTC'
# symbol='VETBTC'
# symbol='WAXPBTC'
# symbol='XEMBTC'
# symbol='XLMBTC'
# symbol='ZILBTC'
data = json.loads(requests.get(
    f'https://www.binance.com/api/v1/klines?symbol={symbol}&interval=15m&limit=10000&startTime=1681603200000&endTime=1682491357000'
).text)
data = DataFrame(
    [
        (item[0] // 1000, float(item[1]), float(item[2]), float(item[3]), float(item[4]), float(item[5]))
        for item in data
    ],
    columns=('timestamp', 'open', 'high', 'low', 'close', 'volume')
)

data.index = to_datetime(data['timestamp'], unit='s')
plot(
    data[['open', 'high', 'low', 'close', 'volume']],
    warn_too_much_data=10000,
    type='candle'
)

print('RSI')
print(RSI(data.close, timeperiod=14))
print()

print('STOCHRSI')
print(STOCHRSI(data.close, timeperiod=14, fastk_period=3, fastd_period=3, fastd_matype=0))
print()

print('MFI')
print(MFI(data.high, data.low, data.close, data.volume, timeperiod=14))
print()

print('CMO')
print(CMO(data.close, timeperiod=14))

OUTPUT

RSI
timestamp
2023-04-16 00:00:00    NaN
2023-04-16 00:15:00    NaN
2023-04-16 00:30:00    NaN
2023-04-16 00:45:00    NaN
2023-04-16 01:00:00    NaN
                      ... 
2023-04-26 05:30:00    0.0
2023-04-26 05:45:00    0.0
2023-04-26 06:00:00    0.0
2023-04-26 06:15:00    0.0
2023-04-26 06:30:00    0.0
Length: 987, dtype: float64

STOCHRSI
(timestamp
2023-04-16 00:00:00    NaN
2023-04-16 00:15:00    NaN
2023-04-16 00:30:00    NaN
2023-04-16 00:45:00    NaN
2023-04-16 01:00:00    NaN
                      ... 
2023-04-26 05:30:00    0.0
2023-04-26 05:45:00    0.0
2023-04-26 06:00:00    0.0
2023-04-26 06:15:00    0.0
2023-04-26 06:30:00    0.0
Length: 987, dtype: float64, timestamp
2023-04-16 00:00:00    NaN
2023-04-16 00:15:00    NaN
2023-04-16 00:30:00    NaN
2023-04-16 00:45:00    NaN
2023-04-16 01:00:00    NaN
                      ... 
2023-04-26 05:30:00    0.0
2023-04-26 05:45:00    0.0
2023-04-26 06:00:00    0.0
2023-04-26 06:15:00    0.0
2023-04-26 06:30:00    0.0
Length: 987, dtype: float64)

MFI
timestamp
2023-04-16 00:00:00          NaN
2023-04-16 00:15:00          NaN
2023-04-16 00:30:00          NaN
2023-04-16 00:45:00          NaN
2023-04-16 01:00:00          NaN
                         ...    
2023-04-26 05:30:00     0.000000
2023-04-26 05:45:00     0.000000
2023-04-26 06:00:00     0.000000
2023-04-26 06:15:00    44.627119
2023-04-26 06:30:00    36.565340
Length: 987, dtype: float64

CMO
timestamp
2023-04-16 00:00:00    NaN
2023-04-16 00:15:00    NaN
2023-04-16 00:30:00    NaN
2023-04-16 00:45:00    NaN
2023-04-16 01:00:00    NaN
                      ... 
2023-04-26 05:30:00    0.0
2023-04-26 05:45:00    0.0
2023-04-26 06:00:00    0.0
2023-04-26 06:15:00    0.0
2023-04-26 06:30:00    0.0
Length: 987, dtype: float64
mrjbq7 commented 1 year ago

This is due to passing "very small numbers" to the underlying TA-Lib C library.

You can workaround it two ways:

1) In Python, you can do something similar to this comment (https://github.com/TA-Lib/ta-lib-python/issues/157#issuecomment-335255783), where you adjust the prices before and after calling TA-Lib.

rsi = talib.RSI(prices * 100000) / 100000

2) Compile your own TA-Lib C library with a modification to the TA_IS_ZERO macro as mentioned in this comment (https://github.com/TA-Lib/ta-lib-python/issues/157#issuecomment-367329661).

It has been proposed to upstream that patch as part of our attempts to take ownership of the TA-Lib C library in https://github.com/ta-lib/ta-lib, but I am not yet familiar enough to say that change is without impact.

Elyasnz commented 1 year ago

thanks for your response

please take a look at this part of the C library

it looks like they fixed this issue but how can i upgrade my C library and is it safe to do so?

Elyasnz commented 1 year ago

just checked the commit time of that change and it was back in 2009 I'm wondering why the problem still persists

Elyasnz commented 1 year ago

after some digging into the codes and indicators here is a little help for anyone still struggling with these issues

first of all, let me say the output of some indicators are incorrect just in the case of "very small numbers" and some indicators are incorrect all the time

FirstCase (very small numbers)

I'm using HBAR/BTC in M15 timeframe for the "very small numbers" case with this snippet, you can change the backend C library to handle this problem (I have commented out the rm lines please be careful with them (you can add your own flags on compile) )

#! /usr/bin/env bash
#rm ta-lib-0.4.0-src.tar.gz
#rm ta-lib -r
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar xvfz ta-lib-0.4.0-src.tar.gz
cd ta-lib
sed -i 's/0.00000001/0.000000000000000000000000001/g' src/ta_func/ta_utility.h
./configure
make
make install
cd ..
#rm ta-lib-0.4.0-src.tar.gz
#rm ta-lib -r

after doing this indicators like RSI, BBANDS and BOP will be fixed right away

SecondCase (incorrect indicators)

for this part, the indicators are mostly copied from pandas_ta library and also note that indicators mentioned in this section will be implemented with Python code so there is a little overhead on that but at least the output is correct :)

MFI

this indicator is mostly incorrect with very small numbers and changing TA_IS_ZERO didn't fix the problem so i looked at pandas_ta.mfi

# talib mfi incorrect for very small numbers
typical_price = (data.high + data.low + data.close) / 3.0
typical_price_diff = typical_price.diff()
raw_money_flow = typical_price * data.volume
psum = raw_money_flow.mask(typical_price_diff < 0).fillna(0).rolling(self.timeperiod).sum()
nsum = raw_money_flow.mask(typical_price_diff > 0).fillna(0).rolling(self.timeperiod).sum()
mfi = 100 * psum / (psum + nsum)

CMO

Talib implementation of this indicator is like RSI (with a little shift) (and is also incorrect with "very small numbers") so I looked at pandas_ta.cmo

# talib CMO is incorrect and is like RSI
mom = data.close.diff()
positive = mom.copy().clip(lower=0).rolling(self.timeperiod).sum()
negative = mom.copy().clip(upper=0).abs().rolling(self.timeperiod).sum()
cmo = 100 * (positive - negative) / (positive + negative)

STOCHRSI

honestly, I didn't understand the Talib implementation of this indicator and did not find any output like Talib`s output so I looked at pandas_ta.stochrsi

note that this snippet uses talib RSI so be careful with "very small numbers" (first case)

from talib import RSI, SMA
from sys import float_info as sflt

# talib STOCHRSI is incorrect
rsi_ = RSI(data.close, self.timeperiod)

_rolling = rsi_.rolling(self.timeperiod)
lowest_rsi = _rolling.min()
highest_rsi = _rolling.max()

stoch = 100 * (rsi_ - lowest_rsi)
_diff = highest_rsi - lowest_rsi
if _diff.eq(0).any().any():
    _diff += sflt.epsilon
stoch /= _diff

stochrsi_k = SMA(stoch, self.fastk_period)
stochrsi_d = SMA(stochrsi_k, self.fastd_period)

and also a little note

in tradingview APO indicator uses ema so if you want that make sure to pass matype=1 kwarg

Double Checking

after these changes, I also checked other indicators (ones that I needed) and didn't find any problems with them feel free to take a look at the indicator chart pictures

attiaAR commented 5 months ago

you can add this lines at the end to confirm values are between 0 and 100 , like MFI

Replace negative values with 0

mfi = mfi.clip(lower=0)

Replace values greater than 100 with 100

mfi = mfi.clip(upper=100)