im getting this error running this code in vscode:PS C:\Users\User\Desktop\binancebot> 2024-09-25 20:17:27,365 - INFO - Using Binance Futures Live API.
2024-09-25 20:17:29,184 - ERROR - Binance API Exception: APIError(code=-2015): Invalid API-key, IP, or permissions for action
2024-09-25 20:17:29,184 - ERROR - Exiting due to client initialization failure.
PS C:\Users\User\Desktop\binancebot>  

my folder contains a file called .env which contains the correct api keys for the binance futures testnet and its contents are : BINANCE_API_KEY=ab752e88e0ed8928a2c5a6179b5f6b62b59c3de2e0bf4314713ed7478318e553
BINANCE_API_SECRET=3092d630a2beeebb4b8ffcd134f201e924737cadf68b0de59fecee7ff7cb8762 and they are my correct API keys

here is my code

# wyckoff_futures.py

import os
import time
import logging
from datetime import datetime, timedelta, timezone
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from binance.client import Client
from binance.exceptions import BinanceAPIException
from binance.enums import *
from dotenv import load_dotenv
import ta

# ==============================
# Configuration Section
# ==============================
class Config:
    # API Credentials
    DOTENV_PATH = '.env'  # Ensure your .env file is named correctly

    # Binance Client Mode
    USE_TESTNET = False  # Set to True for Futures Testnet, False for Live Client

    # Trading Parameters
    CURRENCY_PAIR = 'BTCUSDT'  # e.g., 'BTCUSDT'
    TIMEFRAME = Client.KLINE_INTERVAL_30MINUTE  # 30-minute intervals
    LEVERAGE = 10  # Set leverage to 10x

    # Backtesting Parameters
    RUN_BACKTEST = True
    BACKTEST_DAYS = 30  # Adjusted to 30 days for data availability
    BACKTEST_TIMEFRAME = Client.KLINE_INTERVAL_30MINUTE  # 30-minute data

    # Live Trading Parameters
    RUN_LIVE_TRADING = False  # Set to True to enable live trading
    TRADE_QUANTITY = 0.001  # Adjust based on your balance and requirements

    # Visualization Parameters
    PLOT_RESULTS = True

    # Logging Parameters
    VERBOSE = True  # If True, logs detailed information

    # Strategy Parameters
    SMA_SHORT_WINDOW = 50  # For 30-minute intervals
    SMA_LONG_WINDOW = 200
    RSI_PERIOD = 14
    VOLUME_MULTIPLIER = 1.0  # Adjust volume condition
    RSI_LOWER_THRESHOLD = 30  # RSI Oversold
    RSI_UPPER_THRESHOLD = 70  # RSI Overbought
    CLOSE_THRESHOLD_PERCENTAGE = 0.003  # 0.3%

    # Risk Management Parameters
    STOP_LOSS_PERCENTAGE = 0.02  # 2% stop loss
    TAKE_PROFIT_PERCENTAGE = 0.04  # 4% take profit

# ==============================
# Setup Logging
# ==============================
def setup_logging():
    """
    Configures the logging for the script.
    """
    log_format = '%(asctime)s - %(levelname)s - %(message)s'
    logging.basicConfig(level=logging.INFO if Config.VERBOSE else logging.WARNING, format=log_format)
    return logging.getLogger(__name__)

logger = setup_logging()

# ==============================
# Load Environment Variables
# ==============================
def load_api_keys(dotenv_path):
    """
    Loads API keys from the specified dotenv file.
    """
    load_dotenv(dotenv_path=dotenv_path)
    api_key = os.getenv('BINANCE_API_KEY')
    api_secret = os.getenv('BINANCE_API_SECRET')
    if not api_key or not api_secret:
        logger.error("BINANCE_API_KEY and BINANCE_API_SECRET must be set in the .env file.")
        raise ValueError("API keys not found.")
    return api_key, api_secret

