Build, test, and optimize profitable trading strategies
RLX uses a simple signal format for trading decisions:
Signal Behavior
1 while short → Close short, open long -1 while long → Close long, open short 0 → Close any open position Create custom strategies by inheriting from the Strategy base class:
import pandas as pd
from rlxbt import Strategy, Backtester
class RSIStrategy(Strategy):
def __init__(self, period=14, oversold=30, overbought=70):
super().__init__()
self.name = "RSI Strategy"
self.period = period
self.oversold = oversold
self.overbought = overbought
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
# Calculate RSI
delta = data['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(self.period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(self.period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
# Generate signals
signals = pd.DataFrame(index=data.index)
signals['signal'] = 0
signals.loc[rsi < self.oversold, 'signal'] = 1 # Long
signals.loc[rsi > self.overbought, 'signal'] = -1 # Short
return signals
# Use the strategy
strategy = RSIStrategy(period=14, oversold=25, overbought=75)
backtester = Backtester(initial_capital=100000.0)
result = backtester.run(strategy, df)Add dynamic take-profit and stop-loss levels to your signals:
def generate_signals(self, data: pd.DataFrame) -> pd.DataFrame:
signals = pd.DataFrame(index=data.index)
# Your signal logic...
signals['signal'] = ...
# Dynamic TP/SL based on ATR
atr = calculate_atr(data, period=14)
# 2x ATR take profit, 1x ATR stop loss
signals['take_profit'] = data['close'] + (atr * 2)
signals['stop_loss'] = data['close'] - atr
return signalsPro Tip: Use ATR for Dynamic Levels
ATR-based TP/SL adapts to market volatility automatically.
Define strategies in JSON without writing Python code. Perfect for rapid prototyping and CI/CD pipelines.
{
"entry_long": "RSI_14 < 30",
"exit_long": "RSI_14 > 70",
"entry_short": "RSI_14 > 70",
"exit_short": "RSI_14 < 30"
}{
"entry_rules": [
{
"condition": "RSI_14 < 30 && close > SMA_200",
"signal": "OversoldLong",
"direction": 1
},
{
"condition": "RSI_14 > 70 && close < SMA_200",
"signal": "OverboughtShort",
"direction": -1
}
],
"exit_rules": [
{
"condition": "RSI_14 > 70",
"reason": "Overbought"
},
{
"condition": "current_drawdown > 0.15",
"reason": "PortfolioProtection"
}
],
"stop_loss_pct": 0.025,
"take_profit_pct": 0.045,
"max_hold_bars": 45
}| Operator | Example | Description |
|---|---|---|
| > < >= <= | RSI_14 > 70 | Comparison |
| == != | position_size != 0 | Equality |
| && | RSI < 30 && MACD > 0 | Logical AND |
| || | RSI > 80 || RSI < 20 | Logical OR |
| ( ) | (RSI < 30) && (close > SMA) | Grouping |
Run multiple strategies simultaneously and combine them into a diversified portfolio.
from rlxbt import PortfolioManager, CapitalAllocator
# Define strategies
strategies = [
TrendFollowingStrategy(),
MeanReversionStrategy(),
BreakoutStrategy()
]
# Create portfolio with risk-parity allocation
portfolio = PortfolioManager(
initial_capital=1_000_000,
strategies=strategies,
allocation="risk_parity",
rebalance_frequency="monthly"
)
# Run backtest
results = portfolio.backtest(data)
print(f"Portfolio Return: {results['total_return']:.2%}")
print(f"Sharpe Ratio: {results['sharpe_ratio']:.2f}")equal_weightEqual capital to each strategy
risk_parityInverse volatility weighting
max_sharpeOptimize for maximum Sharpe
min_varianceMinimize portfolio variance
Find optimal parameters using grid search and walk-forward analysis.
from rlxbt import GridSearchOptimizer
optimizer = GridSearchOptimizer(metric="sharpe_ratio")
# Define parameter grid
param_grid = {
'rsi_period': [7, 14, 21],
'oversold': [20, 30, 40],
'overbought': [60, 70, 80]
}
# Run optimization (27 combinations)
results = optimizer.optimize(
strategy_class=RSIStrategy,
param_grid=param_grid,
data=data,
verbose=True
)
print(f"Best params: {results['best_params']}")
print(f"Best Sharpe: {results['best_score']:.4f}")from rlxbt import WalkForwardAnalysis
wfa = WalkForwardAnalysis(
train_size=0.7, # 70% for training
test_size=0.3, # 30% for testing
n_splits=5 # 5 walk-forward periods
)
results = wfa.run(
strategy_class=RSIStrategy,
param_grid=param_grid,
data=data,
verbose=True
)
print(f"Avg Out-Sample Return: {results['avg_out_sample_return']:.2%}")Detect Overfitting
If in-sample performance is much better than out-sample, your strategy may be overfit to historical data.
RSI Strategy on BTCUSDT 1h (33,579 bars, 27 combinations)
| Rank | Period | Oversold | Overbought | Sharpe | Return |
|---|---|---|---|---|---|
| 🥇 1 | 7 | 40 | 80 | 0.0084 | +26.19% |
| 🥈 2 | 7 | 40 | 60 | 0.0060 | +18.42% |
| 🥉 3 | 7 | 40 | 70 | 0.0058 | +21.35% |