Files
profitable-expert-advisor/backtesting/MT5/base_strategy.py
T
zhutoutoutousan 98a87a69ca Update
2026-02-13 08:03:25 +01:00

271 lines
9.2 KiB
Python

"""
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
# For XAUUSD (Gold): 1 lot = 100 oz, typical margin 1-2% of contract value
# For Forex pairs: 1 lot = 100,000 units, typical margin 1-2%
if 'XAU' in self.symbol or 'GOLD' in self.symbol:
contract_size = 100 # 1 lot = 100 oz for gold
margin_percent = 0.02 # 2% margin for gold (more volatile)
else:
contract_size = 100000 # Standard forex lot size
margin_percent = 0.01 # 1% margin for forex
margin_required = volume * contract_size * price * margin_percent
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()
}