# ==============================
# Initialize Binance Futures Client
# ==============================
def initialize_futures_client(api_key, api_secret):
    """
    Initializes the Binance Futures client.
    """
    try:
        client = Client(api_key, api_secret)

        if Config.USE_TESTNET:
            client.futures_testnet = True
            logger.info("Using Binance Futures Testnet.")
        else:
            logger.info("Using Binance Futures Live API.")

        # Set leverage for the specified symbol
        client.futures_change_leverage(symbol=Config.CURRENCY_PAIR, leverage=Config.LEVERAGE)
        logger.info(f"Leverage set to {Config.LEVERAGE}x for {Config.CURRENCY_PAIR}.")

        logger.info("Binance Futures client initialized successfully.")
        return client
    except BinanceAPIException as e:
        logger.error(f"Binance API Exception: {e}")
        raise
    except Exception as e:
        logger.error(f"Failed to initialize Binance Futures client: {e}")
        raise

# ==============================
# Function to Get Historical Data
# ==============================
def get_historical_data(client, symbol, interval, lookback):
    """
    Fetch historical klines/candlestick data from Binance Futures.
    """
    logger.info(f"Fetching historical data for {symbol} from {lookback} to latest")
    try:
        limit = 1000
        time_delta = timedelta(days=Config.BACKTEST_DAYS)
        end_time = datetime.now(timezone.utc)
        start_time = end_time - time_delta

        klines = []
        while True:
            start_time_ms = int(start_time.timestamp() * 1000)
            end_time_ms = int(end_time.timestamp() * 1000)

            logger.info(f"Fetching klines from {start_time} to {end_time}")

            try:
                temp_klines = client.futures_klines(
                    symbol=symbol,
                    interval=interval,
                    startTime=start_time_ms,
                    endTime=end_time_ms,
                    limit=limit
                )
            except BinanceAPIException as e:
                logger.error(f"Binance API Exception: {e}")
                break
            except Exception as e:
                logger.error(f"Exception during fetching klines: {e}")
                break

            if not temp_klines:
                break

            klines.extend(temp_klines)
            last_open_time = temp_klines[-1][0]

            if len(temp_klines) < limit:
                break

            # Calculate interval in milliseconds
            interval_mapping = {
                '1m': 60000,
                '3m': 180000,
                '5m': 300000,
                '15m': 900000,
                '30m': 1800000,
                '1h': 3600000,
                '2h': 7200000,
                '4h': 14400000,
                '6h': 21600000,
                '8h': 28800000,
                '12h': 43200000,
                '1d': 86400000,
                '3d': 259200000,
                '1w': 604800000,
                '1M': 2592000000
            }
            interval_ms = interval_mapping.get(interval, 1800000)  # Default to 30m

            # Update start_time for the next iteration
            start_time = datetime.fromtimestamp((last_open_time / 1000) + (interval_ms / 1000), timezone.utc)

            if start_time >= end_time:
                break

        if not klines:
            logger.warning("No historical data fetched. Please check the parameters.")
            return pd.DataFrame()

        # Create DataFrame
        df = pd.DataFrame(klines, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 'volume',
            'close_time', 'quote_asset_volume', 'number_of_trades',
            'taker_buy_base_asset_volume', 'taker_buy_quote_asset_volume', 'ignore'
        ])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True)
        df.set_index('timestamp', inplace=True)
        for col in ['open', 'high', 'low', 'close', 'volume']:
            df[col] = df[col].astype(float)
        df.sort_index(inplace=True)
        df.drop_duplicates(inplace=True)
        logger.info(f"Fetched {len(df)} rows of historical data.")
        logger.info(f"Data Range: {df.index.min()} to {df.index.max()}")

        # Check for NaNs and remove them
        if df.isnull().values.any():
            logger.warning("Data contains NaN values. Proceeding to drop NaNs.")
            df.dropna(inplace=True)

        return df

    except Exception as e:
        logger.error(f"Error fetching historical data: {e}")
        return pd.DataFrame()

