My python code running in vscode is hanging here: 

PS C:\Users\User\Desktop\binancebot> python wyckoffFUTURES.py
2024-09-25 14:27:06,278 - INFO - Using Binance Futures Testnet.
2024-09-25 14:27:07,573 - INFO - Leverage set to 10x for BTCUSDT.
2024-09-25 14:27:07,574 - INFO - Binance Futures client initialized successfully.
2024-09-25 14:27:07,574 - INFO - === Starting Backtest ===
2024-09-25 14:27:07,575 - INFO - Starting backtest.
2024-09-25 14:27:07,576 - INFO - Fetching historical data for BTCUSDT from 365 day ago UTC to latest


here is the 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 = True  # 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 = 365  # 1 year
    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.API_URL = 'https://testnet.binancefuture.com/fapi'
            client.FUTURES_URL = 'https://testnet.binancefuture.com/fapi'
            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}.")

        # Optional: Set margin type (isolated or cross)
        # Uncomment the following lines if you wish to set margin type
        # client.futures_change_margin_type(symbol=Config.CURRENCY_PAIR, marginType='ISOLATED')  # or 'CROSSED'
        # logger.info(f"Margin type set to ISOLATED 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_str = start_time.strftime("%d %b %Y %H:%M:%S")
            end_str = end_time.strftime("%d %b %Y %H:%M:%S")

            try:
                temp_klines = client.futures_klines(
                    symbol=symbol,
                    interval=interval,
                    start_str=start_str,
                    end_str=end_str,
                    limit=limit
                )
            except BinanceAPIException as e:
                logger.error(f"Binance API Exception: {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
            start_time = datetime.fromtimestamp((last_open_time / 1000) + (interval_ms / 1000), timezone.utc)

        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)
        df.loc[accumulation_condition, 'Event'] = df.loc[accumulation_condition].apply(
            lambda row: 'Spring' if (
                row['low'] < events_before_min.loc[row.name] * (1 - Config.CLOSE_THRESHOLD_PERCENTAGE)
            ) else None, axis=1
        )
        spring_count = (df['Event'] == 'Spring').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)
        df.loc[distribution_condition, 'Event'] = df.loc[distribution_condition].apply(
            lambda row: 'Upthrust' if (
                row['high'] > events_before_max.loc[row.name] * (1 + Config.CLOSE_THRESHOLD_PERCENTAGE)
            ) else None, axis=1
        )
        upthrust_count = (df['Event'] == 'Upthrust').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)
        df.loc[markup_condition, 'Event'] = df.loc[markup_condition].apply(
            lambda row: 'SOS' if (
                row['close'] > events_before_max.loc[row.name] * (1 + Config.CLOSE_THRESHOLD_PERCENTAGE) and
                row['volume'] > row['Volume_SMA']
            ) else None, axis=1
        )
        sos_count = (df['Event'] == 'SOS').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)
        df.loc[markdown_condition, 'Event'] = df.loc[markdown_condition].apply(
            lambda row: 'SOW' if (
                row['close'] < events_before_min.loc[row.name] * (1 - Config.CLOSE_THRESHOLD_PERCENTAGE) and
                row['volume'] > row['Volume_SMA']
            ) else None, axis=1
        )
        sow_count = (df['Event'] == 'SOW').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()