The RSI (Relative Strength Index) strategy uses a momentum oscillator to identify overbought and oversold conditions. It generates buy signals when the market is oversold and sell signals when overbought.
Strategy logic
The strategy uses the RSI indicator to time entries and exits:
Calculate RSI over lookback period (typically 14 bars)
Buy signal - RSI < oversold threshold (typically 30)
Sell signal - RSI > overbought threshold (typically 70)
Position sizing based on configurable percentage of portfolio value
Implementation
Source: crates/gb-types/src/strategy.rs:714-1059
pub struct RsiStrategy {
config : StrategyConfig ,
initialized : bool ,
lookback_period : usize ,
oversold_threshold : Decimal ,
overbought_threshold : Decimal ,
position_size : Decimal ,
}
RSI calculation
From strategy.rs:935-971:
fn calculate_rsi ( & self , prices : & [ Decimal ]) -> Option < Decimal > {
if prices . len () < self . lookback_period + 1 {
return None ;
}
let mut gains = Decimal :: ZERO ;
let mut losses = Decimal :: ZERO ;
let recent_prices = prices . iter ()
. rev ()
. take ( self . lookback_period + 1 )
. cloned ()
. collect :: < Vec < _ >>();
let mut recent_prices = recent_prices . into_iter () . rev ();
let mut previous = recent_prices . next () ? ;
for price in recent_prices {
let change = price - previous ;
if change > Decimal :: ZERO {
gains += change ;
} else if change < Decimal :: ZERO {
losses += change . abs ();
}
previous = price ;
}
let period = Decimal :: from ( self . lookback_period);
let avg_gain = gains / period ;
let avg_loss = losses / period ;
if avg_loss == Decimal :: ZERO {
return Some ( Decimal :: from ( 100 ));
}
if avg_gain == Decimal :: ZERO {
return Some ( Decimal :: ZERO );
}
let rs = avg_gain / avg_loss ;
let rsi = Decimal :: from ( 100 ) - ( Decimal :: from ( 100 ) / ( Decimal :: ONE + rs ));
Some ( rsi )
}
Trading logic
From strategy.rs:989-1037:
fn on_market_event (
& mut self ,
event : & MarketEvent ,
context : & StrategyContext ,
) -> Result < Vec < StrategyAction >, String > {
if ! self . initialized {
return Ok ( vec! []);
}
let symbol = event . symbol ();
if let Some ( buffer ) = context . get_market_data ( symbol ) {
let bars = buffer . get_bars ( self . lookback_period + 1 );
let prices : Vec < Decimal > = bars . iter () . map ( | bar | bar . close) . collect ();
if let Some ( rsi ) = self . calculate_rsi ( & prices ) {
let mut actions = Vec :: new ();
let current_position = context . get_position ( symbol );
let current_quantity = current_position . map ( | p | p . quantity) . unwrap_or ( Decimal :: ZERO );
if rsi < self . oversold_threshold {
// Oversold - buy signal
let target_quantity = if let Some ( price ) = context . get_current_price ( symbol ) {
( context . get_portfolio_value () * self . position_size) / price
} else {
Decimal :: ZERO
};
let quantity_diff = target_quantity - current_quantity ;
if quantity_diff > Decimal :: new ( 1 , 4 ) {
let order = Order :: market_order (
symbol . clone (),
Side :: Buy ,
quantity_diff ,
self . config . strategy_id . clone ()
);
actions . push ( StrategyAction :: PlaceOrder ( order ));
}
} else if rsi > self . overbought_threshold {
// Overbought - sell signal
if current_quantity > Decimal :: ZERO {
let order = Order :: market_order (
symbol . clone (),
Side :: Sell ,
current_quantity ,
self . config . strategy_id . clone ()
);
actions . push ( StrategyAction :: PlaceOrder ( order ));
}
}
return Ok ( actions );
}
}
Ok ( vec! [])
}
Parameters
lookback_period
usize
default: "14"
required
Number of bars for RSI calculation (typically 14)
oversold_threshold
f64
default: "30.0"
required
RSI level below which market is considered oversold (0-100)
overbought_threshold
f64
default: "70.0"
required
RSI level above which market is considered overbought (0-100)
Percentage of portfolio value to allocate (0.0 to 1.0)
Symbols to trade with this strategy
Usage
Basic setup
use gb_types :: strategy :: { RsiStrategy , StrategyConfig , Strategy };
use gb_types :: market :: Symbol ;
// Create 14-period RSI strategy with 30/70 thresholds
let mut strategy = RsiStrategy :: new ( 14 , 30.0 , 70.0 );
// Configure
let mut config = StrategyConfig :: new (
"rsi" . to_string (),
"RSI 14/30/70" . to_string ()
);
config . add_symbol ( Symbol :: equity ( "AAPL" ));
config . set_parameter ( "position_size" , 0.95 f64 );
// Initialize
strategy . initialize ( & config ) ? ;
Testing
RSI calculation test
From strategy.rs:1211-1220:
#[test]
fn test_rsi_calculation () {
let strategy = RsiStrategy :: new ( 5 , 30.0 , 70.0 );
let prices = vec! [ dec! ( 100 ), dec! ( 102 ), dec! ( 104 ), dec! ( 103 ), dec! ( 105 ), dec! ( 107 )];
let rsi = strategy . calculate_rsi ( & prices );
assert! ( rsi . is_some ());
let value = rsi . unwrap ();
assert! ( value > dec! ( 50 )); // Mostly rising prices should produce RSI above 50
}
Buy signal test
From strategy.rs:1222-1242:
#[test]
fn test_rsi_strategy_buy_signal () {
let mut strategy = RsiStrategy :: new ( 5 , 30.0 , 70.0 );
let mut config = StrategyConfig :: new (
"test_rsi" . to_string (),
"Test RSI" . to_string ()
);
config . add_symbol ( create_test_symbol ());
assert! ( strategy . initialize ( & config ) . is_ok ());
let mut bars = Vec :: new ();
let base_time = Utc :: now ();
let prices = vec! [ dec! ( 100 ), dec! ( 98 ), dec! ( 96 ), dec! ( 95 ), dec! ( 94 ), dec! ( 93 )];
for ( i , price ) in prices . into_iter () . enumerate () {
let bar = create_test_bar ( price , base_time + chrono :: Duration :: days ( i as i64 ));
bars . push ( bar );
}
let context = create_test_context_with_data ( bars );
let actions = strategy . on_market_event ( & event , & context );
assert! ( actions . is_ok ());
}
Common configurations
Conservative (wide bands)
let strategy = RsiStrategy :: new ( 14 , 20.0 , 80.0 );
// Trades only extreme oversold/overbought conditions
Aggressive (narrow bands)
let strategy = RsiStrategy :: new ( 14 , 40.0 , 60.0 );
// Trades more frequently on smaller deviations from neutral
Short-term RSI
let strategy = RsiStrategy :: new ( 7 , 30.0 , 70.0 );
// Faster-moving, more responsive to recent price action
Long-term RSI
let strategy = RsiStrategy :: new ( 21 , 30.0 , 70.0 );
// Smoother, filters out short-term noise
Behavior details
RSI = 100 - (100 / (1 + RS))
Where:
RS (Relative Strength) = Average Gain / Average Loss
Average Gain = Sum of gains over period / Period
Average Loss = Sum of losses over period / Period
Position management
Oversold entry - Buys to reach target position size
Overbought exit - Sells entire position
No partial exits - All-or-nothing on overbought signals
Incremental buys - Only adds to position when below target
Data requirements
Requires lookback_period + 1 bars of price data to calculate RSI.
Use cases
Mean reversion - Trade reversals at extremes
Trend confirmation - Filter entries in trending markets
Divergence trading - Spot when RSI diverges from price (requires custom logic)
Oversold bounces - Capture short-term rebounds
Limitations
RSI can remain overbought or oversold for extended periods during strong trends. The indicator may generate premature reversal signals.
False signals in trends - RSI can stay extreme during sustained moves
No trend filter - Trades against strong trends
Lagging indicator - Based on historical price changes
Fixed thresholds - Same levels used across all market conditions
Binary exits - Sells entire position on overbought, no scaling out
Best in - Range-bound, oscillating markets
Worst in - Strong trending markets with persistent directional moves
Win rate - Moderate to high in mean-reverting environments
Drawdowns - Can be significant when fading strong trends
Advanced usage
Multiple timeframes
Combine different RSI periods:
let rsi_short = RsiStrategy :: new ( 7 , 30.0 , 70.0 );
let rsi_long = RsiStrategy :: new ( 21 , 30.0 , 70.0 );
// Only trade when both agree
Dynamic thresholds
Adjust thresholds based on volatility:
let volatility = calculate_volatility ( & prices );
let oversold = 30.0 - ( volatility * 10.0 );
let overbought = 70.0 + ( volatility * 10.0 );
config . set_parameter ( "oversold_threshold" , oversold );
config . set_parameter ( "overbought_threshold" , overbought );
RSI divergence
Detect bullish/bearish divergences (requires custom implementation):
// Bullish divergence: Price makes lower low, RSI makes higher low
// Bearish divergence: Price makes higher high, RSI makes lower high
Mean reversion Similar concept using z-score analysis instead of RSI
Momentum Trend-following approach - opposite philosophy