# Why two libraries give different RSI, EMA and ATR values

RSI, EMA and ATR are recursive, so the seed, the smoothing constant, price adjustment and bar boundaries all change the number. Where libraries differ.

*https://stockmarketstack.com/guides/why-indicator-values-differ · background to Market Analysis & Portfolio Optimization Libraries*

**Answer:** Usually because the formula agrees and everything around it does not. RSI and ATR are recursive: each value folds in the one before, so the result depends on how the recursion was seeded and how many bars preceded it. Libraries differ on that seed, on Wilder's 1/n smoothing versus the EMA's 2/(n+1), on default periods, on whether the prices were adjusted for splits and dividends, and on which trades each bar contains.

## How it works

A simple moving average forgets. A 14-bar SMA is the mean of the last fourteen closes, and two
libraries given the same fourteen numbers return the same answer to the last bit. Three of the
most-used indicators are not built that way.

The **EMA** is a recursion: today's value is yesterday's value nudged toward today's price by a
fixed fraction, `alpha`. **RSI** runs the same kind of recursion twice, once over the bar-to-bar
gains and once over the losses, and divides one by the sum of both. **ATR** runs it over the true
range — the largest of high minus low, high minus the previous close, and low minus the previous
close. A comment in TA-Lib's ATR source notes that Wilder's averaging gives it an unstable period
comparable to an EMA's, and the project's documentation puts the general point plainly: each
output "depends on the previous one, seeded from the start of the data".

So every value on the chart carries a trace of every bar before it, back to the first bar the
calculation was given. That is where the disagreements come from, and there are five of them,
each independent of the others:

1. **The seed** — what the recursion is started from on its first bar.
2. **The smoothing constant** — which `alpha` a given period number turns into.
3. **The defaults** — the period, the averaging type and the implementation a call gets when it
   names none.
4. **The input** — raw closes, split-adjusted closes, or split-and-dividend-adjusted closes.
5. **The bars** — which trades went into each open, high, low and close.

The first two are properties of the library and can be read in its source. The last two are
properties of the data and are usually decided upstream of the code, by whoever built the bars.
None of them is a bug. Each is a choice, and the choices are rarely printed next to the number.

## The seed: where the recursion starts

A recursion needs a yesterday on its first day, and there are two common answers.

**Seed with a simple average.** [TA-Lib](https://stockmarketstack.com/tools/ta-lib) sums the first `period` values, divides
by the period, and starts the recursion from that. Its RSI does it for the first fourteen gains
and losses, its ATR for the first fourteen true ranges, its EMA for the first `period` prices. The
same convention appears in backtrader's `ExponentialSmoothing`, whose docstring says an arithmetic
mean "is used as the seed value considering the first period values of data", and in
pandas-ta-classic, whose `rma` and `ema` both compute an SMA of the first `length` valid values
and place it at the seed position — the `rma` comment reads "SMA-seeded Wilder smoothing (matches
TA-Lib)".