# ==============================
# Function to Calculate Indicators
# ==============================
def calculate_indicators(df):
    """
    Calculate technical indicators for the dataframe.
    """
    logger.info("Calculating technical indicators.")
    try:
        # Calculate Simple Moving Averages
        df['SMA_SHORT'] = ta.trend.SMAIndicator(close=df['close'], window=Config.SMA_SHORT_WINDOW).sma_indicator()
        df['SMA_LONG'] = ta.trend.SMAIndicator(close=df['close'], window=Config.SMA_LONG_WINDOW).sma_indicator()

        # Calculate RSI
        df['RSI'] = ta.momentum.RSIIndicator(close=df['close'], window=Config.RSI_PERIOD).rsi()

        # Calculate Force Index over 13 periods
        df['ForceIndex'] = ta.volume.ForceIndexIndicator(close=df['close'], volume=df['volume'], window=13).force_index()

        # Calculate Volume SMA
        df['Volume_SMA'] = ta.trend.SMAIndicator(close=df['volume'], window=Config.SMA_SHORT_WINDOW).sma_indicator()

        # Drop rows with NaN values resulting from indicator calculations
        df.dropna(inplace=True)

        logger.info("Technical indicators calculated successfully.")
        # Log the last 5 indicator values for verification
        logger.info(f"Sample Indicators:\n{df[['SMA_SHORT', 'SMA_LONG', 'RSI', 'ForceIndex', 'Volume_SMA']].tail()}")
        return df
    except Exception as e:
        logger.error(f"Error calculating indicators: {e}")
        return df

# ==============================
# Function to Identify Wyckoff Phases
# ==============================
def identify_wyckoff_phases(df):
    """
    Identify Wyckoff phases in the given DataFrame.
    """
    logger.info("Identifying Wyckoff phases.")
    try:
        df['Phase'] = None

        # Accumulation Condition
        accumulation_condition = (
            (df['close'] <= df['close'].rolling(window=Config.SMA_LONG_WINDOW).min() * (1 + Config.CLOSE_THRESHOLD_PERCENTAGE)) &
            (df['RSI'] <= Config.RSI_LOWER_THRESHOLD)
        )
        df.loc[accumulation_condition, 'Phase'] = 'Accumulation'
        accumulation_count = accumulation_condition.sum()
        logger.info(f"Accumulation Phases Identified: {accumulation_count}")

        # Distribution Condition
        distribution_condition = (
            (df['close'] >= df['close'].rolling(window=Config.SMA_LONG_WINDOW).max() * (1 - Config.CLOSE_THRESHOLD_PERCENTAGE)) &
            (df['RSI'] >= Config.RSI_UPPER_THRESHOLD)
        )
        df.loc[distribution_condition, 'Phase'] = 'Distribution'
        distribution_count = distribution_condition.sum()
        logger.info(f"Distribution Phases Identified: {distribution_count}")

        # Mark-Up Condition
        markup_condition = (
            (df['close'] > df['SMA_SHORT']) &
            (df['SMA_SHORT'] > df['SMA_LONG'])
        )
        df.loc[markup_condition, 'Phase'] = 'Mark-Up'
        markup_count = markup_condition.sum()
        logger.info(f"Mark-Up Phases Identified: {markup_count}")

        # Mark-Down Condition
        markdown_condition = (
            (df['close'] < df['SMA_SHORT']) &
            (df['SMA_SHORT'] < df['SMA_LONG'])
        )
        df.loc[markdown_condition, 'Phase'] = 'Mark-Down'
        markdown_count = markdown_condition.sum()
        logger.info(f"Mark-Down Phases Identified: {markdown_count}")

        # Drop rows where Phase is still None
        df.dropna(subset=['Phase'], inplace=True)
        logger.info("Wyckoff phases identified successfully.")
        # Log sample phases
        if not df.empty:
            phase_counts = df['Phase'].value_counts()
            logger.info(f"Phase Counts:\n{phase_counts}")
            logger.info(f"Sample Phase Data:\n{df[['close', 'Phase']].head()}")
        else:
            logger.info("No phases identified in the fetched data.")

        return df
    except Exception as e:
        logger.error(f"Error identifying Wyckoff phases: {e}")
        return df

