RSI Mean Reversion Strategy
A classic mean reversion strategy that buys oversold conditions and sells overbought conditions.
Strategy Overview
| Aspect | Details |
|---|
| Type | Mean Reversion |
| Indicators | RSI |
| Timeframes | 15m, 1h, 4h |
| Markets | All (Spot & Futures) |
| Difficulty | Beginner |
How It Works
- Entry: Buy when RSI drops below the oversold level (default 30)
- Exit: Close when RSI rises above the overbought level (default 70)
- 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
| Parameter | Type | Default | Description |
|---|
| RSI_PERIOD | Number | 14 | RSI calculation period |
| RSI_OVERSOLD | Number | 30 | Buy when RSI below this |
| RSI_OVERBOUGHT | Number | 70 | Sell when RSI above this |
| STOP_LOSS_PCT | Number | 2 | Stop loss percentage |
| TAKE_PROFIT_PCT | Number | 4 | Take profit percentage |
| POSITION_SIZE | Number | 100 | Position size (% of balance) |
| USE_CONFIRMATION | Boolean | true | Require RSI to be rising |
| DEBUG_MODE | Boolean | false | Enable 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
- Best timeframes: 1h and 4h tend to give more reliable signals
- Market conditions: Works best in ranging markets, avoid strong trends
- Confirmation: Enable USE_CONFIRMATION to filter false signals
- 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