Update
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Backtest results
|
||||
backtest_results/
|
||||
*.csv
|
||||
*.png
|
||||
*.jpg
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,165 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get up and running with the MT5 Python backtesting framework in 5 minutes!
|
||||
|
||||
## Step 1: Install Dependencies
|
||||
|
||||
```bash
|
||||
cd backtesting/MT5
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Step 2: Test Your Setup
|
||||
|
||||
Before running backtests, verify that MT5 is properly configured:
|
||||
|
||||
```bash
|
||||
python test_setup.py
|
||||
```
|
||||
|
||||
This will:
|
||||
- Test MT5 connection
|
||||
- Check account access
|
||||
- Verify symbol availability
|
||||
- Test historical data retrieval
|
||||
- Test indicator creation
|
||||
|
||||
**If this fails**, make sure:
|
||||
1. MetaTrader5 is installed
|
||||
2. MT5 is running
|
||||
3. You're logged into a demo or live account
|
||||
4. You have historical data downloaded in MT5
|
||||
|
||||
## Step 3: Run Your First Backtest
|
||||
|
||||
### Option A: Command Line (Easiest)
|
||||
|
||||
```bash
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol XAUUSD --start 2023-01-01 --end 2024-01-01
|
||||
```
|
||||
|
||||
This will:
|
||||
- Run the RSI Reversal strategy on Gold (XAUUSD)
|
||||
- Backtest from Jan 1, 2023 to Jan 1, 2024
|
||||
- Generate performance reports in `backtest_results/`
|
||||
|
||||
### Option B: Python Script
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
import MetaTrader5 as mt5
|
||||
from backtest_engine import BacktestEngine
|
||||
from example_strategies import RSIReversalStrategy
|
||||
from performance_analyzer import PerformanceAnalyzer
|
||||
|
||||
# Create strategy
|
||||
strategy = RSIReversalStrategy(
|
||||
symbol='XAUUSD',
|
||||
timeframe=mt5.TIMEFRAME_H1,
|
||||
initial_balance=10000.0
|
||||
)
|
||||
|
||||
# Run backtest
|
||||
engine = BacktestEngine(
|
||||
strategy,
|
||||
start_date=datetime(2023, 1, 1),
|
||||
end_date=datetime(2024, 1, 1)
|
||||
)
|
||||
|
||||
results = engine.run()
|
||||
|
||||
# View results
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report('my_results')
|
||||
```
|
||||
|
||||
## Step 4: Create Your Own Strategy
|
||||
|
||||
1. **Copy an example strategy** from `example_strategies.py`
|
||||
|
||||
2. **Modify the `on_bar()` method** with your trading logic:
|
||||
|
||||
```python
|
||||
def on_bar(self, bar_data):
|
||||
rsi = bar_data.get('rsi')
|
||||
current_price = bar_data['close']
|
||||
|
||||
# Your logic here
|
||||
if rsi < 30 and self.position is None:
|
||||
self.open_position('BUY', 0.1, current_price)
|
||||
```
|
||||
|
||||
3. **Specify required indicators**:
|
||||
|
||||
```python
|
||||
def get_required_indicators(self):
|
||||
return {
|
||||
'rsi': {'period': 14, 'applied_price': mt5.PRICE_CLOSE},
|
||||
'ema': {'period': 50, 'applied_price': mt5.PRICE_CLOSE}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Run your strategy**:
|
||||
|
||||
```python
|
||||
from backtest_engine import BacktestEngine
|
||||
# ... (same as above)
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Different Symbols
|
||||
```bash
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol EURUSD --start 2023-01-01 --end 2024-01-01
|
||||
```
|
||||
|
||||
### Different Timeframes
|
||||
```bash
|
||||
python run_backtest.py --strategy RSIScalpingStrategy --symbol XAUUSD --timeframe M15 --start 2023-01-01 --end 2024-01-01
|
||||
```
|
||||
|
||||
### Custom Parameters
|
||||
```bash
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol XAUUSD --start 2023-01-01 --end 2024-01-01 --rsi-period 28 --rsi-overbought 64 --rsi-oversold 13 --lot-size 0.2
|
||||
```
|
||||
|
||||
## Understanding the Output
|
||||
|
||||
After running a backtest, you'll get:
|
||||
|
||||
1. **Console Summary**: Key metrics printed to terminal
|
||||
2. **Equity Curve**: `*_equity_curve.png` - Account balance over time
|
||||
3. **Drawdown Chart**: `*_drawdown.png` - Drawdown visualization
|
||||
4. **Monthly Returns**: `*_monthly_returns.png` - Monthly performance
|
||||
5. **Trades CSV**: `*_trades.csv` - Detailed trade log
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **Optimize Parameters**: Try different parameter combinations
|
||||
- **Test Multiple Strategies**: Compare different approaches
|
||||
- **Add More Indicators**: Extend `BacktestEngine.setup_indicators()`
|
||||
- **Improve Risk Management**: Customize position sizing and risk rules
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "MT5 initialization failed"
|
||||
- Make sure MT5 is installed and running
|
||||
- Try logging into MT5 manually first
|
||||
- Check that you have a demo/live account configured
|
||||
|
||||
### "No data available"
|
||||
- Check date range - ensure data exists
|
||||
- Verify symbol name (e.g., 'XAUUSD' not 'GOLD')
|
||||
- Download historical data in MT5 (Tools > History Center)
|
||||
|
||||
### "Failed to create indicator"
|
||||
- Ensure enough bars are available (need more bars than indicator period)
|
||||
- Check indicator parameters are valid
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the main [README.md](README.md) for detailed documentation
|
||||
- Review `example_strategies.py` for strategy examples
|
||||
- Look at `example_usage.py` for more usage examples
|
||||
|
||||
Happy backtesting! 🚀
|
||||
@@ -0,0 +1,279 @@
|
||||
# MetaTrader5 Python Backtesting Framework
|
||||
|
||||
A comprehensive Python backtesting framework for algorithmic trading strategies using MetaTrader5 historical data.
|
||||
|
||||
## Features
|
||||
|
||||
- **Easy Strategy Development**: Inherit from `BaseStrategy` and implement your trading logic
|
||||
- **MT5 Integration**: Uses MetaTrader5 Python library for historical data and indicators
|
||||
- **Multiple Indicators**: Built-in support for RSI, EMA, SMA, ATR, MACD, and more
|
||||
- **Risk Management**: Built-in position sizing, stop loss, take profit, and drawdown protection
|
||||
- **Performance Analysis**: Comprehensive metrics and visualization tools
|
||||
- **Example Strategies**: Ready-to-use example strategies (RSI Scalping, EMA Crossover, RSI Reversal)
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Install MetaTrader5**: Make sure you have MetaTrader5 installed on your system.
|
||||
|
||||
2. **Install Python dependencies**:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Initialize MT5 Connection**: The framework will automatically connect to MT5 when you run a backtest. Make sure MT5 is installed and you have a demo or live account configured.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Running a Backtest
|
||||
|
||||
Use the command-line interface to run a backtest:
|
||||
|
||||
```bash
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol XAUUSD --start 2023-01-01 --end 2024-01-01
|
||||
```
|
||||
|
||||
### Creating Your Own Strategy
|
||||
|
||||
1. Create a new Python file or add to `example_strategies.py`:
|
||||
|
||||
```python
|
||||
from base_strategy import BaseStrategy
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
class MyStrategy(BaseStrategy):
|
||||
def __init__(self, symbol, timeframe, initial_balance=10000.0):
|
||||
super().__init__(symbol, timeframe, initial_balance)
|
||||
# Initialize your strategy parameters
|
||||
self.my_param = 42
|
||||
|
||||
def get_required_indicators(self):
|
||||
return {
|
||||
'rsi': {'period': 14, 'applied_price': mt5.PRICE_CLOSE},
|
||||
'ema': {'period': 50, 'applied_price': mt5.PRICE_CLOSE}
|
||||
}
|
||||
|
||||
def on_bar(self, bar_data):
|
||||
# Your trading logic here
|
||||
rsi = bar_data.get('rsi')
|
||||
ema = bar_data.get('ema')
|
||||
current_price = bar_data['close']
|
||||
|
||||
# Example: Buy when RSI < 30 and price > EMA
|
||||
if rsi < 30 and current_price > ema:
|
||||
if self.position is None:
|
||||
self.open_position('BUY', 0.1, current_price)
|
||||
|
||||
def get_parameters(self):
|
||||
return {'my_param': self.my_param}
|
||||
```
|
||||
|
||||
2. Run your strategy:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from backtest_engine import BacktestEngine
|
||||
from performance_analyzer import PerformanceAnalyzer
|
||||
|
||||
# Create strategy
|
||||
strategy = MyStrategy('XAUUSD', mt5.TIMEFRAME_H1, initial_balance=10000.0)
|
||||
|
||||
# Run backtest
|
||||
engine = BacktestEngine(
|
||||
strategy,
|
||||
start_date=datetime(2023, 1, 1),
|
||||
end_date=datetime(2024, 1, 1)
|
||||
)
|
||||
|
||||
results = engine.run()
|
||||
|
||||
# Analyze results
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report('my_backtest_results')
|
||||
```
|
||||
|
||||
## Available Strategies
|
||||
|
||||
### RSIScalpingStrategy
|
||||
RSI-based scalping strategy that enters on RSI crossovers.
|
||||
|
||||
**Parameters:**
|
||||
- `rsi_period`: RSI period (default: 14)
|
||||
- `rsi_overbought`: Overbought level (default: 70)
|
||||
- `rsi_oversold`: Oversold level (default: 30)
|
||||
- `rsi_target_buy`: Exit target for long positions (default: 80)
|
||||
- `rsi_target_sell`: Exit target for short positions (default: 20)
|
||||
|
||||
### EMAStrategy
|
||||
Simple EMA crossover strategy.
|
||||
|
||||
**Parameters:**
|
||||
- `ema_period`: EMA period (default: 50)
|
||||
|
||||
### RSIReversalStrategy
|
||||
RSI reversal strategy similar to your MQL5 implementations.
|
||||
|
||||
**Parameters:**
|
||||
- `rsi_period`: RSI period (default: 14)
|
||||
- `rsi_overbought`: Overbought level (default: 70)
|
||||
- `rsi_oversold`: Oversold level (default: 30)
|
||||
- `rsi_exit`: Neutral exit level (default: 50)
|
||||
|
||||
## Command Line Options
|
||||
|
||||
```bash
|
||||
python run_backtest.py --help
|
||||
```
|
||||
|
||||
**Required Arguments:**
|
||||
- `--strategy`: Strategy name (RSIScalpingStrategy, EMAStrategy, RSIReversalStrategy)
|
||||
- `--start`: Start date (YYYY-MM-DD)
|
||||
- `--end`: End date (YYYY-MM-DD)
|
||||
|
||||
**Optional Arguments:**
|
||||
- `--symbol`: Trading symbol (default: XAUUSD)
|
||||
- `--timeframe`: Timeframe M1, M5, M15, M30, H1, H4, D1 (default: H1)
|
||||
- `--balance`: Initial balance (default: 10000)
|
||||
- `--output`: Output directory (default: backtest_results)
|
||||
- `--rsi-period`: RSI period (default: 14)
|
||||
- `--rsi-overbought`: RSI overbought level (default: 70)
|
||||
- `--rsi-oversold`: RSI oversold level (default: 30)
|
||||
- `--ema-period`: EMA period (default: 50)
|
||||
- `--lot-size`: Lot size (default: 0.1)
|
||||
- `--stop-loss`: Stop loss in pips (default: 50)
|
||||
- `--take-profit`: Take profit in pips (default: 100)
|
||||
|
||||
## Example Commands
|
||||
|
||||
```bash
|
||||
# RSI Scalping on Gold, 1-hour timeframe
|
||||
python run_backtest.py --strategy RSIScalpingStrategy --symbol XAUUSD --timeframe H1 --start 2023-01-01 --end 2024-01-01
|
||||
|
||||
# EMA Strategy on EUR/USD, 4-hour timeframe
|
||||
python run_backtest.py --strategy EMAStrategy --symbol EURUSD --timeframe H4 --start 2023-01-01 --end 2024-01-01 --ema-period 100
|
||||
|
||||
# RSI Reversal with custom parameters
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol XAUUSD --start 2023-01-01 --end 2024-01-01 --rsi-period 28 --rsi-overbought 64 --rsi-oversold 13
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
The backtest generates:
|
||||
|
||||
1. **Console Summary**: Performance metrics printed to console
|
||||
2. **Equity Curve Chart**: Visual representation of account balance over time
|
||||
3. **Drawdown Chart**: Drawdown visualization
|
||||
4. **Monthly Returns Chart**: Monthly performance breakdown
|
||||
5. **Trades CSV**: Detailed trade log in CSV format
|
||||
|
||||
All files are saved in the specified output directory (default: `backtest_results/`).
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
The framework calculates:
|
||||
|
||||
- **Total Return**: Percentage return on initial balance
|
||||
- **Win Rate**: Percentage of winning trades
|
||||
- **Profit Factor**: Total profit / Total loss
|
||||
- **Average Win/Loss**: Average profit per winning/losing trade
|
||||
- **Maximum Drawdown**: Largest peak-to-trough decline
|
||||
- **Total Trades**: Number of completed trades
|
||||
|
||||
## BaseStrategy API
|
||||
|
||||
### Methods to Override
|
||||
|
||||
- `on_bar(bar_data)`: Called on each new bar with market data and indicators
|
||||
- `get_parameters()`: Return strategy parameters for logging
|
||||
- `get_required_indicators()`: Specify which indicators are needed
|
||||
|
||||
### Available Methods
|
||||
|
||||
- `open_position(order_type, volume, price, sl=None, tp=None, comment="")`: Open a position
|
||||
- `close_position(close_price)`: Close current position
|
||||
- `check_stop_loss_take_profit(current_price)`: Check SL/TP (called automatically)
|
||||
- `get_performance_metrics()`: Get performance statistics
|
||||
|
||||
### Bar Data Structure
|
||||
|
||||
The `bar_data` dictionary passed to `on_bar()` contains:
|
||||
|
||||
```python
|
||||
{
|
||||
'time': datetime, # Bar timestamp
|
||||
'open': float, # Opening price
|
||||
'high': float, # High price
|
||||
'low': float, # Low price
|
||||
'close': float, # Closing price
|
||||
'tick_volume': int, # Tick volume
|
||||
'spread': int, # Spread in points
|
||||
'rsi': float, # RSI value (if requested)
|
||||
'ema': float, # EMA value (if requested)
|
||||
'indicators': { # All requested indicators
|
||||
'rsi': float,
|
||||
'ema': float,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Indicators
|
||||
|
||||
- **RSI**: Relative Strength Index
|
||||
- **EMA**: Exponential Moving Average
|
||||
- **SMA**: Simple Moving Average
|
||||
- **ATR**: Average True Range
|
||||
- **MACD**: Moving Average Convergence Divergence
|
||||
|
||||
To add more indicators, modify `BacktestEngine.setup_indicators()`.
|
||||
|
||||
## Risk Management
|
||||
|
||||
The framework includes built-in risk management:
|
||||
|
||||
- **Position Sizing**: Configurable min/max lot sizes
|
||||
- **Stop Loss/Take Profit**: Automatic SL/TP checking
|
||||
- **Spread Filtering**: Skip trades when spread is too high
|
||||
- **Drawdown Protection**: Track and limit maximum drawdown
|
||||
- **Margin Management**: Prevent over-leveraging
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Test on Demo First**: Always test strategies on demo accounts before live trading
|
||||
2. **Start Small**: Begin with small position sizes and gradually increase
|
||||
3. **Multiple Timeframes**: Test strategies on different timeframes
|
||||
4. **Parameter Optimization**: Use the framework to optimize strategy parameters
|
||||
5. **Compare Strategies**: Run multiple strategies and compare results
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### MT5 Connection Issues
|
||||
- Ensure MetaTrader5 is installed and running
|
||||
- Check that you have a demo or live account configured
|
||||
- Verify symbol names match MT5 format (e.g., 'XAUUSD' not 'GOLD')
|
||||
|
||||
### No Data Available
|
||||
- Check date range - ensure data exists for the specified period
|
||||
- Verify symbol name is correct
|
||||
- Check that MT5 has historical data for the symbol/timeframe
|
||||
|
||||
### Indicator Errors
|
||||
- Ensure indicator parameters are valid
|
||||
- Check that enough bars are available for indicator calculation
|
||||
- Verify indicator handle creation succeeded
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to extend this framework with:
|
||||
- Additional indicators
|
||||
- More sophisticated risk management
|
||||
- Optimization tools
|
||||
- Walk-forward analysis
|
||||
- Monte Carlo simulation
|
||||
|
||||
## License
|
||||
|
||||
This framework is provided for educational and research purposes.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Trading involves substantial risk of loss. This framework is provided for educational purposes only. Always test thoroughly on a demo account before using with real money. Past performance does not guarantee future results.
|
||||
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
Backtesting Engine for MetaTrader5
|
||||
|
||||
This module provides the core backtesting functionality using MT5 historical data.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any, List
|
||||
import MetaTrader5 as mt5
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from base_strategy import BaseStrategy
|
||||
|
||||
|
||||
class BacktestEngine:
|
||||
"""
|
||||
Main backtesting engine that runs strategies on historical data.
|
||||
"""
|
||||
|
||||
def __init__(self, strategy: BaseStrategy, start_date: datetime, end_date: datetime):
|
||||
"""
|
||||
Initialize the backtesting engine.
|
||||
|
||||
Args:
|
||||
strategy: Strategy instance to backtest
|
||||
start_date: Start date for backtesting
|
||||
end_date: End date for backtesting
|
||||
"""
|
||||
self.strategy = strategy
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
|
||||
# Initialize MT5 connection
|
||||
if not mt5.initialize():
|
||||
raise RuntimeError(f"MT5 initialization failed: {mt5.last_error()}")
|
||||
|
||||
# Indicator handles
|
||||
self.indicator_handles = {}
|
||||
self.setup_indicators()
|
||||
|
||||
def setup_indicators(self):
|
||||
"""Setup all required indicators for the strategy."""
|
||||
required_indicators = self.strategy.get_required_indicators()
|
||||
|
||||
for indicator_name, params in required_indicators.items():
|
||||
handle = None
|
||||
|
||||
if indicator_name.lower() == 'rsi':
|
||||
handle = mt5.iRSI(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
params.get('period', 14),
|
||||
params.get('applied_price', mt5.PRICE_CLOSE)
|
||||
)
|
||||
elif indicator_name.lower() == 'ema':
|
||||
handle = mt5.iMA(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
params.get('period', 50),
|
||||
0, # shift
|
||||
mt5.MODE_EMA,
|
||||
params.get('applied_price', mt5.PRICE_CLOSE)
|
||||
)
|
||||
elif indicator_name.lower() == 'sma':
|
||||
handle = mt5.iMA(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
params.get('period', 50),
|
||||
0, # shift
|
||||
mt5.MODE_SMA,
|
||||
params.get('applied_price', mt5.PRICE_CLOSE)
|
||||
)
|
||||
elif indicator_name.lower() == 'atr':
|
||||
handle = mt5.iATR(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
params.get('period', 14)
|
||||
)
|
||||
elif indicator_name.lower() == 'macd':
|
||||
handle = mt5.iMACD(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
params.get('fast', 12),
|
||||
params.get('slow', 26),
|
||||
params.get('signal', 9),
|
||||
params.get('applied_price', mt5.PRICE_CLOSE)
|
||||
)
|
||||
|
||||
if handle is not None and handle != mt5.INVALID_HANDLE:
|
||||
self.indicator_handles[indicator_name] = handle
|
||||
else:
|
||||
print(f"Warning: Failed to create {indicator_name} indicator")
|
||||
|
||||
def get_indicator_values(self, indicator_name: str, count: int = 1) -> Optional[np.ndarray]:
|
||||
"""
|
||||
Get indicator values.
|
||||
|
||||
Args:
|
||||
indicator_name: Name of the indicator
|
||||
count: Number of values to retrieve
|
||||
|
||||
Returns:
|
||||
Array of indicator values or None
|
||||
"""
|
||||
if indicator_name not in self.indicator_handles:
|
||||
return None
|
||||
|
||||
handle = self.indicator_handles[indicator_name]
|
||||
buffer = np.zeros(count, dtype=float)
|
||||
|
||||
if indicator_name.lower() == 'macd':
|
||||
# MACD returns 3 buffers
|
||||
result = mt5.copy_buffer(handle, 0, 0, count) # Main line
|
||||
if result is None:
|
||||
return None
|
||||
return np.array(result)
|
||||
else:
|
||||
result = mt5.copy_buffer(handle, 0, 0, count)
|
||||
if result is None:
|
||||
return None
|
||||
return np.array(result)
|
||||
|
||||
def get_bar_data(self, time: datetime) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get bar data and indicator values for a specific time.
|
||||
|
||||
Args:
|
||||
time: Bar time
|
||||
|
||||
Returns:
|
||||
Dictionary with bar data and indicators
|
||||
"""
|
||||
# Get rates
|
||||
rates = mt5.copy_rates_from(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
time,
|
||||
1
|
||||
)
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
return None
|
||||
|
||||
rate = rates[0]
|
||||
|
||||
# Get spread
|
||||
symbol_info = mt5.symbol_info(self.strategy.symbol)
|
||||
spread = symbol_info.spread if symbol_info else 0
|
||||
|
||||
# Build bar data
|
||||
bar_data = {
|
||||
'time': datetime.fromtimestamp(rate['time']),
|
||||
'open': float(rate['open']),
|
||||
'high': float(rate['high']),
|
||||
'low': float(rate['low']),
|
||||
'close': float(rate['close']),
|
||||
'tick_volume': int(rate['tick_volume']),
|
||||
'spread': spread,
|
||||
'indicators': {}
|
||||
}
|
||||
|
||||
# Get indicator values
|
||||
for indicator_name in self.indicator_handles.keys():
|
||||
values = self.get_indicator_values(indicator_name, 2)
|
||||
if values is not None and len(values) >= 1:
|
||||
bar_data['indicators'][indicator_name] = values[0]
|
||||
# Also add to top level for convenience
|
||||
bar_data[indicator_name.lower()] = values[0]
|
||||
|
||||
return bar_data
|
||||
|
||||
def run(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Run the backtest.
|
||||
|
||||
Returns:
|
||||
Dictionary with backtest results and performance metrics
|
||||
"""
|
||||
print(f"Starting backtest from {self.start_date} to {self.end_date}")
|
||||
print(f"Symbol: {self.strategy.symbol}, Timeframe: {self.strategy.timeframe}")
|
||||
|
||||
# Get all bars in the date range
|
||||
rates = mt5.copy_rates_range(
|
||||
self.strategy.symbol,
|
||||
self.strategy.timeframe,
|
||||
self.start_date,
|
||||
self.end_date
|
||||
)
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
raise ValueError(f"No data available for {self.strategy.symbol} in the specified date range")
|
||||
|
||||
print(f"Processing {len(rates)} bars...")
|
||||
|
||||
# Process each bar
|
||||
processed_bars = 0
|
||||
for i, rate in enumerate(rates):
|
||||
bar_time = datetime.fromtimestamp(rate['time'])
|
||||
|
||||
# Get full bar data with indicators
|
||||
bar_data = self.get_bar_data(bar_time)
|
||||
if bar_data is None:
|
||||
continue
|
||||
|
||||
# Check stop loss/take profit on current position
|
||||
if self.strategy.position is not None:
|
||||
self.strategy.check_stop_loss_take_profit(bar_data['close'])
|
||||
|
||||
# Call strategy on_bar method
|
||||
try:
|
||||
self.strategy.on_bar(bar_data)
|
||||
except Exception as e:
|
||||
print(f"Error in strategy on_bar at {bar_time}: {e}")
|
||||
continue
|
||||
|
||||
# Update equity (unrealized P&L)
|
||||
if self.strategy.position is not None:
|
||||
if self.strategy.position['type'] == 'BUY':
|
||||
unrealized_pnl = (bar_data['close'] - self.strategy.position['open_price']) * \
|
||||
self.strategy.position['volume'] * 10000 * 10
|
||||
else:
|
||||
unrealized_pnl = (self.strategy.position['open_price'] - bar_data['close']) * \
|
||||
self.strategy.position['volume'] * 10000 * 10
|
||||
self.strategy.equity = self.strategy.current_balance + unrealized_pnl
|
||||
else:
|
||||
self.strategy.equity = self.strategy.current_balance
|
||||
|
||||
processed_bars += 1
|
||||
|
||||
if processed_bars % 100 == 0:
|
||||
print(f"Processed {processed_bars}/{len(rates)} bars...")
|
||||
|
||||
# Close any open position at the end
|
||||
if self.strategy.position is not None:
|
||||
last_bar = rates[-1]
|
||||
last_price = float(last_bar['close'])
|
||||
self.strategy.close_position(last_price)
|
||||
|
||||
print(f"Backtest completed. Processed {processed_bars} bars.")
|
||||
|
||||
# Get performance metrics
|
||||
metrics = self.strategy.get_performance_metrics()
|
||||
|
||||
# Cleanup
|
||||
self.cleanup()
|
||||
|
||||
return {
|
||||
'metrics': metrics,
|
||||
'trades': self.strategy.closed_trades,
|
||||
'strategy_name': self.strategy.__class__.__name__
|
||||
}
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up indicator handles and MT5 connection."""
|
||||
for handle in self.indicator_handles.values():
|
||||
mt5.indicator_release(handle)
|
||||
mt5.shutdown()
|
||||
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
Base Strategy Class for MetaTrader5 Backtesting
|
||||
|
||||
This module provides a base class that all trading strategies should inherit from.
|
||||
Implement your trading logic by overriding the on_bar() method.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
|
||||
class BaseStrategy(ABC):
|
||||
"""
|
||||
Base class for all trading strategies.
|
||||
|
||||
Inherit from this class and implement:
|
||||
- on_bar(): Your trading logic for each bar
|
||||
- get_parameters(): Return strategy parameters
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, timeframe: int, initial_balance: float = 10000.0):
|
||||
"""
|
||||
Initialize the strategy.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'XAUUSD', 'EURUSD')
|
||||
timeframe: MT5 timeframe constant (e.g., mt5.TIMEFRAME_H1)
|
||||
initial_balance: Starting account balance
|
||||
"""
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
self.initial_balance = initial_balance
|
||||
self.current_balance = initial_balance
|
||||
self.equity = initial_balance
|
||||
|
||||
# Position tracking
|
||||
self.position = None # {'type': 'BUY'/'SELL', 'volume': float, 'open_price': float, 'open_time': datetime}
|
||||
self.trades = []
|
||||
self.closed_trades = []
|
||||
|
||||
# Performance metrics
|
||||
self.max_drawdown = 0.0
|
||||
self.peak_equity = initial_balance
|
||||
self.total_profit = 0.0
|
||||
self.total_loss = 0.0
|
||||
self.winning_trades = 0
|
||||
self.losing_trades = 0
|
||||
|
||||
# Risk management
|
||||
self.max_lot_size = 0.1
|
||||
self.min_lot_size = 0.01
|
||||
self.max_spread = 1000 # in points
|
||||
self.max_drawdown_percent = 0.2 # 20% max drawdown
|
||||
|
||||
@abstractmethod
|
||||
def on_bar(self, bar_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Called on each new bar. Implement your trading logic here.
|
||||
|
||||
Args:
|
||||
bar_data: Dictionary containing:
|
||||
- 'time': datetime of the bar
|
||||
- 'open': float opening price
|
||||
- 'high': float high price
|
||||
- 'low': float low price
|
||||
- 'close': float closing price
|
||||
- 'tick_volume': int tick volume
|
||||
- 'spread': int spread in points
|
||||
- 'rsi': Optional[float] RSI value if requested
|
||||
- 'ema': Optional[float] EMA value if requested
|
||||
- 'indicators': Dict with any other requested indicators
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_parameters(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Return strategy parameters for logging/reporting.
|
||||
|
||||
Returns:
|
||||
Dictionary of parameter names and values
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_required_indicators(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Specify which indicators are needed by the strategy.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping indicator names to their parameters.
|
||||
Example: {
|
||||
'rsi': {'period': 14, 'applied_price': mt5.PRICE_CLOSE},
|
||||
'ema': {'period': 50, 'applied_price': mt5.PRICE_CLOSE}
|
||||
}
|
||||
"""
|
||||
return {}
|
||||
|
||||
def open_position(self, order_type: str, volume: float, price: float,
|
||||
sl: Optional[float] = None, tp: Optional[float] = None,
|
||||
comment: str = "") -> bool:
|
||||
"""
|
||||
Open a trading position.
|
||||
|
||||
Args:
|
||||
order_type: 'BUY' or 'SELL'
|
||||
volume: Lot size
|
||||
price: Entry price
|
||||
sl: Stop loss price (optional)
|
||||
tp: Take profit price (optional)
|
||||
comment: Trade comment
|
||||
|
||||
Returns:
|
||||
True if position opened successfully
|
||||
"""
|
||||
if self.position is not None:
|
||||
return False # Position already open
|
||||
|
||||
# Validate volume
|
||||
volume = max(self.min_lot_size, min(volume, self.max_lot_size))
|
||||
|
||||
# Calculate margin requirement (simplified)
|
||||
contract_size = 100000 # Standard lot size
|
||||
margin_required = volume * contract_size * price * 0.01 # 1% margin (adjust as needed)
|
||||
|
||||
if margin_required > self.equity * 0.9: # Don't use more than 90% of equity
|
||||
return False
|
||||
|
||||
self.position = {
|
||||
'type': order_type,
|
||||
'volume': volume,
|
||||
'open_price': price,
|
||||
'open_time': datetime.now(),
|
||||
'sl': sl,
|
||||
'tp': tp,
|
||||
'comment': comment
|
||||
}
|
||||
|
||||
return True
|
||||
|
||||
def close_position(self, close_price: float) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Close the current position.
|
||||
|
||||
Args:
|
||||
close_price: Price at which to close
|
||||
|
||||
Returns:
|
||||
Trade result dictionary or None if no position
|
||||
"""
|
||||
if self.position is None:
|
||||
return None
|
||||
|
||||
# Calculate profit/loss
|
||||
if self.position['type'] == 'BUY':
|
||||
pips = (close_price - self.position['open_price']) * 10000 # For 5-digit brokers
|
||||
profit = pips * self.position['volume'] * 10 # Simplified P&L calculation
|
||||
else: # SELL
|
||||
pips = (self.position['open_price'] - close_price) * 10000
|
||||
profit = pips * self.position['volume'] * 10
|
||||
|
||||
trade_result = {
|
||||
'type': self.position['type'],
|
||||
'volume': self.position['volume'],
|
||||
'open_price': self.position['open_price'],
|
||||
'close_price': close_price,
|
||||
'open_time': self.position['open_time'],
|
||||
'close_time': datetime.now(),
|
||||
'profit': profit,
|
||||
'pips': pips,
|
||||
'comment': self.position.get('comment', '')
|
||||
}
|
||||
|
||||
# Update balance and metrics
|
||||
self.current_balance += profit
|
||||
self.equity = self.current_balance
|
||||
|
||||
if profit > 0:
|
||||
self.winning_trades += 1
|
||||
self.total_profit += profit
|
||||
else:
|
||||
self.losing_trades += 1
|
||||
self.total_loss += abs(profit)
|
||||
|
||||
# Update drawdown
|
||||
if self.equity > self.peak_equity:
|
||||
self.peak_equity = self.equity
|
||||
|
||||
drawdown = (self.peak_equity - self.equity) / self.peak_equity
|
||||
if drawdown > self.max_drawdown:
|
||||
self.max_drawdown = drawdown
|
||||
|
||||
self.closed_trades.append(trade_result)
|
||||
self.position = None
|
||||
|
||||
return trade_result
|
||||
|
||||
def check_stop_loss_take_profit(self, current_price: float) -> bool:
|
||||
"""
|
||||
Check if stop loss or take profit should be triggered.
|
||||
|
||||
Args:
|
||||
current_price: Current market price
|
||||
|
||||
Returns:
|
||||
True if position was closed
|
||||
"""
|
||||
if self.position is None:
|
||||
return False
|
||||
|
||||
should_close = False
|
||||
|
||||
if self.position['type'] == 'BUY':
|
||||
if self.position.get('sl') and current_price <= self.position['sl']:
|
||||
should_close = True
|
||||
if self.position.get('tp') and current_price >= self.position['tp']:
|
||||
should_close = True
|
||||
else: # SELL
|
||||
if self.position.get('sl') and current_price >= self.position['sl']:
|
||||
should_close = True
|
||||
if self.position.get('tp') and current_price <= self.position['tp']:
|
||||
should_close = True
|
||||
|
||||
if should_close:
|
||||
self.close_position(current_price)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_performance_metrics(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Calculate and return performance metrics.
|
||||
|
||||
Returns:
|
||||
Dictionary with performance statistics
|
||||
"""
|
||||
total_trades = len(self.closed_trades)
|
||||
win_rate = (self.winning_trades / total_trades * 100) if total_trades > 0 else 0
|
||||
|
||||
avg_win = (self.total_profit / self.winning_trades) if self.winning_trades > 0 else 0
|
||||
avg_loss = (self.total_loss / self.losing_trades) if self.losing_trades > 0 else 0
|
||||
profit_factor = (self.total_profit / self.total_loss) if self.total_loss > 0 else 0
|
||||
|
||||
total_return = ((self.equity - self.initial_balance) / self.initial_balance) * 100
|
||||
|
||||
return {
|
||||
'initial_balance': self.initial_balance,
|
||||
'final_balance': self.equity,
|
||||
'total_return_pct': total_return,
|
||||
'total_trades': total_trades,
|
||||
'winning_trades': self.winning_trades,
|
||||
'losing_trades': self.losing_trades,
|
||||
'win_rate_pct': win_rate,
|
||||
'total_profit': self.total_profit,
|
||||
'total_loss': self.total_loss,
|
||||
'profit_factor': profit_factor,
|
||||
'avg_win': avg_win,
|
||||
'avg_loss': avg_loss,
|
||||
'max_drawdown_pct': self.max_drawdown * 100,
|
||||
'parameters': self.get_parameters()
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
"""
|
||||
Example Trading Strategies
|
||||
|
||||
These are example implementations of trading strategies that you can use as templates
|
||||
or modify for your own strategies.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
import MetaTrader5 as mt5
|
||||
from base_strategy import BaseStrategy
|
||||
|
||||
|
||||
class RSIScalpingStrategy(BaseStrategy):
|
||||
"""
|
||||
RSI Scalping Strategy - Example implementation
|
||||
|
||||
Entry:
|
||||
- Buy when RSI crosses above oversold level
|
||||
- Sell when RSI crosses below overbought level
|
||||
|
||||
Exit:
|
||||
- RSI reaches target levels
|
||||
- Stop loss and take profit
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, timeframe: int, initial_balance: float = 10000.0,
|
||||
rsi_period: int = 14, rsi_overbought: float = 70, rsi_oversold: float = 30,
|
||||
rsi_target_buy: float = 80, rsi_target_sell: float = 20,
|
||||
lot_size: float = 0.1, stop_loss_pips: int = 50, take_profit_pips: int = 100):
|
||||
super().__init__(symbol, timeframe, initial_balance)
|
||||
|
||||
self.rsi_period = rsi_period
|
||||
self.rsi_overbought = rsi_overbought
|
||||
self.rsi_oversold = rsi_oversold
|
||||
self.rsi_target_buy = rsi_target_buy
|
||||
self.rsi_target_sell = rsi_target_sell
|
||||
self.lot_size = lot_size
|
||||
self.stop_loss_pips = stop_loss_pips
|
||||
self.take_profit_pips = take_profit_pips
|
||||
|
||||
# Track previous RSI for crossover detection
|
||||
self.prev_rsi = None
|
||||
|
||||
def get_required_indicators(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
'rsi': {
|
||||
'period': self.rsi_period,
|
||||
'applied_price': mt5.PRICE_CLOSE
|
||||
}
|
||||
}
|
||||
|
||||
def on_bar(self, bar_data: Dict[str, Any]) -> None:
|
||||
rsi = bar_data.get('rsi')
|
||||
if rsi is None:
|
||||
return
|
||||
|
||||
current_price = bar_data['close']
|
||||
spread = bar_data.get('spread', 0)
|
||||
|
||||
# Check spread
|
||||
if spread > self.max_spread:
|
||||
return
|
||||
|
||||
# Check if we have a position
|
||||
if self.position is not None:
|
||||
# Check exit conditions
|
||||
if self.position['type'] == 'BUY':
|
||||
if rsi >= self.rsi_target_buy:
|
||||
self.close_position(current_price)
|
||||
elif self.position['type'] == 'SELL':
|
||||
if rsi <= self.rsi_target_sell:
|
||||
self.close_position(current_price)
|
||||
else:
|
||||
# Check entry conditions
|
||||
if self.prev_rsi is not None:
|
||||
# Buy signal: RSI crosses above oversold
|
||||
if self.prev_rsi <= self.rsi_oversold and rsi > self.rsi_oversold:
|
||||
sl = current_price - (self.stop_loss_pips / 10000)
|
||||
tp = current_price + (self.take_profit_pips / 10000)
|
||||
self.open_position('BUY', self.lot_size, current_price, sl, tp, 'RSI Scalping Buy')
|
||||
|
||||
# Sell signal: RSI crosses below overbought
|
||||
elif self.prev_rsi >= self.rsi_overbought and rsi < self.rsi_overbought:
|
||||
sl = current_price + (self.stop_loss_pips / 10000)
|
||||
tp = current_price - (self.take_profit_pips / 10000)
|
||||
self.open_position('SELL', self.lot_size, current_price, sl, tp, 'RSI Scalping Sell')
|
||||
|
||||
self.prev_rsi = rsi
|
||||
|
||||
def get_parameters(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'rsi_period': self.rsi_period,
|
||||
'rsi_overbought': self.rsi_overbought,
|
||||
'rsi_oversold': self.rsi_oversold,
|
||||
'rsi_target_buy': self.rsi_target_buy,
|
||||
'rsi_target_sell': self.rsi_target_sell,
|
||||
'lot_size': self.lot_size,
|
||||
'stop_loss_pips': self.stop_loss_pips,
|
||||
'take_profit_pips': self.take_profit_pips
|
||||
}
|
||||
|
||||
|
||||
class EMAStrategy(BaseStrategy):
|
||||
"""
|
||||
EMA Crossover Strategy
|
||||
|
||||
Entry:
|
||||
- Buy when price crosses above EMA
|
||||
- Sell when price crosses below EMA
|
||||
|
||||
Exit:
|
||||
- Opposite crossover
|
||||
- Stop loss and take profit
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, timeframe: int, initial_balance: float = 10000.0,
|
||||
ema_period: int = 50, lot_size: float = 0.1,
|
||||
stop_loss_pips: int = 50, take_profit_pips: int = 100):
|
||||
super().__init__(symbol, timeframe, initial_balance)
|
||||
|
||||
self.ema_period = ema_period
|
||||
self.lot_size = lot_size
|
||||
self.stop_loss_pips = stop_loss_pips
|
||||
self.take_profit_pips = take_profit_pips
|
||||
|
||||
self.prev_price = None
|
||||
self.prev_ema = None
|
||||
|
||||
def get_required_indicators(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
'ema': {
|
||||
'period': self.ema_period,
|
||||
'applied_price': mt5.PRICE_CLOSE
|
||||
}
|
||||
}
|
||||
|
||||
def on_bar(self, bar_data: Dict[str, Any]) -> None:
|
||||
ema = bar_data.get('ema')
|
||||
current_price = bar_data['close']
|
||||
|
||||
if ema is None:
|
||||
return
|
||||
|
||||
# Check if we have a position
|
||||
if self.position is not None:
|
||||
# Exit on opposite crossover
|
||||
if self.position['type'] == 'BUY' and current_price < ema:
|
||||
self.close_position(current_price)
|
||||
elif self.position['type'] == 'SELL' and current_price > ema:
|
||||
self.close_position(current_price)
|
||||
else:
|
||||
# Check entry conditions
|
||||
if self.prev_price is not None and self.prev_ema is not None:
|
||||
# Buy signal: price crosses above EMA
|
||||
if self.prev_price <= self.prev_ema and current_price > ema:
|
||||
sl = current_price - (self.stop_loss_pips / 10000)
|
||||
tp = current_price + (self.take_profit_pips / 10000)
|
||||
self.open_position('BUY', self.lot_size, current_price, sl, tp, 'EMA Crossover Buy')
|
||||
|
||||
# Sell signal: price crosses below EMA
|
||||
elif self.prev_price >= self.prev_ema and current_price < ema:
|
||||
sl = current_price + (self.stop_loss_pips / 10000)
|
||||
tp = current_price - (self.take_profit_pips / 10000)
|
||||
self.open_position('SELL', self.lot_size, current_price, sl, tp, 'EMA Crossover Sell')
|
||||
|
||||
self.prev_price = current_price
|
||||
self.prev_ema = ema
|
||||
|
||||
def get_parameters(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'ema_period': self.ema_period,
|
||||
'lot_size': self.lot_size,
|
||||
'stop_loss_pips': self.stop_loss_pips,
|
||||
'take_profit_pips': self.take_profit_pips
|
||||
}
|
||||
|
||||
|
||||
class RSIReversalStrategy(BaseStrategy):
|
||||
"""
|
||||
RSI Reversal Strategy - Similar to your MQL5 RSI Reversal strategies
|
||||
|
||||
Entry:
|
||||
- Buy when RSI is oversold and starts rising
|
||||
- Sell when RSI is overbought and starts falling
|
||||
|
||||
Exit:
|
||||
- RSI reaches neutral level
|
||||
- Stop loss and take profit
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, timeframe: int, initial_balance: float = 10000.0,
|
||||
rsi_period: int = 14, rsi_overbought: float = 70, rsi_oversold: float = 30,
|
||||
rsi_exit: float = 50, lot_size: float = 0.1,
|
||||
stop_loss_pips: int = 50, take_profit_pips: int = 100):
|
||||
super().__init__(symbol, timeframe, initial_balance)
|
||||
|
||||
self.rsi_period = rsi_period
|
||||
self.rsi_overbought = rsi_overbought
|
||||
self.rsi_oversold = rsi_oversold
|
||||
self.rsi_exit = rsi_exit
|
||||
self.lot_size = lot_size
|
||||
self.stop_loss_pips = stop_loss_pips
|
||||
self.take_profit_pips = take_profit_pips
|
||||
|
||||
self.prev_rsi = None
|
||||
|
||||
def get_required_indicators(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {
|
||||
'rsi': {
|
||||
'period': self.rsi_period,
|
||||
'applied_price': mt5.PRICE_CLOSE
|
||||
}
|
||||
}
|
||||
|
||||
def on_bar(self, bar_data: Dict[str, Any]) -> None:
|
||||
rsi = bar_data.get('rsi')
|
||||
if rsi is None:
|
||||
return
|
||||
|
||||
current_price = bar_data['close']
|
||||
|
||||
# Check if we have a position
|
||||
if self.position is not None:
|
||||
# Exit when RSI reaches neutral level
|
||||
if self.position['type'] == 'BUY' and rsi >= self.rsi_exit:
|
||||
self.close_position(current_price)
|
||||
elif self.position['type'] == 'SELL' and rsi <= self.rsi_exit:
|
||||
self.close_position(current_price)
|
||||
else:
|
||||
# Check entry conditions
|
||||
if self.prev_rsi is not None:
|
||||
# Buy signal: RSI was oversold and now rising
|
||||
if self.prev_rsi < self.rsi_oversold and rsi > self.prev_rsi:
|
||||
sl = current_price - (self.stop_loss_pips / 10000)
|
||||
tp = current_price + (self.take_profit_pips / 10000)
|
||||
self.open_position('BUY', self.lot_size, current_price, sl, tp, 'RSI Reversal Buy')
|
||||
|
||||
# Sell signal: RSI was overbought and now falling
|
||||
elif self.prev_rsi > self.rsi_overbought and rsi < self.prev_rsi:
|
||||
sl = current_price + (self.stop_loss_pips / 10000)
|
||||
tp = current_price - (self.take_profit_pips / 10000)
|
||||
self.open_position('SELL', self.lot_size, current_price, sl, tp, 'RSI Reversal Sell')
|
||||
|
||||
self.prev_rsi = rsi
|
||||
|
||||
def get_parameters(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'rsi_period': self.rsi_period,
|
||||
'rsi_overbought': self.rsi_overbought,
|
||||
'rsi_oversold': self.rsi_oversold,
|
||||
'rsi_exit': self.rsi_exit,
|
||||
'lot_size': self.lot_size,
|
||||
'stop_loss_pips': self.stop_loss_pips,
|
||||
'take_profit_pips': self.take_profit_pips
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
Example usage of the backtesting framework
|
||||
|
||||
This script demonstrates how to use the framework programmatically
|
||||
without using the command-line interface.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import MetaTrader5 as mt5
|
||||
from backtest_engine import BacktestEngine
|
||||
from example_strategies import RSIReversalStrategy, RSIScalpingStrategy, EMAStrategy
|
||||
from performance_analyzer import PerformanceAnalyzer
|
||||
|
||||
|
||||
def example_rsi_reversal():
|
||||
"""Example: RSI Reversal Strategy backtest"""
|
||||
print("="*60)
|
||||
print("Example 1: RSI Reversal Strategy")
|
||||
print("="*60)
|
||||
|
||||
# Create strategy
|
||||
strategy = RSIReversalStrategy(
|
||||
symbol='XAUUSD',
|
||||
timeframe=mt5.TIMEFRAME_H1,
|
||||
initial_balance=10000.0,
|
||||
rsi_period=14,
|
||||
rsi_overbought=70,
|
||||
rsi_oversold=30,
|
||||
rsi_exit=50,
|
||||
lot_size=0.1,
|
||||
stop_loss_pips=50,
|
||||
take_profit_pips=100
|
||||
)
|
||||
|
||||
# Run backtest
|
||||
engine = BacktestEngine(
|
||||
strategy,
|
||||
start_date=datetime(2023, 1, 1),
|
||||
end_date=datetime(2024, 1, 1)
|
||||
)
|
||||
|
||||
results = engine.run()
|
||||
|
||||
# Analyze results
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report('example_results/rsi_reversal')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def example_rsi_scalping():
|
||||
"""Example: RSI Scalping Strategy backtest"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 2: RSI Scalping Strategy")
|
||||
print("="*60)
|
||||
|
||||
# Create strategy
|
||||
strategy = RSIScalpingStrategy(
|
||||
symbol='EURUSD',
|
||||
timeframe=mt5.TIMEFRAME_M15,
|
||||
initial_balance=10000.0,
|
||||
rsi_period=14,
|
||||
rsi_overbought=71,
|
||||
rsi_oversold=57,
|
||||
rsi_target_buy=80,
|
||||
rsi_target_sell=20,
|
||||
lot_size=0.1,
|
||||
stop_loss_pips=30,
|
||||
take_profit_pips=50
|
||||
)
|
||||
|
||||
# Run backtest
|
||||
engine = BacktestEngine(
|
||||
strategy,
|
||||
start_date=datetime(2023, 6, 1),
|
||||
end_date=datetime(2023, 12, 31)
|
||||
)
|
||||
|
||||
results = engine.run()
|
||||
|
||||
# Analyze results
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report('example_results/rsi_scalping')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def example_ema_crossover():
|
||||
"""Example: EMA Crossover Strategy backtest"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 3: EMA Crossover Strategy")
|
||||
print("="*60)
|
||||
|
||||
# Create strategy
|
||||
strategy = EMAStrategy(
|
||||
symbol='BTCUSD',
|
||||
timeframe=mt5.TIMEFRAME_H4,
|
||||
initial_balance=10000.0,
|
||||
ema_period=50,
|
||||
lot_size=0.1,
|
||||
stop_loss_pips=100,
|
||||
take_profit_pips=200
|
||||
)
|
||||
|
||||
# Run backtest
|
||||
engine = BacktestEngine(
|
||||
strategy,
|
||||
start_date=datetime(2023, 1, 1),
|
||||
end_date=datetime(2024, 1, 1)
|
||||
)
|
||||
|
||||
results = engine.run()
|
||||
|
||||
# Analyze results
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report('example_results/ema_crossover')
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def compare_strategies():
|
||||
"""Compare multiple strategies"""
|
||||
print("\n" + "="*60)
|
||||
print("Example 4: Strategy Comparison")
|
||||
print("="*60)
|
||||
|
||||
strategies = [
|
||||
('RSI Reversal', RSIReversalStrategy(
|
||||
'XAUUSD', mt5.TIMEFRAME_H1, 10000.0,
|
||||
rsi_period=14, rsi_overbought=70, rsi_oversold=30
|
||||
)),
|
||||
('RSI Scalping', RSIScalpingStrategy(
|
||||
'XAUUSD', mt5.TIMEFRAME_H1, 10000.0,
|
||||
rsi_period=14, rsi_overbought=71, rsi_oversold=57
|
||||
)),
|
||||
('EMA Crossover', EMAStrategy(
|
||||
'XAUUSD', mt5.TIMEFRAME_H1, 10000.0,
|
||||
ema_period=50
|
||||
))
|
||||
]
|
||||
|
||||
start_date = datetime(2023, 1, 1)
|
||||
end_date = datetime(2024, 1, 1)
|
||||
|
||||
results_list = []
|
||||
|
||||
for name, strategy in strategies:
|
||||
print(f"\nBacktesting {name}...")
|
||||
engine = BacktestEngine(strategy, start_date, end_date)
|
||||
results = engine.run()
|
||||
results_list.append((name, results))
|
||||
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
print(f"\n{name} Results:")
|
||||
analyzer.print_summary()
|
||||
|
||||
# Print comparison
|
||||
print("\n" + "="*60)
|
||||
print("STRATEGY COMPARISON")
|
||||
print("="*60)
|
||||
print(f"{'Strategy':<20} {'Return %':<12} {'Win Rate %':<12} {'Profit Factor':<15} {'Max DD %':<10}")
|
||||
print("-"*60)
|
||||
|
||||
for name, results in results_list:
|
||||
metrics = results['metrics']
|
||||
print(f"{name:<20} {metrics['total_return_pct']:>10.2f}% "
|
||||
f"{metrics['win_rate_pct']:>10.2f}% "
|
||||
f"{metrics['profit_factor']:>13.2f} "
|
||||
f"{metrics['max_drawdown_pct']:>8.2f}%")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize MT5 (will be done by BacktestEngine, but good to check)
|
||||
if not mt5.initialize():
|
||||
print("MT5 initialization failed. Please ensure MT5 is installed and running.")
|
||||
exit(1)
|
||||
|
||||
print("MetaTrader5 Python Backtesting Framework - Examples")
|
||||
print("="*60)
|
||||
|
||||
# Run examples (comment out the ones you don't want to run)
|
||||
|
||||
# Example 1: RSI Reversal
|
||||
# example_rsi_reversal()
|
||||
|
||||
# Example 2: RSI Scalping
|
||||
# example_rsi_scalping()
|
||||
|
||||
# Example 3: EMA Crossover
|
||||
# example_ema_crossover()
|
||||
|
||||
# Example 4: Compare strategies
|
||||
compare_strategies()
|
||||
|
||||
mt5.shutdown()
|
||||
print("\nExamples completed!")
|
||||
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Performance Analysis and Reporting
|
||||
|
||||
This module provides tools for analyzing backtest results and generating reports.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PerformanceAnalyzer:
|
||||
"""
|
||||
Analyzes backtest performance and generates reports.
|
||||
"""
|
||||
|
||||
def __init__(self, backtest_results: Dict[str, Any]):
|
||||
"""
|
||||
Initialize with backtest results.
|
||||
|
||||
Args:
|
||||
backtest_results: Results dictionary from BacktestEngine.run()
|
||||
"""
|
||||
self.results = backtest_results
|
||||
self.metrics = backtest_results['metrics']
|
||||
self.trades = backtest_results['trades']
|
||||
self.strategy_name = backtest_results['strategy_name']
|
||||
|
||||
def print_summary(self):
|
||||
"""Print a summary of the backtest results."""
|
||||
print("\n" + "="*60)
|
||||
print(f"BACKTEST SUMMARY: {self.strategy_name}")
|
||||
print("="*60)
|
||||
print(f"\nInitial Balance: ${self.metrics['initial_balance']:,.2f}")
|
||||
print(f"Final Balance: ${self.metrics['final_balance']:,.2f}")
|
||||
print(f"Total Return: {self.metrics['total_return_pct']:.2f}%")
|
||||
print(f"\nTotal Trades: {self.metrics['total_trades']}")
|
||||
print(f"Winning Trades: {self.metrics['winning_trades']}")
|
||||
print(f"Losing Trades: {self.metrics['losing_trades']}")
|
||||
print(f"Win Rate: {self.metrics['win_rate_pct']:.2f}%")
|
||||
print(f"\nTotal Profit: ${self.metrics['total_profit']:,.2f}")
|
||||
print(f"Total Loss: ${self.metrics['total_loss']:,.2f}")
|
||||
print(f"Profit Factor: {self.metrics['profit_factor']:.2f}")
|
||||
print(f"\nAverage Win: ${self.metrics['avg_win']:,.2f}")
|
||||
print(f"Average Loss: ${self.metrics['avg_loss']:,.2f}")
|
||||
print(f"Max Drawdown: {self.metrics['max_drawdown_pct']:.2f}%")
|
||||
|
||||
if self.metrics.get('parameters'):
|
||||
print(f"\nStrategy Parameters:")
|
||||
for key, value in self.metrics['parameters'].items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
print("="*60 + "\n")
|
||||
|
||||
def get_trades_dataframe(self) -> pd.DataFrame:
|
||||
"""Convert trades list to pandas DataFrame."""
|
||||
if not self.trades:
|
||||
return pd.DataFrame()
|
||||
|
||||
df = pd.DataFrame(self.trades)
|
||||
df['open_time'] = pd.to_datetime(df['open_time'])
|
||||
df['close_time'] = pd.to_datetime(df['close_time'])
|
||||
df['duration'] = df['close_time'] - df['open_time']
|
||||
|
||||
return df
|
||||
|
||||
def plot_equity_curve(self, save_path: str = None):
|
||||
"""
|
||||
Plot equity curve over time.
|
||||
|
||||
Args:
|
||||
save_path: Optional path to save the plot
|
||||
"""
|
||||
if not self.trades:
|
||||
print("No trades to plot")
|
||||
return
|
||||
|
||||
df = self.get_trades_dataframe()
|
||||
df = df.sort_values('close_time')
|
||||
|
||||
# Calculate cumulative equity
|
||||
cumulative_profit = df['profit'].cumsum()
|
||||
equity_curve = self.metrics['initial_balance'] + cumulative_profit
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.plot(df['close_time'], equity_curve, linewidth=2, label='Equity')
|
||||
plt.axhline(y=self.metrics['initial_balance'], color='r', linestyle='--', label='Initial Balance')
|
||||
plt.xlabel('Time')
|
||||
plt.ylabel('Equity ($)')
|
||||
plt.title(f'Equity Curve - {self.strategy_name}')
|
||||
plt.legend()
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.tight_layout()
|
||||
|
||||
if save_path:
|
||||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||||
print(f"Equity curve saved to {save_path}")
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
def plot_drawdown(self, save_path: str = None):
|
||||
"""
|
||||
Plot drawdown over time.
|
||||
|
||||
Args:
|
||||
save_path: Optional path to save the plot
|
||||
"""
|
||||
if not self.trades:
|
||||
print("No trades to plot")
|
||||
return
|
||||
|
||||
df = self.get_trades_dataframe()
|
||||
df = df.sort_values('close_time')
|
||||
|
||||
# Calculate cumulative equity
|
||||
cumulative_profit = df['profit'].cumsum()
|
||||
equity_curve = self.metrics['initial_balance'] + cumulative_profit
|
||||
|
||||
# Calculate running maximum
|
||||
running_max = equity_curve.expanding().max()
|
||||
drawdown = (equity_curve - running_max) / running_max * 100
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.fill_between(df['close_time'], drawdown, 0, alpha=0.3, color='red', label='Drawdown')
|
||||
plt.plot(df['close_time'], drawdown, linewidth=1, color='darkred')
|
||||
plt.xlabel('Time')
|
||||
plt.ylabel('Drawdown (%)')
|
||||
plt.title(f'Drawdown Chart - {self.strategy_name}')
|
||||
plt.legend()
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.tight_layout()
|
||||
|
||||
if save_path:
|
||||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||||
print(f"Drawdown chart saved to {save_path}")
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
def plot_monthly_returns(self, save_path: str = None):
|
||||
"""
|
||||
Plot monthly returns.
|
||||
|
||||
Args:
|
||||
save_path: Optional path to save the plot
|
||||
"""
|
||||
if not self.trades:
|
||||
print("No trades to plot")
|
||||
return
|
||||
|
||||
df = self.get_trades_dataframe()
|
||||
df = df.sort_values('close_time')
|
||||
|
||||
# Group by month
|
||||
df['month'] = df['close_time'].dt.to_period('M')
|
||||
monthly_returns = df.groupby('month')['profit'].sum()
|
||||
monthly_returns_pct = (monthly_returns / self.metrics['initial_balance']) * 100
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
colors = ['green' if x > 0 else 'red' for x in monthly_returns_pct]
|
||||
plt.bar(range(len(monthly_returns_pct)), monthly_returns_pct, color=colors, alpha=0.7)
|
||||
plt.xlabel('Month')
|
||||
plt.ylabel('Return (%)')
|
||||
plt.title(f'Monthly Returns - {self.strategy_name}')
|
||||
plt.xticks(range(len(monthly_returns_pct)), [str(x) for x in monthly_returns_pct.index], rotation=45)
|
||||
plt.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
|
||||
plt.grid(True, alpha=0.3, axis='y')
|
||||
plt.tight_layout()
|
||||
|
||||
if save_path:
|
||||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||||
print(f"Monthly returns chart saved to {save_path}")
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
def export_trades_csv(self, filepath: str):
|
||||
"""
|
||||
Export trades to CSV file.
|
||||
|
||||
Args:
|
||||
filepath: Path to save CSV file
|
||||
"""
|
||||
df = self.get_trades_dataframe()
|
||||
df.to_csv(filepath, index=False)
|
||||
print(f"Trades exported to {filepath}")
|
||||
|
||||
def generate_report(self, output_dir: str = "backtest_results"):
|
||||
"""
|
||||
Generate a comprehensive report with all charts and data.
|
||||
|
||||
Args:
|
||||
output_dir: Directory to save report files
|
||||
"""
|
||||
import os
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
prefix = f"{self.strategy_name}_{timestamp}"
|
||||
|
||||
# Print summary
|
||||
self.print_summary()
|
||||
|
||||
# Generate plots
|
||||
self.plot_equity_curve(os.path.join(output_dir, f"{prefix}_equity_curve.png"))
|
||||
self.plot_drawdown(os.path.join(output_dir, f"{prefix}_drawdown.png"))
|
||||
self.plot_monthly_returns(os.path.join(output_dir, f"{prefix}_monthly_returns.png"))
|
||||
|
||||
# Export trades
|
||||
self.export_trades_csv(os.path.join(output_dir, f"{prefix}_trades.csv"))
|
||||
|
||||
print(f"\nReport generated in {output_dir}/")
|
||||
@@ -0,0 +1,7 @@
|
||||
MetaTrader5>=5.0.45
|
||||
pandas>=1.3.0
|
||||
numpy>=1.21.0
|
||||
matplotlib>=3.4.0
|
||||
scipy>=1.7.0
|
||||
ta-lib>=0.4.0
|
||||
python-dateutil>=2.8.0
|
||||
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Main script to run backtests
|
||||
|
||||
Example usage:
|
||||
python run_backtest.py --strategy RSIReversalStrategy --symbol XAUUSD --start 2023-01-01 --end 2024-01-01
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import MetaTrader5 as mt5
|
||||
from backtest_engine import BacktestEngine
|
||||
from example_strategies import RSIScalpingStrategy, EMAStrategy, RSIReversalStrategy
|
||||
from performance_analyzer import PerformanceAnalyzer
|
||||
from base_strategy import BaseStrategy
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(description='Run backtest on trading strategy')
|
||||
|
||||
parser.add_argument('--strategy', type=str, required=True,
|
||||
choices=['RSIScalpingStrategy', 'EMAStrategy', 'RSIReversalStrategy'],
|
||||
help='Strategy to backtest')
|
||||
parser.add_argument('--symbol', type=str, default='XAUUSD',
|
||||
help='Trading symbol (default: XAUUSD)')
|
||||
parser.add_argument('--timeframe', type=str, default='H1',
|
||||
choices=['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'],
|
||||
help='Timeframe (default: H1)')
|
||||
parser.add_argument('--start', type=str, required=True,
|
||||
help='Start date (YYYY-MM-DD)')
|
||||
parser.add_argument('--end', type=str, required=True,
|
||||
help='End date (YYYY-MM-DD)')
|
||||
parser.add_argument('--balance', type=float, default=10000.0,
|
||||
help='Initial balance (default: 10000)')
|
||||
parser.add_argument('--output', type=str, default='backtest_results',
|
||||
help='Output directory for results (default: backtest_results)')
|
||||
|
||||
# Strategy-specific parameters
|
||||
parser.add_argument('--rsi-period', type=int, default=14,
|
||||
help='RSI period (default: 14)')
|
||||
parser.add_argument('--rsi-overbought', type=float, default=70.0,
|
||||
help='RSI overbought level (default: 70)')
|
||||
parser.add_argument('--rsi-oversold', type=float, default=30.0,
|
||||
help='RSI oversold level (default: 30)')
|
||||
parser.add_argument('--ema-period', type=int, default=50,
|
||||
help='EMA period (default: 50)')
|
||||
parser.add_argument('--lot-size', type=float, default=0.1,
|
||||
help='Lot size (default: 0.1)')
|
||||
parser.add_argument('--stop-loss', type=int, default=50,
|
||||
help='Stop loss in pips (default: 50)')
|
||||
parser.add_argument('--take-profit', type=int, default=100,
|
||||
help='Take profit in pips (default: 100)')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_timeframe(timeframe_str: str) -> int:
|
||||
"""Convert timeframe string to MT5 constant."""
|
||||
timeframe_map = {
|
||||
'M1': mt5.TIMEFRAME_M1,
|
||||
'M5': mt5.TIMEFRAME_M5,
|
||||
'M15': mt5.TIMEFRAME_M15,
|
||||
'M30': mt5.TIMEFRAME_M30,
|
||||
'H1': mt5.TIMEFRAME_H1,
|
||||
'H4': mt5.TIMEFRAME_H4,
|
||||
'D1': mt5.TIMEFRAME_D1
|
||||
}
|
||||
return timeframe_map.get(timeframe_str, mt5.TIMEFRAME_H1)
|
||||
|
||||
|
||||
def create_strategy(strategy_name: str, symbol: str, timeframe: int,
|
||||
initial_balance: float, args) -> BaseStrategy:
|
||||
"""Create strategy instance based on name."""
|
||||
if strategy_name == 'RSIScalpingStrategy':
|
||||
return RSIScalpingStrategy(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
initial_balance=initial_balance,
|
||||
rsi_period=args.rsi_period,
|
||||
rsi_overbought=args.rsi_overbought,
|
||||
rsi_oversold=args.rsi_oversold,
|
||||
lot_size=args.lot_size,
|
||||
stop_loss_pips=args.stop_loss,
|
||||
take_profit_pips=args.take_profit
|
||||
)
|
||||
elif strategy_name == 'EMAStrategy':
|
||||
return EMAStrategy(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
initial_balance=initial_balance,
|
||||
ema_period=args.ema_period,
|
||||
lot_size=args.lot_size,
|
||||
stop_loss_pips=args.stop_loss,
|
||||
take_profit_pips=args.take_profit
|
||||
)
|
||||
elif strategy_name == 'RSIReversalStrategy':
|
||||
return RSIReversalStrategy(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
initial_balance=initial_balance,
|
||||
rsi_period=args.rsi_period,
|
||||
rsi_overbought=args.rsi_overbought,
|
||||
rsi_oversold=args.rsi_oversold,
|
||||
lot_size=args.lot_size,
|
||||
stop_loss_pips=args.stop_loss,
|
||||
take_profit_pips=args.take_profit
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unknown strategy: {strategy_name}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run backtest."""
|
||||
args = parse_args()
|
||||
|
||||
# Parse dates
|
||||
start_date = datetime.strptime(args.start, '%Y-%m-%d')
|
||||
end_date = datetime.strptime(args.end, '%Y-%m-%d')
|
||||
|
||||
# Get timeframe
|
||||
timeframe = get_timeframe(args.timeframe)
|
||||
|
||||
# Create strategy
|
||||
print(f"Creating {args.strategy} strategy...")
|
||||
strategy = create_strategy(
|
||||
args.strategy,
|
||||
args.symbol,
|
||||
timeframe,
|
||||
args.balance,
|
||||
args
|
||||
)
|
||||
|
||||
# Create and run backtest
|
||||
print("Initializing backtest engine...")
|
||||
engine = BacktestEngine(strategy, start_date, end_date)
|
||||
|
||||
print("Running backtest...")
|
||||
results = engine.run()
|
||||
|
||||
# Analyze results
|
||||
print("Analyzing results...")
|
||||
analyzer = PerformanceAnalyzer(results)
|
||||
analyzer.generate_report(args.output)
|
||||
|
||||
print("\nBacktest completed successfully!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Test script to verify MT5 connection and setup
|
||||
|
||||
Run this script first to ensure everything is configured correctly.
|
||||
"""
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def test_mt5_connection():
|
||||
"""Test MT5 connection and basic functionality"""
|
||||
print("Testing MetaTrader5 Connection...")
|
||||
print("="*60)
|
||||
|
||||
# Initialize MT5
|
||||
if not mt5.initialize():
|
||||
print(f"ERROR: MT5 initialization failed")
|
||||
print(f"Error code: {mt5.last_error()}")
|
||||
print("\nTroubleshooting:")
|
||||
print("1. Make sure MetaTrader5 is installed")
|
||||
print("2. Make sure MT5 is running")
|
||||
print("3. Try logging into MT5 manually first")
|
||||
return False
|
||||
|
||||
print("✓ MT5 initialized successfully")
|
||||
|
||||
# Get account info
|
||||
account_info = mt5.account_info()
|
||||
if account_info is None:
|
||||
print("WARNING: Could not get account info")
|
||||
else:
|
||||
print(f"✓ Account: {account_info.login}")
|
||||
print(f" Server: {account_info.server}")
|
||||
print(f" Balance: ${account_info.balance:.2f}")
|
||||
|
||||
# Test symbol access
|
||||
test_symbols = ['XAUUSD', 'EURUSD', 'BTCUSD']
|
||||
print("\nTesting symbol access...")
|
||||
|
||||
for symbol in test_symbols:
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
if symbol_info is None:
|
||||
print(f"✗ {symbol}: Not available")
|
||||
else:
|
||||
print(f"✓ {symbol}: Available")
|
||||
print(f" Bid: {symbol_info.bid:.5f}, Ask: {symbol_info.ask:.5f}")
|
||||
print(f" Spread: {symbol_info.spread} points")
|
||||
|
||||
# Test historical data
|
||||
print("\nTesting historical data retrieval...")
|
||||
symbol = 'XAUUSD'
|
||||
timeframe = mt5.TIMEFRAME_H1
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=7)
|
||||
|
||||
rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
|
||||
if rates is None or len(rates) == 0:
|
||||
print(f"✗ Could not retrieve historical data for {symbol}")
|
||||
print(" Make sure you have historical data in MT5")
|
||||
else:
|
||||
print(f"✓ Retrieved {len(rates)} bars for {symbol}")
|
||||
print(f" Date range: {datetime.fromtimestamp(rates[0]['time'])} to {datetime.fromtimestamp(rates[-1]['time'])}")
|
||||
|
||||
# Test indicator creation
|
||||
print("\nTesting indicator creation...")
|
||||
rsi_handle = mt5.iRSI(symbol, timeframe, 14, mt5.PRICE_CLOSE)
|
||||
if rsi_handle == mt5.INVALID_HANDLE:
|
||||
print("✗ Failed to create RSI indicator")
|
||||
else:
|
||||
print("✓ RSI indicator created successfully")
|
||||
# Get RSI values
|
||||
rsi_values = mt5.copy_buffer(rsi_handle, 0, 0, 10)
|
||||
if rsi_values is not None:
|
||||
print(f" Latest RSI values: {rsi_values[-3:]}")
|
||||
mt5.indicator_release(rsi_handle)
|
||||
|
||||
ema_handle = mt5.iMA(symbol, timeframe, 50, 0, mt5.MODE_EMA, mt5.PRICE_CLOSE)
|
||||
if ema_handle == mt5.INVALID_HANDLE:
|
||||
print("✗ Failed to create EMA indicator")
|
||||
else:
|
||||
print("✓ EMA indicator created successfully")
|
||||
mt5.indicator_release(ema_handle)
|
||||
|
||||
# Cleanup
|
||||
mt5.shutdown()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Setup test completed!")
|
||||
print("="*60)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = test_mt5_connection()
|
||||
if not success:
|
||||
exit(1)
|
||||
@@ -0,0 +1 @@
|
||||
PLACEHOLDER
|
||||
Reference in New Issue
Block a user