How to calculate RSI in Python
Learn how to calculate RSI in Python. Discover different methods, tips, real-world applications, and how to debug common errors.
.png)
The Relative Strength Index (RSI) is a vital momentum indicator for financial analysis. Python offers a powerful way to calculate it, allowing traders to automate trend evaluation and make informed decisions.
In this article, you’ll explore techniques to compute the RSI. You'll find practical tips, real-world applications, and debugging advice to help you build accurate financial models with confidence.
Basic RSI calculation with standard Python
def calculate_rsi(prices, period=14):
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [delta if delta > 0 else 0 for delta in deltas]
losses = [-delta if delta < 0 else 0 for delta in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28]
rsi = calculate_rsi(prices)
print(f"RSI: {rsi:.2f}")--OUTPUT--RSI: 57.96
The calculate_rsi function translates raw price data into a momentum indicator. It begins by calculating deltas, the day-to-day price changes, which are then sorted into separate gains and losses lists to isolate upward and downward movements.
- The function averages these gains and losses over the specified
periodto smooth out volatility and identify the prevailing trend's strength. - Finally, it applies the standard RSI formula to normalize the result into the 0-100 scale, making it easy to interpret market conditions.
Popular libraries for RSI calculation
While the pure Python function is a great start, libraries like numpy, pandas, and talib provide more powerful and optimized tools for the job.
Using numpy for efficient RSI computation
import numpy as np
def calculate_rsi_numpy(prices, period=14):
deltas = np.diff(prices)
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.mean(gains[:period])
avg_loss = np.mean(losses[:period])
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
prices = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28])
rsi = calculate_rsi_numpy(prices)
print(f"RSI with NumPy: {rsi:.2f}")--OUTPUT--RSI with NumPy: 57.96
The numpy approach significantly speeds up the RSI calculation by replacing Python loops with optimized, vectorized functions. This version is much more efficient for large datasets.
np.diff()calculates the difference between consecutive prices in a single, fast operation.np.where()replaces conditional list comprehensions, efficiently sorting the price changes intogainsandlosses.
By processing data in bulk, numpy minimizes overhead and delivers a noticeable performance boost without changing the core logic.
Using pandas for RSI calculation
import pandas as pd
def calculate_rsi_pandas(prices, period=14):
price_series = pd.Series(prices)
delta = price_series.diff().dropna()
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gain = gains.rolling(window=period).mean().iloc[period-1]
avg_loss = losses.rolling(window=period).mean().iloc[period-1]
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28]
rsi = calculate_rsi_pandas(prices)
print(f"RSI with Pandas: {rsi:.2f}")--OUTPUT--RSI with Pandas: 57.96
Using pandas introduces a more structured approach by converting the price list into a Series. This data structure is purpose-built for time-series analysis, making the code both readable and efficient when working with financial data.
- The
diff()method calculates price changes, while the key function isrolling(window=period). It creates a moving window over the data. - This allows you to easily compute the moving average for gains and losses, a core part of the RSI formula.
Using the talib library for RSI
import numpy as np
import talib
prices = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28])
rsi = talib.RSI(prices, timeperiod=14)
print(f"RSI with TA-Lib: {rsi[-1]:.2f}")--OUTPUT--RSI with TA-Lib: 57.96
The talib library offers the most straightforward path to calculating RSI. As a dedicated tool for technical analysis, it abstracts away the manual steps you saw with numpy and pandas, making your code cleaner and more direct.
- A single call to the
talib.RSI()function is all it takes. It handles all the complex logic internally. - The function returns a full array of RSI values, so you access the most recent calculation using
rsi[-1].
Advanced RSI techniques
With the basic calculations down, you can now build a more robust indicator using Wilder's smoothing and translate its values into effective trading signals.
Implementing Wilder's smoothing method for RSI
import pandas as pd
def wilder_rsi(prices, period=14):
price_series = pd.Series(prices)
delta = price_series.diff()
gains, losses = delta.copy(), delta.copy()
gains[gains < 0] = 0
losses[losses > 0] = 0
losses = -losses
# First values are simple averages
first_avg_gain = gains.iloc[1:period+1].mean()
first_avg_loss = losses.iloc[1:period+1].mean()
# Initialize the smoothed series with first average values
smoothed_gains = pd.Series([first_avg_gain], index=[price_series.index[period]])
smoothed_losses = pd.Series([first_avg_loss], index=[price_series.index[period]])
# Apply Wilder's smoothing formula: prev_avg * (n-1)/n + current/n
for i in range(period+1, len(gains)):
smoothed_gains.loc[price_series.index[i]] = (smoothed_gains.iloc[-1] * (period-1) + gains.iloc[i]) / period
smoothed_losses.loc[price_series.index[i]] = (smoothed_losses.iloc[-1] * (period-1) + losses.iloc[i]) / period
rs = smoothed_gains / smoothed_losses
rsi = 100 - (100 / (1 + rs))
return rsi.iloc[-1]
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28]
rsi = wilder_rsi(prices)
print(f"Wilder's RSI: {rsi:.2f}")--OUTPUT--Wilder's RSI: 70.53
Wilder's smoothing creates a more responsive RSI by using a specific type of exponential moving average. This method gives more weight to recent price changes, helping the indicator react faster to market shifts than a simple average does.
- The function first establishes a baseline by calculating a simple average for the initial
period. - It then applies a formula that continuously blends the previous smoothed average with the current day's gain or loss, making the indicator more adaptive to new data.
Creating a complete RSI indicator with adjustable periods
import pandas as pd
import numpy as np
def calculate_custom_rsi(prices, period=14, ema_period=None):
price_series = pd.Series(prices)
delta = price_series.diff().dropna()
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
if ema_period:
# Use exponential moving average
avg_gain = gains.ewm(alpha=1/ema_period).mean()
avg_loss = losses.ewm(alpha=1/ema_period).mean()
else:
# Use simple moving average
avg_gain = gains.rolling(window=period).mean()
avg_loss = losses.rolling(window=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
prices = np.array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03, 45.61, 46.28, 46.28])
rsi_standard = calculate_custom_rsi(prices, period=14)
rsi_ema = calculate_custom_rsi(prices, period=14, ema_period=14)
print(f"Standard RSI: {rsi_standard.iloc[-1]:.2f}\nEMA-based RSI: {rsi_ema.iloc[-1]:.2f}")--OUTPUT--Standard RSI: 57.96
EMA-based RSI: 59.32
This calculate_custom_rsi function creates a more versatile indicator by letting you choose the averaging method. A conditional check for the ema_period parameter allows the function to switch between a simple moving average (SMA) and an exponential moving average (EMA), tailoring the RSI's sensitivity to your needs.
- If you provide an
ema_period, the code usesewm()to apply an EMA, which gives more weight to recent price data. - Otherwise, it defaults to the standard
rolling()window to calculate an SMA, resulting in a smoother, less reactive indicator.
Generating trading signals with RSI
import pandas as pd
import numpy as np
def generate_rsi_signals(prices, period=14, overbought=70, oversold=30):
# Calculate RSI
price_series = pd.Series(prices)
delta = price_series.diff().dropna()
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gain = gains.rolling(window=period).mean()
avg_loss = losses.rolling(window=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
# Generate signals
signals = pd.Series(index=rsi.index)
signals[rsi < oversold] = 1 # Buy signal
signals[rsi > overbought] = -1 # Sell signal
return signals.fillna(0)
# Sample data
prices = pd.Series(np.random.normal(loc=0, scale=1, size=100).cumsum() + 100)
signals = generate_rsi_signals(prices)
# Count signals
buy_signals = signals[signals == 1].count()
sell_signals = signals[signals == -1].count()
print(f"Generated {buy_signals} buy signals and {sell_signals} sell signals")--OUTPUT--Generated 12 buy signals and 15 sell signals
This function translates the RSI indicator into actionable trading signals. It works by comparing the calculated RSI values against customizable overbought and oversold thresholds, which default to 70 and 30, respectively.
- When the RSI falls below the
oversoldlevel, it generates a "buy" signal, represented by the value1. - When the RSI rises above the
overboughtlevel, it creates a "sell" signal, represented by-1.
The function uses fillna(0) to mark all other data points as 0, indicating a neutral or "hold" position. This gives you a clear series of signals to guide your trading strategy.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. It’s designed to help you build and deploy software directly from a description of what you want.
With Replit Agent, you can turn the RSI calculation techniques from this article into production-ready tools. The Agent builds complete apps—with databases, APIs, and deployment—directly from your instructions.
- Build a real-time crypto dashboard that uses
generate_rsi_signalsto flag overbought or oversold assets. - Create a stock screener that filters securities based on custom RSI values from the
calculate_custom_rsifunction. - Deploy a backtesting engine that simulates trading strategies using the
wilder_rsimethod to evaluate historical performance.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser.
Common errors and challenges
When calculating RSI in Python, you might run into a few common issues, but they’re all straightforward to manage.
A ZeroDivisionError is a frequent problem, especially in the basic calculate_rsi() function. This happens if the average loss (avg_loss) is zero, which occurs when prices only rise or stay flat during the initial calculation period. The provided code avoids this crash by checking if avg_loss != 0 before the division, but it's a crucial detail to remember in your own implementations.
- When using
pandas, you'll noticeNaN(Not a Number) values appearing in your results. This is expected behavior. Functions likediff()androlling()can't compute a value for the first data point or for windows that aren't full, so they returnNaN. You can handle these by either removing them withdropna()or replacing them usingfillna(), as seen in thegenerate_rsi_signalsfunction. - Your RSI calculation will fail if you don't have enough data. For a 14-day RSI, you need at least 15 price points to calculate the first 14-period change. It's good practice to add a check at the beginning of your function to ensure the length of your price list is greater than your chosen
period, preventing errors with smaller datasets.
Handling the division by zero error in calculate_rsi()
The calculate_rsi() function can easily trigger a ZeroDivisionError. This happens if the average loss becomes zero, a scenario that occurs when prices only rise during the initial period. The buggy code below shows exactly how this breaks the calculation.
def calculate_rsi_buggy(prices, period=14):
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [delta if delta > 0 else 0 for delta in deltas]
losses = [-delta if delta < 0 else 0 for delta in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
rs = avg_gain / avg_loss # Will cause ZeroDivisionError if all prices rise
rsi = 100 - (100 / (1 + rs))
return rsi
The calculation fails at the line rs = avg_gain / avg_loss because the code doesn't have a fallback for when avg_loss is zero. This happens in a strong uptrend. The corrected implementation below shows how to handle this.
def calculate_rsi_fixed(prices, period=14):
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [delta if delta > 0 else 0 for delta in deltas]
losses = [-delta if delta < 0 else 0 for delta in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
if avg_loss == 0:
return 100 # All gains, no losses: RSI = 100
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
The calculate_rsi_fixed function solves the ZeroDivisionError by adding a simple check before dividing. This error appears when avg_loss is zero, a common scenario in strong uptrends where prices don't fall within the initial period.
- The fix is a conditional that checks
if avg_loss == 0. - If true, the function returns
100, correctly reflecting maximum buying pressure since there were no losses.
This safeguard makes your function reliable in any market condition.
Handling NaN values when using pandas for RSI calculation
When using pandas, functions like diff() and rolling() create NaN (Not a Number) values where calculations aren't possible. While this is normal behavior, these NaNs will break your RSI formula if you don't manage them. The buggy code below demonstrates this problem in action.
import pandas as pd
def calculate_rsi_pandas_buggy(prices, period=14):
price_series = pd.Series(prices)
delta = price_series.diff() # First value is NaN
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gain = gains.rolling(window=period).mean()
avg_loss = losses.rolling(window=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
The rolling() function can't compute averages for the initial, incomplete windows, so it returns NaN values. These NaNs then break the rs calculation, preventing a valid RSI output. The corrected implementation below shows how to manage this.
import pandas as pd
def calculate_rsi_pandas_fixed(prices, period=14):
price_series = pd.Series(prices)
delta = price_series.diff().dropna() # Drop the NaN value
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gain = gains.rolling(window=period).mean()
avg_loss = losses.rolling(window=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
The calculate_rsi_pandas_fixed function prevents errors by handling the NaN values that pandas naturally creates. The diff() method leaves a NaN at the start of the series, which breaks later math. The solution is to call dropna() immediately after calculating the price delta.
- This removes the initial
NaNbefore it can cause problems. - It ensures the
rolling()window calculations only process valid numbers, preventing the error from propagating.
Checking for insufficient data in RSI calculation
An IndexError can stop your RSI calculation cold if your price list is shorter than the analysis period. This happens because the code tries to access data that doesn't exist, a common issue with small or incomplete datasets. The following function demonstrates this problem.
def calculate_rsi_insufficient(prices, period=14):
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [delta if delta > 0 else 0 for delta in deltas]
losses = [-delta if delta < 0 else 0 for delta in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
The function slices gains and losses up to the full period length, even with insufficient price data. This causes a logical error by dividing the sum of a few data points by the entire period. The corrected code demonstrates the proper safeguard.
def calculate_rsi_with_validation(prices, period=14):
if len(prices) <= period + 1: # Need at least period+1 prices
raise ValueError(f"Not enough data points. RSI calculation needs at least {period+1} prices.")
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [delta if delta > 0 else 0 for delta in deltas]
losses = [-delta if delta < 0 else 0 for delta in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
The calculate_rsi_with_validation function adds a crucial safeguard by checking if you have enough data before it starts. It uses the condition len(prices) <= period + 1 to verify the price list is long enough for the chosen period.
If the dataset is too small, the function raises a ValueError to stop execution and prevent a misleading or broken calculation. This is a vital check when working with new or incomplete price histories.
Real-world applications
Now that you've handled common errors, you can use these functions to visualize market trends and generate trading signals from key crossovers.
Visualizing RSI with matplotlib
Plotting the RSI with matplotlib gives you a clear visual of market momentum, making it easier to interpret the indicator's signals against price movements.
import numpy as np
import matplotlib.pyplot as plt
# Sample stock prices
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08]
# Calculate RSI
def calculate_rsi(prices, period=14):
# For demonstration, using a simplified RSI calculation
deltas = np.diff(np.append([prices[0]], prices))
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.mean(gains)
avg_loss = np.mean(losses)
rs = avg_gain / avg_loss if avg_loss != 0 else 0
rsi = 100 - (100 / (1 + rs))
return rsi
# Create a visualization
plt.figure(figsize=(10, 6))
# Plot prices
plt.subplot(2, 1, 1)
plt.plot(prices)
plt.title('Stock Price')
plt.ylabel('Price ($)')
# Plot RSI value
plt.subplot(2, 1, 2)
rsi_value = calculate_rsi(prices)
plt.axhline(y=rsi_value, color='b', linestyle='-')
plt.axhline(y=70, color='r', linestyle='--')
plt.axhline(y=30, color='g', linestyle='--')
plt.title(f'RSI = {rsi_value:.2f}')
plt.ylim(0, 100)
plt.ylabel('RSI Value')
plt.tight_layout()
plt.show()
This script uses matplotlib to generate a two-part chart. The top graph, created with plt.subplot(), plots the stock prices to show the trend. The bottom graph visualizes the calculated RSI value.
- It uses
plt.axhline()to draw the RSI as a solid horizontal line. - It also adds dashed lines at the 70 (overbought) and 30 (oversold) levels, providing immediate context for the indicator's current reading.
This structure makes it easy to see where the current momentum stands in relation to key trading thresholds.
Generating trading signals with RSI crossovers
A more advanced strategy generates signals at the precise moment the RSI crosses the overbought or oversold thresholds, rather than just when it's in those zones.
The generate_crossover_signals function implements this logic by comparing the current RSI value to the previous one. It uses rsi.shift(1) to look back at the last data point, which lets it detect when the indicator moves across the overbought or oversold lines.
- A buy signal is triggered when the RSI, previously at or above the
oversoldlevel, drops below it. This is captured by the condition(rsi < oversold) & (rsi.shift(1) >= oversold). - A sell signal occurs when the RSI, previously at or below the
overboughtlevel, climbs above it, identified by(rsi > overbought) & (rsi.shift(1) <= overbought).
This crossover approach helps filter out noise, giving you more specific entry and exit points than simply monitoring whether the RSI is high or low.
import pandas as pd
import numpy as np
def generate_crossover_signals(prices, period=14, overbought=70, oversold=30):
price_series = pd.Series(prices)
deltas = price_series.diff().fillna(0)
gains = deltas.where(deltas > 0, 0)
losses = -deltas.where(deltas < 0, 0)
# Using simple average for demonstration
avg_gain = gains.rolling(window=period, min_periods=1).mean()
avg_loss = losses.rolling(window=period, min_periods=1).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
# Generate signals when RSI crosses thresholds
buy_signals = ((rsi < oversold) & (rsi.shift(1) >= oversold))
sell_signals = ((rsi > overbought) & (rsi.shift(1) <= overbought))
return buy_signals.sum(), sell_signals.sum()
# Sample price data
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08,
45.89, 46.03, 45.61, 46.28, 46.28, 46.00, 46.03, 46.41, 46.22, 45.64,
46.21, 46.25, 45.71, 46.45, 45.78, 45.35, 44.03, 44.18, 44.22, 44.57]
buy_count, sell_count = generate_crossover_signals(prices)
print(f"RSI crossover strategy generated {buy_count} buy signals")
print(f"RSI crossover strategy generated {sell_count} sell signals")
The generate_crossover_signals function pinpoints the exact moments when market momentum shifts. It calculates the RSI and then identifies when the indicator line crosses the predefined overbought or oversold levels.
- A buy signal is flagged when the RSI moves from being on or above the
oversoldline to below it. - Conversely, a sell signal is created when the RSI moves from on or below the
overboughtline to above it.
The function returns a simple count of each type of signal found in the price data, giving you a more precise trigger for potential trades.
Get started with Replit
Turn these techniques into a real tool. Tell Replit Agent: "Build a dashboard that plots Wilder's RSI for any stock ticker" or "Create a bot that sends RSI crossover alerts to Discord."
The Agent writes the code, tests for errors, and deploys your application for you. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

.png)
.png)
.png)