import sqlite3
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, classification_report
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout
from keras.optimizers import Adam
import talib

def fetch_data_from_db(symbol, conn):
    query = f"SELECT * FROM ohlcv WHERE symbol=‘{symbol}’“
    df = pd.read_sql_query(query, conn)
    df[‘timestamp’] = pd.to_datetime(df[‘timestamp’])
    return df

def prepare_data(df):
    “””
    Prepare the dataset for machine learning models.
    “”“
    df[‘price_change’] = df[‘close’].pct_change()
    df[‘volatility’] = df[‘price_change’].rolling(window=20).std()
    df[‘rsi’] = talib.RSI(df[‘close’], timeperiod=14)
    df[‘sma50’] = talib.SMA(df[‘close’], timeperiod=50)
    df[‘sma200’] = talib.SMA(df[‘close’], timeperiod=200)

    df.dropna(inplace=True)

    # Create labels (1 for buy, -1 for sell, 0 for hold)
    df[‘label’] = 0
    df.loc[df[‘close’].shift(-1) > df[‘close’], ‘label’] = 1
    df.loc[df[‘close’].shift(-1) < df[‘close’], ‘label’] = -1

    return df

def split_data(df):
    “””
    Split the dataset into training and test sets.
    “”“
    features = df[[‘price_change’, ‘volatility’, ‘rsi’, ‘sma50’, ‘sma200’]]
    labels = df[‘label’]
    
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(features)
    
    X_train, X_test, y_train, y_test = train_test_split(X_scaled, labels, test_size=0.2, random_state=42)
    return X_train, X_test, y_train, y_test, scaler

def train_gbm(X_train, y_train):
    “””
    Train a Gradient Boosting Machine (XGBoost) with hyperparameter tuning.
    “”“
    param_grid = {
        ‘n_estimators’: [50, 100, 200],
        ‘learning_rate’: [0.01, 0.1, 0.2],
        ‘max_depth’: [3, 5, 7]
    }
    gbm_model = XGBClassifier()
    grid_search = GridSearchCV(gbm_model, param_grid, cv=3, scoring=‘f1_macro’)
    grid_search.fit(X_train, y_train)
    return grid_search.best_estimator_

def train_random_forest(X_train, y_train):
    “””
    Train a Random Forest Classifier with hyperparameter tuning.
    “”“
    param_grid = {
        ‘n_estimators’: [50, 100, 200],
        ‘max_depth’: [None, 10, 20],
        ‘min_samples_split’: [2, 5, 10]
    }
    rf_model = RandomForestClassifier(random_state=42)
    grid_search = GridSearchCV(rf_model, param_grid, cv=3, scoring=‘f1_macro’)
    grid_search.fit(X_train, y_train)
    return grid_search.best_estimator_

def train_rnn(X_train, y_train):
    “””
    Train a Recurrent Neural Network (LSTM).
    “”“
    X_train_array = X_train.reshape((X_train.shape[0], 1, X_train.shape[1]))
    y_train_array = y_train.values

    model = Sequential()
    model.add(LSTM(units=50, return_sequences=True, input_shape=(1, X_train.shape[1])))
    model.add(Dropout(0.2))
    model.add(LSTM(units=50))
    model.add(Dropout(0.2))
    model.add(Dense(1, activation=‘sigmoid’))

    model.compile(optimizer=Adam(lr=0.001), loss=‘binary_crossentropy’, metrics=[‘accuracy’])
    model.fit(X_train_array, y_train_array, epochs=50, batch_size=32, verbose=1)
    
    return model

def generate_signals(models, X_test):
    “””
    Generate trading signals by aggregating predictions from multiple models.
    “”“
    rf_model, gbm_model, rnn_model = models
    X_test_array = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))

    rf_preds = rf_model.predict(X_test)
    gbm_preds = gbm_model.predict(X_test)
    rnn_preds = (rnn_model.predict(X_test_array) > 0.5).astype(int).flatten()

    # Aggregating using majority voting
    aggregated_preds = np.array([rf_preds, gbm_preds, rnn_preds]).T
    final_preds = [np.bincount(preds).argmax() for preds in aggregated_preds]

    return final_preds