# ==============================
# Function to Identify Wyckoff Events
# ==============================
def identify_wyckoff_events(df):
    """
    Identify Wyckoff events in the given DataFrame based on the identified phases.
    """
    logger.info("Identifying Wyckoff events.")
    try:
        df['Event'] = None

        # Define windows
        window_30 = 30
        window_5 = 5

        # Accumulation Events (Spring)
        accumulation_condition = df['Phase'] == 'Accumulation'
        events_before_min = df['low'].rolling(window=window_30).min().shift(1)
        spring_condition = (
            accumulation_condition &
            (df['low'] < events_before_min * (1 - Config.CLOSE_THRESHOLD_PERCENTAGE))
        )
        df.loc[spring_condition, 'Event'] = 'Spring'
        spring_count = spring_condition.sum()
        logger.info(f"Spring Events Identified: {spring_count}")

        # Distribution Events (Upthrust)
        distribution_condition = df['Phase'] == 'Distribution'
        events_before_max = df['high'].rolling(window=window_30).max().shift(1)
        upthrust_condition = (
            distribution_condition &
            (df['high'] > events_before_max * (1 + Config.CLOSE_THRESHOLD_PERCENTAGE))
        )
        df.loc[upthrust_condition, 'Event'] = 'Upthrust'
        upthrust_count = upthrust_condition.sum()
        logger.info(f"Upthrust Events Identified: {upthrust_count}")

        # Mark-Up Events (SOS)
        markup_condition = df['Phase'] == 'Mark-Up'
        events_before_max = df['high'].rolling(window=window_5).max().shift(1)
        sos_condition = (
            markup_condition &
            (df['close'] > events_before_max * (1 + Config.CLOSE_THRESHOLD_PERCENTAGE)) &
            (df['volume'] > df['Volume_SMA'])
        )
        df.loc[sos_condition, 'Event'] = 'SOS'
        sos_count = sos_condition.sum()
        logger.info(f"SOS Events Identified: {sos_count}")

        # Mark-Down Events (SOW)
        markdown_condition = df['Phase'] == 'Mark-Down'
        events_before_min = df['low'].rolling(window=window_5).min().shift(1)
        sow_condition = (
            markdown_condition &
            (df['close'] < events_before_min * (1 - Config.CLOSE_THRESHOLD_PERCENTAGE)) &
            (df['volume'] > df['Volume_SMA'])
        )
        df.loc[sow_condition, 'Event'] = 'SOW'
        sow_count = sow_condition.sum()
        logger.info(f"SOW Events Identified: {sow_count}")

        # Drop rows where Event is still None
        df.dropna(subset=['Event'], inplace=True)
        logger.info("Wyckoff events identified successfully.")
        # Log sample events
        if not df.empty:
            event_counts = df['Event'].value_counts()
            logger.info(f"Event Counts:\n{event_counts}")
            logger.info(f"Sample Event Data:\n{df[['close', 'Phase', 'Event']].head()}")
        else:
            logger.info("No events identified in the fetched data.")

        return df
    except Exception as e:
        logger.error(f"Error identifying Wyckoff events: {e}")
        return df

# ==============================
# Function to Generate Signals
# ==============================
def generate_signals(df):
    """
    Generate trading signals based on identified Wyckoff events.
    """
    logger.info("Generating trading signals.")
    try:
        df['Signal'] = None

        for idx, row in df.iterrows():
            event = row['Event']
            phase = row['Phase']

            if event == 'Spring':
                df.at[idx, 'Signal'] = 'Buy'
                logger.info(f"Buy Signal Generated at {idx}: Event - Spring")
            elif event == 'Upthrust':
                df.at[idx, 'Signal'] = 'Sell'
                logger.info(f"Sell Signal Generated at {idx}: Event - Upthrust")
            elif event == 'SOS':
                df.at[idx, 'Signal'] = 'Buy'
                logger.info(f"Buy Signal Generated at {idx}: Event - SOS")
            elif event == 'SOW':
                df.at[idx, 'Signal'] = 'Sell'
                logger.info(f"Sell Signal Generated at {idx}: Event - SOW")

        # Drop rows where Signal is still None
        df.dropna(subset=['Signal'], inplace=True)
        logger.info("Trading signals generated successfully.")
        # Log count of signals
        signal_counts = df['Signal'].value_counts()
        logger.info(f"Signal Counts:\n{signal_counts}")
        return df
    except Exception as e:
        logger.error(f"Error generating signals: {e}")
        return df