**Seed with the first value.** pandas' own `ewm(..., adjust=False)` is defined as `y0 = x0`: the
first observation is the first average. The last release of the original
[pandas-ta](https://stockmarketstack.com/tools/pandas-ta), 0.4.71b0, builds its Wilder average as
`close.ewm(alpha=1/length, adjust=False).mean()` with no SMA step in front of it, so its native RSI
starts from the first gain and the first loss rather than from an average of fourteen. Its ATR, in
the same release, does put an SMA seed in front of the true range, behind a `presma` option that
defaults to on — two indicators in one package, seeded two ways.
TA-Lib used to offer the same choice for its EMA as a MetaStock compatibility mode; the 0.8.1
release notes record that variant as removed, with the default behaviour unchanged.

A third answer is pandas' default, `adjust=True`, which is not a recursion at all but a weighted
mean over every value so far, with weights `(1 − alpha)^i` normalised by their sum. It converges
to the same place and takes a different path getting there.

**How long the seed matters** is arithmetic, not opinion. After `k` further bars the seed's weight
in the average is `(1 − alpha)^k`:

| Smoothing | alpha | Seed weight after 50 bars | after 100 | Bars until under 1% | under 0.1% |
|---|---|---|---|---|---|
| Wilder, period 14 | 1/14 | 2.5% | 0.06% | 63 | 94 |
| EMA, period 14 | 2/15 | 0.08% | 0.00006% | 33 | 49 |
| EMA, period 30 | 2/31 | 3.6% | 0.13% | 70 | 104 |
| Wilder, period 20 | 1/20 | 7.7% | 0.6% | 90 | 135 |

Two consequences follow. First, two libraries with different seeds agree only once enough bars
have passed, and for a 14-period RSI that is roughly a hundred bars, not fourteen. Second, the same
library disagrees *with itself* when it is given a different amount of history: an RSI computed
from 1 January and one computed from 1 March will not match on 1 April, because they were seeded
on different bars. A charting platform seeds on the first bar it loaded, which is a property of
the chart's history, not of the formula.

TA-Lib makes the warm-up explicit. The first RSI value it returns is at index `period`, the first
EMA at `period − 1`, and on top of that it has an **unstable period** per function — zero by
default — which computes that many extra bars and throws them away before returning anything. The
documented list includes EMA, RSI, ATR, NATR, ADX, CMO, KAMA and T3. In Python it is
`talib.set_unstable_period('RSI', 100)`. The setting follows the function into anything built on
it, so the EMA setting also moves MACD and DEMA. The project recommends no value, saying only that
"the larger the value, the later the first output", and its own illustration — three 20-bar EMAs
of the same prices fed from bars 0, 10 and 20 — shows them settling within an acceptable margin
from bar 65 on. The same page is candid that ignoring the problem is "what most charting sites
do, and usually fine", because the latest bar has plenty of history behind it; it is the start of
a short series, or of a backtest, that quietly carries the distorted values.

## Wilder's smoothing is not the EMA's

"Period 14" does not name a smoothing constant. It names one of two, and which one depends on the
indicator.

- **The EMA convention**: `alpha = 2 / (period + 1)`. For period 14 that is 2/15, about 0.133.
  TA-Lib's EMA computes `2.0 / (period + 1)`; backtrader's default is `2.0 / (1.0 + period)`;
  pandas calls the same mapping `span`.
- **Wilder's convention**: `alpha = 1 / period`. For period 14 that is 1/14, about 0.071. TA-Lib's
  RSI implements it the long way round — multiply the previous average by `period − 1`, add
  today's value, scale by `1/period` — and its ATR as `(period − 1)/period` of the previous
  average plus `1/period` of today's true range; both are the same recursion. pandas
  reaches it with `ewm(alpha=1/period)` or, equivalently, `com=period − 1`. backtrader calls it
  `SmoothedMovingAverage`, pandas-ta calls it `rma`, and TradingView's help page for RSI shows its
  averages as `rma(gain, 14)` and `rma(loss, 14)`.

Wilder's 1/14 is the EMA's alpha for a period of 27. A "14-period RSI" rebuilt with the EMA mapping —
`ewm(span=14)` over the gains and losses, which is what a hand-rolled pandas version often does —
is therefore not a slightly different RSI but an RSI with roughly half the memory, and it
diverges from TA-Lib's on every bar, not only in the warm-up. backtrader ships the variants side
by side under separate names: `RSI_EMA`, and `RSI_SMA` (aliased `RSI_Cutler`), beside a default
`RSI` — aliased `RSI_Wilder` — whose `movav` parameter is the smoothed average.

ATR has the same fork, and here it is a user setting rather than a coding slip. TradingView's help
page says its ATR is by default an RMA of the true range, "but the smoothing type can be changed
to SMA, EMA or WMA in the settings". pandas-ta's `atr` takes a `mamode` argument defaulting to
`"rma"`. A chart that someone once switched to EMA smoothing keeps showing a number labelled ATR
14 that no library called with defaults will reproduce.

There is a quieter version of this in the RSI formula itself. Wilder's `100 − 100 / (1 + RS)` and
TA-Lib's `100 × gain / (gain + loss)` are algebraically identical — TA-Lib's source says so and
uses the second for speed — but they part company when both averages are zero, in a series that
has not moved since the seed. TA-Lib returns 0. pandas-ta's native path divides zero by zero and
returns NaN. backtrader divides as well unless `safediv` is switched on, in which case its
docstring says it returns `safelow`, 50 by default, for the `0 / 0` case and `safehigh`, 100, for
`x / 0`. It takes fourteen unchanged closes in a row, which daily bars of a traded stock almost
never produce and one-minute bars of a thinly traded one can.

## Same name, different defaults

A call that names no period gets the library's opinion, and the libraries do not share one. RSI
and ATR default to 14 almost everywhere — TA-Lib, pandas-ta, pandas-ta-classic and backtrader all
agree. The EMA does not: TA-Lib's defaults to 30, backtrader's moving averages to 30, pandas-ta's
and pandas-ta-classic's to 10. `EMA(close)` in one and `df.ta.ema()` in the other are a 30-bar and
a 10-bar average under the same name.

Defaults also move between versions of the same library. TA-Lib 0.8.1's release notes list two
that change output without changing a line of calling code: BBANDS' default period went from 5
to 20, and PPO and APO now default to an EMA where they used to default to an SMA, with
`TA_MAType_SMA` to be passed explicitly for the old behaviour. The same release re-ordered
floating-point work in RSI, EMA and ATR; the notes quantify it at no more than 5.7e-14 on RSI's
0–100 scale at the default period, which is noise, and is listed here only so it is not mistaken
for the cause of a real disagreement.

The quietest default is **which implementation runs at all**. pandas-ta 0.4.71b0's `rsi`, `atr`
and `ema` each take a `talib` argument whose default is `True`, and the code path is
`if Imports["talib"] and mode_tal:` — so when TA-Lib happens to be installed, the call returns
TA-Lib's numbers, and when it is not, the call returns pandas-ta's own. For RSI those are the two
different seeds described above. The same notebook gives different early values on two machines
because of a package neither line of it imports. pandas-ta-classic reversed the default: its
`talib` flag defaults to `False`, so TA-Lib is used only when asked for.

## Adjusted or raw prices

Every indicator here is computed from price differences or price levels, so it inherits whatever
was done to the prices before they arrived. [Why two providers give different returns for the
same stock](https://stockmarketstack.com/guides/why-adjusted-close-differs) covers how adjustment works; what follows is what
each choice does to an indicator.

**Raw prices put the corporate action into the indicator.** A four-for-one split in an unadjusted
series is a one-bar fall of three quarters of the price. RSI books it as a single loss of that
size, and its share of the loss average then decays on the schedule in the table above — slowly,
from a loss dozens of times the size of an ordinary day's move, so it can outweigh everything
else in that average for weeks of daily bars. ATR's true range on the ex-date is the distance from the
previous close to the new low, again about three quarters of the old price, and ATR takes it in
at weight 1/14. An EMA drifts down from the old level toward the new one over the following bars.
None of this is an error in the library: the input says the price fell, and the formula believed
it. A cash dividend does the same on a small scale, as a one-bar gap down of roughly the amount
paid.

**Ratio adjustment rescales history, and the indicators measured in price rescale with it.** A
multiplicative factor applied to every bar before an event multiplies every EMA and ATR value
before it by the same factor, so the ATR a chart shows for three years ago is not the dollar
range anybody saw that day. RSI is a ratio of two averages built from the same differences, so a
factor applied uniformly to all of its history cancels; its values before the newest event do not
move when that event is applied. Where it does change is across the event, because the
differences either side of it are now on different scales.

**Difference adjustment preserves differences.** Subtracting the cash from every earlier price
leaves every bar-to-bar change exactly as traded and removes only the ex-date gap, so RSI and ATR
before the event are untouched and differ afterwards only by the ex-date bar's contribution,
decaying on the schedule above. It changes the price levels an EMA is built from, and over decades it
can drive early prices below zero, as the adjusted-close guide shows.

The upshot: raw, ratio-adjusted and difference-adjusted inputs give three different ATR histories
and three different RSI paths through every split and dividend, from the same library, called the
same way. A chart and a script disagree most often not because of the code but because one was
fed adjusted bars and the other was not, and the provider's documentation is where to find out
which.

## Where a bar begins and ends

Before any library runs, somebody decided which trades belong to each bar, and for US equities
there are three decisions in it.

**Which session.** NYSE's core session runs 9:30 a.m. to 4:00 p.m. ET. Its sister venues trade
outside it — NYSE Arca from 4:00 a.m., and late trading until 8:00 p.m. — and the exchange closes
at 1:00 p.m. on certain days around holidays. A bar series built from regular hours only and one that includes
extended hours differ in their highs and lows, and therefore in every true range, before a single
ATR is computed. [TradingView](https://stockmarketstack.com/tools/tradingview) makes it a chart setting — the ETH option, or
the Symbol tab in Settings — and its help page describes extended hours as something shown on
intraday charts, where the symbol's exchange provides them. A 5-minute RSI on a chart with ETH on and a 5-minute RSI from a regular-hours feed are
computed over different bars.

**Where an intraday bar starts.** A regular session that opens at 9:30 does not divide into
clock hours. pandas' `resample` aligns its bins to midnight of the first day by default
(`origin='start_day'`) and labels each bin by its left edge, so hourly bars built from minute data
with default arguments run 9:00–10:00, 10:00–11:00 and so on, and the first "hour" holds thirty
minutes. Pass `offset='30min'` and the bins start at 9:30 instead — and now the last one, from
15:30, is the half-hour bar. Neither is wrong; they are different bars, with different closes, and an hourly EMA over
one does not match an hourly EMA over the other at any bar.

**Which clock.** The same `resample` bins in whatever timezone the index carries. A minute series
stored in UTC and resampled to daily bars cuts each "day" at midnight UTC, which is 8 p.m. or
7 p.m. in New York depending on daylight saving time. If the minute data includes extended hours,
the daily bar then holds the session plus the trading around it, and in winter the last hour of
late trading lands in the next day's bar; where one day ends and the next begins moves by an hour
twice a year. Converting the index to `America/New_York` before resampling, or
building daily bars from an exchange calendar, removes the problem.
[exchange_calendars](https://stockmarketstack.com/tools/exchange-calendars) publishes each session's open and close, in UTC,
early closes included, which is the list a daily bar is supposed to follow.

## What you can do about it

**Feed more history than the period, and throw the start away.** For a 14-period RSI or ATR, a
hundred bars of warm-up takes the seed's weight under 0.1%, and the differences between seeding
conventions go with it. Either slice the first hundred outputs off yourself or let TA-Lib do it
with `talib.set_unstable_period`. The corollary: an indicator value for the first few weeks of a
series, or of a newly listed stock, is a property of the seed more than of the prices, and should
not be compared across tools at all.

**Pin the implementation, not just the package.** If you use pandas-ta, pass `talib=True` or
`talib=False` explicitly so the result does not depend on what else is installed. With
[TA-Lib](https://stockmarketstack.com/tools/ta-lib), read the release notes before upgrading across 0.8.1 — BBANDS, PPO and APO
changed defaults there. Pass every period and averaging type explicitly instead of relying on a
default; `EMA(close, timeperiod=20)` is a 20-bar EMA wherever it runs, while `EMA(close)` is 30 bars
in one library and 10 in another.

**Match the smoothing to the indicator.** Wilder's RSI and ATR use `alpha = 1/period`. In pandas
that is `ewm(alpha=1/period, adjust=False)` over an SMA seed — not `ewm(span=period)`, and not the
default `adjust=True`. On a chart, check the ATR's smoothing setting before comparing it with
anything.

**Reconcile on one bar, from the same inputs.** To find out why a chart and a script disagree,
export the chart's bars if the platform allows it, run the library over exactly those bars with
the same history length, and compare. If the numbers now agree, the difference was in the data —
adjustment, session, or bar boundaries — and not in the formula. If they still disagree after a
hundred bars, the smoothing constant or the averaging type differs.

**Decide the input before the indicator.** Choose raw, ratio-adjusted or difference-adjusted
prices on purpose and record the choice next to the output, the way
[the adjusted-close guide](https://stockmarketstack.com/guides/why-adjusted-close-differs) recommends storing raw prices plus a
corporate actions table. An ATR from three years ago is the dollar range that traded that day only
on the raw series; on a ratio-adjusted one it has been rescaled by every event since.

**Build bars in exchange time.** Convert timestamps to `America/New_York` before resampling, align
intraday bins to 9:30 when that is what you are comparing against, decide whether extended hours
belong in your bars, and take session opens, closes and early closes from
[exchange_calendars](https://stockmarketstack.com/tools/exchange-calendars) rather than from a hard-coded 9:30–16:00. The rest
of the libraries in this section are listed under
[analysis libraries](https://stockmarketstack.com/categories/analysis-libraries).

## Tools this bears on

- [TA-Lib](https://stockmarketstack.com/tools/ta-lib.md) — The C indicator library everything else wraps — and pip now ships the C part with it.
- [pandas-ta](https://stockmarketstack.com/tools/pandas-ta.md) — The DataFrame indicator library whose repo is gone and whose last release is a year old.
- [TradingView](https://stockmarketstack.com/tools/tradingview.md) — Charting across 50-plus markets, with Pine Script, server-side alerts and broker trading.
- [exchange_calendars](https://stockmarketstack.com/tools/exchange-calendars.md) — Sessions, minutes and holidays for 69 exchanges, keyed by ISO-10383 code.

## FAQ

### Why does my RSI not match TradingView's for the same stock?

The formula is almost certainly the same Wilder average; the inputs around it are not. The usual causes, in order, are a different amount of history before the bar you are comparing, split or dividend adjustment applied on one side and not the other, extended-hours bars on an intraday chart, and hourly bars that start at 9:00 in one place and 9:30 in the other.

### How many bars of history does a 14-period RSI need before it is reliable?

RSI uses Wilder's smoothing, alpha 1/14, so the seed's weight falls below 1 per cent after 63 further bars and below 0.1 per cent after 94. Supplying about a hundred bars before the first value you use removes almost all of the difference between seeding conventions. Fourteen bars is the minimum to produce a number, not the minimum to produce a stable one.

### Is Wilder's smoothing the same as an EMA?

It is the same recursion with a different constant. An EMA of period n uses alpha 2/(n+1); Wilder's average of period n uses alpha 1/n, which is the EMA of period 2n-1. So a 14-period Wilder average behaves like a 27-period EMA, and an RSI rebuilt with a 14-period EMA is a faster, different indicator.

### Why does pandas-ta give different values on two machines?

In release 0.4.71b0, rsi, atr and ema default to calling TA-Lib when TA-Lib is installed and fall back to their own code when it is not, and the native RSI seeds its average differently from TA-Lib's. Pass the talib argument explicitly, or use pandas-ta-classic, where the default is to use its own code.

### Should indicators be computed on adjusted or unadjusted prices?

It depends on what the number is for, and the choice has to be made on purpose. Unadjusted prices put every split and dividend into the indicator as a real price move; ratio-adjusted prices remove those gaps but rescale every earlier price-denominated value such as ATR and EMA. Record which one you used next to the output.

## Sources

1. [ta_RSI.c](https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_RSI.c) — TA-Lib project (GitHub), read 2026-09-27
2. [ta_EMA.c](https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_EMA.c) — TA-Lib project (GitHub), read 2026-09-27
3. [ta_ATR.c](https://github.com/TA-Lib/ta-lib/blob/main/src/ta_func/ta_ATR.c) — TA-Lib project (GitHub), read 2026-09-27
4. [ta_EMA.c at v0.6.4 (TA_MA_METASTOCK seeding)](https://github.com/TA-Lib/ta-lib/blob/v0.6.4/src/ta_func/ta_EMA.c) — TA-Lib project (GitHub), read 2026-09-27
5. [talib/__init__.py (set_unstable_period)](https://github.com/TA-Lib/ta-lib-python/blob/master/talib/__init__.py) — TA-Lib Python wrapper project (GitHub), read 2026-09-27
6. [Unstable Period](https://ta-lib.org/api/unstable-period/) — TA-Lib project, 2026-09-08
7. [TA-Lib 0.8.1 release notes](https://github.com/TA-Lib/ta-lib/releases/tag/v0.8.1) — TA-Lib project (GitHub), 2026-09-12
8. [pandas-ta 0.4.71b0 (wheel source, overlap/rma.py, overlap/ema.py, momentum/rsi.py, volatility/atr.py)](https://pypi.org/project/pandas-ta/0.4.71b0/) — Python Package Index, 2025-09-14
9. [pandas_ta_classic/overlap/rma.py](https://github.com/xgboosted/pandas-ta-classic/blob/main/pandas_ta_classic/overlap/rma.py) — pandas-ta-classic project (GitHub), read 2026-09-27
10. [pandas_ta_classic/overlap/ema.py](https://github.com/xgboosted/pandas-ta-classic/blob/main/pandas_ta_classic/overlap/ema.py) — pandas-ta-classic project (GitHub), read 2026-09-27
11. [backtrader/indicators/basicops.py](https://github.com/mementum/backtrader/blob/master/backtrader/indicators/basicops.py) — backtrader project (GitHub), read 2026-09-27
12. [backtrader/indicators/mabase.py (default period 30)](https://github.com/mementum/backtrader/blob/master/backtrader/indicators/mabase.py) — backtrader project (GitHub), read 2026-09-27
13. [backtrader/indicators/rsi.py](https://github.com/mementum/backtrader/blob/master/backtrader/indicators/rsi.py) — backtrader project (GitHub), read 2026-09-27
14. [pandas.DataFrame.ewm (pandas 3.0.6)](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html) — pandas development team, read 2026-09-27
15. [pandas.DataFrame.resample (pandas 3.0.6)](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html) — pandas development team, read 2026-09-27
16. [Relative Strength Index (RSI)](https://www.tradingview.com/support/solutions/43000502338-relative-strength-index-rsi/) — TradingView, read 2026-09-27
17. [Average True Range (ATR)](https://www.tradingview.com/support/solutions/43000501823-average-true-range-atr/) — TradingView, read 2026-09-27
18. [I want to access Extended Hours data](https://www.tradingview.com/support/solutions/43000502023-i-want-to-access-extended-hours-data/) — TradingView, read 2026-09-27
19. [Holidays and Trading Hours](https://www.nyse.com/markets/hours-calendars) — New York Stock Exchange, read 2026-09-27

*Last updated 2026-09-27. A reference page, corrected in place — not a dated post.*