def evaluate_model(y_test, signals):
    “””
    Evaluate the model using various metrics.
    “”“
    accuracy = accuracy_score(y_test, signals)
    f1 = f1_score(y_test, signals, average=‘macro’)
    roc_auc = roc_auc_score(y_test, signals, multi_class=‘ovr’)
    
    print(f’Accuracy: {accuracy}‘)
    print(f’F1 Score: {f1}’)
    print(f’ROC AUC Score: {roc_auc}')
    print(classification_report(y_test, signals))

def backtest_model(df, signals, initial_balance=10000):
    “””
    Simple backtesting framework to evaluate strategy performance.
    “”"
    balance = initial_balance
    position = 0  # 1 = long position, -1 = short position, 0 = no position
    for i in range(len(signals)):
        signal = signals[i]
        price = df.iloc[i][‘close’]
        
        if signal == 1 and position == 0:
            position = 1
            entry_price = price
        elif signal == -1 and position == 0:
            position = -1
            entry_price = price
        elif signal == 0 and position != 0:
            if position == 1:
                balance += (price - entry_price) * balance / entry_price
            elif position == -1:
                balance += (entry_price - price) * balance / entry_price
            position = 0
    
    return balance

if name == “main”:
    # Example usage
    conn = sqlite3.connect(‘trading_data.db’)
    symbol = ‘BTC/USDT’
    df = fetch_data_from_db(symbol, conn)
    df = prepare_data(df)
    
    X_train, X_test, y_train, y_test, scaler = split_data(df)

    # Train models
    rf_model = train_random_forest(X_train, y_train)
    gbm_model = train_gbm(X_train, y_train)
    rnn_model = train_rnn(X_train, y_train)

    # Generate signals
    models = (rf_model, gbm_model, rnn_model)
    signals = generate_signals(models, X_test)

    # Evaluate performance
    evaluate_model(y_test, signals)

    # Backtest model
    final_balance = backtest_model(df.iloc[X_test.index], signals)
    print(f’Final Balance after Backtesting: ${final_balance}')

Te above code is for machine learning model that generates crypto trading signals but needs improvements as below. Please include them for me.Feature Engineering and Selection
Explore Deeper Feature Engineering: Create more complex features that capture intricate market dynamics. For instance, consider combining multiple technical indicators, calculating higher-order moments of price changes, or incorporating external economic data.
Feature Importance Analysis: Utilize feature importance metrics provided by your models to identify the most influential features. Prioritize these features and consider removing less relevant ones to reduce noise and improve efficiency.
Dimensionality Reduction: If you have a large number of features, techniques like Principal Component Analysis (PCA) can help reduce dimensionality while preserving essential information.

   
Hyperparameter Tuning
Expand Hyperparameter Search: Explore a wider range of hyperparameter values for your Random Forest and XGBoost models. Consider using more sophisticated optimization techniques like Bayesian optimization or randomized search.
Ensemble Different Hyperparameter Settings: Combine multiple models trained with different hyperparameter configurations to create an ensemble, potentially improving overall performance.   
Model Ensembling (Within Existing Models)
Bagging and Boosting: While you're not adding new models, you can still leverage ensemble techniques within your existing models. Experiment with different bagging and boosting algorithms to enhance performance.
Weighted Averaging: Assign different weights to the predictions of your three models based on their performance on a validation set. This can help improve overall accuracy.
Model Architecture Refinement (LSTM)
Adjust LSTM Architecture: Experiment with different numbers of LSTM layers, units per layer, and dropout rates to optimize the LSTM model's performance.
Input Feature Engineering: Consider creating lagged features or other time-series-specific features as input to the LSTM.
Evaluation Metrics
Custom Metrics: Develop custom metrics that align more closely with your trading objectives. For example, consider metrics that reward accurate prediction of large price movements.
Imbalanced Dataset Handling: If your dataset is imbalanced (more hold signals than buy/sell), explore techniques like oversampling, undersampling, or class weighting to improve model performance on the minority classes.
Data Preprocessing Refinements
Time Series Considerations: Incorporate time-series-specific preprocessing techniques like differencing or normalization.
Outlier Detection and Handling: Identify and handle outliers in your data to prevent them from negatively impacting model performance.
Regularization
Strengthen Regularization: Increase regularization parameters (e.g., L1 or L2 regularization) in your models to prevent overfitting.
Model Monitoring and Retraining
Monitor Model Performance: Continuously track your model's performance on new data and retrain it as needed to adapt to changing market conditions.