# ==============================
# Function to Execute Trades (Backtest Mode)
# ==============================
def backtest_execute_trade(trades, returns, signal, timestamp, price, position_info):
    """
    Execute a trade in backtest by updating trades and returns lists.
    """
    position = position_info['position']
    entry_price = position_info['entry_price']
    stop_loss_price = position_info['stop_loss_price']
    take_profit_price = position_info['take_profit_price']

    if signal == 'Buy' and not position:
        entry_price = price
        stop_loss_price = entry_price * (1 - Config.STOP_LOSS_PERCENTAGE)
        take_profit_price = entry_price * (1 + Config.TAKE_PROFIT_PERCENTAGE)
        trades.append({'type': 'Buy', 'timestamp': timestamp, 'price': price})
        position = True
        logger.info(f"Trade Executed: Buy at {price} on {timestamp}")
    elif signal == 'Sell' and position:
        trades.append({'type': 'Sell', 'timestamp': timestamp, 'price': price})
        trade_return = (price - entry_price) / entry_price
        returns.append(trade_return)
        logger.info(f"Trade Executed: Sell at {price} on {timestamp}, Return: {trade_return * 100:.2f}%")
        position = False
        entry_price = 0
        stop_loss_price = 0
        take_profit_price = 0

    position_info.update({
        'position': position,
        'entry_price': entry_price,
        'stop_loss_price': stop_loss_price,
        'take_profit_price': take_profit_price
    })

    return position_info

# ==============================
# Function for Wyckoff Strategy Execution (Backtest Mode)
# ==============================
def backtest_strategy(client, symbol, interval, lookback, quantity):
    """
    Backtest the Wyckoff strategy over a specified period.
    """
    logger.info("Starting backtest.")
    df = get_historical_data(client, symbol, interval, lookback)

    if df.empty:
        logger.warning("No data fetched. Exiting backtest.")
        return df, [], []

    logger.info(f"Data Range: {df.index.min()} to {df.index.max()}")
    logger.info(f"Number of Data Points: {len(df)}")

    df = calculate_indicators(df)
    df = identify_wyckoff_phases(df)
    df = identify_wyckoff_events(df)
    df = generate_signals(df)

    # Initialize backtesting variables
    trades = []
    returns = []
    position_info = {
        'position': False,
        'entry_price': 0,
        'stop_loss_price': 0,
        'take_profit_price': 0
    }

    for index, row in df.iterrows():
        price = row['close']
        timestamp = index

        # Check for stop loss or take profit before new signals
        if position_info['position']:
            if price <= position_info['stop_loss_price']:
                trades.append({'type': 'Sell', 'timestamp': timestamp, 'price': price})
                trade_return = (price - position_info['entry_price']) / position_info['entry_price']
                returns.append(trade_return)
                logger.info(f"Trade Executed: Stop Loss at {price} on {timestamp}, Return: {trade_return * 100:.2f}%")
                position_info['position'] = False
                position_info['entry_price'] = 0
                continue  # Skip processing the signal after stop loss
            elif price >= position_info['take_profit_price']:
                trades.append({'type': 'Sell', 'timestamp': timestamp, 'price': price})
                trade_return = (price - position_info['entry_price']) / position_info['entry_price']
                returns.append(trade_return)
                logger.info(f"Trade Executed: Take Profit at {price} on {timestamp}, Return: {trade_return * 100:.2f}%")
                position_info['position'] = False
                position_info['entry_price'] = 0
                continue  # Skip processing the signal after take profit

        signal = row['Signal'] if 'Signal' in row else None
        if signal:
            position_info = backtest_execute_trade(trades, returns, signal, timestamp, price, position_info)

    # Handle open position at the end
    if position_info['position']:
        final_price = df['close'].iloc[-1]
        final_timestamp = df.index[-1]
        trades.append({'type': 'Sell', 'timestamp': final_timestamp, 'price': final_price})
        trade_return = (final_price - position_info['entry_price']) / position_info['entry_price']
        returns.append(trade_return)
        logger.info(f"Final Trade Executed: Sell at {final_price} on {final_timestamp}, Return: {trade_return * 100:.2f}%")
        position_info['position'] = False

    # Calculate Metrics
    if returns:
        total_return = (np.prod([1 + r for r in returns]) - 1) * 100
        avg_return = np.mean(returns) * 100
        num_trades = len(returns)
        win_trades = [r for r in returns if r > 0]
        win_rate = (len(win_trades) / num_trades) * 100 if num_trades > 0 else 0
    else:
        total_return = 0.0
        avg_return = 0.0
        num_trades = 0
        win_rate = 0.0

    # Print Results
    logger.info("\n=== Backtest Results ===")
    logger.info(f"Total Return: {total_return:.2f}%")
    logger.info(f"Average Return per Trade: {avg_return:.2f}%")
    logger.info(f"Number of Trades: {num_trades}")
    logger.info(f"Win Rate: {win_rate:.2f}%")
    logger.info("========================\n")

    # Plot Results
    if Config.PLOT_RESULTS:
        plot_backtest_results(df, trades)

    return df, trades, returns

