433 lines
10 KiB
ReStructuredText
433 lines
10 KiB
ReStructuredText
Backtesting System
|
|
==================
|
|
|
|
OpenClaw includes a comprehensive backtesting engine for testing strategies against historical data.
|
|
|
|
Overview
|
|
--------
|
|
|
|
The backtesting system simulates trading using historical data to evaluate strategy performance before risking real capital.
|
|
|
|
Key Features
|
|
~~~~~~~~~~~~
|
|
|
|
* Historical simulation with accurate price data
|
|
* Multiple strategy support
|
|
* Performance analytics and metrics
|
|
* Risk-adjusted returns calculation
|
|
* Trade-by-trade analysis
|
|
|
|
Quick Start
|
|
-----------
|
|
|
|
Basic Backtest
|
|
~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.engine import BacktestEngine
|
|
from datetime import datetime, timedelta
|
|
|
|
# Create engine
|
|
engine = BacktestEngine()
|
|
|
|
# Configure backtest
|
|
engine.configure(
|
|
symbols=["AAPL"],
|
|
start_date=datetime(2023, 1, 1),
|
|
end_date=datetime(2023, 12, 31),
|
|
initial_capital=10000.0
|
|
)
|
|
|
|
# Run backtest
|
|
results = engine.run()
|
|
|
|
# Print summary
|
|
print(f"Total Return: {results.total_return:.2%}")
|
|
print(f"Sharpe Ratio: {results.sharpe_ratio:.2f}")
|
|
|
|
Advanced Configuration
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.engine import BacktestEngine
|
|
from openclaw.strategy.trend_following import TrendFollowingStrategy
|
|
|
|
# Create engine with custom strategy
|
|
engine = BacktestEngine()
|
|
|
|
strategy = TrendFollowingStrategy(
|
|
sma_period=50,
|
|
position_size=0.1
|
|
)
|
|
|
|
engine.configure(
|
|
symbols=["AAPL", "MSFT", "GOOGL"],
|
|
strategy=strategy,
|
|
start_date="2023-01-01",
|
|
end_date="2023-12-31",
|
|
initial_capital=100000.0,
|
|
commission=0.001, # 0.1% per trade
|
|
slippage=0.0005 # 0.05% slippage
|
|
)
|
|
|
|
results = engine.run()
|
|
|
|
Backtest Engine
|
|
---------------
|
|
|
|
Configuration
|
|
~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.engine import BacktestEngine
|
|
|
|
engine = BacktestEngine()
|
|
|
|
# Set parameters
|
|
engine.configure(
|
|
# Required
|
|
symbols=["AAPL", "MSFT"],
|
|
start_date="2023-01-01",
|
|
end_date="2023-12-31",
|
|
|
|
# Optional
|
|
initial_capital=10000.0,
|
|
strategy=None, # Use default strategy
|
|
commission=0.001,
|
|
slippage=0.0005,
|
|
enable_caching=True
|
|
)
|
|
|
|
Running Backtests
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Run single backtest
|
|
results = engine.run()
|
|
|
|
# Run with progress callback
|
|
def on_progress(progress: float):
|
|
print(f"Progress: {progress:.0%}")
|
|
|
|
results = engine.run(progress_callback=on_progress)
|
|
|
|
# Run multiple backtests (parameter sweep)
|
|
param_grid = {
|
|
"sma_period": [20, 50, 200],
|
|
"position_size": [0.05, 0.1, 0.2]
|
|
}
|
|
|
|
results = engine.run_sweep(param_grid)
|
|
|
|
Performance Metrics
|
|
-------------------
|
|
|
|
Basic Metrics
|
|
~~~~~~~~~~~~~
|
|
|
|
* **Total Return**: Overall percentage return
|
|
* **Annualized Return**: Return adjusted to yearly basis
|
|
* **Volatility**: Standard deviation of returns
|
|
* **Sharpe Ratio**: Risk-adjusted return metric
|
|
* **Max Drawdown**: Largest peak-to-trough decline
|
|
* **Win Rate**: Percentage of winning trades
|
|
|
|
Advanced Metrics
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.analyzer import BacktestAnalyzer
|
|
|
|
analyzer = BacktestAnalyzer(results)
|
|
|
|
# Get all metrics
|
|
metrics = analyzer.calculate_metrics()
|
|
|
|
print(f"Sortino Ratio: {metrics.sortino_ratio:.2f}")
|
|
print(f"Calmar Ratio: {metrics.calmar_ratio:.2f}")
|
|
print(f"Omega Ratio: {metrics.omega_ratio:.2f}")
|
|
print(f"Profit Factor: {metrics.profit_factor:.2f}")
|
|
print(f"Expectancy: ${metrics.expectancy:.2f}")
|
|
|
|
Trade Analysis
|
|
~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Get individual trades
|
|
trades = results.trades
|
|
|
|
for trade in trades[:5]: # First 5 trades
|
|
print(f"Date: {trade.date}")
|
|
print(f"Symbol: {trade.symbol}")
|
|
print(f"Side: {trade.side}")
|
|
print(f"Entry: ${trade.entry_price:.2f}")
|
|
print(f"Exit: ${trade.exit_price:.2f}")
|
|
print(f"PnL: ${trade.pnl:.2f}")
|
|
|
|
# Trade statistics
|
|
stats = analyzer.get_trade_statistics()
|
|
print(f"Avg winning trade: ${stats.avg_winner:.2f}")
|
|
print(f"Avg losing trade: ${stats.avg_loser:.2f}")
|
|
print(f"Largest winner: ${stats.max_winner:.2f}")
|
|
print(f"Largest loser: ${stats.max_loser:.2f}")
|
|
|
|
Visualization
|
|
-------------
|
|
|
|
Equity Curve
|
|
~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.analyzer import BacktestAnalyzer
|
|
|
|
analyzer = BacktestAnalyzer(results)
|
|
|
|
# Plot equity curve
|
|
analyzer.plot_equity_curve(
|
|
filename="equity_curve.png",
|
|
show_drawdowns=True
|
|
)
|
|
|
|
Drawdown Analysis
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Plot drawdown chart
|
|
analyzer.plot_drawdown(
|
|
filename="drawdown.png"
|
|
)
|
|
|
|
# Get drawdown statistics
|
|
dd_stats = analyzer.get_drawdown_statistics()
|
|
print(f"Max drawdown: {dd_stats.max_drawdown:.2%}")
|
|
print(f"Avg drawdown: {dd_stats.avg_drawdown:.2%}")
|
|
print(f"Max duration: {dd_stats.max_duration} days")
|
|
|
|
Monthly Returns
|
|
~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Plot monthly returns heatmap
|
|
analyzer.plot_monthly_returns(
|
|
filename="monthly_returns.png"
|
|
)
|
|
|
|
Trade Distribution
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Plot trade distribution
|
|
analyzer.plot_trade_distribution(
|
|
filename="trade_dist.png"
|
|
)
|
|
|
|
Multi-Symbol Backtests
|
|
----------------------
|
|
|
|
Portfolio Backtest
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.engine import BacktestEngine
|
|
|
|
engine = BacktestEngine()
|
|
|
|
engine.configure(
|
|
symbols=["AAPL", "MSFT", "GOOGL", "AMZN", "META"],
|
|
weights="equal", # Equal weighting
|
|
start_date="2023-01-01",
|
|
end_date="2023-12-31",
|
|
initial_capital=100000.0
|
|
)
|
|
|
|
results = engine.run()
|
|
|
|
# Per-symbol results
|
|
for symbol in results.symbol_results:
|
|
result = results.symbol_results[symbol]
|
|
print(f"{symbol}: {result.total_return:.2%}")
|
|
|
|
Custom Weights
|
|
~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Custom portfolio weights
|
|
engine.configure(
|
|
symbols=["AAPL", "MSFT", "GOOGL"],
|
|
weights={
|
|
"AAPL": 0.5,
|
|
"MSFT": 0.3,
|
|
"GOOGL": 0.2
|
|
}
|
|
)
|
|
|
|
Strategy Development
|
|
--------------------
|
|
|
|
Creating Custom Strategies
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.strategy.base import Strategy, Signal
|
|
from typing import List, Dict
|
|
import pandas as pd
|
|
|
|
class MyCustomStrategy(Strategy):
|
|
"""Custom trading strategy."""
|
|
|
|
def __init__(self, param1: float = 1.0, param2: int = 10):
|
|
super().__init__()
|
|
self.param1 = param1
|
|
self.param2 = param2
|
|
|
|
def generate_signals(
|
|
self,
|
|
data: pd.DataFrame
|
|
) -> List[Signal]:
|
|
"""Generate trading signals."""
|
|
signals = []
|
|
|
|
# Your strategy logic here
|
|
for i in range(len(data)):
|
|
if self.should_buy(data, i):
|
|
signals.append(Signal(
|
|
date=data.index[i],
|
|
action="buy",
|
|
confidence=0.8
|
|
))
|
|
elif self.should_sell(data, i):
|
|
signals.append(Signal(
|
|
date=data.index[i],
|
|
action="sell",
|
|
confidence=0.8
|
|
))
|
|
|
|
return signals
|
|
|
|
def should_buy(self, data: pd.DataFrame, index: int) -> bool:
|
|
"""Buy condition."""
|
|
# Implement buy logic
|
|
return False
|
|
|
|
def should_sell(self, data: pd.DataFrame, index: int) -> bool:
|
|
"""Sell condition."""
|
|
# Implement sell logic
|
|
return False
|
|
|
|
Strategy Optimization
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.optimizer import StrategyOptimizer
|
|
|
|
optimizer = StrategyOptimizer()
|
|
|
|
# Define parameter grid
|
|
param_grid = {
|
|
"sma_fast": [10, 20, 30],
|
|
"sma_slow": [50, 100, 200],
|
|
"position_size": [0.05, 0.1, 0.15]
|
|
}
|
|
|
|
# Run optimization
|
|
best_params = optimizer.optimize(
|
|
strategy_class=TrendFollowingStrategy,
|
|
param_grid=param_grid,
|
|
metric="sharpe_ratio", # Optimize for Sharpe
|
|
data=data
|
|
)
|
|
|
|
print(f"Best parameters: {best_params}")
|
|
|
|
Walk-Forward Analysis
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.backtest.walk_forward import WalkForwardTester
|
|
|
|
wf_tester = WalkForwardTester()
|
|
|
|
results = wf_tester.run(
|
|
strategy=strategy,
|
|
data=data,
|
|
train_size=252, # 1 year training
|
|
test_size=63, # 3 months testing
|
|
step_size=63 # Move forward 3 months at a time
|
|
)
|
|
|
|
print(f"Average in-sample Sharpe: {results.avg_in_sample_sharpe:.2f}")
|
|
print(f"Average out-of-sample Sharpe: {results.avg_out_sample_sharpe:.2f}")
|
|
|
|
Data Handling
|
|
-------------
|
|
|
|
Data Sources
|
|
~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
from openclaw.data.sources import YahooFinanceData
|
|
|
|
# Use Yahoo Finance data
|
|
data_source = YahooFinanceData()
|
|
|
|
# Fetch historical data
|
|
data = data_source.get_data(
|
|
symbols=["AAPL"],
|
|
start_date="2023-01-01",
|
|
end_date="2023-12-31",
|
|
interval="1d"
|
|
)
|
|
|
|
Custom Data
|
|
~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
# Use custom data
|
|
import pandas as pd
|
|
|
|
custom_data = pd.read_csv("my_data.csv", index_col=0, parse_dates=True)
|
|
|
|
engine = BacktestEngine()
|
|
engine.set_data(custom_data)
|
|
engine.configure(
|
|
symbols=["CUSTOM"],
|
|
start_date="2023-01-01",
|
|
end_date="2023-12-31"
|
|
)
|
|
|
|
Best Practices
|
|
--------------
|
|
|
|
1. **Out-of-sample testing**: Reserve data for final validation
|
|
2. **Transaction costs**: Always include realistic commissions and slippage
|
|
3. **Multiple regimes**: Test across different market conditions
|
|
4. **Robustness checks**: Sensitivity analysis on parameters
|
|
5. **Risk metrics**: Focus on risk-adjusted returns, not just total return
|
|
6. **Realistic assumptions**: Account for market impact and liquidity
|
|
|
|
Common Pitfalls
|
|
---------------
|
|
|
|
* **Overfitting**: Too many parameters optimized on limited data
|
|
* **Look-ahead bias**: Using future information in strategy logic
|
|
* **Survivorship bias**: Testing only on currently active companies
|
|
* **Data mining**: Testing too many strategies on same data
|
|
* **Ignoring costs**: Not accounting for fees and slippage
|