ome/elliot/Downloads/quant work/transformers/trans/lib/python3.11/site-packages/keras/src/optimizers/base_optimizer.py:731: UserWarning: Gradients do not exist for variables ['kernel', 'bias', 'kernel', 'bias', 'kernel', 'bias'] when minimizing the loss. If using model.compile(), did you forget to provide a loss argument?
  warnings.warn( 


def build_multi_output_transformer_model(
    input_shape, 
    num_classes_classification, 
    head_size=256, 
    num_heads=4, 
    ff_dim=128, 
    num_transformer_blocks=2, 
    dropout=0.1
):
    """
    Builds a Transformer model with multiple outputs.
    """
    inputs = Input(shape=input_shape, name='input')

    # Positional Encoding (Learnable)
    position_indices = tf.range(start=0, limit=input_shape[0], delta=1)
    positional_embedding = Dense(input_shape[1])(tf.expand_dims(position_indices, axis=1))
    x = inputs + positional_embedding  # Broadcasting addition

    # Transformer Encoder Blocks
    for _ in range(num_transformer_blocks):
        # Layer Normalization 1
        x_norm = LayerNormalization(epsilon=1e-6)(x)
        
        # Multi-Head Attention
        attention = MultiHeadAttention(key_dim=head_size, num_heads=num_heads, dropout=dropout)(x_norm, x_norm)
        attention = Dropout(dropout)(attention)
        x = x + attention  # Residual connection

        # Layer Normalization 2
        x_norm = LayerNormalization(epsilon=1e-6)(x)
        
        # Feed-Forward Network
        ffn = Dense(ff_dim, activation="relu")(x_norm)
        ffn = Dropout(dropout)(ffn)
        ffn = Dense(input_shape[-1])(ffn)
        ffn = Dropout(dropout)(ffn)
        x = x + ffn  # Residual connection

    # Global Average Pooling
    x = GlobalAveragePooling1D()(x)
    x = Dropout(dropout)(x)
    x = Dense(128, activation="relu")(x)
    x = Dropout(dropout)(x)

    # Output: Predicted Close Price (Regression)
    close_price_output = Dense(1, activation='linear', name='close_price')(x)

    # Output: Percent Change Classification (7 classes)
    percent_change_output = Dense(num_classes_classification, activation='softmax', name='percent_change_classification')(x)

    # Output: Leg Direction (Binary Classification)
    leg_direction_output = Dense(2, activation='softmax', name='leg_direction')(x)

    # Output: Leg Length (Regression)
    leg_length_output = Dense(1, activation='linear', name='leg_length')(x)

    # Define the Model
    model = Model(
        inputs=inputs, 
        outputs=[
            close_price_output, 
            percent_change_output, 
            leg_direction_output, 
            leg_length_output
        ]
    )
    
    return model

def compile_multi_output_model(model):
    """
    Compiles the multi-output Transformer model with appropriate loss functions and metrics.
    """
    model.compile(
        optimizer='adam',
        loss={
            'close_price': 'mse',
            'percent_change_classification': 'sparse_categorical_crossentropy',
            'leg_direction': 'sparse_categorical_crossentropy',
            'leg_length': 'mse'
        },
        metrics={
            'close_price': ['mae'],
            'percent_change_classification': ['accuracy'],
            'leg_direction': ['accuracy'],
            'leg_length': ['mae']
        }
    )
    return model


# Configuration
BINANCE_API_KEY = 'YOUR_API_KEY'
BINANCE_API_SECRET = 'YOUR_API_SECRET'

CLIENT = Client(BINANCE_API_KEY, BINANCE_API_SECRET)

SYMBOLS = [ 'BATUSDT']  # Expand to 15 symbols as needed
INTERVAL = '1s'  # For MVP, using 1-minute intervals
SEQ_LENGTH = 80    # Number of time steps in each input sequence
TARGET_STEPS = 1   # Predicting the next time step
LIMIT = 1000        # Number of historical data points to fetch per symbol
import matplotlib.pyplot as plt
import seaborn as sns
import pickle
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

def main():
    all_dfs = {}
    label_encoders = {}
    scalers = {}
    
    # Step 1: Load Data for Each Symbol
    for symbol in SYMBOLS:
        print(f"Loading data for {symbol}...")
        df = load_data(CLIENT, symbol, interval=INTERVAL, limit=LIMIT)
        if df.empty:
            print(f"No data for {symbol}. Skipping.")
            continue
        print(f"Data for {symbol} loaded. {len(df)} rows.")
        all_dfs[symbol] = df
    
    # Step 2: Feature Engineering
    for symbol, df in all_dfs.items():
        print(f"\nProcessing {symbol}...")
        print("  Adding technical indicators...")
        df = add_technical_indicators(df)
        print("  Classifying market environments...")
        df = classify_market_environments(df)
        print("  Calculating leg data...")
        df = calculate_leg_data(df)
        print("  Classifying percent changes...")
        df = classify_percent_change(df)
        print("  Encoding and scaling features...")
        df, le, sc = encode_and_scale(df)
        label_encoders[symbol] = le
        scalers[symbol] = sc
        all_dfs[symbol] = df
        # Optionally, save label encoders and scalers for each symbol
        with open(f'label_encoders_{symbol}.pkl', 'wb') as f:
            pickle.dump(le, f)
        with open(f'scalers_{symbol}.pkl', 'wb') as f:
            pickle.dump(sc, f)
    
    # Step 3: Combine Data from All Symbols
    print("\nCombining data from all symbols...")
    combined_df = pd.concat(all_dfs.values(), keys=all_dfs.keys(), names=['Symbol', 'Index']).reset_index(level=0).reset_index(drop=True)
    print(f"Combined DataFrame shape: {combined_df.shape}")
    
    # Step 4: Prepare Sequences and Targets
    print("\nPreparing sequences and targets...")
    X, Y = prepare_sequences(combined_df, seq_length=SEQ_LENGTH, target_steps=TARGET_STEPS)
    print(f"Input shape: {X.shape}, Target shapes: {Y.shape}")
  
    # Step 5: Split into Training and Testing
    print("\nSplitting data into training and testing sets...")
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, shuffle=False)
    print(f"Training set: {X_train.shape}, Testing set: {X_test.shape}")
    
    # Step 7: Build the Model
    print("\nBuilding the Transformer model...")
    num_features = X_train.shape[-1]  # Number of features per time step
    num_classes = combined_df['percent_change_classification'].nunique()  # Assuming 'percent_change_classification' has unique classes
    model = build_multi_output_transformer_model(
        input_shape=(SEQ_LENGTH, num_features),
        num_classes_classification=num_classes)
    model = compile_multi_output_model(model)
    model.summary()
    
    # Step 8: Train the Model
    print("\nTraining the model...")
    history = model.fit(
        X_train, Y_train,
        validation_data=(X_test, Y_test),
        epochs=100,            # Adjust the number of epochs as needed
        batch_size=32
    )