# ==============================
# Function to Execute Trades (Live Trading)
# ==============================
def execute_live_trade(client, symbol, side, quantity):
    """
    Execute a live trade on Binance Futures.
    """
    try:
        order_type = ORDER_TYPE_MARKET
        side_enum = SIDE_BUY if side == 'Buy' else SIDE_SELL

        # For Futures, specify the position side and other parameters if needed
        order = client.futures_create_order(
            symbol=symbol,
            side=side_enum,
            type=order_type,
            quantity=quantity
            # You can add more parameters here such as reduceOnly, timeInForce, etc.
        )
        logger.info(f"{side} order of {quantity} {symbol} executed.")
        return order
    except BinanceAPIException as e:
        logger.error(f"Binance API Exception during trade execution: {e}")
        return None
    except Exception as e:
        logger.error(f"An error occurred during trade execution: {e}")
        return None

# ==============================
# Function for Wyckoff Strategy Execution (Live Trading)
# ==============================
def wyckoff_strategy_live(client, symbol, interval, lookback, quantity):
    """
    Execute the Wyckoff strategy for live trading.
    """
    df = get_historical_data(client, symbol, interval, lookback)

    if df.empty:
        logger.warning("No data fetched. Exiting strategy execution.")
        return

    df = calculate_indicators(df)
    df = identify_wyckoff_phases(df)
    df = identify_wyckoff_events(df)
    df = generate_signals(df)

    if df.empty:
        logger.info("No trading signals found.")
        return

    # Get the last signal
    last_signal = df['Signal'].iloc[-1]
    try:
        current_price = float(client.futures_mark_price(symbol=symbol)['markPrice'])
    except BinanceAPIException as e:
        logger.error(f"Binance API Exception when fetching mark price: {e}")
        return
    except Exception as e:
        logger.error(f"Error fetching current price: {e}")
        return

    timestamp = datetime.now(timezone.utc)
    logger.info(f"Latest Signal: {last_signal} at price {current_price}")

    # Implement a basic position tracking logic
    # Note: This simplistic approach assumes the script maintains state in variables.
    # For production, consider persisting state externally.
    position = getattr(wyckoff_strategy_live, 'position', False)
    entry_price = getattr(wyckoff_strategy_live, 'entry_price', 0)

    if last_signal == 'Buy' and not position:
        # Place a Buy order
        execute_live_trade(client, symbol, 'Buy', quantity)
        wyckoff_strategy_live.position = True
        wyckoff_strategy_live.entry_price = current_price
        logger.info(f"Bought {quantity} units of {symbol} at {current_price}")
    elif last_signal == 'Sell' and position:
        # Place a Sell order
        execute_live_trade(client, symbol, 'Sell', quantity)
        trade_return = (current_price - entry_price) / entry_price
        wyckoff_strategy_live.position = False
        logger.info(f"Sold {quantity} units of {symbol} at {current_price}, Return: {trade_return * 100:.2f}%")
    else:
        logger.info(f"No actionable signal for {symbol} at {timestamp}.")

    # Save the position state
    setattr(wyckoff_strategy_live, 'position', position)
    setattr(wyckoff_strategy_live, 'entry_price', entry_price)

