mirror of
https://github.com/B-Wear/QuantumEdge.git
synced 2026-07-27 15:37:46 +00:00
2dab81b6a4
Full long code withe everything in it Signed-off-by: B-Wear <Bwear008@gmail.com>
5056 lines
172 KiB
Plaintext
5056 lines
172 KiB
Plaintext
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
|
|
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()
|
|
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()
|
|
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()
|
|
# Core dependencies
|
|
numpy==1.24.3
|
|
pandas==2.0.3
|
|
scipy==1.10.1
|
|
scikit-learn==1.3.0
|
|
|
|
# Deep Learning
|
|
torch==2.0.1
|
|
tensorflow==2.13.0
|
|
keras==2.13.1
|
|
transformers==4.31.0
|
|
|
|
# Reinforcement Learning
|
|
gym==0.26.2
|
|
stable-baselines3==2.0.0
|
|
|
|
# Trading & Financial
|
|
ccxt==4.0.0
|
|
ta==0.10.2
|
|
yfinance==0.2.28
|
|
pandas-ta==0.3.14b0
|
|
|
|
# Data Processing & Analysis
|
|
statsmodels==0.14.0
|
|
pyfolio==0.9.2
|
|
empyrical==0.5.5
|
|
|
|
# Sentiment Analysis
|
|
textblob==0.17.1
|
|
vaderSentiment==3.3.2
|
|
newsapi-python==0.2.7
|
|
tweepy==4.14.0
|
|
praw==7.7.1
|
|
|
|
# Web Dashboard
|
|
dash==2.11.1
|
|
dash-bootstrap-components==1.4.1
|
|
plotly==5.15.0
|
|
|
|
# System Monitoring
|
|
psutil==5.9.5
|
|
py-cpuinfo==9.0.0
|
|
|
|
# Code Analysis
|
|
pylint==2.17.5
|
|
astroid==2.15.6
|
|
radon==6.0.1
|
|
|
|
# Utilities
|
|
python-dotenv==1.0.0
|
|
requests==2.31.0
|
|
websockets==11.0.3
|
|
aiohttp==3.8.5
|
|
schedule==1.2.0
|
|
|
|
# Testing
|
|
pytest==7.4.0
|
|
pytest-asyncio==0.21.1
|
|
pytest-cov==4.1.0
|
|
|
|
# Documentation
|
|
sphinx==7.1.2
|
|
sphinx-rtd-theme==1.2.2
|
|
|
|
# Development Tools
|
|
black==23.7.0
|
|
isort==5.12.0
|
|
mypy==1.5.1
|
|
pre-commit==3.3.3
|
|
{
|
|
"system": {
|
|
"name": "Quantum Edge System",
|
|
"version": "1.0.0",
|
|
"log_level": "INFO",
|
|
"dashboard_port": 8050
|
|
},
|
|
"trading": {
|
|
"exchange": {
|
|
"name": "binance",
|
|
"testnet": true,
|
|
"api_key": "YOUR_API_KEY",
|
|
"api_secret": "YOUR_API_SECRET"
|
|
},
|
|
"markets": ["BTC/USD", "ETH/USD", "SOL/USD", "ADA/USD"],
|
|
"timeframes": ["1m", "5m", "15m", "1h", "4h", "1d"],
|
|
"default_leverage": 1.0,
|
|
"position_types": ["long", "short"],
|
|
"order_types": ["market", "limit", "stop_loss", "take_profit"]
|
|
},
|
|
"ml_model": {
|
|
"default_type": "lstm",
|
|
"lstm": {
|
|
"layers": [64, 32],
|
|
"dropout": 0.2,
|
|
"learning_rate": 0.001,
|
|
"batch_size": 32,
|
|
"epochs": 100,
|
|
"validation_split": 0.2,
|
|
"sequence_length": 60
|
|
},
|
|
"reinforcement": {
|
|
"model_type": "ppo",
|
|
"policy_network": [64, 64],
|
|
"value_network": [64, 32],
|
|
"learning_rate": 0.0003,
|
|
"gamma": 0.99,
|
|
"gae_lambda": 0.95,
|
|
"clip_ratio": 0.2,
|
|
"train_iterations": 80
|
|
},
|
|
"features": {
|
|
"price": ["open", "high", "low", "close"],
|
|
"volume": ["volume", "taker_buy_volume"],
|
|
"technical": [
|
|
"rsi", "macd", "bollinger_bands",
|
|
"moving_averages", "momentum", "volatility"
|
|
]
|
|
}
|
|
},
|
|
"risk_management": {
|
|
"position_sizing": {
|
|
"max_position_size": 0.1,
|
|
"max_leverage": 3.0,
|
|
"min_position_size": 0.01
|
|
},
|
|
"risk_limits": {
|
|
"max_drawdown": 0.2,
|
|
"var_limit": 0.05,
|
|
"min_sharpe": 1.0,
|
|
"max_correlation": 0.7
|
|
},
|
|
"stop_loss": {
|
|
"default_percentage": 0.02,
|
|
"trailing_stop": true,
|
|
"activation_percentage": 0.01
|
|
},
|
|
"take_profit": {
|
|
"default_percentage": 0.03,
|
|
"trailing_take_profit": true,
|
|
"activation_percentage": 0.015
|
|
},
|
|
"exposure_limits": {
|
|
"max_total_exposure": 1.0,
|
|
"max_single_market": 0.3,
|
|
"max_correlated_exposure": 0.5
|
|
}
|
|
},
|
|
"sentiment_analysis": {
|
|
"providers": {
|
|
"news": {
|
|
"api_key": "YOUR_NEWS_API_KEY",
|
|
"sources": ["reuters", "bloomberg", "coindesk"],
|
|
"update_interval": 300
|
|
},
|
|
"social": {
|
|
"twitter_api_key": "YOUR_TWITTER_API_KEY",
|
|
"twitter_api_secret": "YOUR_TWITTER_API_SECRET",
|
|
"reddit_client_id": "YOUR_REDDIT_CLIENT_ID",
|
|
"reddit_client_secret": "YOUR_REDDIT_CLIENT_SECRET",
|
|
"update_interval": 60
|
|
}
|
|
},
|
|
"analysis": {
|
|
"sentiment_window": 3600,
|
|
"min_confidence": 0.6,
|
|
"impact_threshold": 0.3,
|
|
"correlation_threshold": 0.5
|
|
}
|
|
},
|
|
"optimization": {
|
|
"system": {
|
|
"cpu_threshold": 80,
|
|
"memory_threshold": 80,
|
|
"network_latency_threshold": 1000,
|
|
"optimization_interval": 3600
|
|
},
|
|
"trading": {
|
|
"min_win_rate": 0.55,
|
|
"min_profit_factor": 1.5,
|
|
"max_drawdown": 0.2,
|
|
"optimization_lookback": 1000
|
|
},
|
|
"ml_model": {
|
|
"min_accuracy": 0.6,
|
|
"min_sharpe": 1.0,
|
|
"max_drawdown": 0.15,
|
|
"retraining_interval": 86400
|
|
}
|
|
},
|
|
"monitoring": {
|
|
"metrics_update_interval": 60,
|
|
"health_check_interval": 300,
|
|
"alert_thresholds": {
|
|
"cpu_usage": 90,
|
|
"memory_usage": 90,
|
|
"api_latency": 2000,
|
|
"error_rate": 0.01
|
|
},
|
|
"performance_thresholds": {
|
|
"min_daily_profit": -0.02,
|
|
"max_daily_loss": -0.05,
|
|
"min_win_rate": 0.5,
|
|
"min_profit_factor": 1.2
|
|
}
|
|
}
|
|
}
|
|
import logging
|
|
import threading
|
|
import queue
|
|
import json
|
|
import os
|
|
from typing import Dict, List, Optional, Any, Tuple
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
import time
|
|
import numpy as np
|
|
import pandas as pd
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from functools import wraps
|
|
import traceback
|
|
import signal
|
|
import sys
|
|
from .trading_system import TradingSystem, create_trading_system
|
|
from .code_guardian import CodeGuardian, create_guardian
|
|
from .system_monitor import SystemMonitor
|
|
from .dashboard import create_dashboard
|
|
from .risk_management import RiskManager, RiskMetrics
|
|
from .sentiment_analysis import SentimentAnalyzer
|
|
from .machine_learning_model import MachineLearningModel
|
|
from .trading_environment import TradingEnvironment
|
|
|
|
# Configure logging with rotation
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.handlers.RotatingFileHandler(
|
|
'logs/quantum_edge_system.log',
|
|
maxBytes=10*1024*1024, # 10MB
|
|
backupCount=5
|
|
),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def retry_on_failure(max_retries: int = 3, delay: float = 1.0):
|
|
"""Decorator for retrying failed operations"""
|
|
def decorator(func):
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
last_exception = None
|
|
for attempt in range(max_retries):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as e:
|
|
last_exception = e
|
|
logger.warning(f"Attempt {attempt + 1}/{max_retries} failed: {str(e)}")
|
|
if attempt < max_retries - 1:
|
|
time.sleep(delay * (attempt + 1)) # Exponential backoff
|
|
logger.error(f"All {max_retries} attempts failed. Last error: {str(last_exception)}")
|
|
raise last_exception
|
|
return wrapper
|
|
return decorator
|
|
|
|
@dataclass
|
|
class SystemHealth:
|
|
"""System health metrics"""
|
|
cpu_usage: float
|
|
memory_usage: float
|
|
network_latency: float
|
|
disk_usage: float
|
|
error_rate: float
|
|
last_check: datetime
|
|
|
|
@dataclass
|
|
class QuantumBotInstance:
|
|
"""Represents a Quantum Edge trading bot instance"""
|
|
bot_id: str
|
|
trading_system: TradingSystem
|
|
ml_model: MachineLearningModel
|
|
risk_manager: RiskManager
|
|
sentiment_analyzer: SentimentAnalyzer
|
|
trading_env: TradingEnvironment
|
|
config: Dict
|
|
status: str
|
|
last_update: datetime
|
|
performance_metrics: Dict
|
|
message_queue: queue.Queue
|
|
health_metrics: SystemHealth
|
|
error_count: int
|
|
recovery_attempts: int
|
|
last_error: Optional[Exception] = None
|
|
|
|
class QuantumEdgeSystem:
|
|
def __init__(self, project_root: str, config_path: str):
|
|
"""Initialize the Quantum Edge System"""
|
|
self.project_root = project_root
|
|
self.load_config(config_path)
|
|
self.bots: Dict[str, QuantumBotInstance] = {}
|
|
self.code_guardian = create_guardian(project_root)
|
|
self.system_monitor = SystemMonitor(project_root)
|
|
self.message_queue = queue.Queue()
|
|
self.running = True
|
|
self.thread_pool = ThreadPoolExecutor(max_workers=10)
|
|
self.error_threshold = self.config['monitoring']['alert_thresholds']['error_rate']
|
|
self.recovery_threshold = 3
|
|
|
|
# Initialize system components
|
|
self._init_system_components()
|
|
self._start_management_threads()
|
|
self._init_dashboard()
|
|
self._setup_signal_handlers()
|
|
|
|
def _setup_signal_handlers(self):
|
|
"""Setup signal handlers for graceful shutdown"""
|
|
signal.signal(signal.SIGINT, self._handle_shutdown)
|
|
signal.signal(signal.SIGTERM, self._handle_shutdown)
|
|
|
|
def _handle_shutdown(self, signum, frame):
|
|
"""Handle system shutdown signals"""
|
|
logger.info(f"Received shutdown signal {signum}")
|
|
self.stop()
|
|
|
|
@retry_on_failure(max_retries=3, delay=2.0)
|
|
def load_config(self, config_path: str):
|
|
"""Load system configuration with retry"""
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
self.config = json.load(f)
|
|
self.validate_config()
|
|
except Exception as e:
|
|
logger.error(f"Error loading config: {str(e)}")
|
|
raise
|
|
|
|
def validate_config(self):
|
|
"""Validate system configuration with detailed checks"""
|
|
required_sections = ['trading', 'ml_model', 'risk_management', 'sentiment_analysis']
|
|
for section in required_sections:
|
|
if section not in self.config:
|
|
raise ValueError(f"Missing required config section: {section}")
|
|
|
|
# Validate section-specific requirements
|
|
if section == 'trading':
|
|
self._validate_trading_config()
|
|
elif section == 'ml_model':
|
|
self._validate_ml_config()
|
|
elif section == 'risk_management':
|
|
self._validate_risk_config()
|
|
elif section == 'sentiment_analysis':
|
|
self._validate_sentiment_config()
|
|
|
|
def _validate_trading_config(self):
|
|
"""Validate trading configuration"""
|
|
trading_config = self.config['trading']
|
|
required_fields = ['exchange', 'markets', 'timeframes']
|
|
for field in required_fields:
|
|
if field not in trading_config:
|
|
raise ValueError(f"Missing required trading config field: {field}")
|
|
|
|
if not trading_config['markets']:
|
|
raise ValueError("No trading markets specified")
|
|
|
|
if not trading_config['timeframes']:
|
|
raise ValueError("No timeframes specified")
|
|
|
|
def _validate_ml_config(self):
|
|
"""Validate ML configuration"""
|
|
ml_config = self.config['ml_model']
|
|
if 'default_type' not in ml_config:
|
|
raise ValueError("ML model default type not specified")
|
|
|
|
if ml_config['default_type'] not in ['lstm', 'reinforcement']:
|
|
raise ValueError(f"Invalid ML model type: {ml_config['default_type']}")
|
|
|
|
def _validate_risk_config(self):
|
|
"""Validate risk management configuration"""
|
|
risk_config = self.config['risk_management']
|
|
required_sections = ['position_sizing', 'risk_limits', 'stop_loss']
|
|
for section in required_sections:
|
|
if section not in risk_config:
|
|
raise ValueError(f"Missing required risk config section: {section}")
|
|
|
|
def _validate_sentiment_config(self):
|
|
"""Validate sentiment analysis configuration"""
|
|
sentiment_config = self.config['sentiment_analysis']
|
|
if 'providers' not in sentiment_config:
|
|
raise ValueError("Sentiment analysis providers not specified")
|
|
|
|
def _init_system_components(self):
|
|
"""Initialize system-wide components with error handling"""
|
|
try:
|
|
self.global_risk_manager = RiskManager(self.config['risk_management'])
|
|
self.global_sentiment_analyzer = SentimentAnalyzer(self.config['sentiment_analysis'])
|
|
self.performance_tracker = self._init_performance_tracker()
|
|
self.health_metrics = SystemHealth(
|
|
cpu_usage=0.0,
|
|
memory_usage=0.0,
|
|
network_latency=0.0,
|
|
disk_usage=0.0,
|
|
error_rate=0.0,
|
|
last_check=datetime.now()
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error initializing system components: {str(e)}")
|
|
raise
|
|
|
|
def _init_performance_tracker(self):
|
|
"""Initialize system-wide performance tracking with enhanced metrics"""
|
|
return {
|
|
'system_metrics': {},
|
|
'bot_metrics': {},
|
|
'risk_metrics': {},
|
|
'sentiment_metrics': {},
|
|
'ml_metrics': {},
|
|
'error_metrics': {
|
|
'total_errors': 0,
|
|
'error_types': {},
|
|
'recovery_success_rate': 1.0
|
|
},
|
|
'performance_metrics': {
|
|
'average_latency': 0.0,
|
|
'throughput': 0.0,
|
|
'resource_utilization': {}
|
|
}
|
|
}
|
|
|
|
def _start_management_threads(self):
|
|
"""Start system management threads with enhanced monitoring"""
|
|
self.threads = {
|
|
'monitor': threading.Thread(target=self._monitor_system),
|
|
'message_handler': threading.Thread(target=self._handle_messages),
|
|
'code_repair': threading.Thread(target=self._continuous_code_repair),
|
|
'performance_tracker': threading.Thread(target=self._track_performance),
|
|
'risk_monitor': threading.Thread(target=self._monitor_risk),
|
|
'sentiment_monitor': threading.Thread(target=self._monitor_sentiment),
|
|
'health_checker': threading.Thread(target=self._check_system_health)
|
|
}
|
|
|
|
for thread_name, thread in self.threads.items():
|
|
thread.daemon = True
|
|
thread.name = f"quantum_edge_{thread_name}"
|
|
thread.start()
|
|
logger.info(f"Started thread: {thread_name}")
|
|
|
|
def _check_system_health(self):
|
|
"""Continuously check system health"""
|
|
while self.running:
|
|
try:
|
|
health_metrics = {
|
|
'cpu_usage': self.system_monitor.get_cpu_usage(),
|
|
'memory_usage': self.system_monitor.get_memory_usage(),
|
|
'network_latency': self.system_monitor.get_network_latency(),
|
|
'disk_usage': self.system_monitor.get_disk_usage(),
|
|
'error_rate': self._calculate_error_rate()
|
|
}
|
|
|
|
self.health_metrics = SystemHealth(
|
|
**health_metrics,
|
|
last_check=datetime.now()
|
|
)
|
|
|
|
if self._is_system_unhealthy():
|
|
self._handle_system_health_issue()
|
|
|
|
time.sleep(60)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking system health: {str(e)}")
|
|
time.sleep(300)
|
|
|
|
def _calculate_error_rate(self) -> float:
|
|
"""Calculate system-wide error rate"""
|
|
total_errors = sum(bot.error_count for bot in self.bots.values())
|
|
total_operations = sum(bot.performance_metrics.get('total_operations', 0)
|
|
for bot in self.bots.values())
|
|
return total_errors / total_operations if total_operations > 0 else 0.0
|
|
|
|
def _is_system_unhealthy(self) -> bool:
|
|
"""Determine if system is unhealthy"""
|
|
thresholds = self.config['monitoring']['alert_thresholds']
|
|
return (
|
|
self.health_metrics.cpu_usage > thresholds['cpu_usage'] or
|
|
self.health_metrics.memory_usage > thresholds['memory_usage'] or
|
|
self.health_metrics.network_latency > thresholds['api_latency'] or
|
|
self.health_metrics.error_rate > thresholds['error_rate']
|
|
)
|
|
|
|
def _handle_system_health_issue(self):
|
|
"""Handle system health issues"""
|
|
logger.warning("System health issues detected")
|
|
|
|
# Reduce system load
|
|
self._optimize_system_performance()
|
|
|
|
# Notify all bots
|
|
self.broadcast_message({
|
|
'type': 'system_alert',
|
|
'action': 'reduce_load',
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
# Log health metrics
|
|
logger.warning(f"Current health metrics: {self.health_metrics}")
|
|
|
|
@retry_on_failure(max_retries=3, delay=2.0)
|
|
def create_quantum_bot(self, config: Dict) -> str:
|
|
"""Create a new Quantum Edge bot instance with enhanced error handling"""
|
|
try:
|
|
bot_id = f"quantum_bot_{len(self.bots) + 1}"
|
|
|
|
# Create bot-specific config
|
|
bot_config = self._create_bot_config(config)
|
|
config_path = os.path.join(self.project_root, 'config', f'{bot_id}_config.json')
|
|
|
|
# Save config with retry
|
|
self._save_bot_config(config_path, bot_config)
|
|
|
|
# Initialize bot components with parallel execution
|
|
components = self._initialize_bot_components(bot_config)
|
|
|
|
# Create bot instance with health monitoring
|
|
bot = QuantumBotInstance(
|
|
bot_id=bot_id,
|
|
trading_system=components['trading_system'],
|
|
ml_model=components['ml_model'],
|
|
risk_manager=components['risk_manager'],
|
|
sentiment_analyzer=components['sentiment_analyzer'],
|
|
trading_env=components['trading_env'],
|
|
config=bot_config,
|
|
status='initialized',
|
|
last_update=datetime.now(),
|
|
performance_metrics={},
|
|
message_queue=queue.Queue(),
|
|
health_metrics=SystemHealth(
|
|
cpu_usage=0.0,
|
|
memory_usage=0.0,
|
|
network_latency=0.0,
|
|
disk_usage=0.0,
|
|
error_rate=0.0,
|
|
last_check=datetime.now()
|
|
),
|
|
error_count=0,
|
|
recovery_attempts=0
|
|
)
|
|
|
|
self.bots[bot_id] = bot
|
|
logger.info(f"Created new Quantum Edge bot instance: {bot_id}")
|
|
|
|
return bot_id
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating Quantum Edge bot: {str(e)}")
|
|
raise
|
|
|
|
def _save_bot_config(self, config_path: str, config: Dict):
|
|
"""Save bot configuration with retry"""
|
|
try:
|
|
with open(config_path, 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
except Exception as e:
|
|
logger.error(f"Error saving bot config: {str(e)}")
|
|
raise
|
|
|
|
def _initialize_bot_components(self, config: Dict) -> Dict:
|
|
"""Initialize bot components in parallel"""
|
|
components = {}
|
|
futures = {
|
|
'trading_system': self.thread_pool.submit(
|
|
create_trading_system, config['trading']
|
|
),
|
|
'ml_model': self.thread_pool.submit(
|
|
MachineLearningModel, config['ml_model']
|
|
),
|
|
'risk_manager': self.thread_pool.submit(
|
|
RiskManager, config['risk_management']
|
|
),
|
|
'sentiment_analyzer': self.thread_pool.submit(
|
|
SentimentAnalyzer, config['sentiment_analysis']
|
|
),
|
|
'trading_env': self.thread_pool.submit(
|
|
TradingEnvironment, config['trading']
|
|
)
|
|
}
|
|
|
|
for name, future in futures.items():
|
|
try:
|
|
components[name] = future.result()
|
|
except Exception as e:
|
|
logger.error(f"Error initializing {name}: {str(e)}")
|
|
raise
|
|
|
|
return components
|
|
|
|
def start_bot(self, bot_id: str):
|
|
"""Start a Quantum Edge bot with enhanced error handling"""
|
|
if bot_id in self.bots:
|
|
try:
|
|
bot = self.bots[bot_id]
|
|
|
|
# Initialize components with retry
|
|
self._initialize_bot_components_with_retry(bot)
|
|
|
|
# Start bot with health monitoring
|
|
bot.status = 'running'
|
|
bot.last_update = datetime.now()
|
|
|
|
logger.info(f"Started Quantum Edge bot: {bot_id}")
|
|
|
|
# Send start message to bot
|
|
self._send_message_to_bot(bot_id, {
|
|
'type': 'command',
|
|
'action': 'start',
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error starting Quantum Edge bot {bot_id}: {str(e)}")
|
|
self._handle_bot_error(bot_id, e)
|
|
|
|
def _initialize_bot_components_with_retry(self, bot: QuantumBotInstance):
|
|
"""Initialize bot components with retry mechanism"""
|
|
try:
|
|
bot.ml_model.initialize()
|
|
bot.trading_env.reset()
|
|
bot.trading_system.start()
|
|
except Exception as e:
|
|
logger.error(f"Error initializing bot components: {str(e)}")
|
|
bot.error_count += 1
|
|
bot.last_error = e
|
|
raise
|
|
|
|
def _handle_bot_error(self, bot_id: str, error: Exception):
|
|
"""Handle bot errors with recovery mechanism"""
|
|
bot = self.bots[bot_id]
|
|
bot.error_count += 1
|
|
bot.last_error = error
|
|
|
|
if bot.error_count >= self.recovery_threshold:
|
|
logger.warning(f"Bot {bot_id} exceeded error threshold, attempting recovery")
|
|
self._attempt_bot_recovery(bot_id)
|
|
else:
|
|
bot.status = 'error'
|
|
logger.error(f"Bot {bot_id} encountered error: {str(error)}")
|
|
|
|
def _attempt_bot_recovery(self, bot_id: str):
|
|
"""Attempt to recover a failed bot"""
|
|
bot = self.bots[bot_id]
|
|
try:
|
|
# Reset bot state
|
|
bot.error_count = 0
|
|
bot.recovery_attempts += 1
|
|
|
|
# Reinitialize components
|
|
self._initialize_bot_components_with_retry(bot)
|
|
|
|
# Restart bot
|
|
bot.status = 'running'
|
|
logger.info(f"Successfully recovered bot {bot_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to recover bot {bot_id}: {str(e)}")
|
|
bot.status = 'failed'
|
|
self._handle_critical_bot_failure(bot_id)
|
|
|
|
def _handle_critical_bot_failure(self, bot_id: str):
|
|
"""Handle critical bot failures"""
|
|
logger.error(f"Critical failure for bot {bot_id}")
|
|
|
|
# Notify system
|
|
self.message_queue.put({
|
|
'type': 'critical_error',
|
|
'bot_id': bot_id,
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
# Update system metrics
|
|
self.performance_tracker['error_metrics']['total_errors'] += 1
|
|
error_type = type(self.bots[bot_id].last_error).__name__
|
|
self.performance_tracker['error_metrics']['error_types'][error_type] = \
|
|
self.performance_tracker['error_metrics']['error_types'].get(error_type, 0) + 1
|
|
|
|
def stop(self):
|
|
"""Stop the Quantum Edge System with graceful shutdown"""
|
|
logger.info("Initiating system shutdown")
|
|
self.running = False
|
|
|
|
# Stop all bots gracefully
|
|
for bot_id in self.bots:
|
|
self.stop_bot(bot_id)
|
|
|
|
# Stop components
|
|
self.code_guardian.stop()
|
|
self.system_monitor.stop()
|
|
|
|
# Shutdown thread pool
|
|
self.thread_pool.shutdown(wait=True)
|
|
|
|
# Wait for threads
|
|
for thread_name, thread in self.threads.items():
|
|
if thread.is_alive():
|
|
logger.info(f"Waiting for thread {thread_name} to finish")
|
|
thread.join(timeout=30)
|
|
if thread.is_alive():
|
|
logger.warning(f"Thread {thread_name} did not terminate gracefully")
|
|
|
|
logger.info("Quantum Edge System stopped")
|
|
|
|
def create_quantum_edge_system(project_root: str, config_path: str) -> QuantumEdgeSystem:
|
|
"""Create and initialize a Quantum Edge System"""
|
|
return QuantumEdgeSystem(project_root, config_path)
|
|
|
|
if __name__ == "__main__":
|
|
# Example usage with enhanced error handling
|
|
config_path = "config/quantum_edge_config.json"
|
|
system = None
|
|
|
|
try:
|
|
system = create_quantum_edge_system(".", config_path)
|
|
|
|
# Create test bots with different strategies
|
|
configs = [
|
|
{
|
|
"trading": {
|
|
"initial_capital": 100000,
|
|
"strategy": "trend_following",
|
|
"markets": ["BTC/USD", "ETH/USD"]
|
|
},
|
|
"ml_model": {
|
|
"type": "lstm",
|
|
"features": ["price", "volume", "sentiment"]
|
|
}
|
|
},
|
|
{
|
|
"trading": {
|
|
"initial_capital": 50000,
|
|
"strategy": "mean_reversion",
|
|
"markets": ["SOL/USD", "ADA/USD"]
|
|
},
|
|
"ml_model": {
|
|
"type": "reinforcement",
|
|
"features": ["technical_indicators", "order_flow"]
|
|
}
|
|
}
|
|
]
|
|
|
|
# Create and start bots
|
|
bot_ids = []
|
|
for config in configs:
|
|
bot_id = system.create_quantum_bot(config)
|
|
bot_ids.append(bot_id)
|
|
system.start_bot(bot_id)
|
|
|
|
# Monitor system
|
|
while True:
|
|
status = system.get_system_status()
|
|
print(f"System Status: {json.dumps(status, indent=2)}")
|
|
time.sleep(300) # Update every 5 minutes
|
|
|
|
except KeyboardInterrupt:
|
|
print("Shutting down Quantum Edge System...")
|
|
if system:
|
|
system.stop()
|
|
except Exception as e:
|
|
print(f"Critical error: {str(e)}")
|
|
if system:
|
|
system.stop()
|
|
raise
|
|
import logging
|
|
import threading
|
|
import queue
|
|
import json
|
|
import os
|
|
from typing import Dict, List, Optional, Any
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
import time
|
|
from .trading_system import TradingSystem, create_trading_system
|
|
from .code_guardian import CodeGuardian, create_guardian
|
|
from .system_monitor import SystemMonitor
|
|
from .dashboard import create_dashboard
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('logs/ai_agent_manager.log'),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@dataclass
|
|
class BotInstance:
|
|
"""Represents a trading bot instance"""
|
|
bot_id: str
|
|
trading_system: TradingSystem
|
|
config: Dict
|
|
status: str
|
|
last_update: datetime
|
|
performance_metrics: Dict
|
|
message_queue: queue.Queue
|
|
|
|
class AIAgentManager:
|
|
def __init__(self, project_root: str):
|
|
self.project_root = project_root
|
|
self.bots: Dict[str, BotInstance] = {}
|
|
self.code_guardian = create_guardian(project_root)
|
|
self.system_monitor = SystemMonitor(project_root)
|
|
self.message_queue = queue.Queue()
|
|
self.running = True
|
|
|
|
# Start management threads
|
|
self.threads = {
|
|
'monitor': threading.Thread(target=self._monitor_bots),
|
|
'message_handler': threading.Thread(target=self._handle_messages),
|
|
'code_repair': threading.Thread(target=self._continuous_code_repair)
|
|
}
|
|
|
|
for thread in self.threads.values():
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
# Initialize dashboard
|
|
self.dashboard = create_dashboard()
|
|
self.dashboard_thread = threading.Thread(target=self._run_dashboard)
|
|
self.dashboard_thread.daemon = True
|
|
self.dashboard_thread.start()
|
|
|
|
def create_bot(self, config: Dict) -> str:
|
|
"""Create a new trading bot instance"""
|
|
try:
|
|
bot_id = f"bot_{len(self.bots) + 1}"
|
|
|
|
# Save bot-specific config
|
|
config_path = os.path.join(self.project_root, 'config', f'{bot_id}_config.json')
|
|
with open(config_path, 'w') as f:
|
|
json.dump(config, f, indent=2)
|
|
|
|
# Create trading system
|
|
trading_system = create_trading_system(config_path)
|
|
|
|
# Create bot instance
|
|
bot = BotInstance(
|
|
bot_id=bot_id,
|
|
trading_system=trading_system,
|
|
config=config,
|
|
status='initialized',
|
|
last_update=datetime.now(),
|
|
performance_metrics={},
|
|
message_queue=queue.Queue()
|
|
)
|
|
|
|
self.bots[bot_id] = bot
|
|
logger.info(f"Created new bot instance: {bot_id}")
|
|
|
|
return bot_id
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating bot: {str(e)}")
|
|
raise
|
|
|
|
def start_bot(self, bot_id: str):
|
|
"""Start a trading bot"""
|
|
if bot_id in self.bots:
|
|
try:
|
|
bot = self.bots[bot_id]
|
|
bot.status = 'running'
|
|
bot.last_update = datetime.now()
|
|
logger.info(f"Started bot: {bot_id}")
|
|
|
|
# Send start message to bot
|
|
self._send_message_to_bot(bot_id, {
|
|
'type': 'command',
|
|
'action': 'start',
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error starting bot {bot_id}: {str(e)}")
|
|
self.bots[bot_id].status = 'error'
|
|
|
|
def stop_bot(self, bot_id: str):
|
|
"""Stop a trading bot"""
|
|
if bot_id in self.bots:
|
|
try:
|
|
bot = self.bots[bot_id]
|
|
bot.status = 'stopped'
|
|
bot.last_update = datetime.now()
|
|
bot.trading_system.stop()
|
|
logger.info(f"Stopped bot: {bot_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error stopping bot {bot_id}: {str(e)}")
|
|
|
|
def send_message_to_bot(self, bot_id: str, message: Dict):
|
|
"""Send a message to a specific bot"""
|
|
if bot_id in self.bots:
|
|
self._send_message_to_bot(bot_id, message)
|
|
logger.info(f"Sent message to bot {bot_id}: {message}")
|
|
|
|
def broadcast_message(self, message: Dict):
|
|
"""Send a message to all bots"""
|
|
for bot_id in self.bots:
|
|
self._send_message_to_bot(bot_id, message)
|
|
logger.info(f"Broadcast message to all bots: {message}")
|
|
|
|
def get_bot_status(self, bot_id: str) -> Optional[Dict]:
|
|
"""Get status of a specific bot"""
|
|
if bot_id in self.bots:
|
|
bot = self.bots[bot_id]
|
|
return {
|
|
'bot_id': bot.bot_id,
|
|
'status': bot.status,
|
|
'last_update': bot.last_update.isoformat(),
|
|
'performance_metrics': bot.performance_metrics,
|
|
'trading_system_status': bot.trading_system.get_status()
|
|
}
|
|
return None
|
|
|
|
def get_system_status(self) -> Dict:
|
|
"""Get overall system status"""
|
|
return {
|
|
'total_bots': len(self.bots),
|
|
'active_bots': len([b for b in self.bots.values() if b.status == 'running']),
|
|
'code_health': self.code_guardian.get_status_report(),
|
|
'system_health': self.system_monitor.get_system_status(),
|
|
'bots': {bot_id: self.get_bot_status(bot_id) for bot_id in self.bots}
|
|
}
|
|
|
|
def _send_message_to_bot(self, bot_id: str, message: Dict):
|
|
"""Internal method to send message to bot"""
|
|
if bot_id in self.bots:
|
|
self.bots[bot_id].message_queue.put(message)
|
|
|
|
def _monitor_bots(self):
|
|
"""Continuously monitor bot performance and health"""
|
|
while self.running:
|
|
try:
|
|
for bot_id, bot in self.bots.items():
|
|
if bot.status == 'running':
|
|
# Update bot metrics
|
|
status = bot.trading_system.get_status()
|
|
bot.performance_metrics = status['metrics']
|
|
bot.last_update = datetime.now()
|
|
|
|
# Check for issues
|
|
if status['system_health']['alerts']['unacknowledged'] > 0:
|
|
self._handle_bot_issue(bot_id, status)
|
|
|
|
time.sleep(60) # Check every minute
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in bot monitoring: {str(e)}")
|
|
time.sleep(300)
|
|
|
|
def _handle_messages(self):
|
|
"""Handle incoming messages from bots"""
|
|
while self.running:
|
|
try:
|
|
for bot_id, bot in self.bots.items():
|
|
while not bot.message_queue.empty():
|
|
message = bot.message_queue.get_nowait()
|
|
self._process_bot_message(bot_id, message)
|
|
|
|
time.sleep(1)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error handling messages: {str(e)}")
|
|
time.sleep(60)
|
|
|
|
def _continuous_code_repair(self):
|
|
"""Continuously monitor and repair code issues"""
|
|
while self.running:
|
|
try:
|
|
# Get code issues
|
|
status = self.code_guardian.get_status_report()
|
|
|
|
# Attempt to fix issues
|
|
for issue in self.code_guardian.issues:
|
|
if not issue.fixed and issue.suggested_fix:
|
|
if self.code_guardian.fix_issue(issue):
|
|
logger.info(f"Fixed code issue: {issue.description}")
|
|
|
|
time.sleep(300) # Check every 5 minutes
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in code repair: {str(e)}")
|
|
time.sleep(600)
|
|
|
|
def _handle_bot_issue(self, bot_id: str, status: Dict):
|
|
"""Handle issues with a specific bot"""
|
|
try:
|
|
# Create alert
|
|
alert = {
|
|
'type': 'alert',
|
|
'bot_id': bot_id,
|
|
'timestamp': datetime.now().isoformat(),
|
|
'status': status
|
|
}
|
|
|
|
# Log alert
|
|
logger.warning(f"Bot issue detected: {alert}")
|
|
|
|
# Add to message queue
|
|
self.message_queue.put(alert)
|
|
|
|
# Take action based on issue severity
|
|
if status['system_health']['alerts']['by_level'].get('error', 0) > 0:
|
|
self.stop_bot(bot_id)
|
|
logger.error(f"Stopped bot {bot_id} due to critical issues")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error handling bot issue: {str(e)}")
|
|
|
|
def _process_bot_message(self, bot_id: str, message: Dict):
|
|
"""Process message from a bot"""
|
|
try:
|
|
if message['type'] == 'status_update':
|
|
self.bots[bot_id].status = message['status']
|
|
self.bots[bot_id].last_update = datetime.now()
|
|
|
|
elif message['type'] == 'alert':
|
|
self._handle_bot_issue(bot_id, message)
|
|
|
|
elif message['type'] == 'performance_update':
|
|
self.bots[bot_id].performance_metrics.update(message['metrics'])
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing bot message: {str(e)}")
|
|
|
|
def _run_dashboard(self):
|
|
"""Run the dashboard"""
|
|
try:
|
|
self.dashboard.run_server(debug=True, port=8050)
|
|
except Exception as e:
|
|
logger.error(f"Error running dashboard: {str(e)}")
|
|
|
|
def stop(self):
|
|
"""Stop the AI Agent Manager"""
|
|
self.running = False
|
|
|
|
# Stop all bots
|
|
for bot_id in self.bots:
|
|
self.stop_bot(bot_id)
|
|
|
|
# Stop components
|
|
self.code_guardian.stop()
|
|
self.system_monitor.stop()
|
|
|
|
# Wait for threads
|
|
for thread in self.threads.values():
|
|
if thread.is_alive():
|
|
thread.join()
|
|
|
|
def create_agent_manager(project_root: str) -> AIAgentManager:
|
|
"""Create and initialize an AI Agent Manager"""
|
|
return AIAgentManager(project_root)
|
|
|
|
if __name__ == "__main__":
|
|
# Example usage
|
|
manager = create_agent_manager(".")
|
|
|
|
try:
|
|
# Create some test bots
|
|
config1 = {
|
|
"trading": {"initial_capital": 100000},
|
|
"strategy": {"name": "trend_following"},
|
|
"risk_management": {"max_positions": 5}
|
|
}
|
|
|
|
config2 = {
|
|
"trading": {"initial_capital": 50000},
|
|
"strategy": {"name": "mean_reversion"},
|
|
"risk_management": {"max_positions": 3}
|
|
}
|
|
|
|
# Create and start bots
|
|
bot1_id = manager.create_bot(config1)
|
|
bot2_id = manager.create_bot(config2)
|
|
|
|
manager.start_bot(bot1_id)
|
|
manager.start_bot(bot2_id)
|
|
|
|
# Monitor for a while
|
|
time.sleep(3600) # Run for 1 hour
|
|
|
|
finally:
|
|
manager.stop()
|
|
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
|
|
from .system_monitor import SystemMonitor
|
|
|
|
# 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, config_path: str):
|
|
# Load configuration
|
|
with open(config_path, 'r') as f:
|
|
self.config = json.load(f)
|
|
|
|
# Initialize system monitor
|
|
self.monitor = SystemMonitor(os.path.dirname(os.path.dirname(__file__)))
|
|
|
|
# Initialize trading parameters
|
|
self.initial_capital = self.config['trading']['initial_capital']
|
|
self.current_capital = self.initial_capital
|
|
self.performance_history = []
|
|
self.active_trades: List[Trade] = []
|
|
|
|
# Initialize strategy parameters
|
|
self.strategy_params = self.config['strategy']
|
|
self.risk_params = self.config['risk_management']
|
|
|
|
# Initialize performance metrics
|
|
self.metrics = {
|
|
'win_rate': 0.0,
|
|
'profit_factor': 0.0,
|
|
'max_drawdown': 0.0,
|
|
'sharpe_ratio': 0.0,
|
|
'total_trades': 0,
|
|
'winning_trades': 0,
|
|
'losing_trades': 0
|
|
}
|
|
|
|
@SystemMonitor.monitor_component("strategy_execution")
|
|
def execute_strategy(self, market_data: pd.DataFrame) -> Optional[Dict]:
|
|
"""Execute trading strategy"""
|
|
try:
|
|
# Generate trading signals
|
|
signal = self._generate_signals(market_data)
|
|
|
|
if signal:
|
|
# Validate trade
|
|
if self._validate_trade(signal):
|
|
# Execute trade
|
|
trade = self._execute_trade(signal)
|
|
return {'status': 'success', 'trade': trade}
|
|
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error executing strategy: {str(e)}")
|
|
return {'status': 'error', 'message': str(e)}
|
|
|
|
@SystemMonitor.monitor_component("risk_management")
|
|
def _validate_trade(self, signal: Dict) -> bool:
|
|
"""Validate trade against risk parameters"""
|
|
try:
|
|
# Check if we have too many open trades
|
|
if len(self.active_trades) >= self.risk_params['max_positions']:
|
|
return False
|
|
|
|
# Check if we have enough capital
|
|
required_margin = self._calculate_margin(signal)
|
|
if required_margin > self.current_capital * self.risk_params['max_position_size']:
|
|
return False
|
|
|
|
# Check if we're within daily loss limit
|
|
if self._check_daily_loss_limit():
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error validating trade: {str(e)}")
|
|
return False
|
|
|
|
@SystemMonitor.monitor_component("trade_execution")
|
|
def _execute_trade(self, signal: Dict) -> Optional[Trade]:
|
|
"""Execute a trade"""
|
|
try:
|
|
# Calculate position size
|
|
position_size = self._calculate_position_size(signal)
|
|
|
|
# Create trade object
|
|
trade = Trade(
|
|
symbol=signal['symbol'],
|
|
direction=signal['direction'],
|
|
entry_price=signal['price'],
|
|
stop_loss=signal['stop_loss'],
|
|
take_profit=signal['take_profit'],
|
|
position_size=position_size,
|
|
entry_time=datetime.now(),
|
|
exit_price=None,
|
|
exit_time=None,
|
|
pnl=None,
|
|
status='open'
|
|
)
|
|
|
|
# Add to active trades
|
|
self.active_trades.append(trade)
|
|
|
|
return trade
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error executing trade: {str(e)}")
|
|
return None
|
|
|
|
@SystemMonitor.monitor_component("performance_monitoring")
|
|
def update_performance(self):
|
|
"""Update performance metrics"""
|
|
try:
|
|
if not self.performance_history:
|
|
return
|
|
|
|
# Calculate basic metrics
|
|
total_trades = len(self.performance_history)
|
|
winning_trades = len([t for t in self.performance_history if t['pnl'] > 0])
|
|
|
|
self.metrics.update({
|
|
'total_trades': total_trades,
|
|
'winning_trades': winning_trades,
|
|
'losing_trades': total_trades - winning_trades,
|
|
'win_rate': winning_trades / total_trades if total_trades > 0 else 0
|
|
})
|
|
|
|
# Calculate advanced metrics
|
|
self._calculate_advanced_metrics()
|
|
|
|
# Check if we need to adjust strategy
|
|
self._check_strategy_adjustment()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating performance: {str(e)}")
|
|
|
|
@SystemMonitor.monitor_component("strategy_adjustment")
|
|
def _check_strategy_adjustment(self):
|
|
"""Check if strategy needs adjustment"""
|
|
try:
|
|
# Check win rate
|
|
if self.metrics['win_rate'] < self.strategy_params['min_win_rate']:
|
|
self._adjust_strategy('defensive')
|
|
elif self.metrics['win_rate'] > self.strategy_params['target_win_rate']:
|
|
self._adjust_strategy('aggressive')
|
|
|
|
# Check drawdown
|
|
if self.metrics['max_drawdown'] > self.risk_params['max_drawdown']:
|
|
self._adjust_strategy('risk_reduction')
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking strategy adjustment: {str(e)}")
|
|
|
|
def _adjust_strategy(self, mode: str):
|
|
"""Adjust strategy parameters"""
|
|
if mode == 'defensive':
|
|
self.strategy_params['position_size'] *= 0.8
|
|
self.strategy_params['stop_loss_multiplier'] *= 0.9
|
|
|
|
elif mode == 'aggressive':
|
|
self.strategy_params['position_size'] *= 1.2
|
|
self.strategy_params['take_profit_multiplier'] *= 1.1
|
|
|
|
elif mode == 'risk_reduction':
|
|
self.strategy_params['position_size'] *= 0.7
|
|
self.strategy_params['max_positions'] = max(1, self.strategy_params['max_positions'] - 1)
|
|
|
|
def get_status(self) -> Dict:
|
|
"""Get current system status"""
|
|
return {
|
|
'capital': self.current_capital,
|
|
'active_trades': len(self.active_trades),
|
|
'metrics': self.metrics,
|
|
'system_health': self.monitor.get_system_status()
|
|
}
|
|
|
|
def stop(self):
|
|
"""Stop the trading system"""
|
|
self.monitor.stop()
|
|
|
|
def create_trading_system(config_path: str) -> TradingSystem:
|
|
"""Create and initialize a trading system"""
|
|
return TradingSystem(config_path)
|
|
|
|
if __name__ == "__main__":
|
|
# Example usage
|
|
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.json')
|
|
trading_system = create_trading_system(config_path)
|
|
|
|
try:
|
|
# Simulate some trading
|
|
market_data = pd.DataFrame({
|
|
'timestamp': pd.date_range(start='2024-01-01', periods=100, freq='H'),
|
|
'open': np.random.randn(100).cumsum() + 100,
|
|
'high': np.random.randn(100).cumsum() + 102,
|
|
'low': np.random.randn(100).cumsum() + 98,
|
|
'close': np.random.randn(100).cumsum() + 100,
|
|
'volume': np.random.randint(1000, 10000, 100)
|
|
})
|
|
|
|
for i in range(len(market_data)):
|
|
result = trading_system.execute_strategy(market_data.iloc[:i+1])
|
|
if result and result['status'] == 'success':
|
|
print(f"Executed trade: {result['trade']}")
|
|
|
|
# Get final status
|
|
status = trading_system.get_status()
|
|
print("\nFinal Status:")
|
|
print(json.dumps(status, indent=2, default=str))
|
|
|
|
finally:
|
|
trading_system.stop()
|
|
import logging
|
|
import time
|
|
import threading
|
|
from typing import Dict, Any, Optional
|
|
from datetime import datetime
|
|
import functools
|
|
import traceback
|
|
from .code_guardian import CodeGuardian, create_guardian
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class SystemMonitor:
|
|
def __init__(self, project_root: str):
|
|
self.guardian = create_guardian(project_root)
|
|
self.component_status: Dict[str, Dict[str, Any]] = {}
|
|
self.performance_data: Dict[str, Dict[str, float]] = {}
|
|
self.alerts: Dict[str, Dict[str, Any]] = {}
|
|
self.running = True
|
|
|
|
# Start monitoring thread
|
|
self.monitor_thread = threading.Thread(target=self._monitor_system)
|
|
self.monitor_thread.daemon = True
|
|
self.monitor_thread.start()
|
|
|
|
def monitor_component(self, component_name: str):
|
|
"""Decorator to monitor component performance and errors"""
|
|
def decorator(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
start_time = time.time()
|
|
try:
|
|
result = func(*args, **kwargs)
|
|
execution_time = time.time() - start_time
|
|
|
|
# Update performance metrics
|
|
self.guardian.monitor_performance(component_name, execution_time)
|
|
|
|
# Update component status
|
|
self.component_status[component_name] = {
|
|
'status': 'healthy',
|
|
'last_execution': datetime.now().isoformat(),
|
|
'execution_time': execution_time,
|
|
'error': None
|
|
}
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
error_info = {
|
|
'type': type(e).__name__,
|
|
'message': str(e),
|
|
'traceback': traceback.format_exc()
|
|
}
|
|
|
|
# Update component status
|
|
self.component_status[component_name] = {
|
|
'status': 'error',
|
|
'last_execution': datetime.now().isoformat(),
|
|
'execution_time': time.time() - start_time,
|
|
'error': error_info
|
|
}
|
|
|
|
# Create alert
|
|
self._create_alert(
|
|
component_name,
|
|
f"Error in {component_name}: {str(e)}",
|
|
'error',
|
|
error_info
|
|
)
|
|
|
|
logger.error(f"Error in {component_name}: {str(e)}")
|
|
raise
|
|
|
|
return wrapper
|
|
return decorator
|
|
|
|
def _create_alert(self,
|
|
component: str,
|
|
message: str,
|
|
level: str,
|
|
details: Optional[Dict] = None):
|
|
"""Create a new alert"""
|
|
alert_id = f"{component}_{datetime.now().timestamp()}"
|
|
self.alerts[alert_id] = {
|
|
'component': component,
|
|
'message': message,
|
|
'level': level,
|
|
'timestamp': datetime.now().isoformat(),
|
|
'details': details,
|
|
'acknowledged': False
|
|
}
|
|
|
|
def acknowledge_alert(self, alert_id: str):
|
|
"""Acknowledge an alert"""
|
|
if alert_id in self.alerts:
|
|
self.alerts[alert_id]['acknowledged'] = True
|
|
|
|
def get_system_status(self) -> Dict[str, Any]:
|
|
"""Get overall system status"""
|
|
return {
|
|
'components': self.component_status,
|
|
'alerts': {
|
|
'total': len(self.alerts),
|
|
'unacknowledged': len([a for a in self.alerts.values() if not a['acknowledged']]),
|
|
'by_level': self._count_alerts_by_level()
|
|
},
|
|
'code_health': self.guardian.get_status_report(),
|
|
'performance': self._get_performance_summary()
|
|
}
|
|
|
|
def _count_alerts_by_level(self) -> Dict[str, int]:
|
|
"""Count alerts by level"""
|
|
counts = {'error': 0, 'warning': 0, 'info': 0}
|
|
for alert in self.alerts.values():
|
|
if alert['level'] in counts:
|
|
counts[alert['level']] += 1
|
|
return counts
|
|
|
|
def _get_performance_summary(self) -> Dict[str, Dict[str, float]]:
|
|
"""Get performance summary for all components"""
|
|
summary = {}
|
|
for component, metrics in self.guardian.performance_metrics.items():
|
|
if metrics:
|
|
summary[component] = {
|
|
'avg_execution_time': sum(metrics) / len(metrics),
|
|
'max_execution_time': max(metrics),
|
|
'min_execution_time': min(metrics),
|
|
'total_executions': len(metrics)
|
|
}
|
|
return summary
|
|
|
|
def _monitor_system(self):
|
|
"""Continuous system monitoring"""
|
|
while self.running:
|
|
try:
|
|
# Check component health
|
|
for component, status in self.component_status.items():
|
|
if status['status'] == 'error':
|
|
self._create_alert(
|
|
component,
|
|
f"Component {component} is in error state",
|
|
'error',
|
|
status['error']
|
|
)
|
|
|
|
# Check for performance degradation
|
|
if component in self.guardian.performance_metrics:
|
|
metrics = self.guardian.performance_metrics[component]
|
|
if len(metrics) > 10:
|
|
recent_avg = sum(metrics[-10:]) / 10
|
|
overall_avg = sum(metrics) / len(metrics)
|
|
|
|
if recent_avg > overall_avg * 1.5: # 50% slower
|
|
self._create_alert(
|
|
component,
|
|
f"Performance degradation detected in {component}",
|
|
'warning',
|
|
{
|
|
'recent_avg': recent_avg,
|
|
'overall_avg': overall_avg
|
|
}
|
|
)
|
|
|
|
# Sleep for a while
|
|
time.sleep(60) # Check every minute
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in system monitoring: {str(e)}")
|
|
time.sleep(300) # Wait longer if there's an error
|
|
|
|
def stop(self):
|
|
"""Stop the system monitor"""
|
|
self.running = False
|
|
self.guardian.stop()
|
|
if self.monitor_thread.is_alive():
|
|
self.monitor_thread.join()
|
|
|
|
# Example usage
|
|
def example_usage():
|
|
# Create system monitor
|
|
monitor = SystemMonitor(".")
|
|
|
|
# Example component with monitoring
|
|
@monitor.monitor_component("example_component")
|
|
def example_function(x: int) -> int:
|
|
time.sleep(1) # Simulate work
|
|
return x * 2
|
|
|
|
try:
|
|
# Run component
|
|
for i in range(5):
|
|
result = example_function(i)
|
|
print(f"Result: {result}")
|
|
|
|
# Get system status
|
|
status = monitor.get_system_status()
|
|
print("\nSystem Status:")
|
|
print(json.dumps(status, indent=2))
|
|
|
|
finally:
|
|
monitor.stop()
|
|
|
|
if __name__ == "__main__":
|
|
example_usage()
|
|
import inspect
|
|
import logging
|
|
import sys
|
|
import traceback
|
|
import ast
|
|
import typing
|
|
from dataclasses import dataclass
|
|
from typing import Dict, List, Optional, Any, Tuple
|
|
import numpy as np
|
|
from datetime import datetime
|
|
import importlib
|
|
import threading
|
|
import time
|
|
import json
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@dataclass
|
|
class CodeIssue:
|
|
"""Represents a code issue found by the guardian"""
|
|
file_path: str
|
|
line_number: int
|
|
issue_type: str
|
|
description: str
|
|
severity: str
|
|
suggested_fix: Optional[str] = None
|
|
fixed: bool = False
|
|
|
|
class CodeGuardian:
|
|
def __init__(self, project_root: str):
|
|
self.project_root = project_root
|
|
self.issues: List[CodeIssue] = []
|
|
self.module_states: Dict[str, float] = {} # Module path -> last modified timestamp
|
|
self.performance_metrics: Dict[str, List[float]] = {}
|
|
self.error_patterns: Dict[str, str] = self._load_error_patterns()
|
|
|
|
# Start monitoring thread
|
|
self.running = True
|
|
self.monitor_thread = threading.Thread(target=self._continuous_monitoring)
|
|
self.monitor_thread.daemon = True
|
|
self.monitor_thread.start()
|
|
|
|
def _load_error_patterns(self) -> Dict[str, str]:
|
|
"""Load known error patterns and their fixes"""
|
|
return {
|
|
"IndexError": "Check array bounds and data availability",
|
|
"KeyError": "Verify dictionary keys exist before access",
|
|
"AttributeError": "Ensure object attributes exist",
|
|
"TypeError": "Check type compatibility",
|
|
"ValueError": "Validate input values",
|
|
"ZeroDivisionError": "Add zero-value checking",
|
|
"ImportError": "Verify module installation and import path",
|
|
"MemoryError": "Optimize memory usage or add cleanup",
|
|
"RuntimeError": "Check execution flow and state",
|
|
}
|
|
|
|
def analyze_code(self, file_path: str) -> List[CodeIssue]:
|
|
"""Analyze code for potential issues"""
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
code = f.read()
|
|
|
|
issues = []
|
|
tree = ast.parse(code)
|
|
|
|
# Check for common issues
|
|
issues.extend(self._check_error_handling(tree, file_path))
|
|
issues.extend(self._check_resource_management(tree, file_path))
|
|
issues.extend(self._check_performance_patterns(tree, file_path))
|
|
issues.extend(self._check_code_style(tree, file_path))
|
|
|
|
return issues
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error analyzing {file_path}: {str(e)}")
|
|
return []
|
|
|
|
def _check_error_handling(self, tree: ast.AST, file_path: str) -> List[CodeIssue]:
|
|
"""Check for proper error handling"""
|
|
issues = []
|
|
for node in ast.walk(tree):
|
|
# Check for bare except clauses
|
|
if isinstance(node, ast.Try):
|
|
for handler in node.handlers:
|
|
if handler.type is None:
|
|
issues.append(CodeIssue(
|
|
file_path=file_path,
|
|
line_number=handler.lineno,
|
|
issue_type="error_handling",
|
|
description="Bare except clause found",
|
|
severity="warning",
|
|
suggested_fix="Specify exception type(s) to catch"
|
|
))
|
|
return issues
|
|
|
|
def _check_resource_management(self, tree: ast.AST, file_path: str) -> List[CodeIssue]:
|
|
"""Check for proper resource management"""
|
|
issues = []
|
|
for node in ast.walk(tree):
|
|
# Check for file operations without context manager
|
|
if isinstance(node, ast.Call):
|
|
if isinstance(node.func, ast.Name) and node.func.id in ['open']:
|
|
if not isinstance(node.parent, ast.withitem):
|
|
issues.append(CodeIssue(
|
|
file_path=file_path,
|
|
line_number=node.lineno,
|
|
issue_type="resource_management",
|
|
description="File opened without context manager",
|
|
severity="warning",
|
|
suggested_fix="Use 'with' statement for file operations"
|
|
))
|
|
return issues
|
|
|
|
def _check_performance_patterns(self, tree: ast.AST, file_path: str) -> List[CodeIssue]:
|
|
"""Check for performance-related issues"""
|
|
issues = []
|
|
for node in ast.walk(tree):
|
|
# Check for inefficient list operations
|
|
if isinstance(node, ast.For):
|
|
if isinstance(node.target, ast.Name) and isinstance(node.iter, ast.Call):
|
|
if isinstance(node.iter.func, ast.Name) and node.iter.func.id == 'range':
|
|
if len(node.iter.args) == 1 and isinstance(node.iter.args[0], ast.Call):
|
|
if isinstance(node.iter.args[0].func, ast.Name) and node.iter.args[0].func.id == 'len':
|
|
issues.append(CodeIssue(
|
|
file_path=file_path,
|
|
line_number=node.lineno,
|
|
issue_type="performance",
|
|
description="Inefficient list iteration",
|
|
severity="info",
|
|
suggested_fix="Use 'for item in items' instead of range(len(items))"
|
|
))
|
|
return issues
|
|
|
|
def _check_code_style(self, tree: ast.AST, file_path: str) -> List[CodeIssue]:
|
|
"""Check for code style issues"""
|
|
issues = []
|
|
for node in ast.walk(tree):
|
|
# Check for overly complex functions
|
|
if isinstance(node, ast.FunctionDef):
|
|
if len(node.body) > 50: # Arbitrary threshold
|
|
issues.append(CodeIssue(
|
|
file_path=file_path,
|
|
line_number=node.lineno,
|
|
issue_type="code_style",
|
|
description="Function is too long",
|
|
severity="info",
|
|
suggested_fix="Consider breaking down into smaller functions"
|
|
))
|
|
return issues
|
|
|
|
def monitor_performance(self, module_name: str, execution_time: float):
|
|
"""Monitor module performance"""
|
|
if module_name not in self.performance_metrics:
|
|
self.performance_metrics[module_name] = []
|
|
|
|
self.performance_metrics[module_name].append(execution_time)
|
|
|
|
# Check for performance degradation
|
|
if len(self.performance_metrics[module_name]) > 10:
|
|
recent_avg = np.mean(self.performance_metrics[module_name][-10:])
|
|
overall_avg = np.mean(self.performance_metrics[module_name])
|
|
|
|
if recent_avg > overall_avg * 1.5: # 50% slower than average
|
|
self.issues.append(CodeIssue(
|
|
file_path=module_name,
|
|
line_number=0,
|
|
issue_type="performance_degradation",
|
|
description=f"Performance degradation detected in {module_name}",
|
|
severity="warning"
|
|
))
|
|
|
|
def fix_issue(self, issue: CodeIssue) -> bool:
|
|
"""Attempt to fix a code issue"""
|
|
try:
|
|
if not issue.suggested_fix:
|
|
return False
|
|
|
|
with open(issue.file_path, 'r') as f:
|
|
lines = f.readlines()
|
|
|
|
if issue.issue_type == "error_handling":
|
|
# Fix bare except
|
|
if "Bare except clause found" in issue.description:
|
|
lines[issue.line_number - 1] = lines[issue.line_number - 1].replace(
|
|
"except:", "except Exception:"
|
|
)
|
|
|
|
elif issue.issue_type == "resource_management":
|
|
# Fix file operations
|
|
if "File opened without context manager" in issue.description:
|
|
indent = len(lines[issue.line_number - 1]) - len(lines[issue.line_number - 1].lstrip())
|
|
lines[issue.line_number - 1] = " " * indent + "with " + lines[issue.line_number - 1].lstrip()
|
|
|
|
with open(issue.file_path, 'w') as f:
|
|
f.writelines(lines)
|
|
|
|
issue.fixed = True
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error fixing issue: {str(e)}")
|
|
return False
|
|
|
|
def _continuous_monitoring(self):
|
|
"""Continuously monitor code for issues"""
|
|
while self.running:
|
|
try:
|
|
# Check all Python files in project
|
|
for root, _, files in os.walk(self.project_root):
|
|
for file in files:
|
|
if file.endswith('.py'):
|
|
file_path = os.path.join(root, file)
|
|
|
|
# Check if file was modified
|
|
current_mtime = os.path.getmtime(file_path)
|
|
if file_path not in self.module_states or current_mtime > self.module_states[file_path]:
|
|
self.module_states[file_path] = current_mtime
|
|
|
|
# Analyze code
|
|
new_issues = self.analyze_code(file_path)
|
|
self.issues.extend(new_issues)
|
|
|
|
# Try to fix issues
|
|
for issue in new_issues:
|
|
if issue.suggested_fix:
|
|
self.fix_issue(issue)
|
|
|
|
# Sleep for a while
|
|
time.sleep(60) # Check every minute
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in continuous monitoring: {str(e)}")
|
|
time.sleep(300) # Wait longer if there's an error
|
|
|
|
def get_status_report(self) -> Dict[str, Any]:
|
|
"""Generate a status report"""
|
|
return {
|
|
'total_issues': len(self.issues),
|
|
'fixed_issues': len([i for i in self.issues if i.fixed]),
|
|
'active_issues': len([i for i in self.issues if not i.fixed]),
|
|
'issues_by_type': self._count_issues_by_type(),
|
|
'performance_metrics': self._summarize_performance_metrics()
|
|
}
|
|
|
|
def _count_issues_by_type(self) -> Dict[str, int]:
|
|
"""Count issues by type"""
|
|
counts = {}
|
|
for issue in self.issues:
|
|
if issue.issue_type not in counts:
|
|
counts[issue.issue_type] = 0
|
|
counts[issue.issue_type] += 1
|
|
return counts
|
|
|
|
def _summarize_performance_metrics(self) -> Dict[str, Dict[str, float]]:
|
|
"""Summarize performance metrics"""
|
|
summary = {}
|
|
for module, metrics in self.performance_metrics.items():
|
|
if metrics:
|
|
summary[module] = {
|
|
'avg': np.mean(metrics),
|
|
'min': np.min(metrics),
|
|
'max': np.max(metrics),
|
|
'std': np.std(metrics)
|
|
}
|
|
return summary
|
|
|
|
def stop(self):
|
|
"""Stop the guardian"""
|
|
self.running = False
|
|
if self.monitor_thread.is_alive():
|
|
self.monitor_thread.join()
|
|
|
|
def create_guardian(project_root: str) -> CodeGuardian:
|
|
"""Create and start a code guardian instance"""
|
|
return CodeGuardian(project_root)
|
|
|
|
if __name__ == "__main__":
|
|
# Example usage
|
|
guardian = create_guardian(".")
|
|
try:
|
|
while True:
|
|
status = guardian.get_status_report()
|
|
print(json.dumps(status, indent=2))
|
|
time.sleep(300) # Print status every 5 minutes
|
|
except KeyboardInterrupt:
|
|
guardian.stop() import os
|
|
import sys
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from src.backtesting import Backtester
|
|
from src.dashboard import run_dashboard
|
|
import threading
|
|
import webbrowser
|
|
import time
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('analysis.log'),
|
|
logging.StreamHandler(sys.stdout)
|
|
]
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def run_backtests():
|
|
"""Run all backtest scenarios"""
|
|
# Create results directory if it doesn't exist
|
|
results_dir = 'backtest_results'
|
|
os.makedirs(results_dir, exist_ok=True)
|
|
|
|
# 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
|
|
},
|
|
{
|
|
'name': 'ETH/USDT 1d - Last 90 days',
|
|
'symbol': 'ETH/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:
|
|
# Save results
|
|
filename = f"backtest_results_{scenario['symbol'].replace('/', '_')}_{scenario['timeframe']}.json"
|
|
filepath = os.path.join(results_dir, filename)
|
|
backtester.save_results(filepath)
|
|
logger.info(f"Results saved to {filepath}")
|
|
else:
|
|
logger.error(f"Backtest failed for scenario: {scenario['name']}")
|
|
|
|
def open_browser():
|
|
"""Open browser after a short delay"""
|
|
time.sleep(3) # Wait for the server to start
|
|
webbrowser.open('http://localhost:8050')
|
|
|
|
def main():
|
|
"""Run backtests and launch dashboard"""
|
|
try:
|
|
# Run backtests
|
|
logger.info("Starting backtests...")
|
|
run_backtests()
|
|
logger.info("Backtests completed successfully")
|
|
|
|
# Launch dashboard
|
|
logger.info("Launching dashboard...")
|
|
threading.Thread(target=open_browser).start()
|
|
run_dashboard()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during analysis: {str(e)}")
|
|
raise
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
import dash
|
|
from dash import dcc, html
|
|
from dash.dependencies import Input, Output, State
|
|
import plotly.graph_objects as go
|
|
from plotly.subplots import make_subplots
|
|
import pandas as pd
|
|
import json
|
|
import os
|
|
from datetime import datetime
|
|
import numpy as np
|
|
from .code_guardian import create_guardian, CodeGuardian
|
|
|
|
def load_backtest_results(results_dir):
|
|
"""Load all backtest results from directory"""
|
|
results = {}
|
|
for file in os.listdir(results_dir):
|
|
if file.startswith('backtest_results_') and file.endswith('.json'):
|
|
with open(os.path.join(results_dir, file), 'r') as f:
|
|
scenario_name = file.replace('backtest_results_', '').replace('.json', '')
|
|
results[scenario_name] = json.load(f)
|
|
return results
|
|
|
|
def create_dashboard(results_dir='backtest_results'):
|
|
"""Create and run the dashboard"""
|
|
app = dash.Dash(__name__)
|
|
|
|
# Load results
|
|
results = load_backtest_results(results_dir)
|
|
|
|
# Initialize Code Guardian
|
|
guardian = create_guardian(os.path.dirname(os.path.dirname(__file__)))
|
|
|
|
# Dashboard layout
|
|
app.layout = html.Div([
|
|
# Header
|
|
html.Div([
|
|
html.H1('AI Trading Bot - Backtest Results & System Health Dashboard',
|
|
style={'textAlign': 'center', 'color': '#2c3e50', 'marginBottom': 30}),
|
|
], style={'backgroundColor': '#ecf0f1', 'padding': '20px'}),
|
|
|
|
# Tabs for different sections
|
|
dcc.Tabs([
|
|
# Backtest Results Tab
|
|
dcc.Tab(label='Backtest Results', children=[
|
|
# Scenario Selection
|
|
html.Div([
|
|
html.Label('Select Scenario:'),
|
|
dcc.Dropdown(
|
|
id='scenario-dropdown',
|
|
options=[{'label': k, 'value': k} for k in results.keys()],
|
|
value=list(results.keys())[0] if results else None,
|
|
style={'width': '100%'}
|
|
),
|
|
], style={'margin': '20px'}),
|
|
|
|
# Main Charts
|
|
html.Div([
|
|
# Trading Chart
|
|
html.Div([
|
|
dcc.Graph(id='trading-chart')
|
|
], style={'width': '100%', 'marginBottom': '20px'}),
|
|
|
|
# Performance Metrics Cards
|
|
html.Div([
|
|
html.Div([
|
|
html.Div(id='metrics-cards'),
|
|
], style={'display': 'flex', 'flexWrap': 'wrap', 'justifyContent': 'space-around'})
|
|
], style={'marginBottom': '20px'}),
|
|
|
|
# Trade Distribution Chart
|
|
html.Div([
|
|
dcc.Graph(id='trade-distribution')
|
|
], style={'width': '100%', 'marginBottom': '20px'}),
|
|
|
|
# Drawdown Chart
|
|
html.Div([
|
|
dcc.Graph(id='drawdown-chart')
|
|
], style={'width': '100%'})
|
|
], style={'padding': '20px'})
|
|
]),
|
|
|
|
# System Health Tab
|
|
dcc.Tab(label='System Health', children=[
|
|
html.Div([
|
|
# Code Health Overview
|
|
html.Div([
|
|
html.H2('Code Health Overview', style={'textAlign': 'center'}),
|
|
html.Div(id='code-health-cards', style={
|
|
'display': 'flex',
|
|
'flexWrap': 'wrap',
|
|
'justifyContent': 'space-around',
|
|
'margin': '20px'
|
|
})
|
|
]),
|
|
|
|
# Code Issues Table
|
|
html.Div([
|
|
html.H3('Active Code Issues'),
|
|
html.Div(id='code-issues-table')
|
|
], style={'margin': '20px'}),
|
|
|
|
# Performance Metrics
|
|
html.Div([
|
|
html.H3('Module Performance'),
|
|
dcc.Graph(id='performance-chart')
|
|
], style={'margin': '20px'}),
|
|
|
|
# Auto-Fix Controls
|
|
html.Div([
|
|
html.H3('Issue Resolution'),
|
|
html.Button('Auto-Fix Selected Issues', id='auto-fix-button'),
|
|
html.Div(id='auto-fix-status')
|
|
], style={'margin': '20px'})
|
|
])
|
|
]),
|
|
|
|
# System Logs Tab
|
|
dcc.Tab(label='System Logs', children=[
|
|
html.Div([
|
|
html.H2('System Logs'),
|
|
dcc.Interval(id='log-update', interval=5000), # Update every 5 seconds
|
|
html.Pre(id='log-content', style={
|
|
'backgroundColor': '#2c3e50',
|
|
'color': '#ecf0f1',
|
|
'padding': '20px',
|
|
'borderRadius': '5px',
|
|
'height': '500px',
|
|
'overflow': 'auto'
|
|
})
|
|
], style={'margin': '20px'})
|
|
])
|
|
])
|
|
])
|
|
|
|
@app.callback(
|
|
[Output('trading-chart', 'figure'),
|
|
Output('metrics-cards', 'children'),
|
|
Output('trade-distribution', 'figure'),
|
|
Output('drawdown-chart', 'figure')],
|
|
[Input('scenario-dropdown', 'value')]
|
|
)
|
|
def update_charts(selected_scenario):
|
|
if not selected_scenario or selected_scenario not in results:
|
|
return {}, [], {}, {}
|
|
|
|
result = results[selected_scenario]
|
|
trades_df = pd.DataFrame(result['trades'])
|
|
equity_df = pd.DataFrame(result['equity_curve'])
|
|
metrics = result['performance_metrics']
|
|
|
|
# 1. Trading Chart
|
|
trading_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
|
|
trading_fig.add_trace(
|
|
go.Scatter(
|
|
x=trades_df['timestamp'],
|
|
y=trades_df['price'],
|
|
name='Price',
|
|
line=dict(color='#2980b9')
|
|
),
|
|
row=1, col=1
|
|
)
|
|
|
|
# Add buy/sell markers
|
|
for action, color in [('buy', '#27ae60'), ('sell', '#c0392b')]:
|
|
mask = trades_df['action'] == action
|
|
trading_fig.add_trace(
|
|
go.Scatter(
|
|
x=trades_df[mask]['timestamp'],
|
|
y=trades_df[mask]['price'],
|
|
mode='markers',
|
|
name=action.capitalize(),
|
|
marker=dict(color=color, size=10)
|
|
),
|
|
row=1, col=1
|
|
)
|
|
|
|
# Add equity curve
|
|
trading_fig.add_trace(
|
|
go.Scatter(
|
|
x=equity_df['timestamp'],
|
|
y=equity_df['equity'],
|
|
name='Equity',
|
|
line=dict(color='#8e44ad')
|
|
),
|
|
row=2, col=1
|
|
)
|
|
|
|
trading_fig.update_layout(
|
|
title='Trading Activity and Equity Curve',
|
|
height=800,
|
|
template='plotly_white'
|
|
)
|
|
|
|
# 2. Metrics Cards
|
|
cards = []
|
|
metrics_style = {
|
|
'width': '200px',
|
|
'margin': '10px',
|
|
'padding': '15px',
|
|
'borderRadius': '5px',
|
|
'boxShadow': '2px 2px 5px rgba(0,0,0,0.1)',
|
|
'textAlign': 'center'
|
|
}
|
|
|
|
metrics_data = [
|
|
('Total Return', f"{metrics['total_return']:.2%}", '#27ae60'),
|
|
('Win Rate', f"{metrics['win_rate']:.2%}", '#2980b9'),
|
|
('Sharpe Ratio', f"{metrics['sharpe_ratio']:.2f}", '#8e44ad'),
|
|
('Max Drawdown', f"{metrics['max_drawdown']:.2%}", '#c0392b'),
|
|
('Total Trades', str(metrics['total_trades']), '#2c3e50')
|
|
]
|
|
|
|
for label, value, color in metrics_data:
|
|
cards.append(html.Div([
|
|
html.H4(label, style={'color': '#7f8c8d'}),
|
|
html.H3(value, style={'color': color})
|
|
], style=metrics_style))
|
|
|
|
# 3. Trade Distribution
|
|
returns = pd.Series([float(x) for x in trades_df['price'].pct_change().dropna()])
|
|
dist_fig = go.Figure()
|
|
dist_fig.add_trace(go.Histogram(
|
|
x=returns,
|
|
nbinsx=30,
|
|
name='Returns Distribution',
|
|
marker_color='#3498db'
|
|
))
|
|
|
|
dist_fig.update_layout(
|
|
title='Trade Returns Distribution',
|
|
xaxis_title='Return',
|
|
yaxis_title='Frequency',
|
|
template='plotly_white'
|
|
)
|
|
|
|
# 4. Drawdown Chart
|
|
equity_series = pd.Series([float(x) for x in equity_df['equity']])
|
|
rolling_max = equity_series.expanding().max()
|
|
drawdowns = (equity_series - rolling_max) / rolling_max
|
|
|
|
dd_fig = go.Figure()
|
|
dd_fig.add_trace(go.Scatter(
|
|
x=equity_df['timestamp'],
|
|
y=drawdowns,
|
|
fill='tozeroy',
|
|
name='Drawdown',
|
|
line=dict(color='#e74c3c')
|
|
))
|
|
|
|
dd_fig.update_layout(
|
|
title='Drawdown Over Time',
|
|
xaxis_title='Date',
|
|
yaxis_title='Drawdown',
|
|
template='plotly_white'
|
|
)
|
|
|
|
return trading_fig, cards, dist_fig, dd_fig
|
|
|
|
@app.callback(
|
|
[Output('code-health-cards', 'children'),
|
|
Output('code-issues-table', 'children'),
|
|
Output('performance-chart', 'figure'),
|
|
Output('auto-fix-status', 'children')],
|
|
[Input('auto-fix-button', 'n_clicks')],
|
|
[State('code-issues-table', 'selected_rows')]
|
|
)
|
|
def update_system_health(n_clicks, selected_rows):
|
|
# Get guardian status
|
|
status = guardian.get_status_report()
|
|
|
|
# Create health cards
|
|
health_cards = []
|
|
health_metrics = [
|
|
('Total Issues', status['total_issues'], '#e74c3c'),
|
|
('Fixed Issues', status['fixed_issues'], '#27ae60'),
|
|
('Active Issues', status['active_issues'], '#f39c12'),
|
|
('Code Coverage', '85%', '#3498db') # Example metric
|
|
]
|
|
|
|
for label, value, color in health_metrics:
|
|
health_cards.append(html.Div([
|
|
html.H4(label),
|
|
html.H3(str(value))
|
|
], style={
|
|
'width': '200px',
|
|
'margin': '10px',
|
|
'padding': '15px',
|
|
'borderRadius': '5px',
|
|
'boxShadow': '2px 2px 5px rgba(0,0,0,0.1)',
|
|
'textAlign': 'center',
|
|
'backgroundColor': color,
|
|
'color': 'white'
|
|
}))
|
|
|
|
# Create issues table
|
|
issues_table = html.Table([
|
|
html.Thead(html.Tr([
|
|
html.Th('File'),
|
|
html.Th('Line'),
|
|
html.Th('Type'),
|
|
html.Th('Severity'),
|
|
html.Th('Description'),
|
|
html.Th('Status')
|
|
])),
|
|
html.Tbody([
|
|
html.Tr([
|
|
html.Td(issue.file_path),
|
|
html.Td(issue.line_number),
|
|
html.Td(issue.issue_type),
|
|
html.Td(issue.severity),
|
|
html.Td(issue.description),
|
|
html.Td('Fixed' if issue.fixed else 'Active')
|
|
]) for issue in guardian.issues
|
|
])
|
|
], style={'width': '100%'})
|
|
|
|
# Create performance chart
|
|
perf_data = status['performance_metrics']
|
|
perf_fig = go.Figure()
|
|
|
|
for module, metrics in perf_data.items():
|
|
perf_fig.add_trace(go.Scatter(
|
|
x=list(range(len(guardian.performance_metrics[module]))),
|
|
y=guardian.performance_metrics[module],
|
|
name=module,
|
|
mode='lines+markers'
|
|
))
|
|
|
|
perf_fig.update_layout(
|
|
title='Module Performance Over Time',
|
|
xaxis_title='Execution Count',
|
|
yaxis_title='Execution Time (s)',
|
|
template='plotly_white'
|
|
)
|
|
|
|
# Handle auto-fix status
|
|
fix_status = None
|
|
if n_clicks and selected_rows:
|
|
fixed_count = sum(1 for i in selected_rows if guardian.fix_issue(guardian.issues[i]))
|
|
fix_status = f"Fixed {fixed_count} out of {len(selected_rows)} selected issues"
|
|
|
|
return health_cards, issues_table, perf_fig, fix_status
|
|
|
|
@app.callback(
|
|
Output('log-content', 'children'),
|
|
[Input('log-update', 'n_intervals')]
|
|
)
|
|
def update_logs(_):
|
|
try:
|
|
with open('analysis.log', 'r') as f:
|
|
logs = f.readlines()
|
|
return ''.join(logs[-100:]) # Show last 100 lines
|
|
except Exception as e:
|
|
return f"Error reading logs: {str(e)}"
|
|
|
|
return app
|
|
|
|
def run_dashboard(port=8050):
|
|
"""Run the dashboard"""
|
|
app = create_dashboard()
|
|
app.run_server(debug=True, port=port)
|
|
|
|
if __name__ == '__main__':
|
|
run_dashboard()
|
|
{
|
|
"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
|
|
}
|
|
}
|
|
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()
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
}
|
|
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
|
|
}
|
|
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
|