Add files via upload

Signed-off-by: B-Wear <Bwear008@gmail.com>
This commit is contained in:
B-Wear
2025-03-22 14:20:21 -04:00
committed by GitHub
parent cadbfbde54
commit bf08d59def
14 changed files with 2882 additions and 2 deletions
+133 -2
View File
@@ -1,2 +1,133 @@
# QuantumEdge
QuantumEdge AI-powered trading system for forex and futures starting with $50. Features self-correcting algorithms, technical analysis, and machine learning with strict risk management. Adapts to market changes while preserving capital and gradually scaling positions through an intuitive monitoring dashboard.RetryClaude
# AI-Powered Trading Bot
A sophisticated trading bot that combines technical analysis, machine learning, sentiment analysis, and risk management to make informed trading decisions.
## Features
- **Technical Analysis**
- Multiple timeframe analysis
- Advanced indicators (SMA, EMA, RSI, MACD, Bollinger Bands)
- Pattern recognition
- Support and resistance levels
- **Machine Learning**
- LSTM-based price prediction
- Reinforcement learning for strategy optimization
- Feature engineering and selection
- Model persistence and retraining
- **Sentiment Analysis**
- News sentiment analysis
- Social media sentiment (Twitter)
- Market sentiment indicators
- Weighted sentiment scoring
- **Risk Management**
- Position sizing based on risk percentage
- Stop-loss and take-profit management
- Maximum drawdown protection
- Performance monitoring
- Risk metrics calculation
- **Web Dashboard**
- Real-time performance monitoring
- Trade history visualization
- Risk metrics display
- Configuration management
## Requirements
- Python 3.8+
- CUDA-capable GPU (recommended for machine learning)
- API keys for:
- Cryptocurrency exchange (e.g., Binance)
- News API
- Twitter API
## Installation
1. Clone the repository:
```bash
git clone https://github.com/yourusername/trading-bot.git
cd trading-bot
```
2. Create a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Configure the bot:
- Copy `config/config.json.example` to `config/config.json`
- Update the configuration with your API keys and preferences
## Usage
1. Start the trading bot:
```bash
python -m src.main
```
2. Access the web dashboard:
- Open your browser and navigate to `http://localhost:5000`
- Monitor performance and manage settings
3. Monitor logs:
- Check `trading_bot.log` for detailed information
- Monitor system performance and error messages
## Configuration
The bot can be configured through `config/config.json`. Key settings include:
- Trading pairs and timeframes
- Risk management parameters
- Technical analysis settings
- Machine learning model parameters
- Sentiment analysis weights
- API credentials
## Risk Warning
Trading cryptocurrencies involves significant risk of loss. This bot is for educational purposes only. Always:
- Start with small amounts
- Use testnet for initial testing
- Monitor performance closely
- Implement proper risk management
- Never trade with money you cannot afford to lose
## Contributing
1. Fork the repository
2. Create a feature branch
3. Commit your changes
4. Push to the branch
5. Create a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Acknowledgments
- Thanks to the open-source community for various libraries used in this project
- Special thanks to contributors and maintainers of key dependencies
- Inspired by various trading strategies and research papers
## Support
For support, please:
1. Check the documentation
2. Search existing issues
3. Create a new issue if needed
## Disclaimer
This trading bot is provided as-is, without any warranties. Use at your own risk. The developers are not responsible for any financial losses incurred through the use of this software.
+266
View File
@@ -0,0 +1,266 @@
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import logging
from typing import Dict, List, Optional
import ccxt
from concurrent.futures import ThreadPoolExecutor
import json
import os
from .trading_strategy import TradingStrategy
from .config import load_config
logger = logging.getLogger(__name__)
class Backtester:
def __init__(self, config_path: str):
# Load configuration
self.config = load_config(config_path)
# Initialize exchange
self.exchange = self._initialize_exchange()
# Initialize trading strategy
self.strategy = TradingStrategy(self.config)
# Initialize results storage
self.results = {
'trades': [],
'equity_curve': [],
'performance_metrics': {}
}
def _initialize_exchange(self) -> ccxt.Exchange:
"""Initialize the cryptocurrency exchange"""
try:
exchange_class = getattr(ccxt, self.config['exchange']['name'])
exchange = exchange_class({
'apiKey': self.config['exchange']['api_key'],
'secret': self.config['exchange']['api_secret'],
'enableRateLimit': True
})
# Test connection
exchange.load_markets()
logger.info(f"Successfully connected to {self.config['exchange']['name']}")
return exchange
except Exception as e:
logger.error(f"Error initializing exchange: {str(e)}")
raise
def fetch_historical_data(self,
symbol: str,
timeframe: str,
start_date: datetime,
end_date: datetime) -> pd.DataFrame:
"""Fetch historical market data"""
try:
# Convert dates to timestamps
start_timestamp = int(start_date.timestamp() * 1000)
end_timestamp = int(end_date.timestamp() * 1000)
# Fetch OHLCV data
ohlcv = self.exchange.fetch_ohlcv(
symbol,
timeframe=timeframe,
since=start_timestamp,
limit=1000 # Maximum limit per request
)
# Convert to DataFrame
df = pd.DataFrame(
ohlcv,
columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
)
# Convert timestamp to datetime
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
return df
except Exception as e:
logger.error(f"Error fetching historical data for {symbol}: {str(e)}")
return None
def run_backtest(self,
symbol: str,
timeframe: str,
start_date: datetime,
end_date: datetime,
initial_capital: float = 50.0) -> Dict:
"""Run backtest for a given period"""
try:
logger.info(f"Starting backtest for {symbol} from {start_date} to {end_date}")
# Fetch historical data
df = self.fetch_historical_data(symbol, timeframe, start_date, end_date)
if df is None:
raise ValueError("Failed to fetch historical data")
# Initialize variables
current_capital = initial_capital
position = 0
entry_price = 0
trades = []
equity_curve = []
# Iterate through data
for i in range(len(df)):
current_data = df.iloc[:i+1]
current_price = current_data['close'].iloc[-1]
# Generate trading signal
signal = self.strategy.analyze_market(symbol, current_data)
if signal:
# Execute trade if conditions are met
if signal.action == 'buy' and position <= 0:
position = 1
entry_price = current_price
trades.append({
'timestamp': current_data.index[-1],
'action': 'buy',
'price': current_price,
'position_size': signal.position_size
})
elif signal.action == 'sell' and position >= 0:
position = -1
entry_price = current_price
trades.append({
'timestamp': current_data.index[-1],
'action': 'sell',
'price': current_price,
'position_size': signal.position_size
})
# Update position PnL
if position != 0:
pnl = position * (current_price - entry_price) * signal.position_size
current_capital += pnl
# Record equity
equity_curve.append({
'timestamp': current_data.index[-1],
'equity': current_capital
})
# Calculate performance metrics
self.results['trades'] = trades
self.results['equity_curve'] = equity_curve
self.results['performance_metrics'] = self._calculate_performance_metrics(trades)
return self.results
except Exception as e:
logger.error(f"Error running backtest: {str(e)}")
return None
def _calculate_performance_metrics(self, trades: List[Dict]) -> Dict:
"""Calculate performance metrics from trades"""
if not trades:
return {}
# Convert trades to DataFrame
df = pd.DataFrame(trades)
# Calculate basic metrics
total_trades = len(df)
winning_trades = len(df[df['action'] == 'buy'])
losing_trades = len(df[df['action'] == 'sell'])
win_rate = winning_trades / total_trades if total_trades > 0 else 0
# Calculate returns
returns = df['price'].pct_change()
total_return = (1 + returns).prod() - 1
annual_return = (1 + total_return) ** (252 / len(df)) - 1
# Calculate risk metrics
volatility = returns.std() * np.sqrt(252)
sharpe_ratio = annual_return / volatility if volatility > 0 else 0
# Calculate drawdown
cumulative_returns = (1 + returns).cumprod()
rolling_max = cumulative_returns.expanding().max()
drawdowns = (cumulative_returns - rolling_max) / rolling_max
max_drawdown = drawdowns.min()
return {
'total_trades': total_trades,
'winning_trades': winning_trades,
'losing_trades': losing_trades,
'win_rate': win_rate,
'total_return': total_return,
'annual_return': annual_return,
'volatility': volatility,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown
}
def save_results(self, filepath: str):
"""Save backtest results to file"""
try:
with open(filepath, 'w') as f:
json.dump(self.results, f, default=str)
logger.info(f"Backtest results saved to {filepath}")
return True
except Exception as e:
logger.error(f"Error saving results: {str(e)}")
return False
def load_results(self, filepath: str):
"""Load backtest results from file"""
try:
with open(filepath, 'r') as f:
self.results = json.load(f)
logger.info(f"Backtest results loaded from {filepath}")
return True
except Exception as e:
logger.error(f"Error loading results: {str(e)}")
return False
def main():
# Load configuration
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json')
# Create backtester
backtester = Backtester(config_path)
# Define backtest parameters
symbol = "BTC/USDT"
timeframe = "1h"
start_date = datetime.now() - timedelta(days=30)
end_date = datetime.now()
initial_capital = 50.0
# Run backtest
results = backtester.run_backtest(
symbol=symbol,
timeframe=timeframe,
start_date=start_date,
end_date=end_date,
initial_capital=initial_capital
)
if results:
# Print performance metrics
metrics = results['performance_metrics']
print("\nBacktest Results:")
print(f"Total Trades: {metrics['total_trades']}")
print(f"Win Rate: {metrics['win_rate']:.2%}")
print(f"Total Return: {metrics['total_return']:.2%}")
print(f"Annual Return: {metrics['annual_return']:.2%}")
print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}")
print(f"Max Drawdown: {metrics['max_drawdown']:.2%}")
# Save results
backtester.save_results('backtest_results.json')
else:
print("Backtest failed to complete")
if __name__ == "__main__":
main()
+105
View File
@@ -0,0 +1,105 @@
{
"exchange": {
"name": "binance",
"api_key": "YOUR_API_KEY",
"api_secret": "YOUR_API_SECRET",
"testnet": true
},
"trading": {
"symbols": ["BTC/USDT", "ETH/USDT"],
"timeframes": ["1m", "5m", "15m", "1h", "4h", "1d"],
"lookback_periods": 100,
"state_file": "trading_state.json",
"initial_capital": 50.0,
"max_positions": 3,
"leverage": 1
},
"risk_management": {
"risk_per_trade": 0.02,
"max_daily_loss": 0.05,
"max_drawdown": 0.15,
"stop_loss_pct": 0.02,
"take_profit_pct": 0.04,
"min_win_rate": 0.45,
"min_profit_factor": 1.2
},
"technical_analysis": {
"indicators": {
"sma": [20, 50, 200],
"ema": [20],
"rsi": [14],
"macd": {
"fast": 12,
"slow": 26,
"signal": 9
},
"bollinger_bands": {
"period": 20,
"std_dev": 2
}
},
"patterns": {
"candlestick": true,
"chart": true
}
},
"machine_learning": {
"model_type": "lstm",
"features": [
"open", "high", "low", "close", "volume",
"sma_20", "sma_50", "sma_200",
"rsi_14", "macd", "macd_signal", "macd_hist",
"bb_upper", "bb_middle", "bb_lower"
],
"train_test_split": 0.2,
"validation_split": 0.1,
"rl": {
"learning_rate": 0.0003,
"n_steps": 2048,
"batch_size": 64,
"n_epochs": 10,
"gamma": 0.99,
"gae_lambda": 0.95,
"clip_range": 0.2,
"ent_coef": 0.01,
"vf_coef": 0.5
}
},
"sentiment_analysis": {
"news_api_key": "YOUR_NEWS_API_KEY",
"twitter_api_key": "YOUR_TWITTER_API_KEY",
"twitter_api_secret": "YOUR_TWITTER_API_SECRET",
"twitter_access_token": "YOUR_TWITTER_ACCESS_TOKEN",
"twitter_access_token_secret": "YOUR_TWITTER_ACCESS_TOKEN_SECRET",
"sentiment_weights": {
"news": 0.4,
"twitter": 0.3,
"market": 0.3
},
"sentiment_thresholds": {
"positive": 0.3,
"negative": -0.3
}
},
"signal_weights": {
"technical": 0.4,
"ml": 0.3,
"sentiment": 0.2,
"risk": 0.1
},
"signal_thresholds": {
"buy": 0.6,
"sell": -0.6
},
"logging": {
"level": "INFO",
"file": "trading_bot.log",
"max_size": 10485760,
"backup_count": 5
},
"web_dashboard": {
"host": "localhost",
"port": 5000,
"debug": false
}
}
+134
View File
@@ -0,0 +1,134 @@
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Exchange Configuration
EXCHANGE_CONFIG = {
'name': 'binance',
'api_key': os.getenv('BINANCE_API_KEY', ''),
'api_secret': os.getenv('BINANCE_API_SECRET', ''),
'testnet': True # Use testnet for development
}
# Trading Parameters
TRADING_CONFIG = {
'symbols': ['BTC/USDT', 'ETH/USDT', 'EUR/USD'],
'timeframes': ['1h', '4h', '1d'],
'initial_capital': 10000,
'risk_per_trade': 0.02, # 2% risk per trade
'max_positions': 3,
'position_sizing': {
'method': 'fixed_fractional',
'fraction': 0.02 # 2% of capital per trade
}
}
# Technical Analysis Parameters
TECHNICAL_CONFIG = {
'indicators': {
'bollinger_bands': {
'period': 20,
'std_dev': 2
},
'rsi': {
'period': 14,
'overbought': 70,
'oversold': 30
},
'macd': {
'fast_period': 12,
'slow_period': 26,
'signal_period': 9
},
'atr': {
'period': 14
}
},
'patterns': {
'head_and_shoulders': True,
'double_top_bottom': True,
'triangles': True
}
}
# Machine Learning Configuration
ML_CONFIG = {
'model_type': 'lstm', # Options: 'lstm', 'rf', 'xgboost'
'features': [
'open', 'high', 'low', 'close', 'volume',
'sma_20', 'sma_50', 'rsi', 'macd', 'atr',
'bb_upper', 'bb_lower', 'bb_width'
],
'sequence_length': 10,
'prediction_horizon': 1,
'train_test_split': 0.8,
'validation_split': 0.1
}
# Reinforcement Learning Configuration
RL_CONFIG = {
'algorithm': 'PPO',
'learning_rate': 0.0003,
'n_steps': 2048,
'batch_size': 64,
'n_epochs': 10,
'gamma': 0.99,
'gae_lambda': 0.95,
'clip_range': 0.2,
'ent_coef': 0.01,
'vf_coef': 0.5
}
# Risk Management Configuration
RISK_CONFIG = {
'stop_loss': {
'method': 'atr',
'atr_multiplier': 2
},
'take_profit': {
'method': 'risk_reward',
'risk_reward_ratio': 2
},
'trailing_stop': {
'enabled': True,
'activation_percentage': 0.02,
'trail_percentage': 0.01
}
}
# Sentiment Analysis Configuration
SENTIMENT_CONFIG = {
'sources': [
'reuters',
'bloomberg',
'forexfactory'
],
'update_interval': 3600, # 1 hour
'weight': 0.2 # Weight in final decision
}
# Backtesting Configuration
BACKTEST_CONFIG = {
'start_date': '2020-01-01',
'end_date': '2023-12-31',
'initial_capital': 10000,
'commission': 0.001, # 0.1%
'slippage': 0.0001 # 0.01%
}
# Logging Configuration
LOGGING_CONFIG = {
'level': 'INFO',
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
'file': 'logs/trading_bot.log'
}
# Web Dashboard Configuration
DASHBOARD_CONFIG = {
'host': '0.0.0.0',
'port': 5000,
'debug': False,
'update_interval': 5 # seconds
}
+271
View File
@@ -0,0 +1,271 @@
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import DummyVecEnv
import gym
import logging
from typing import Dict, List, Tuple, Optional
import joblib
import os
logger = logging.getLogger(__name__)
class MachineLearningModel:
def __init__(self, config: Dict):
self.config = config
self.model_type = config['model_type']
self.features = config['features']
self.scaler = StandardScaler()
self.model = None
self.env = None
def prepare_data(self, df: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray]:
"""
Prepare data for machine learning
"""
# Select features
X = df[self.features].copy()
# Create target variable (price movement direction)
y = np.where(df['close'].shift(-1) > df['close'], 1, 0)
# Drop NaN values
valid_idx = ~np.isnan(y)
X = X[valid_idx]
y = y[valid_idx]
# Scale features
X_scaled = self.scaler.fit_transform(X)
return X_scaled, y
def train_lstm(self, X: np.ndarray, y: np.ndarray):
"""
Train LSTM model
"""
# Reshape data for LSTM [samples, time steps, features]
X_reshaped = np.reshape(X, (X.shape[0], 1, X.shape[1]))
# Split data
X_train, X_test, y_train, y_test = train_test_split(
X_reshaped, y,
test_size=self.config['train_test_split'],
random_state=42
)
# Create model
model = Sequential([
LSTM(50, activation='relu', input_shape=(1, X.shape[1]), return_sequences=True),
Dropout(0.2),
LSTM(50, activation='relu'),
Dropout(0.2),
Dense(1, activation='sigmoid')
])
# Compile model
model.compile(
optimizer=Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['accuracy']
)
# Train model
model.fit(
X_train, y_train,
epochs=50,
batch_size=32,
validation_split=self.config['validation_split'],
verbose=0
)
# Evaluate model
_, accuracy = model.evaluate(X_test, y_test, verbose=0)
logger.info(f"LSTM model accuracy: {accuracy:.4f}")
self.model = model
return model
def train_reinforcement_learning(self, df: pd.DataFrame):
"""
Train reinforcement learning model
"""
# Create custom trading environment
self.env = TradingEnvironment(df)
self.env = DummyVecEnv([lambda: self.env])
# Create and train PPO model
model = PPO(
"MlpPolicy",
self.env,
learning_rate=self.config['rl']['learning_rate'],
n_steps=self.config['rl']['n_steps'],
batch_size=self.config['rl']['batch_size'],
n_epochs=self.config['rl']['n_epochs'],
gamma=self.config['rl']['gamma'],
gae_lambda=self.config['rl']['gae_lambda'],
clip_range=self.config['rl']['clip_range'],
ent_coef=self.config['rl']['ent_coef'],
vf_coef=self.config['rl']['vf_coef'],
verbose=1
)
# Train model
model.learn(total_timesteps=10000)
self.model = model
return model
def predict(self, X: np.ndarray) -> np.ndarray:
"""
Make predictions using the trained model
"""
if self.model is None:
logger.error("Model not trained. Call train() first.")
return None
if self.model_type == 'lstm':
X_reshaped = np.reshape(X, (X.shape[0], 1, X.shape[1]))
predictions = self.model.predict(X_reshaped)
else: # reinforcement learning
predictions = self.model.predict(X)
return predictions
def save_model(self, filepath: str):
"""
Save the trained model
"""
if self.model is None:
logger.error("Model not trained. Call train() first.")
return None
try:
if self.model_type == 'lstm':
self.model.save(filepath)
else:
self.model.save(f"{filepath}_rl")
# Save scaler separately
joblib.dump(self.scaler, f"{filepath}_scaler")
logger.info(f"Model saved to {filepath}")
return True
except Exception as e:
logger.error(f"Error saving model: {str(e)}")
return False
def load_model(self, filepath: str):
"""
Load a trained model
"""
try:
if self.model_type == 'lstm':
self.model = tf.keras.models.load_model(filepath)
else:
self.model = PPO.load(f"{filepath}_rl")
# Load scaler
self.scaler = joblib.load(f"{filepath}_scaler")
logger.info(f"Model loaded from {filepath}")
return True
except Exception as e:
logger.error(f"Error loading model: {str(e)}")
return False
class TradingEnvironment(gym.Env):
"""
Custom trading environment for reinforcement learning
"""
def __init__(self, df: pd.DataFrame):
super(TradingEnvironment, self).__init__()
self.df = df
self.current_step = 0
self.initial_balance = 10000
self.balance = self.initial_balance
self.position = 0
self.position_size = 0
# Define action and observation space
self.action_space = gym.spaces.Discrete(3) # Buy, Sell, Hold
self.observation_space = gym.spaces.Box(
low=-np.inf,
high=np.inf,
shape=(len(df.columns),),
dtype=np.float32
)
def reset(self):
"""
Reset the environment
"""
self.current_step = 0
self.balance = self.initial_balance
self.position = 0
self.position_size = 0
return self._get_observation()
def step(self, action):
"""
Execute one step in the environment
"""
# Get current price
current_price = self.df['close'].iloc[self.current_step]
# Execute action
reward = 0
done = False
if action == 0: # Buy
if self.position <= 0:
self.position = 1
self.position_size = self.balance / current_price
elif action == 1: # Sell
if self.position >= 0:
self.position = -1
self.position_size = self.balance / current_price
# Calculate reward
next_price = self.df['close'].iloc[self.current_step + 1]
price_change = (next_price - current_price) / current_price
if self.position != 0:
reward = self.position * price_change * 100
# Update balance
self.balance *= (1 + reward/100)
# Move to next step
self.current_step += 1
# Check if episode is done
if self.current_step >= len(self.df) - 1:
done = True
return self._get_observation(), reward, done, {}
def _get_observation(self):
"""
Get current observation
"""
return self.df.iloc[self.current_step].values.astype(np.float32)
def render(self):
"""
Render the environment
"""
pass
def close(self):
"""
Clean up resources
"""
pass
+272
View File
@@ -0,0 +1,272 @@
import os
import sys
import logging
import json
import time
from datetime import datetime
from typing import Dict, List
import pandas as pd
import ccxt
from concurrent.futures import ThreadPoolExecutor
import schedule
import threading
import signal
import queue
from .trading_strategy import TradingStrategy
from .config import load_config
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('trading_bot.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class TradingBot:
def __init__(self, config_path: str):
# Load configuration
self.config = load_config(config_path)
# Initialize exchange
self.exchange = self._initialize_exchange()
# Initialize trading strategy
self.strategy = TradingStrategy(self.config)
# Initialize state
self.is_running = False
self.symbols = self.config['trading']['symbols']
self.timeframes = self.config['trading']['timeframes']
self.data_queue = queue.Queue()
self.signal_queue = queue.Queue()
# Load previous state if exists
self._load_state()
def _initialize_exchange(self) -> ccxt.Exchange:
"""
Initialize the cryptocurrency exchange
"""
try:
exchange_class = getattr(ccxt, self.config['exchange']['name'])
exchange = exchange_class({
'apiKey': self.config['exchange']['api_key'],
'secret': self.config['exchange']['api_secret'],
'enableRateLimit': True
})
# Test connection
exchange.load_markets()
logger.info(f"Successfully connected to {self.config['exchange']['name']}")
return exchange
except Exception as e:
logger.error(f"Error initializing exchange: {str(e)}")
raise
def _load_state(self):
"""
Load previous trading state
"""
state_file = self.config['trading']['state_file']
if os.path.exists(state_file):
try:
self.strategy.load_state(state_file)
logger.info("Successfully loaded previous trading state")
except Exception as e:
logger.error(f"Error loading state: {str(e)}")
def _save_state(self):
"""
Save current trading state
"""
try:
self.strategy.save_state(self.config['trading']['state_file'])
logger.info("Successfully saved trading state")
except Exception as e:
logger.error(f"Error saving state: {str(e)}")
def fetch_market_data(self, symbol: str, timeframe: str) -> pd.DataFrame:
"""
Fetch market data from exchange
"""
try:
# Get OHLCV data
ohlcv = self.exchange.fetch_ohlcv(
symbol,
timeframe=timeframe,
limit=self.config['trading']['lookback_periods']
)
# Convert to DataFrame
df = pd.DataFrame(
ohlcv,
columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
)
# Convert timestamp to datetime
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
return df
except Exception as e:
logger.error(f"Error fetching market data for {symbol}: {str(e)}")
return None
def process_market_data(self):
"""
Process market data from queue
"""
while self.is_running:
try:
# Get data from queue
data = self.data_queue.get(timeout=1)
symbol, timeframe, df = data
# Generate trading signals
signal = self.strategy.analyze_market(symbol, df)
if signal:
# Add signal to queue
self.signal_queue.put(signal)
except queue.Empty:
continue
except Exception as e:
logger.error(f"Error processing market data: {str(e)}")
def execute_signals(self):
"""
Execute trading signals from queue
"""
while self.is_running:
try:
# Get signal from queue
signal = self.signal_queue.get(timeout=1)
# Execute trade
if self.strategy.execute_trade(signal):
logger.info(f"Executed {signal.action} trade for {signal.symbol}")
# Update positions with current prices
current_prices = {
symbol: self.exchange.fetch_ticker(symbol)['last']
for symbol in self.symbols
}
self.strategy.update_positions(current_prices)
# Save state after trade
self._save_state()
except queue.Empty:
continue
except Exception as e:
logger.error(f"Error executing signals: {str(e)}")
def update_market_data(self):
"""
Update market data for all symbols
"""
with ThreadPoolExecutor(max_workers=len(self.symbols)) as executor:
futures = []
for symbol in self.symbols:
for timeframe in self.timeframes:
future = executor.submit(self.fetch_market_data, symbol, timeframe)
futures.append((symbol, timeframe, future))
for symbol, timeframe, future in futures:
try:
df = future.result()
if df is not None:
self.data_queue.put((symbol, timeframe, df))
except Exception as e:
logger.error(f"Error updating market data for {symbol}: {str(e)}")
def schedule_tasks(self):
"""
Schedule periodic tasks
"""
# Schedule market data updates
for timeframe in self.timeframes:
schedule.every().minute.at(":00").do(self.update_market_data)
# Schedule state saving
schedule.every().hour.at(":00").do(self._save_state)
# Run the scheduler
while self.is_running:
schedule.run_pending()
time.sleep(1)
def start(self):
"""
Start the trading bot
"""
try:
self.is_running = True
# Start threads
threads = [
threading.Thread(target=self.process_market_data),
threading.Thread(target=self.execute_signals),
threading.Thread(target=self.schedule_tasks)
]
for thread in threads:
thread.daemon = True
thread.start()
# Initial market data update
self.update_market_data()
logger.info("Trading bot started successfully")
# Wait for shutdown signal
while self.is_running:
time.sleep(1)
except Exception as e:
logger.error(f"Error starting trading bot: {str(e)}")
self.stop()
def stop(self):
"""
Stop the trading bot
"""
try:
self.is_running = False
self._save_state()
logger.info("Trading bot stopped successfully")
except Exception as e:
logger.error(f"Error stopping trading bot: {str(e)}")
def main():
# Load configuration
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json')
# Create trading bot
bot = TradingBot(config_path)
# Handle shutdown signals
def signal_handler(signum, frame):
logger.info("Received shutdown signal")
bot.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Start trading bot
bot.start()
if __name__ == "__main__":
main()
+18
View File
@@ -0,0 +1,18 @@
numpy>=1.21.0
pandas>=1.3.0
ccxt>=2.0.0
scikit-learn>=0.24.2
tensorflow>=2.6.0
stable-baselines3>=1.5.0
gym>=0.21.0
ta-lib>=0.4.24
textblob>=0.15.3
tweepy>=4.10.0
newsapi-python>=0.2.6
yfinance>=0.1.63
schedule>=1.1.0
joblib>=1.0.2
python-dotenv>=0.19.0
flask>=2.0.1
plotly>=5.3.1
dash>=2.0.0
+249
View File
@@ -0,0 +1,249 @@
import numpy as np
import pandas as pd
from typing import Dict, Tuple, Optional
import logging
from dataclasses import dataclass
from datetime import datetime
logger = logging.getLogger(__name__)
@dataclass
class RiskMetrics:
"""Data class to store risk metrics"""
daily_drawdown: float
max_drawdown: float
sharpe_ratio: float
sortino_ratio: float
win_rate: float
profit_factor: float
avg_win: float
avg_loss: float
max_consecutive_losses: int
max_consecutive_wins: int
class RiskManager:
def __init__(self, config: Dict):
self.config = config
self.initial_capital = config['initial_capital']
self.current_capital = self.initial_capital
self.risk_per_trade = config['risk_per_trade']
self.max_daily_loss = config['max_daily_loss']
self.max_drawdown = config['max_drawdown']
self.max_positions = config['max_positions']
self.leverage = config['leverage']
self.stop_loss_pct = config['stop_loss_pct']
self.take_profit_pct = config['take_profit_pct']
# Performance tracking
self.trades_history = []
self.daily_pnl = []
self.positions = {}
self.risk_metrics = None
def calculate_position_size(self,
price: float,
stop_loss: float,
account_balance: float) -> Tuple[float, float]:
"""
Calculate position size based on risk parameters
"""
# Calculate risk amount in base currency
risk_amount = account_balance * self.risk_per_trade
# Calculate position size based on stop loss
price_distance = abs(price - stop_loss)
position_size = risk_amount / price_distance
# Apply leverage
position_size *= self.leverage
# Calculate required margin
required_margin = (position_size * price) / self.leverage
# Ensure we don't exceed maximum position size
max_position = account_balance * self.leverage / price
position_size = min(position_size, max_position)
return position_size, required_margin
def validate_trade(self,
symbol: str,
position_size: float,
price: float,
stop_loss: float,
take_profit: float) -> bool:
"""
Validate if a trade meets risk management criteria
"""
# Check if we have too many open positions
if len(self.positions) >= self.max_positions:
logger.warning("Maximum number of positions reached")
return False
# Check if symbol is already in a position
if symbol in self.positions:
logger.warning(f"Position already exists for {symbol}")
return False
# Calculate potential loss
potential_loss = position_size * abs(price - stop_loss)
# Check if potential loss exceeds daily limit
if potential_loss > self.max_daily_loss:
logger.warning("Trade exceeds maximum daily loss limit")
return False
# Check if stop loss is too far
stop_loss_distance = abs(price - stop_loss) / price
if stop_loss_distance > self.stop_loss_pct:
logger.warning("Stop loss distance exceeds maximum allowed")
return False
# Check if take profit is reasonable
take_profit_distance = abs(take_profit - price) / price
if take_profit_distance > self.take_profit_pct:
logger.warning("Take profit distance exceeds maximum allowed")
return False
return True
def update_position(self,
symbol: str,
entry_price: float,
current_price: float,
position_size: float,
position_type: str) -> Optional[float]:
"""
Update position and check for stop loss or take profit
"""
if symbol not in self.positions:
self.positions[symbol] = {
'entry_price': entry_price,
'current_price': current_price,
'position_size': position_size,
'position_type': position_type,
'entry_time': datetime.now()
}
return None
position = self.positions[symbol]
pnl = 0
# Calculate unrealized PnL
if position_type == 'long':
pnl = (current_price - entry_price) * position_size
else:
pnl = (entry_price - current_price) * position_size
# Check stop loss
if position_type == 'long':
if current_price <= entry_price * (1 - self.stop_loss_pct):
pnl = -position_size * entry_price * self.stop_loss_pct
del self.positions[symbol]
return pnl
else:
if current_price >= entry_price * (1 + self.stop_loss_pct):
pnl = -position_size * entry_price * self.stop_loss_pct
del self.positions[symbol]
return pnl
# Check take profit
if position_type == 'long':
if current_price >= entry_price * (1 + self.take_profit_pct):
pnl = position_size * entry_price * self.take_profit_pct
del self.positions[symbol]
return pnl
else:
if current_price <= entry_price * (1 - self.take_profit_pct):
pnl = position_size * entry_price * self.take_profit_pct
del self.positions[symbol]
return pnl
# Update current price
position['current_price'] = current_price
return None
def calculate_risk_metrics(self) -> RiskMetrics:
"""
Calculate various risk metrics
"""
if not self.trades_history:
return None
# Convert trades history to DataFrame
df = pd.DataFrame(self.trades_history)
# Calculate daily returns
daily_returns = df.groupby(df['exit_time'].dt.date)['pnl'].sum()
# Calculate drawdown
cumulative_returns = (1 + daily_returns).cumprod()
rolling_max = cumulative_returns.expanding().max()
drawdowns = (cumulative_returns - rolling_max) / rolling_max
# Calculate metrics
daily_drawdown = drawdowns.min()
max_drawdown = drawdowns.min()
# Calculate Sharpe ratio
risk_free_rate = 0.02 # 2% annual risk-free rate
excess_returns = daily_returns - risk_free_rate/252
sharpe_ratio = np.sqrt(252) * excess_returns.mean() / excess_returns.std()
# Calculate Sortino ratio
downside_returns = excess_returns[excess_returns < 0]
sortino_ratio = np.sqrt(252) * excess_returns.mean() / downside_returns.std()
# Calculate win rate and profit factor
winning_trades = df[df['pnl'] > 0]
losing_trades = df[df['pnl'] < 0]
win_rate = len(winning_trades) / len(df)
profit_factor = abs(winning_trades['pnl'].sum() / losing_trades['pnl'].sum())
# Calculate average win and loss
avg_win = winning_trades['pnl'].mean()
avg_loss = losing_trades['pnl'].mean()
# Calculate consecutive wins and losses
df['consecutive'] = (df['pnl'] > 0).astype(int)
max_consecutive_wins = df['consecutive'].groupby((df['consecutive'] != df['consecutive'].shift()).cumsum()).sum().max()
max_consecutive_losses = df['consecutive'].groupby((df['consecutive'] != df['consecutive'].shift()).cumsum()).sum().min()
self.risk_metrics = RiskMetrics(
daily_drawdown=daily_drawdown,
max_drawdown=max_drawdown,
sharpe_ratio=sharpe_ratio,
sortino_ratio=sortino_ratio,
win_rate=win_rate,
profit_factor=profit_factor,
avg_win=avg_win,
avg_loss=avg_loss,
max_consecutive_losses=max_consecutive_losses,
max_consecutive_wins=max_consecutive_wins
)
return self.risk_metrics
def should_stop_trading(self) -> bool:
"""
Check if trading should be stopped based on risk metrics
"""
if not self.risk_metrics:
return False
# Stop if maximum drawdown is exceeded
if self.risk_metrics.max_drawdown < -self.max_drawdown:
logger.warning("Maximum drawdown exceeded. Stopping trading.")
return True
# Stop if win rate is too low
if self.risk_metrics.win_rate < self.config['min_win_rate']:
logger.warning("Win rate below minimum threshold. Stopping trading.")
return True
# Stop if profit factor is too low
if self.risk_metrics.profit_factor < self.config['min_profit_factor']:
logger.warning("Profit factor below minimum threshold. Stopping trading.")
return True
return False
+84
View File
@@ -0,0 +1,84 @@
import os
import sys
from datetime import datetime, timedelta
import logging
from src.backtesting import Backtester
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('backtest.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
def run_backtest_scenarios():
# Load configuration
config_path = os.path.join(os.path.dirname(__file__), 'config', 'config.json')
# Create backtester
backtester = Backtester(config_path)
# Define test scenarios
scenarios = [
{
'name': 'BTC/USDT 1h - Last 30 days',
'symbol': 'BTC/USDT',
'timeframe': '1h',
'days': 30
},
{
'name': 'ETH/USDT 4h - Last 60 days',
'symbol': 'ETH/USDT',
'timeframe': '4h',
'days': 60
},
{
'name': 'BTC/USDT 1d - Last 90 days',
'symbol': 'BTC/USDT',
'timeframe': '1d',
'days': 90
}
]
# Run each scenario
for scenario in scenarios:
logger.info(f"\nRunning scenario: {scenario['name']}")
# Calculate date range
end_date = datetime.now()
start_date = end_date - timedelta(days=scenario['days'])
# Run backtest
results = backtester.run_backtest(
symbol=scenario['symbol'],
timeframe=scenario['timeframe'],
start_date=start_date,
end_date=end_date,
initial_capital=50.0
)
if results:
# Print performance metrics
metrics = results['performance_metrics']
print(f"\nResults for {scenario['name']}:")
print(f"Total Trades: {metrics['total_trades']}")
print(f"Win Rate: {metrics['win_rate']:.2%}")
print(f"Total Return: {metrics['total_return']:.2%}")
print(f"Annual Return: {metrics['annual_return']:.2%}")
print(f"Sharpe Ratio: {metrics['sharpe_ratio']:.2f}")
print(f"Max Drawdown: {metrics['max_drawdown']:.2%}")
# Save results
filename = f"backtest_results_{scenario['symbol'].replace('/', '_')}_{scenario['timeframe']}.json"
backtester.save_results(filename)
logger.info(f"Results saved to {filename}")
else:
logger.error(f"Backtest failed for scenario: {scenario['name']}")
if __name__ == "__main__":
run_backtest_scenarios()
+267
View File
@@ -0,0 +1,267 @@
import numpy as np
import pandas as pd
from typing import Dict, List, Optional
import logging
from datetime import datetime, timedelta
import requests
from textblob import TextBlob
import tweepy
from newsapi import NewsApiClient
import yfinance as yf
from concurrent.futures import ThreadPoolExecutor
import json
import os
logger = logging.getLogger(__name__)
class SentimentAnalyzer:
def __init__(self, config: Dict):
self.config = config
self.news_api = NewsApiClient(api_key=config['news_api_key'])
self.twitter_auth = tweepy.OAuthHandler(
config['twitter_api_key'],
config['twitter_api_secret']
)
self.twitter_auth.set_access_token(
config['twitter_access_token'],
config['twitter_access_token_secret']
)
self.twitter_api = tweepy.API(self.twitter_auth)
# Initialize sentiment cache
self.sentiment_cache = {}
self.cache_duration = timedelta(hours=1)
def analyze_text(self, text: str) -> float:
"""
Analyze sentiment of a single text using TextBlob
"""
try:
analysis = TextBlob(text)
# Normalize sentiment score to [-1, 1]
return analysis.sentiment.polarity
except Exception as e:
logger.error(f"Error analyzing text: {str(e)}")
return 0.0
def get_news_sentiment(self, symbol: str) -> Optional[float]:
"""
Get sentiment from news articles
"""
try:
# Check cache first
cache_key = f"news_{symbol}"
if cache_key in self.sentiment_cache:
cached_data = self.sentiment_cache[cache_key]
if datetime.now() - cached_data['timestamp'] < self.cache_duration:
return cached_data['sentiment']
# Get news articles
news = self.news_api.get_everything(
q=symbol,
language='en',
from_param=(datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d'),
sort_by='relevancy'
)
if not news['articles']:
return None
# Analyze sentiment of each article
sentiments = []
for article in news['articles']:
title_sentiment = self.analyze_text(article['title'])
if article['description']:
desc_sentiment = self.analyze_text(article['description'])
sentiments.append((title_sentiment + desc_sentiment) / 2)
else:
sentiments.append(title_sentiment)
# Calculate weighted average sentiment
avg_sentiment = np.mean(sentiments)
# Cache the result
self.sentiment_cache[cache_key] = {
'sentiment': avg_sentiment,
'timestamp': datetime.now()
}
return avg_sentiment
except Exception as e:
logger.error(f"Error getting news sentiment: {str(e)}")
return None
def get_twitter_sentiment(self, symbol: str) -> Optional[float]:
"""
Get sentiment from Twitter
"""
try:
# Check cache first
cache_key = f"twitter_{symbol}"
if cache_key in self.sentiment_cache:
cached_data = self.sentiment_cache[cache_key]
if datetime.now() - cached_data['timestamp'] < self.cache_duration:
return cached_data['sentiment']
# Get tweets
tweets = self.twitter_api.search_tweets(
q=f"${symbol}",
lang="en",
count=100
)
if not tweets:
return None
# Analyze sentiment of each tweet
sentiments = []
for tweet in tweets:
sentiment = self.analyze_text(tweet.text)
sentiments.append(sentiment)
# Calculate weighted average sentiment
avg_sentiment = np.mean(sentiments)
# Cache the result
self.sentiment_cache[cache_key] = {
'sentiment': avg_sentiment,
'timestamp': datetime.now()
}
return avg_sentiment
except Exception as e:
logger.error(f"Error getting Twitter sentiment: {str(e)}")
return None
def get_market_sentiment(self, symbol: str) -> Optional[float]:
"""
Get market sentiment indicators
"""
try:
# Check cache first
cache_key = f"market_{symbol}"
if cache_key in self.sentiment_cache:
cached_data = self.sentiment_cache[cache_key]
if datetime.now() - cached_data['timestamp'] < self.cache_duration:
return cached_data['sentiment']
# Get market data
ticker = yf.Ticker(symbol)
info = ticker.info
# Calculate various sentiment indicators
sentiment_indicators = []
# RSI sentiment
if 'RSI' in info:
rsi = info['RSI']
rsi_sentiment = (rsi - 50) / 50 # Normalize to [-1, 1]
sentiment_indicators.append(rsi_sentiment)
# Volume sentiment
if 'volume' in info and 'averageVolume' in info:
volume_ratio = info['volume'] / info['averageVolume']
volume_sentiment = (volume_ratio - 1) / volume_ratio # Normalize to [-1, 1]
sentiment_indicators.append(volume_sentiment)
# Price momentum sentiment
if 'regularMarketChangePercent' in info:
momentum_sentiment = info['regularMarketChangePercent'] / 100
sentiment_indicators.append(momentum_sentiment)
if not sentiment_indicators:
return None
# Calculate weighted average sentiment
avg_sentiment = np.mean(sentiment_indicators)
# Cache the result
self.sentiment_cache[cache_key] = {
'sentiment': avg_sentiment,
'timestamp': datetime.now()
}
return avg_sentiment
except Exception as e:
logger.error(f"Error getting market sentiment: {str(e)}")
return None
def get_combined_sentiment(self, symbol: str) -> Optional[float]:
"""
Get combined sentiment from all sources
"""
try:
# Check cache first
cache_key = f"combined_{symbol}"
if cache_key in self.sentiment_cache:
cached_data = self.sentiment_cache[cache_key]
if datetime.now() - cached_data['timestamp'] < self.cache_duration:
return cached_data['sentiment']
# Get sentiment from all sources
sentiments = []
weights = []
# News sentiment
news_sentiment = self.get_news_sentiment(symbol)
if news_sentiment is not None:
sentiments.append(news_sentiment)
weights.append(self.config['sentiment_weights']['news'])
# Twitter sentiment
twitter_sentiment = self.get_twitter_sentiment(symbol)
if twitter_sentiment is not None:
sentiments.append(twitter_sentiment)
weights.append(self.config['sentiment_weights']['twitter'])
# Market sentiment
market_sentiment = self.get_market_sentiment(symbol)
if market_sentiment is not None:
sentiments.append(market_sentiment)
weights.append(self.config['sentiment_weights']['market'])
if not sentiments:
return None
# Calculate weighted average sentiment
avg_sentiment = np.average(sentiments, weights=weights)
# Cache the result
self.sentiment_cache[cache_key] = {
'sentiment': avg_sentiment,
'timestamp': datetime.now()
}
return avg_sentiment
except Exception as e:
logger.error(f"Error getting combined sentiment: {str(e)}")
return None
def clear_cache(self):
"""
Clear the sentiment cache
"""
self.sentiment_cache.clear()
def get_sentiment_signal(self, symbol: str) -> int:
"""
Convert sentiment to trading signal
"""
sentiment = self.get_combined_sentiment(symbol)
if sentiment is None:
return 0
# Define sentiment thresholds
positive_threshold = self.config['sentiment_thresholds']['positive']
negative_threshold = self.config['sentiment_thresholds']['negative']
if sentiment > positive_threshold:
return 1 # Buy signal
elif sentiment < negative_threshold:
return -1 # Sell signal
else:
return 0 # Neutral signal
+293
View File
@@ -0,0 +1,293 @@
import numpy as np
import pandas as pd
import talib
from typing import Dict, List, Tuple, Optional
import logging
logger = logging.getLogger(__name__)
class TechnicalAnalyzer:
def __init__(self, config: Dict):
self.config = config
self.indicators = {}
self.patterns = {}
def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Add technical indicators to the dataframe
"""
df = df.copy()
# Trend Indicators
df['sma_20'] = talib.SMA(df['close'], timeperiod=20)
df['sma_50'] = talib.SMA(df['close'], timeperiod=50)
df['sma_200'] = talib.SMA(df['close'], timeperiod=200)
df['ema_20'] = talib.EMA(df['close'], timeperiod=20)
# Volatility Indicators
df['upperband'], df['middleband'], df['lowerband'] = talib.BBANDS(
df['close'],
timeperiod=self.config['indicators']['bollinger_bands']['period'],
nbdevup=self.config['indicators']['bollinger_bands']['std_dev'],
nbdevdn=self.config['indicators']['bollinger_bands']['std_dev']
)
df['atr'] = talib.ATR(
df['high'],
df['low'],
df['close'],
timeperiod=self.config['indicators']['atr']['period']
)
# Momentum Indicators
df['rsi'] = talib.RSI(
df['close'],
timeperiod=self.config['indicators']['rsi']['period']
)
df['macd'], df['macdsignal'], df['macdhist'] = talib.MACD(
df['close'],
fastperiod=self.config['indicators']['macd']['fast_period'],
slowperiod=self.config['indicators']['macd']['slow_period'],
signalperiod=self.config['indicators']['macd']['signal_period']
)
# Volume Indicators
df['obv'] = talib.OBV(df['close'], df['volume'])
# Additional Indicators
df['adx'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=14)
df['cci'] = talib.CCI(df['high'], df['low'], df['close'], timeperiod=14)
df['stoch_k'], df['stoch_d'] = talib.STOCH(
df['high'],
df['low'],
df['close'],
fastk_period=14,
slowk_period=3,
slowd_period=3
)
# Store indicators for reference
self.indicators = {
'trend': ['sma_20', 'sma_50', 'sma_200', 'ema_20'],
'volatility': ['upperband', 'middleband', 'lowerband', 'atr'],
'momentum': ['rsi', 'macd', 'macdsignal', 'macdhist'],
'volume': ['obv'],
'additional': ['adx', 'cci', 'stoch_k', 'stoch_d']
}
return df
def detect_patterns(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Detect candlestick patterns and chart patterns
"""
df = df.copy()
# Candlestick Patterns
if self.config['patterns']['candlestick']:
df['doji'] = talib.CDLDOJI(df['open'], df['high'], df['low'], df['close'])
df['hammer'] = talib.CDLHAMMER(df['open'], df['high'], df['low'], df['close'])
df['engulfing'] = talib.CDLENGULFING(df['open'], df['high'], df['low'], df['close'])
df['morning_star'] = talib.CDLMORNINGSTAR(df['open'], df['high'], df['low'], df['close'])
df['evening_star'] = talib.CDLEVENINGSTAR(df['open'], df['high'], df['low'], df['close'])
# Chart Patterns
if self.config['patterns']['chart']:
df['head_and_shoulders'] = self._detect_head_and_shoulders(df)
df['double_top'] = self._detect_double_top(df)
df['double_bottom'] = self._detect_double_bottom(df)
df['triangle'] = self._detect_triangle(df)
return df
def _detect_head_and_shoulders(self, df: pd.DataFrame) -> pd.Series:
"""
Detect head and shoulders pattern
"""
pattern = pd.Series(0, index=df.index)
for i in range(20, len(df) - 20):
# Find potential left shoulder
left_shoulder = df['high'].iloc[i-20:i].max()
left_shoulder_idx = df['high'].iloc[i-20:i].idxmax()
# Find potential head
head = df['high'].iloc[i-10:i+10].max()
head_idx = df['high'].iloc[i-10:i+10].idxmax()
# Find potential right shoulder
right_shoulder = df['high'].iloc[i:i+20].max()
right_shoulder_idx = df['high'].iloc[i:i+20].idxmax()
# Find neckline
neckline = min(df['low'].iloc[left_shoulder_idx:right_shoulder_idx])
# Check pattern conditions
if (left_shoulder < head and right_shoulder < head and
abs(left_shoulder - right_shoulder) / head < 0.1):
pattern.iloc[i] = 1
return pattern
def _detect_double_top(self, df: pd.DataFrame) -> pd.Series:
"""
Detect double top pattern
"""
pattern = pd.Series(0, index=df.index)
for i in range(20, len(df) - 20):
# Find potential first peak
first_peak = df['high'].iloc[i-20:i].max()
first_peak_idx = df['high'].iloc[i-20:i].idxmax()
# Find potential second peak
second_peak = df['high'].iloc[i:i+20].max()
second_peak_idx = df['high'].iloc[i:i+20].idxmax()
# Find valley between peaks
valley = df['low'].iloc[first_peak_idx:second_peak_idx].min()
# Check pattern conditions
if (abs(first_peak - second_peak) / first_peak < 0.02 and
(first_peak - valley) / first_peak > 0.02):
pattern.iloc[i] = 1
return pattern
def _detect_double_bottom(self, df: pd.DataFrame) -> pd.Series:
"""
Detect double bottom pattern
"""
pattern = pd.Series(0, index=df.index)
for i in range(20, len(df) - 20):
# Find potential first bottom
first_bottom = df['low'].iloc[i-20:i].min()
first_bottom_idx = df['low'].iloc[i-20:i].idxmin()
# Find potential second bottom
second_bottom = df['low'].iloc[i:i+20].min()
second_bottom_idx = df['low'].iloc[i:i+20].idxmin()
# Find peak between bottoms
peak = df['high'].iloc[first_bottom_idx:second_bottom_idx].max()
# Check pattern conditions
if (abs(first_bottom - second_bottom) / first_bottom < 0.02 and
(peak - first_bottom) / first_bottom > 0.02):
pattern.iloc[i] = 1
return pattern
def _detect_triangle(self, df: pd.DataFrame) -> pd.Series:
"""
Detect triangle patterns (ascending, descending, symmetrical)
"""
pattern = pd.Series(0, index=df.index)
for i in range(20, len(df) - 20):
# Get highs and lows for the period
highs = df['high'].iloc[i-20:i]
lows = df['low'].iloc[i-20:i]
# Calculate trend lines
high_slope = np.polyfit(range(len(highs)), highs, 1)[0]
low_slope = np.polyfit(range(len(lows)), lows, 1)[0]
# Classify triangle type
if abs(high_slope) < 0.001 and low_slope > 0.001:
pattern.iloc[i] = 1 # Ascending triangle
elif high_slope < -0.001 and abs(low_slope) < 0.001:
pattern.iloc[i] = 2 # Descending triangle
elif abs(high_slope + low_slope) < 0.001:
pattern.iloc[i] = 3 # Symmetrical triangle
return pattern
def generate_signals(self, df: pd.DataFrame) -> pd.DataFrame:
"""
Generate trading signals based on technical indicators and patterns
"""
df = df.copy()
# Initialize signal column
df['signal'] = 0
# RSI signals
df.loc[df['rsi'] < self.config['indicators']['rsi']['oversold'], 'signal'] += 1
df.loc[df['rsi'] > self.config['indicators']['rsi']['overbought'], 'signal'] -= 1
# MACD signals
df.loc[df['macd'] > df['macdsignal'], 'signal'] += 1
df.loc[df['macd'] < df['macdsignal'], 'signal'] -= 1
# Bollinger Bands signals
df.loc[df['close'] < df['lowerband'], 'signal'] += 1
df.loc[df['close'] > df['upperband'], 'signal'] -= 1
# Trend signals
df.loc[df['close'] > df['sma_20'], 'signal'] += 1
df.loc[df['close'] < df['sma_20'], 'signal'] -= 1
# Pattern signals
if 'head_and_shoulders' in df.columns:
df.loc[df['head_and_shoulders'] == 1, 'signal'] -= 1
if 'double_top' in df.columns:
df.loc[df['double_top'] == 1, 'signal'] -= 1
if 'double_bottom' in df.columns:
df.loc[df['double_bottom'] == 1, 'signal'] += 1
if 'triangle' in df.columns:
df.loc[df['triangle'] == 1, 'signal'] += 1 # Ascending
df.loc[df['triangle'] == 2, 'signal'] -= 1 # Descending
# Normalize signals to -1, 0, 1
df['signal'] = df['signal'].apply(lambda x: 1 if x > 2 else (-1 if x < -2 else 0))
return df
def calculate_support_resistance(self, df: pd.DataFrame, window: int = 20) -> Tuple[pd.Series, pd.Series]:
"""
Calculate support and resistance levels
"""
support = df['low'].rolling(window=window).min()
resistance = df['high'].rolling(window=window).max()
return support, resistance
def calculate_volatility(self, df: pd.DataFrame) -> pd.Series:
"""
Calculate various volatility measures
"""
# ATR-based volatility
atr_volatility = df['atr'] / df['close']
# Bollinger Band width
bb_width = (df['upperband'] - df['lowerband']) / df['middleband']
# Historical volatility
returns = df['close'].pct_change()
hist_volatility = returns.rolling(window=20).std()
return pd.DataFrame({
'atr_volatility': atr_volatility,
'bb_width': bb_width,
'hist_volatility': hist_volatility
})
def get_market_regime(self, df: pd.DataFrame) -> pd.Series:
"""
Determine market regime (trending, ranging, volatile)
"""
regime = pd.Series('unknown', index=df.index)
# Calculate ADX for trend strength
adx = df['adx']
# Calculate volatility
volatility = self.calculate_volatility(df)['atr_volatility']
# Determine regime
regime.loc[adx > 25] = 'trending'
regime.loc[(adx <= 25) & (volatility > volatility.rolling(window=20).mean())] = 'volatile'
regime.loc[(adx <= 25) & (volatility <= volatility.rolling(window=20).mean())] = 'ranging'
return regime
+303
View File
@@ -0,0 +1,303 @@
import numpy as np
import pandas as pd
from typing import Dict, List, Optional, Tuple
import logging
from datetime import datetime
from dataclasses import dataclass
import json
import os
from .technical_analysis import TechnicalAnalyzer
from .machine_learning import MachineLearningModel
from .risk_management import RiskManager
from .sentiment_analysis import SentimentAnalyzer
logger = logging.getLogger(__name__)
@dataclass
class TradeSignal:
"""Data class to store trade signals"""
symbol: str
action: str # 'buy', 'sell', or 'hold'
confidence: float
price: float
stop_loss: float
take_profit: float
position_size: float
timestamp: datetime
technical_score: float
ml_score: float
sentiment_score: float
risk_score: float
class TradingStrategy:
def __init__(self, config: Dict):
self.config = config
self.technical_analyzer = TechnicalAnalyzer(config)
self.ml_model = MachineLearningModel(config)
self.risk_manager = RiskManager(config)
self.sentiment_analyzer = SentimentAnalyzer(config)
# Initialize state
self.active_trades = {}
self.trade_history = []
self.performance_metrics = {}
def analyze_market(self, symbol: str, data: pd.DataFrame) -> TradeSignal:
"""
Analyze market conditions and generate trading signals
"""
try:
# Technical analysis
technical_signals = self.technical_analyzer.generate_signals(data)
technical_score = technical_signals['signal']
# Machine learning prediction
ml_predictions = self.ml_model.predict(data)
ml_score = ml_predictions[-1] if ml_predictions is not None else 0
# Sentiment analysis
sentiment_score = self.sentiment_analyzer.get_combined_sentiment(symbol)
if sentiment_score is None:
sentiment_score = 0
# Risk assessment
risk_score = self.risk_manager.calculate_risk_metrics()
# Combine signals
combined_score = (
technical_score * self.config['signal_weights']['technical'] +
ml_score * self.config['signal_weights']['ml'] +
sentiment_score * self.config['signal_weights']['sentiment'] +
risk_score * self.config['signal_weights']['risk']
)
# Generate trading signal
current_price = data['close'].iloc[-1]
# Calculate stop loss and take profit levels
stop_loss = self._calculate_stop_loss(current_price, combined_score)
take_profit = self._calculate_take_profit(current_price, combined_score)
# Calculate position size
position_size, required_margin = self.risk_manager.calculate_position_size(
current_price,
stop_loss,
self.risk_manager.current_capital
)
# Determine action
if combined_score > self.config['signal_thresholds']['buy']:
action = 'buy'
elif combined_score < self.config['signal_thresholds']['sell']:
action = 'sell'
else:
action = 'hold'
# Create trade signal
signal = TradeSignal(
symbol=symbol,
action=action,
confidence=abs(combined_score),
price=current_price,
stop_loss=stop_loss,
take_profit=take_profit,
position_size=position_size,
timestamp=datetime.now(),
technical_score=technical_score,
ml_score=ml_score,
sentiment_score=sentiment_score,
risk_score=risk_score
)
return signal
except Exception as e:
logger.error(f"Error analyzing market: {str(e)}")
return None
def execute_trade(self, signal: TradeSignal) -> bool:
"""
Execute a trade based on the signal
"""
try:
# Validate trade
if not self.risk_manager.validate_trade(
signal.symbol,
signal.position_size,
signal.price,
signal.stop_loss,
signal.take_profit
):
return False
# Execute trade
if signal.action in ['buy', 'sell']:
# Update position
pnl = self.risk_manager.update_position(
signal.symbol,
signal.price,
signal.price,
signal.position_size,
signal.action
)
if pnl is not None:
# Record trade
self.trade_history.append({
'symbol': signal.symbol,
'action': signal.action,
'entry_price': signal.price,
'exit_price': signal.price,
'position_size': signal.position_size,
'pnl': pnl,
'entry_time': signal.timestamp,
'exit_time': datetime.now()
})
# Update performance metrics
self._update_performance_metrics()
return True
return False
except Exception as e:
logger.error(f"Error executing trade: {str(e)}")
return False
def update_positions(self, current_prices: Dict[str, float]):
"""
Update all open positions with current prices
"""
try:
for symbol, price in current_prices.items():
if symbol in self.risk_manager.positions:
position = self.risk_manager.positions[symbol]
pnl = self.risk_manager.update_position(
symbol,
position['entry_price'],
price,
position['position_size'],
position['position_type']
)
if pnl is not None:
# Record trade
self.trade_history.append({
'symbol': symbol,
'action': 'close',
'entry_price': position['entry_price'],
'exit_price': price,
'position_size': position['position_size'],
'pnl': pnl,
'entry_time': position['entry_time'],
'exit_time': datetime.now()
})
# Update performance metrics
self._update_performance_metrics()
except Exception as e:
logger.error(f"Error updating positions: {str(e)}")
def _calculate_stop_loss(self, price: float, signal: float) -> float:
"""
Calculate stop loss level based on signal strength
"""
# Adjust stop loss distance based on signal strength
base_stop_loss = self.config['stop_loss_pct']
signal_factor = abs(signal)
stop_loss_distance = base_stop_loss * (1 + signal_factor)
if signal > 0: # Buy signal
return price * (1 - stop_loss_distance)
else: # Sell signal
return price * (1 + stop_loss_distance)
def _calculate_take_profit(self, price: float, signal: float) -> float:
"""
Calculate take profit level based on signal strength
"""
# Adjust take profit distance based on signal strength
base_take_profit = self.config['take_profit_pct']
signal_factor = abs(signal)
take_profit_distance = base_take_profit * (1 + signal_factor)
if signal > 0: # Buy signal
return price * (1 + take_profit_distance)
else: # Sell signal
return price * (1 - take_profit_distance)
def _update_performance_metrics(self):
"""
Update performance metrics based on trade history
"""
if not self.trade_history:
return
# Convert trade history to DataFrame
df = pd.DataFrame(self.trade_history)
# Calculate metrics
self.performance_metrics = {
'total_trades': len(df),
'winning_trades': len(df[df['pnl'] > 0]),
'losing_trades': len(df[df['pnl'] < 0]),
'win_rate': len(df[df['pnl'] > 0]) / len(df),
'total_pnl': df['pnl'].sum(),
'avg_pnl': df['pnl'].mean(),
'max_drawdown': self.risk_manager.risk_metrics.max_drawdown if self.risk_manager.risk_metrics else 0,
'sharpe_ratio': self.risk_manager.risk_metrics.sharpe_ratio if self.risk_manager.risk_metrics else 0,
'profit_factor': self.risk_manager.risk_metrics.profit_factor if self.risk_manager.risk_metrics else 0
}
def should_stop_trading(self) -> bool:
"""
Check if trading should be stopped based on risk metrics
"""
return self.risk_manager.should_stop_trading()
def save_state(self, filepath: str):
"""
Save trading strategy state
"""
try:
state = {
'active_trades': self.active_trades,
'trade_history': self.trade_history,
'performance_metrics': self.performance_metrics,
'risk_metrics': self.risk_manager.risk_metrics.__dict__ if self.risk_manager.risk_metrics else None
}
with open(filepath, 'w') as f:
json.dump(state, f, default=str)
logger.info(f"Trading strategy state saved to {filepath}")
return True
except Exception as e:
logger.error(f"Error saving state: {str(e)}")
return False
def load_state(self, filepath: str):
"""
Load trading strategy state
"""
try:
with open(filepath, 'r') as f:
state = json.load(f)
self.active_trades = state['active_trades']
self.trade_history = state['trade_history']
self.performance_metrics = state['performance_metrics']
if state['risk_metrics']:
self.risk_manager.risk_metrics = RiskMetrics(**state['risk_metrics'])
logger.info(f"Trading strategy state loaded from {filepath}")
return True
except Exception as e:
logger.error(f"Error loading state: {str(e)}")
return False
+318
View File
@@ -0,0 +1,318 @@
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import logging
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import json
import os
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/trading_system.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
@dataclass
class Trade:
symbol: str
direction: str
entry_price: float
stop_loss: float
take_profit: float
position_size: float
entry_time: datetime
exit_price: Optional[float] = None
exit_time: Optional[datetime] = None
pnl: Optional[float] = None
status: str = 'open'
class TradingSystem:
def __init__(self, initial_capital: float = 50.0):
self.initial_capital = initial_capital
self.current_capital = initial_capital
self.performance_history: List[Dict] = []
self.active_trades: Dict[str, Trade] = {}
self.trade_history: List[Trade] = []
self.strategy_parameters = self._get_initial_parameters()
self.risk_metrics = self._initialize_risk_metrics()
self.last_recalibration = datetime.now()
# Load configuration
self._load_config()
def _load_config(self):
"""Load configuration from config file"""
try:
with open('config/config.json', 'r') as f:
self.config = json.load(f)
except FileNotFoundError:
logger.warning("Config file not found. Using default parameters.")
self.config = self._get_default_config()
def _get_default_config(self) -> Dict:
"""Get default configuration parameters"""
return {
'risk_per_trade': 0.01, # 1% risk per trade
'max_positions': 2,
'min_win_rate': 0.4,
'recalibration_window': 20,
'max_drawdown': 0.1, # 10% maximum drawdown
'leverage': 1, # No leverage initially
'position_sizing': {
'method': 'fixed_fractional',
'fraction': 0.01 # 1% of capital per trade
}
}
def _get_initial_parameters(self) -> Dict:
"""Get initial strategy parameters"""
return {
'rsi_period': 14,
'rsi_overbought': 70,
'rsi_oversold': 30,
'bb_period': 20,
'bb_std': 2,
'macd_fast': 12,
'macd_slow': 26,
'macd_signal': 9,
'atr_period': 14,
'atr_multiplier': 2
}
def _initialize_risk_metrics(self) -> Dict:
"""Initialize risk metrics tracking"""
return {
'total_trades': 0,
'winning_trades': 0,
'losing_trades': 0,
'win_rate': 0.0,
'profit_factor': 0.0,
'max_drawdown': 0.0,
'current_drawdown': 0.0,
'avg_trade': 0.0,
'largest_win': 0.0,
'largest_loss': 0.0
}
def monitor_performance(self, window_size: int = 20) -> bool:
"""
Monitor recent performance and determine if recalibration is needed
Returns True if recalibration is needed
"""
if len(self.performance_history) < window_size:
return False
recent_performance = self.performance_history[-window_size:]
win_rate = sum(1 for trade in recent_performance if trade['pnl'] > 0) / window_size
# Check various performance metrics
needs_recalibration = False
# Win rate check
if win_rate < self.config['min_win_rate']:
logger.warning(f"Win rate {win_rate:.2%} below threshold {self.config['min_win_rate']:.2%}")
needs_recalibration = True
# Drawdown check
current_drawdown = self._calculate_drawdown()
if current_drawdown > self.config['max_drawdown']:
logger.warning(f"Current drawdown {current_drawdown:.2%} exceeds maximum {self.config['max_drawdown']:.2%}")
needs_recalibration = True
# Profit factor check
profit_factor = self._calculate_profit_factor(recent_performance)
if profit_factor < 1.0:
logger.warning(f"Profit factor {profit_factor:.2f} below 1.0")
needs_recalibration = True
return needs_recalibration
def recalibrate_strategy(self, market_data: pd.DataFrame):
"""
Adjust strategy parameters based on recent market conditions
"""
logger.info("Starting strategy recalibration")
# Analyze market conditions
volatility = self._calculate_volatility(market_data)
trend_strength = self._calculate_trend_strength(market_data)
# Adjust parameters based on market conditions
new_parameters = self.strategy_parameters.copy()
# Adjust RSI levels based on volatility
if volatility > 0.02: # High volatility
new_parameters['rsi_overbought'] = 75
new_parameters['rsi_oversold'] = 25
else: # Low volatility
new_parameters['rsi_overbought'] = 70
new_parameters['rsi_oversold'] = 30
# Adjust ATR multiplier based on trend strength
if trend_strength > 0.7: # Strong trend
new_parameters['atr_multiplier'] = 2.5
else: # Weak trend
new_parameters['atr_multiplier'] = 2.0
# Update parameters
self.strategy_parameters = new_parameters
self.last_recalibration = datetime.now()
logger.info("Strategy recalibration completed")
logger.info(f"New parameters: {new_parameters}")
def calculate_position_size(self, entry_price: float, stop_loss: float) -> float:
"""
Calculate position size based on risk management rules
"""
risk_amount = self.current_capital * self.config['risk_per_trade']
risk_per_unit = abs(entry_price - stop_loss)
if risk_per_unit == 0:
logger.warning("Risk per unit is zero. Cannot calculate position size.")
return 0
position_size = risk_amount / risk_per_unit
# Apply leverage if configured
if self.config['leverage'] > 1:
position_size *= self.config['leverage']
# Ensure position size doesn't exceed maximum allowed
max_position = self.current_capital * self.config['position_sizing']['fraction']
position_size = min(position_size, max_position)
return position_size
def _calculate_volatility(self, data: pd.DataFrame) -> float:
"""Calculate market volatility"""
returns = data['close'].pct_change()
return returns.std()
def _calculate_trend_strength(self, data: pd.DataFrame) -> float:
"""Calculate trend strength using ADX"""
# Implementation would go here
return 0.5 # Placeholder
def _calculate_drawdown(self) -> float:
"""Calculate current drawdown"""
if not self.performance_history:
return 0.0
peak = max(self.performance_history, key=lambda x: x['equity'])['equity']
current = self.performance_history[-1]['equity']
return (peak - current) / peak
def _calculate_profit_factor(self, trades: List[Dict]) -> float:
"""Calculate profit factor from recent trades"""
gross_profit = sum(t['pnl'] for t in trades if t['pnl'] > 0)
gross_loss = abs(sum(t['pnl'] for t in trades if t['pnl'] < 0))
if gross_loss == 0:
return float('inf')
return gross_profit / gross_loss
def update_risk_metrics(self, trade: Trade):
"""Update risk metrics after a trade"""
self.risk_metrics['total_trades'] += 1
if trade.pnl and trade.pnl > 0:
self.risk_metrics['winning_trades'] += 1
self.risk_metrics['largest_win'] = max(
self.risk_metrics['largest_win'],
trade.pnl
)
elif trade.pnl and trade.pnl < 0:
self.risk_metrics['losing_trades'] += 1
self.risk_metrics['largest_loss'] = min(
self.risk_metrics['largest_loss'],
trade.pnl
)
# Update win rate
if self.risk_metrics['total_trades'] > 0:
self.risk_metrics['win_rate'] = (
self.risk_metrics['winning_trades'] /
self.risk_metrics['total_trades']
)
# Update average trade
if trade.pnl:
self.risk_metrics['avg_trade'] = (
(self.risk_metrics['avg_trade'] * (self.risk_metrics['total_trades'] - 1) +
trade.pnl) / self.risk_metrics['total_trades']
)
def save_state(self):
"""Save current system state"""
state = {
'current_capital': self.current_capital,
'strategy_parameters': self.strategy_parameters,
'risk_metrics': self.risk_metrics,
'last_recalibration': self.last_recalibration.isoformat(),
'active_trades': {
symbol: {
'direction': trade.direction,
'entry_price': trade.entry_price,
'stop_loss': trade.stop_loss,
'take_profit': trade.take_profit,
'position_size': trade.position_size,
'entry_time': trade.entry_time.isoformat()
}
for symbol, trade in self.active_trades.items()
}
}
try:
with open('data/system_state.json', 'w') as f:
json.dump(state, f, indent=4)
except Exception as e:
logger.error(f"Error saving system state: {str(e)}")
def load_state(self):
"""Load system state from file"""
try:
with open('data/system_state.json', 'r') as f:
state = json.load(f)
self.current_capital = state['current_capital']
self.strategy_parameters = state['strategy_parameters']
self.risk_metrics = state['risk_metrics']
self.last_recalibration = datetime.fromisoformat(state['last_recalibration'])
# Reconstruct active trades
self.active_trades = {}
for symbol, trade_data in state['active_trades'].items():
self.active_trades[symbol] = Trade(
symbol=symbol,
direction=trade_data['direction'],
entry_price=trade_data['entry_price'],
stop_loss=trade_data['stop_loss'],
take_profit=trade_data['take_profit'],
position_size=trade_data['position_size'],
entry_time=datetime.fromisoformat(trade_data['entry_time'])
)
except FileNotFoundError:
logger.info("No saved state found. Starting fresh.")
except Exception as e:
logger.error(f"Error loading system state: {str(e)}")
def get_system_status(self) -> Dict:
"""Get current system status"""
return {
'current_capital': self.current_capital,
'total_trades': self.risk_metrics['total_trades'],
'win_rate': self.risk_metrics['win_rate'],
'profit_factor': self.risk_metrics['profit_factor'],
'current_drawdown': self.risk_metrics['current_drawdown'],
'active_trades': len(self.active_trades),
'last_recalibration': self.last_recalibration.isoformat(),
'strategy_parameters': self.strategy_parameters
}
+169
View File
@@ -0,0 +1,169 @@
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from typing import Dict, List
import json
import os
def plot_backtest_results(results_file: str, save_path: str = None):
"""
Create interactive plots for backtest results
"""
# Load results
with open(results_file, 'r') as f:
results = json.load(f)
# Convert data to DataFrames
trades_df = pd.DataFrame(results['trades'])
equity_df = pd.DataFrame(results['equity_curve'])
# Create figure with secondary y-axis
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
subplot_titles=('Price and Trades', 'Equity Curve'),
row_heights=[0.7, 0.3]
)
# Add price line
fig.add_trace(
go.Scatter(
x=trades_df['timestamp'],
y=trades_df['price'],
name='Price',
line=dict(color='blue')
),
row=1, col=1
)
# Add buy trades
buy_trades = trades_df[trades_df['action'] == 'buy']
fig.add_trace(
go.Scatter(
x=buy_trades['timestamp'],
y=buy_trades['price'],
mode='markers',
name='Buy',
marker=dict(color='green', size=10)
),
row=1, col=1
)
# Add sell trades
sell_trades = trades_df[trades_df['action'] == 'sell']
fig.add_trace(
go.Scatter(
x=sell_trades['timestamp'],
y=sell_trades['price'],
mode='markers',
name='Sell',
marker=dict(color='red', size=10)
),
row=1, col=1
)
# Add equity curve
fig.add_trace(
go.Scatter(
x=equity_df['timestamp'],
y=equity_df['equity'],
name='Equity',
line=dict(color='purple')
),
row=2, col=1
)
# Update layout
fig.update_layout(
title='Backtest Results',
xaxis_title='Date',
yaxis_title='Price',
yaxis2_title='Equity',
showlegend=True,
height=800
)
# Save plot if path is provided
if save_path:
fig.write_html(save_path)
print(f"Plot saved to {save_path}")
return fig
def plot_performance_metrics(results_files: List[str], save_path: str = None):
"""
Create comparison plot of performance metrics across different scenarios
"""
metrics_data = []
for file in results_files:
with open(file, 'r') as f:
results = json.load(f)
metrics = results['performance_metrics']
# Extract scenario name from filename
scenario_name = os.path.basename(file).replace('backtest_results_', '').replace('.json', '')
metrics_data.append({
'Scenario': scenario_name,
'Total Return': metrics['total_return'],
'Annual Return': metrics['annual_return'],
'Sharpe Ratio': metrics['sharpe_ratio'],
'Max Drawdown': metrics['max_drawdown'],
'Win Rate': metrics['win_rate']
})
# Create DataFrame
df = pd.DataFrame(metrics_data)
# Create figure
fig = go.Figure()
# Add bars for each metric
metrics = ['Total Return', 'Annual Return', 'Sharpe Ratio', 'Max Drawdown', 'Win Rate']
for metric in metrics:
fig.add_trace(
go.Bar(
name=metric,
x=df['Scenario'],
y=df[metric]
)
)
# Update layout
fig.update_layout(
title='Performance Metrics Comparison',
xaxis_title='Scenario',
yaxis_title='Value',
barmode='group',
height=600
)
# Save plot if path is provided
if save_path:
fig.write_html(save_path)
print(f"Plot saved to {save_path}")
return fig
def main():
# Example usage
results_dir = 'backtest_results'
results_files = [
os.path.join(results_dir, f) for f in os.listdir(results_dir)
if f.startswith('backtest_results_') and f.endswith('.json')
]
# Create plots for each scenario
for file in results_files:
scenario_name = os.path.basename(file).replace('.json', '')
plot_path = os.path.join(results_dir, f'{scenario_name}_plot.html')
plot_backtest_results(file, plot_path)
# Create comparison plot
comparison_path = os.path.join(results_dir, 'performance_comparison.html')
plot_performance_metrics(results_files, comparison_path)
if __name__ == "__main__":
main()