# ==============================
# Function for Visualization
# ==============================
def plot_backtest_results(df, trades):
    """
    Plot the backtest results with trading signals and technical indicators.
    """
    logger.info("Plotting backtest results.")
    plt.figure(figsize=(18, 12))

    # Plot Close Price and SMAs
    plt.subplot(3, 1, 1)
    plt.plot(df.index, df['close'], label='Close Price', color='blue')
    plt.plot(df.index, df['SMA_SHORT'], label=f'SMA {Config.SMA_SHORT_WINDOW}', color='orange', linestyle='--')
    plt.plot(df.index, df['SMA_LONG'], label=f'SMA {Config.SMA_LONG_WINDOW}', color='magenta', linestyle='--')

    # Plot Buy and Sell Signals
    buy_signals = [trade for trade in trades if trade['type'] == 'Buy']
    sell_signals = [trade for trade in trades if trade['type'] == 'Sell']

    if buy_signals:
        buy_dates = [trade['timestamp'] for trade in buy_signals]
        buy_prices = [trade['price'] for trade in buy_signals]
        plt.scatter(buy_dates, buy_prices, marker='^', color='green', label='Buy Signal', s=100)

    if sell_signals:
        sell_dates = [trade['timestamp'] for trade in sell_signals]
        sell_prices = [trade['price'] for trade in sell_signals]
        plt.scatter(sell_dates, sell_prices, marker='v', color='red', label='Sell Signal', s=100)

    plt.title(f'Wyckoff Strategy Backtest Results for {Config.CURRENCY_PAIR}')
    plt.xlabel('Date')
    plt.ylabel('Price (USDT)')
    plt.legend(loc='best')
    plt.grid(True)

    # Plot RSI
    plt.subplot(3, 1, 2)
    plt.plot(df.index, df['RSI'], label='RSI', color='green')
    plt.axhline(y=Config.RSI_LOWER_THRESHOLD, color='red', linestyle='--', label='RSI Lower Threshold')
    plt.axhline(y=Config.RSI_UPPER_THRESHOLD, color='red', linestyle='--', label='RSI Upper Threshold')
    plt.title('Relative Strength Index (RSI)')
    plt.xlabel('Date')
    plt.ylabel('RSI Value')
    plt.legend(loc='best')
    plt.grid(True)

    # Plot Volume and Force Index
    plt.subplot(3, 1, 3)
    plt.bar(df.index, df['volume'], label='Volume', color='gray', alpha=0.5)
    plt.plot(df.index, df['ForceIndex'], label='Force Index', color='purple')
    plt.title('Volume and Force Index')
    plt.xlabel('Date')
    plt.ylabel('Volume / Force Index')
    plt.legend(loc='best')
    plt.grid(True)

    plt.tight_layout()
    plt.show()
    logger.info("Backtest results plotted successfully.")

# ==============================
# Main Execution Block
# ==============================
def main():
    # Load API keys
    try:
        api_key, api_secret = load_api_keys(Config.DOTENV_PATH)
    except ValueError as ve:
        logger.error(ve)
        return

    # Initialize Binance Futures client
    try:
        client = initialize_futures_client(api_key, api_secret)
    except Exception as e:
        logger.error("Exiting due to client initialization failure.")
        return

    # Run Backtest
    if Config.RUN_BACKTEST:
        logger.info("=== Starting Backtest ===")
        lookback = f"{Config.BACKTEST_DAYS} day ago UTC"

        backtest_df, backtest_trades, backtest_returns = backtest_strategy(
            client,
            Config.CURRENCY_PAIR,
            Config.BACKTEST_TIMEFRAME,
            lookback,
            Config.TRADE_QUANTITY
        )
        logger.info("=== Backtest Completed ===\n")

    # Run Live Trading
    if Config.RUN_LIVE_TRADING:
        logger.info("=== Starting Live Trading ===")
        while True:
            wyckoff_strategy_live(
                client,
                Config.CURRENCY_PAIR,
                Config.TIMEFRAME,
                f"{Config.BACKTEST_DAYS} days ago UTC",
                Config.TRADE_QUANTITY
            )
            logger.info("Waiting for the next interval...\n")
            time.sleep(1800)  # Wait for 30 minutes; adjust based on timeframe

if __name__ == "__main__":
    main()