How BanTA Calculates RSI and Its Variations: A Deep Dive into the banta Library
BanTA calculates RSI using Wilder's smoothing EMA with α=1/period, offering both stateless slice functions and stateful Series APIs that support classic RSI, RSI-50, Connors RSI, and Stochastic RSI variations.
The BanTA technical analysis engine powers the open-source banbox/banta repository, providing high-performance RSI calculations in pure Go. Whether you are processing historical batches or streaming real-time data, understanding how BanTA implements the Relative Strength Index and its derivatives ensures you select the right API for your trading algorithms.
Core RSI Implementation Architecture
BanTA organizes its RSI logic into two distinct layers to optimize for both batch processing and incremental updates.
Stateless Numeric Functions in tav/indicators.go
The foundation resides in tav/indicators.go, where pure functions operate on raw []float64 slices without maintaining state between calls. The entry point RSI delegates to RSIBy, which implements the classic Wilder smoothing method.
// RSI calculates the Relative Strength Index.
// It simply forwards to RSIBy with a zero subtraction.
func RSI(data []float64, period int) []float64 {
return RSIBy(data, period, 0)
}
RSIBy accepts a subVal parameter that subtracts a constant from the final result. This mechanism powers the RSI-50 variant by passing subVal = 50, shifting the output range from 0-100 to -50 to +50.
The function handles Wilder's smoothing through a recursive EMA with α = 1/period. During the initial seed phase (the first period bars), it calculates a simple moving average (SMA) of gains and losses. Once the seed period completes, it switches to the recursive formula:
avgGain = (avgGain*float64(period-1) + gainDelta) / float64(period)
avgLoss = (avgLoss*float64(period-1) + lossDelta) / float64(period)
Stateful Series API in sta_inds.go
For streaming applications where new data arrives incrementally, BanTA provides a stateful Series API in sta_inds.go. The rsiBy function maintains a state vector stored in res.More containing four floats: previous close, average gain, average loss, and valid count.
// State vector stored in res.More:
// [0] previous close, [1] avgGain, [2] avgLoss, [3] validCount
This design enables memoization through the To method, which checks res.Cached() to avoid recomputing values when the same series is queried repeatedly. The wrapper functions RSI and RSI50 provide clean interfaces:
func RSI(obj *Series, period int) *Series {
return rsiBy(obj, period, 0) // classic 0-100 RSI
}
func RSI50(obj *Series, period int) *Series {
return rsiBy(obj, period, 50) // shifted -50..+50 variant
}
RSI Variations and Composite Indicators
BanTA extends the base RSI calculation to support several specialized variants used in quantitative trading strategies.
RSI-50 (Centered RSI)
The RSI-50 variation centers the oscillator around zero by subtracting 50 from the standard RSI value. This transformation, common on Chinese trading platforms, produces a range of -50 to +50 instead of 0 to 100. BanTA implements this efficiently through the subVal parameter in RSIBy and rsiBy, avoiding redundant calculations.
Connors RSI (CRSI)
Connors RSI combines three distinct components into a single oscillator. The implementation in CRSI and CRSIBy averages:
- Standard RSI: The classic Wilder-smoothed RSI of the close prices.
- Up-Down RSI: An RSI calculated on the up-down streak length (consecutive days of gains/losses).
- ROC Component: Either a Rate-of-Change percentile or PercentRank of the current close relative to historical closes, depending on the
vtypeparameter.
The three values are equally weighted (averaged) to produce the final CRSI value, which ranges from 0 to 100 and is particularly useful for identifying short-term overbought/oversold conditions.
Stochastic RSI (StochRSI)
Stochastic RSI applies the stochastic oscillator formula to RSI values rather than raw prices. BanTA implements this as a two-stage process:
- Calculate the RSI of the input series.
- Apply the Stochastic oscillator to that RSI series using the high, low, and close all set to the RSI value.
- Smooth the resulting %K line with an SMA of length
maK. - Smooth the %K result again with an SMA of length
maDto produce the %D line.
This approach, found in tav/indicators.go, produces the K and D lines that range between 0 and 1 (or 0-100 depending on scaling), indicating where the current RSI sits relative to its recent high-low range.
Accuracy and Performance Considerations
BanTA's RSI implementation prioritizes both computational efficiency and numerical accuracy compared to reference libraries.
Wilder Smoothing vs. SMA Initialization
The key differentiator between BanTA and libraries like MyTT lies in the initialization method. BanTA uses Wilder's EMA (exponential moving average with α = 1/period) from the first valid delta, matching the behavior of TA-LIB. In contrast, MyTT uses a simple moving average (SMA) to seed the gain/loss averages, which requires period + (period-1) bars (approximately 120 bars when period=14) before producing stable values.
This distinction explains why BanTA produces valid RSI values after exactly period bars, while MyTT implementations may show NaN or bias in the first ~120 periods.
NaN Handling and State Reset
Both the stateless RSIBy and stateful rsiBy functions implement robust NaN handling. When a NaN value appears in the input series, the calculation preserves the previous source value but outputs NaN for that period. This prevents invalid deltas from corrupting the average gain/loss calculations and ensures that missing data does not propagate errors through subsequent bars, maintaining consistency with TA-LIB behavior.
Practical Code Examples
The following examples demonstrate how to use BanTA's RSI calculations in both Go and Python environments.
Go - Stateless Slice Calculation
For batch processing of historical data, use the tav package functions directly on float slices:
package main
import (
"fmt"
"github.com/banbox/banta/tav"
)
func main() {
close := []float64{101, 102, 100, 103, 104, 102, 105, 106, 107, 108, 109, 110, 111, 112}
// Classic 0-100 RSI
rsi := tav.RSI(close, 14)
fmt.Println("RSI:", rsi)
// RSI-50 variant (-50 to +50 range)
rsi50 := tav.RSIBy(close, 14, 50)
fmt.Println("RSI-50:", rsi50)
}
Go - Stateful Series API
For streaming or incremental updates, use the Series API which caches intermediate state:
package main
import (
"fmt"
"github.com/banbox/banta"
)
func main() {
// Initialize series with historical data
closeSeries := banta.NewSeries([]float64{101,102,100,103,104,102,105,106,107,108,109,110,111,112})
// Classic RSI using Series API
rsi := banta.RSI(closeSeries, 14).Values()
fmt.Println("RSI (Series):", rsi)
// RSI-50 using Series API
rsi50 := banta.RSI50(closeSeries, 14).Values()
fmt.Println("RSI-50 (Series):", rsi50)
// Connors RSI (period=3, upDn=2, roc=100)
crsi := banta.CRSI(closeSeries, 3, 2, 100).Values()
fmt.Println("CRSI:", crsi)
// Stochastic RSI
k, d := banta.StochRSI(closeSeries, 14, 14, 3, 3)
fmt.Println("StochRSI K:", k.Values())
fmt.Println("StochRSI D:", d.Values())
}
Python - Using Generated Bindings
BanTA exposes its Go engine to Python via CGO bindings:
import numpy as np
import banta.tav as tav
close = np.array([101,102,100,103,104,102,105,106,107,108,109,110,111,112], dtype=float)
# Classic RSI (0-100)
rsi = tav.RSI(close, 14)
print("RSI:", rsi)
# RSI-50 (range -50 to +50)
rsi50 = tav.RSIBy(close, 14, 50)
print("RSI-50:", rsi50)
# Connors RSI
crsi = tav.CRSI(close, 3, 2, 100)
print("CRSI:", crsi)
# Stochastic RSI
k, d = tav.StochRSI(close, 14, 14, 3, 3)
print("StochRSI K:", k)
print("StochRSI D:", d)
Summary
- BanTA implements RSI through Wilder's smoothing EMA (α=1/period) in
tav/indicators.go, matching TA-LIB accuracy rather than MyTT's SMA-based approach. - Two API layers exist: stateless functions for batch processing (
RSI,RSIBy) and stateful Series objects (rsiBy) for incremental streaming with memoization. - RSI-50 centers the oscillator around zero by subtracting 50, producing a -50 to +50 range popular in Asian markets.
- Connors RSI combines standard RSI, up-down streak RSI, and ROC/PercentRank components through the
CRSIandCRSIByfunctions. - Stochastic RSI applies stochastic oscillator logic to RSI values, outputting smoothed K and D lines via
StochRSI. - Python accessibility comes through CGO-generated bindings in
banta.tav, enabling high-performance RSI calculations within Python data science workflows.
Frequently Asked Questions
What is the difference between RSI and RSI-50 in BanTA?
RSI-50 is a centered variant of the classic RSI that shifts the output range from 0-100 to -50 to +50. BanTA implements this through the subVal parameter in RSIBy and rsiBy functions. When subVal equals 50, the calculation subtracts 50 from the final RSI value, making it easier to identify mean reversion around the zero line, a convention common in Chinese trading platforms.
How does BanTA handle missing data (NaN) in RSI calculations?
BanTA preserves calculation integrity by skipping NaN values without resetting the smoothing state. In both the stateless RSIBy function and the stateful rsiBy implementation, when a NaN is encountered in the input series, the function outputs NaN for that period but retains the previous valid source value. This prevents invalid deltas from corrupting the average gain/loss calculations, ensuring that the Wilder smoothing continues correctly once valid data resumes, matching the behavior of industry-standard libraries like TA-LIB.
Why does BanTA's RSI match TA-LIB but differ from MyTT initially?
BanTA uses Wilder's EMA smoothing from the first valid delta, while MyTT uses SMA initialization, causing a ~120-bar discrepancy when period=14. The classic RSI requires seeding the average gain and loss values. BanTA implements the standard Wilder approach (EMA with α=1/period) starting immediately after the first period, producing valid outputs after exactly period bars. In contrast, MyTT smooths the delta series with an SMA first, requiring approximately period + (period-1) bars (around 120 for a 14-period RSI) before stabilizing, which explains the initial divergence between the two implementations.
Can I use BanTA's RSI calculations in Python?
Yes, BanTA exposes its high-performance Go RSI implementations to Python through CGO-generated bindings. The repository includes a Python bridge (located in python/tav/index.go) that compiles the Go code into a Python-importable module. You can import banta.tav and call functions like RSI, RSIBy, CRSI, and StochRSI directly on NumPy arrays, achieving near-native Go performance while working within Python data science workflows.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →