Introduction
In this tutorial, we'll build and validate a quantitative trading strategy using OctoBot, a powerful open-source trading bot framework. We'll focus on creating a rule-based strategy that combines technical indicators like RSI, EMA, and ATR to make trading decisions. This tutorial will guide you through setting up OctoBot, implementing the strategy, performing walk-forward backtesting, optimizing parameters, and analyzing results - all while keeping the environment isolated from Colab's preinstalled dependencies.
By the end of this tutorial, you'll have a complete backtesting workflow that you can use to validate trading strategies with real market data.
Prerequisites
To follow along with this tutorial, you'll need:
- Python 3.8 or higher installed on your system
- Basic understanding of Python programming and technical analysis concepts
- Some familiarity with backtesting and trading strategies
- Access to a terminal or command line interface
Step-by-Step Instructions
1. Setting Up the Environment
First, we need to create an isolated Python environment to avoid conflicts with Colab's preinstalled dependencies. This is crucial for maintaining reproducibility.
python -m venv octobot_env
source octobot_env/bin/activate # On Windows: octobot_env\Scripts\activate
pip install octobot octobot-script
Why: Creating a virtual environment ensures that all dependencies are isolated from your system's Python installation, preventing conflicts and making your setup reproducible across different machines.
2. Installing Required Libraries
Next, we'll install additional libraries needed for data analysis and visualization:
pip install pandas numpy matplotlib seaborn
Why: These libraries are essential for handling market data, performing calculations, and visualizing strategy performance metrics.
3. Creating the Trading Strategy
Now we'll create a Python file for our strategy. Let's name it rsi_ema_strategy.py:
import octobot_trading.enums as enums
import octobot_trading.constants as constants
import octobot_trading.orders as orders
import pandas as pd
import numpy as np
class RSIEMAStrategy:
def __init__(self, config, exchange, symbol):
self.config = config
self.exchange = exchange
self.symbol = symbol
self.rsi_length = 14
self.ema_length = 50
self.atr_length = 14
self.rsi_overbought = 70
self.rsi_oversold = 30
self.stop_loss_pct = 2.0
self.take_profit_pct = 4.0
def get_rsi(self, data):
delta = data['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=self.rsi_length).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=self.rsi_length).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
def get_ema(self, data):
return data['close'].ewm(span=self.ema_length, adjust=False).mean()
def get_atr(self, data):
high_low = data['high'] - data['low']
high_close = np.abs(data['high'] - data['close'].shift(1))
low_close = np.abs(data['low'] - data['close'].shift(1))
ranges = pd.concat([high_low, high_close, low_close], axis=1)
true_range = ranges.max(axis=1)
atr = true_range.rolling(window=self.atr_length).mean()
return atr
def should_long(self, data):
rsi = self.get_rsi(data)
ema = self.get_ema(data)
# RSI oversold signal
rsi_signal = rsi.iloc[-1] < self.rsi_oversold
# EMA trend confirmation
trend_signal = data['close'].iloc[-1] > ema.iloc[-1]
return rsi_signal and trend_signal
def should_short(self, data):
rsi = self.get_rsi(data)
ema = self.get_ema(data)
# RSI overbought signal
rsi_signal = rsi.iloc[-1] > self.rsi_overbought
# EMA trend confirmation
trend_signal = data['close'].iloc[-1] < ema.iloc[-1]
return rsi_signal and trend_signal
def get_stop_loss(self, data):
atr = self.get_atr(data)
return atr.iloc[-1] * (self.stop_loss_pct / 100)
def get_take_profit(self, data):
atr = self.get_atr(data)
return atr.iloc[-1] * (self.take_profit_pct / 100)
Why: This strategy combines three technical indicators to make informed trading decisions. The RSI helps identify oversold/overbought conditions, EMA confirms the trend direction, and ATR provides adaptive stop-loss and take-profit levels based on volatility.
4. Configuring OctoBot
Next, we'll create a configuration file to set up our backtesting environment:
import octobot_commons.constants as commons_constants
import octobot_trading.constants as trading_constants
OCTOBOT_CONFIG = {
"trading": {
"enabled": True,
"risk": 0.02,
"use_registered_trader": True,
"use_registered_simulator": True,
},
"exchange": {
"name": "binance",
"enabled": True,
"is_simulated": True,
"api_key": "",
"api_secret": "",
"use_testnet": False,
},
"strategy": {
"name": "RSIEMAStrategy",
"enabled": True,
},
"backtesting": {
"enabled": True,
"start_timestamp": 1609459200000, # 2021-01-01
"end_timestamp": 1640995200000, # 2022-01-01
"symbols": ["BTC/USDT"],
"timeframes": ["1h"],
}
}
Why: This configuration file sets up OctoBot with the necessary parameters for backtesting, including the exchange, trading pairs, timeframes, and date ranges.
5. Running Walk-Forward Backtesting
Let's implement the walk-forward backtesting process:
import octobot_backtesting.api as backtesting_api
import octobot_trading.api as trading_api
def run_walk_forward_backtesting(strategy_class, config, symbols, timeframes):
# Define the walk-forward parameters
start_date = 1609459200000 # 2021-01-01
end_date = 1640995200000 # 2022-01-01
window_size = 30 * 24 * 60 * 60 * 1000 # 30 days in milliseconds
results = []
current_start = start_date
while current_start + window_size < end_date:
# Set up the backtesting period
config["backtesting"]["start_timestamp"] = current_start
config["backtesting"]["end_timestamp"] = current_start + window_size
# Run backtesting
backtesting_result = backtesting_api.run_backtesting(
strategy_class,
config,
symbols,
timeframes
)
results.append({
"start": current_start,
"end": current_start + window_size,
"profit": backtesting_result.get("profit", 0)
})
# Move to next window
current_start += window_size
return results
Why: Walk-forward backtesting is crucial for validating strategies across different market conditions. It simulates how a strategy would perform in real-time by using historical data in sequential windows, preventing overfitting and ensuring robustness.
6. Parameter Optimization
Let's implement a simple optimization process:
import itertools
def optimize_strategy_parameters(strategy_class, config, symbols, timeframes, param_grid):
best_result = None
best_params = None
best_profit = float('-inf')
# Generate all combinations of parameters
param_combinations = list(itertools.product(*param_grid.values()))
param_names = list(param_grid.keys())
for combination in param_combinations:
# Set parameters
params = dict(zip(param_names, combination))
# Update strategy parameters
strategy_instance = strategy_class(config, None, symbols[0])
for param_name, param_value in params.items():
setattr(strategy_instance, param_name, param_value)
# Run backtesting with these parameters
backtesting_result = backtesting_api.run_backtesting(
strategy_class,
config,
symbols,
timeframes
)
profit = backtesting_result.get("profit", 0)
if profit > best_profit:
best_profit = profit
best_params = params
best_result = backtesting_result
return best_params, best_result
Why: Parameter optimization helps find the best combination of strategy parameters that maximize performance metrics. This prevents overfitting and improves strategy robustness.
7. Interactive Analysis
Finally, let's create a simple visualization for analyzing results:
import matplotlib.pyplot as plt
import seaborn as sns
def analyze_results(results):
df = pd.DataFrame(results)
df['start_date'] = pd.to_datetime(df['start'], unit='ms')
df['end_date'] = pd.to_datetime(df['end'], unit='ms')
# Plot profit over time
plt.figure(figsize=(12, 6))
plt.plot(df['start_date'], df['profit'], marker='o')
plt.title('Strategy Performance Over Time')
plt.xlabel('Date')
plt.ylabel('Profit')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f"Average Profit: {df['profit'].mean():.2f}")
print(f"Best Profit: {df['profit'].max():.2f}")
print(f"Worst Profit: {df['profit'].min():.2f}")
Why: Interactive analysis allows you to visualize strategy performance across different time periods and understand how parameters affect results, making it easier to identify optimal configurations.
Summary
In this tutorial, we've built a complete quantitative trading workflow using OctoBot. We've implemented a strategy combining RSI, EMA, and ATR indicators, set up walk-forward backtesting to validate performance across market conditions, optimized parameters to maximize returns, and created interactive analysis tools to visualize results. This approach ensures that your strategies are robust and not overfitted to specific historical data periods.
The modular approach we've demonstrated allows you to easily modify indicators, add new parameters, or even swap out different technical analysis methods while maintaining the same backtesting and optimization framework.



