This commit is contained in:
zhutoutoutousan
2026-01-05 05:37:33 +01:00
parent 7d41b04aef
commit 5b44e14211
79 changed files with 19884 additions and 0 deletions
+41
View File
@@ -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
+165
View File
@@ -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! 🚀
+279
View File
@@ -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.
+257
View File
@@ -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()
+262
View File
@@ -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()
}
+256
View File
@@ -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
}
+196
View File
@@ -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!")
+212
View File
@@ -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}/")
+7
View File
@@ -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
+149
View File
@@ -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()
+98
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
PLACEHOLDER