RSI Mean Reversion Strategy

A classic mean reversion strategy that buys oversold conditions and sells overbought conditions.

Strategy Overview

AspectDetails
TypeMean Reversion
IndicatorsRSI
Timeframes15m, 1h, 4h
MarketsAll (Spot & Futures)
DifficultyBeginner

How It Works

  1. Entry: Buy when RSI drops below the oversold level (default 30)
  2. Exit: Close when RSI rises above the overbought level (default 70)
  3. Risk Management: Stop loss and take profit based on percentage from entry

Complete Code

// ============================================
// RSI MEAN REVERSION STRATEGY
// ============================================
// A classic mean reversion strategy that buys
// oversold conditions and sells overbought.
// ============================================

// ============================================
// PARAMETERS
// ============================================
const RSI_PERIOD = td.config.getNumber('RSI_PERIOD', 14);
const RSI_OVERSOLD = td.config.getNumber('RSI_OVERSOLD', 30);
const RSI_OVERBOUGHT = td.config.getNumber('RSI_OVERBOUGHT', 70);
const STOP_LOSS_PCT = td.config.getNumber('STOP_LOSS_PCT', 2);
const TAKE_PROFIT_PCT = td.config.getNumber('TAKE_PROFIT_PCT', 4);
const POSITION_SIZE = td.config.getNumber('POSITION_SIZE', 100);
const USE_CONFIRMATION = td.config.getBoolean('USE_CONFIRMATION', true);

// ============================================
// VALIDATION
// ============================================
if (RSI_PERIOD < 2 || RSI_PERIOD > 100) {
    td.utils.log('ERROR: RSI_PERIOD must be between 2 and 100');
    return;
}

if (RSI_OVERSOLD >= RSI_OVERBOUGHT) {
    td.utils.log('ERROR: RSI_OVERSOLD must be less than RSI_OVERBOUGHT');
    return;
}

if (td.market.candles.length < RSI_PERIOD + 10) {
    td.utils.log('Waiting for sufficient candle data');
    return;
}

// ============================================
// CALCULATE INDICATORS
// ============================================
// Note: Standard RSI (14) is built-in. Custom periods require custom indicator configured in bot settings.
const rsi = RSI_PERIOD === 14 ? td.indicators.rsi : td.indicators.custom['rsi_' + RSI_PERIOD];
const previousRsi = td.state.get('previousRsi', rsi);
const price = td.market.price;

// ============================================
// ENTRY LOGIC
// ============================================
if (!td.position.hasPosition) {
    // Basic oversold condition
    let shouldEnter = rsi < RSI_OVERSOLD;

    // Optional: Require RSI to be rising (confirmation)
    if (USE_CONFIRMATION) {
        shouldEnter = shouldEnter && rsi > previousRsi;
    }

    if (shouldEnter) {
        const stopLoss = td.utils.round(price * (1 - STOP_LOSS_PCT / 100), 2);
        const takeProfit = td.utils.round(price * (1 + TAKE_PROFIT_PCT / 100), 2);

        td.trade.buy({
            amountPercent: POSITION_SIZE,
            stopLoss: stopLoss,
            takeProfit: takeProfit,
            reason: `RSI oversold at ${rsi.toFixed(2)} (rising: ${rsi > previousRsi})`
        });

        td.utils.log('ENTRY SIGNAL', {
            rsi: rsi.toFixed(2),
            price: price,
            stopLoss: stopLoss,
            takeProfit: takeProfit
        });
    }
}

// ============================================
// EXIT LOGIC
// ============================================
if (td.position.hasPosition) {
    // Exit on overbought
    if (rsi > RSI_OVERBOUGHT) {
        td.trade.close(`RSI overbought at ${rsi.toFixed(2)}`);

        td.utils.log('EXIT SIGNAL', {
            rsi: rsi.toFixed(2),
            pnl: td.position.pnl,
            pnlPercent: td.position.pnlPercent
        });
    }
}

// ============================================
// STATE UPDATE
// ============================================
td.state.set('previousRsi', rsi);

// ============================================
// LOGGING (DEBUG)
// ============================================
const DEBUG = td.config.getBoolean('DEBUG_MODE', false);
if (DEBUG) {
    td.utils.log('Strategy tick', {
        rsi: rsi.toFixed(2),
        previousRsi: previousRsi.toFixed(2),
        price: price,
        hasPosition: td.position.hasPosition,
        pnl: td.position.pnl
    });
}

Parameters

ParameterTypeDefaultDescription
RSI_PERIODNumber14RSI calculation period
RSI_OVERSOLDNumber30Buy when RSI below this
RSI_OVERBOUGHTNumber70Sell when RSI above this
STOP_LOSS_PCTNumber2Stop loss percentage
TAKE_PROFIT_PCTNumber4Take profit percentage
POSITION_SIZENumber100Position size (% of balance)
USE_CONFIRMATIONBooleantrueRequire RSI to be rising
DEBUG_MODEBooleanfalseEnable verbose logging

Recommended Settings

Conservative

RSI_OVERSOLD: 25
RSI_OVERBOUGHT: 75
STOP_LOSS_PCT: 1.5
TAKE_PROFIT_PCT: 3
USE_CONFIRMATION: true

Moderate (Default)

RSI_OVERSOLD: 30
RSI_OVERBOUGHT: 70
STOP_LOSS_PCT: 2
TAKE_PROFIT_PCT: 4
USE_CONFIRMATION: true

Aggressive

RSI_OVERSOLD: 35
RSI_OVERBOUGHT: 65
STOP_LOSS_PCT: 3
TAKE_PROFIT_PCT: 6
USE_CONFIRMATION: false

Performance Tips

  1. Best timeframes: 1h and 4h tend to give more reliable signals
  2. Market conditions: Works best in ranging markets, avoid strong trends
  3. Confirmation: Enable USE_CONFIRMATION to filter false signals
  4. Risk/Reward: Maintain at least 1:2 ratio (SL:TP)

Variations

With ADX Trend Filter

// Only trade when trend is weak (ranging market)
const adx = td.indicators.adx;
if (adx.value > 25) {
    td.utils.log('Strong trend - skipping RSI signals');
    return;
}

// ... rest of strategy

With Volume Confirmation

// Require above-average volume
const avgVolume = td.market.candles.slice(-20)
    .reduce((sum, c) => sum + c.volume, 0) / 20;
const currentVolume = td.market.candles[td.market.candles.length - 1].volume;

if (currentVolume < avgVolume * 1.2) {
    // Skip entry on low volume
    shouldEnter = false;
}

Next Steps