fix the issue in this that it gives warning that logs/closed_trades.csv not found. logs/active_trades.csv not found. and active_trades.csv isnt present for risk summary tab. Fix this these and make sure these files are updated in real time when the orders get processed etc. currently this app is been tested for sending stock orders only i want  to be able to send index and fututres orders too but they need different data. i am attaching some help from the documentation. i want to be able enter entries like this form the csv for them: AVGO	long	215	50%	10%	2000	2%	25%	RTH	Extended	RTH	DAY		Partial	stock	USD
SPY	short	600	50%	10%	2000	2%	25%	RTH	Extended	RTH	DAY		Partial	index	USD
MNQH5	short	21900	50%	10%	2000	2%	25%	RTH	Extended	RTH	DAY		Partial	futures	USD
some documentation :  """

    ``Contract(**kwargs)`` can create any contract using keyword

    arguments. To simplify working with contracts, there are also more

    specialized contracts that take optional positional arguments.

    Some examples::

 

        Contract(conId=270639)

        Stock('AMD', 'SMART', 'USD')

        Stock('INTC', 'SMART', 'USD', primaryExchange='NASDAQ')

        Forex('EURUSD')

        CFD('IBUS30')

        Future('ES', '20180921', 'GLOBEX')

        Option('SPY', '20170721', 240, 'C', 'SMART')

        Bond(secIdType='ISIN', secId='US03076KAA60')

        Crypto('BTC', 'PAXOS', 'USD')

 

    Args:

        conId (int): The unique IB contract identifier.

        symbol (str): The contract (or its underlying) symbol.

        secType (str): The security type:

 

            * 'STK' = Stock (or ETF)

            * 'OPT' = Option

            * 'FUT' = Future

            * 'IND' = Index

            * 'FOP' = Futures option

            * 'CASH' = Forex pair

            * 'CFD' = CFD

            * 'BAG' = Combo

            * 'WAR' = Warrant

            * 'BOND' = Bond

            * 'CMDTY' = Commodity

            * 'NEWS' = News

            * 'FUND' = Mutual fund

            * 'CRYPTO' = Crypto currency

            * 'EVENT' = Bet on an event

        lastTradeDateOrContractMonth (str): The contract's last trading

            day or contract month (for Options and Futures).

            Strings with format YYYYMM will be interpreted as the

            Contract Month whereas YYYYMMDD will be interpreted as

            Last Trading Day.

        strike (float): The option's strike price.

        right (str): Put or Call.

            Valid values are 'P', 'PUT', 'C', 'CALL', or '' for non-options.

        multiplier (str): The instrument's multiplier (i.e. options, futures).

        exchange (str): The destination exchange.

        currency (str): The underlying's currency.

        localSymbol (str): The contract's symbol within its primary exchange.

            For options, this will be the OCC symbol.

        primaryExchange (str): The contract's primary exchange.

            For smart routed contracts, used to define contract in case

            of ambiguity. Should be defined as native exchange of contract,

            e.g. ISLAND for MSFT. For exchanges which contain a period in name,

            will only be part of exchange name prior to period, i.e. ENEXT

            for ENEXT.BE.

        tradingClass (str): The trading class name for this contract.

            Available in TWS contract description window as well.

            For example, GBL Dec '13 future's trading class is "FGBL".

        includeExpired (bool): If set to true, contract details requests

            and historical data queries can be performed pertaining to

            expired futures contracts. Expired options or other instrument

            types are not available.

        secIdType (str): Security identifier type. Examples for Apple:

 

                * secIdType='ISIN', secId='US0378331005'

                * secIdType='CUSIP', secId='037833100'

        secId (str): Security identifier.

        comboLegsDescription (str): Description of the combo legs.

        comboLegs (List[ComboLeg]): The legs of a combined contract definition.

        deltaNeutralContract (DeltaNeutralContract): Delta and underlying

            price for Delta-Neutral combo orders.

    """

 code to fix : # -*- coding: utf-8 -*-
"""
Created on Fri Jan  3 02:06:02 2025

@author: Administrator
"""

# -*- coding: utf-8 -*-
"""
Created on Fri Jan  3 01:35:49 2025

Author: Administrator
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
from ib_insync import *
import csv
from datetime import datetime
import queue
import threading
import asyncio
import os
import traceback
import math
import time

# Initialize the queues for thread-safe communication
ui_queue = queue.Queue()

# Define separate queues for each log type
active_orders_queue = queue.Queue()
active_trades_queue = queue.Queue()
closed_trades_queue = queue.Queue()
active_risk_queue = queue.Queue()

# Define log file paths
LOG_DIR = 'logs'
ACTIVE_ORDERS_LOG = os.path.join(LOG_DIR, 'active_orders.csv')
ACTIVE_TRADES_LOG = os.path.join(LOG_DIR, 'active_trades.csv')
CLOSED_TRADES_LOG = os.path.join(LOG_DIR, 'closed_trades.csv')
GENERAL_LOGS = os.path.join(LOG_DIR, 'general_logs.csv')

# Ensure log directory exists
os.makedirs(LOG_DIR, exist_ok=True)

def round_to_tick(price, tick_size):
    """
    Rounds the price to the nearest tick size.
    """
    return math.floor(price / tick_size) * tick_size

# Function to log general messages
def log_general(message, level='INFO'):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    formatted_message = f"{timestamp} - {level} - {message}"
    ui_queue.put(('log', formatted_message))
    print(formatted_message)  # Also print to console for debugging

    # Append to general_logs.csv
    try:
        with open(GENERAL_LOGS, 'a', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow([timestamp, level, message])
    except Exception as e:
        print(f"Failed to write to general_logs.csv: {e}")

# Function to log active orders
def log_active_order(order_id, symbol, action, quantity, price, status):
    try:
        with open(ACTIVE_ORDERS_LOG, 'a', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow([order_id, symbol, action, quantity, price, status])
    except Exception as e:
        log_general(f"Failed to write to active_orders.csv: {e}", level='ERROR')

# Function to log active trades
def log_active_trade(trade_id, symbol, entry_price, stop_loss_price, quantity, status):
    try:
        with open(ACTIVE_TRADES_LOG, 'a', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow([trade_id, symbol, entry_price, stop_loss_price, quantity, status])
    except Exception as e:
        log_general(f"Failed to write to active_trades.csv: {e}", level='ERROR')

# Function to log closed trades
def log_closed_trade(trade_id, symbol, entry_price, exit_price, profit_loss, exit_time):
    try:
        with open(CLOSED_TRADES_LOG, 'a', newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow([trade_id, symbol, entry_price, exit_price, profit_loss, exit_time])
    except Exception as e:
        log_general(f"Failed to write to closed_trades.csv: {e}", level='ERROR')

# Read the CSV file
def read_daily_entries(csv_file):
    try:
        entries_df = pd.read_csv(csv_file)
        log_general("CSV file read successfully.")

        # Convert percentage strings to float
        percentage_columns = [
            'ManualStopLossAdjustment',      # [8]
            'EntryPriceRangePercentage1',    # [2]
            'EntryPriceRangePercentage2',    # [3]
            'TakeProfitSharePercentage'      # [7]
        ]
        for col in percentage_columns:
            if col in entries_df.columns:
                try:
                    log_general(f"Converting percentage strings to floats in column: {col}")
                    entries_df[col] = pd.to_numeric(entries_df[col].astype(str).str.rstrip('%'), errors='coerce') / 100.0
                    log_general(f"Converted {col} successfully.")
                except Exception as e:
                    log_general(f"Error converting column {col}: {e}", level='ERROR')
            else:
                log_general(f"Column {col} not found in CSV.", level='WARNING')

        # Ensure 'TakeProfitType' column exists and clean up
        if 'TakeProfitType' not in entries_df.columns:
            entries_df['TakeProfitType'] = 'partial'
        else:
            entries_df['TakeProfitType'] = entries_df['TakeProfitType'].fillna('partial').astype(str).str.lower().str.strip()

        return entries_df
    except Exception as e:
        log_general(f"Error reading daily entries: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')
        return pd.DataFrame()  # Return empty DataFrame on error

# Get previous day's high or low
def get_previous_day_price(symbol, price_type, ib):
    try:
        log_general(f"Fetching previous day's {price_type} price for symbol: {symbol}")
        contract = Stock(symbol, 'SMART', 'USD')
        ib.qualifyContracts(contract)
        log_general(f"Contract qualified: {contract}")

        # Retrieve contract details to get minTick
        contract_details = ib.reqContractDetails(contract)
        if not contract_details:
            log_general(f"No contract details found for {symbol}.", level='ERROR')
            return None
        min_tick = contract_details[0].minTick
        if min_tick is None or min_tick <= 0:
            log_general(f"Invalid minTick for {symbol}. Using default tick size of 0.01.", level='WARNING')
            min_tick = 0.01
        contract.minTick = min_tick  # Assign minTick to the contract object

        bars = ib.reqHistoricalData(
            contract,
            endDateTime='',
            durationStr='2 D',
            barSizeSetting='1 day',
            whatToShow='TRADES',
            useRTH=True,
            formatDate=1
        )
        log_general(f"Received {len(bars)} historical bars for {symbol}")

        if len(bars) >= 2:
            prev_day_bar = bars[-2]
            if price_type == 'high':
                log_general(f"Previous day high for {symbol}: {prev_day_bar.high}")
                return prev_day_bar.high  # [9]
            elif price_type == 'low':
                log_general(f"Previous day low for {symbol}: {prev_day_bar.low}")
                return prev_day_bar.low  # [9]
        else:
            log_general(f"Not enough historical data for {symbol}", level='WARNING')
            return None
    except Exception as e:
        log_general(f"Error fetching previous day price for {symbol}: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')
        return None

# Function to create contract based on SecurityType
def create_contract(row, ib):
    security_type = row['SecurityType'].strip().lower()
    symbol = row['Ticker'].strip()
    currency = row.get('Currency', 'USD').strip()
    primary_exchange = row.get('PrimaryExchange', 'SMART').strip()

    try:
        if security_type == 'stock':
            if pd.notna(row.get('PrimaryExchange')):
                contract = Stock(symbol, primary_exchange, currency, primaryExchange=row['PrimaryExchange'])
            else:
                contract = Stock(symbol, primary_exchange, currency)
        elif security_type == 'forex':
            contract = Forex(symbol)
        elif security_type == 'cfd':
            contract = CFD(symbol)
        elif security_type in ['future', 'futures']:
            expiry = row.get('Expiry', '').strip()  # Expected format: YYYYMMDD
            exchange = row.get('Exchange', 'GLOBEX').strip()
            contract = Future(symbol, expiry, exchange)
        elif security_type == 'option':
            expiry = row.get('Expiry', '').strip()  # Expected format: YYYYMMDD
            strike = float(row.get('Strike', 0))
            right = row.get('Right', 'C').strip().upper()  # 'C' or 'P'
            exchange = row.get('Exchange', 'SMART').strip()
            contract = Option(symbol, expiry, strike, right, exchange)
        elif security_type == 'bond':
            sec_id_type = row.get('SecIdType', 'ISIN').strip()
            sec_id = row.get('SecId', '').strip()
            contract = Bond(secIdType=sec_id_type, secId=sec_id)
        elif security_type == 'crypto':
            exchange = row.get('Exchange', 'PAXOS').strip()
            currency = row.get('Currency', 'USD').strip()
            contract = Crypto(symbol, exchange, currency)
        else:
            raise ValueError(f"Unsupported SecurityType: {security_type}")
        
        # Qualify contract and retrieve minTick
        ib.qualifyContracts(contract)
        contract_details = ib.reqContractDetails(contract)
        if not contract_details:
            log_general(f"No contract details found for {symbol}.", level='ERROR')
            return None
        min_tick = contract_details[0].minTick
        if min_tick is None or min_tick <= 0:
            log_general(f"Invalid minTick for {symbol}. Using default tick size of 0.01.", level='WARNING')
            min_tick = 0.01
        contract.minTick = min_tick  # Assign minTick to the contract object

        log_general(f"Created contract for {symbol}: {contract}, minTick: {contract.minTick}")
        return contract
    except Exception as e:
        log_general(f"Error creating contract for symbol {symbol}: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')
        return None

# Place conditional orders
def place_order(row, ib, app_instance):
    try:
        symbol = row['Ticker']  # [4] Ticker
        position_type = row['PositionType']  # [5] Position Type (Long/Short)
        entry_price1 = float(row['EntryPrice1'])  # [11] Entry Price 1
        entry_price2 = float(row['EntryPrice2'])  # [12] Entry Price 2
        quantity = int(row['Quantity'])  # [13] Quantity
        account = str(row.get('IBAccount', 'default_account'))
        entry_time_in_force = row.get('EntryTimeInForce', 'GTC')
        entry_type_hours = row.get('EntryTypeHours', 'rth')
        stop_loss_hours = row.get('StopLossHours', 'rth')
        take_profit_hours = row.get('TakeProfitHours', 'rth')
        take_profit_type = row.get('TakeProfitType', 'partial')

        if pd.isna(symbol):
            log_general(f"Skipping order due to missing 'Ticker' in row: {row}", level='WARNING')
            return None, None

        log_general(f"Placing order for {position_type} position on {symbol}: Quantity={quantity}, StopLimit Prices=({entry_price1}, {entry_price2})")

        # Instantiate the appropriate contract based on SecurityType
        contract = create_contract(row, ib)
        if not contract:
            log_general(f"Failed to create contract for {symbol}. Skipping order placement.", level='ERROR')
            return None, None

        # Retrieve minTick
        tick_size = contract.minTick
        if tick_size is None or tick_size <= 0:
            log_general(f"Invalid or undefined minTick for {symbol}. Using default tick size of 0.01.", level='WARNING')
            tick_size = 0.01  # Default tick size if undefined

        # Round entry prices to conform to minTick
        rounded_entry_price1 = round_to_tick(entry_price1, tick_size)
        rounded_entry_price2 = round_to_tick(entry_price2, tick_size)

        log_general(f"Rounded Entry Prices: {rounded_entry_price1} (Original: {entry_price1}), {rounded_entry_price2} (Original: {entry_price2}) for {symbol}.")

        action = 'BUY' if position_type.lower() == 'long' else 'SELL'
        outside_rth = False if entry_type_hours.lower() == 'rth' else True

        # Create StopLimitOrder with the rounded entry prices
        entry_order = StopLimitOrder(action, quantity, rounded_entry_price2, rounded_entry_price1, tif=entry_time_in_force, outsideRth=outside_rth)

        trade = ib.placeOrder(contract, entry_order)
        log_general(f"Placed {position_type} order for {symbol}: {quantity} contracts StopLimit({rounded_entry_price1:.2f}, {rounded_entry_price2:.2f}), Outside RTH: {outside_rth}")

        log_active_order(
            order_id=trade.order.orderId,
            symbol=symbol,
            action=action,
            quantity=quantity,
            price=rounded_entry_price1,
            status=trade.orderStatus.status
        )

        # Assign callback
        trade.filledEvent += lambda fill: app_instance.enqueue_trade_fill(fill, trade, contract, row, ib)

        return trade, contract
    except Exception as e:
        log_general(f"Error placing order for {row.get('Ticker', 'Unknown')}: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')
        return None, None

# Function to retrieve entry price from active trades log
def get_entry_price(trade_id, symbol, app_instance):
    """
    Retrieves the entry price for a given trade ID and symbol from the active trades log.

    :param trade_id: The ID of the trade.
    :param symbol: The trading symbol.
    :param app_instance: The TradingApp instance.
    :return: The entry price as a float, or None if not found.
    """
    try:
        if not os.path.exists(ACTIVE_TRADES_LOG):
            log_general(f"Active trades log file not found: {ACTIVE_TRADES_LOG}", level='ERROR')
            return None
        
        active_trades_df = pd.read_csv(ACTIVE_TRADES_LOG, header=None)
        # Ensure columns are correctly named
        active_trades_df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status']
        trade_row = active_trades_df[(active_trades_df['Trade ID'] == trade_id) & (active_trades_df['Symbol'] == symbol)]
        
        if not trade_row.empty:
            return float(trade_row.iloc[0]['Entry Price'])
        else:
            log_general(f"No active trade found with Trade ID {trade_id} and Symbol {symbol}.", level='ERROR')
            return None
    except Exception as e:
        log_general(f"Error retrieving entry price for Trade ID {trade_id}, Symbol {symbol}: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')
        return None

def remove_closed_trade(trade_id, symbol):
    """
    Removes a closed trade from the active trades log.

    :param trade_id: The ID of the trade.
    :param symbol: The trading symbol.
    """
    try:
        if not os.path.exists(ACTIVE_TRADES_LOG):
            log_general(f"Active trades log file not found: {ACTIVE_TRADES_LOG}", level='ERROR')
            return
        
        active_trades_df = pd.read_csv(ACTIVE_TRADES_LOG, header=None)
        active_trades_df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status']
        updated_trades_df = active_trades_df[(active_trades_df['Trade ID'] != trade_id) | (active_trades_df['Symbol'] != symbol)]
        
        # Overwrite the active_trades_log with updated data
        updated_trades_df.to_csv(ACTIVE_TRADES_LOG, index=False, header=False)
        log_general(f"Removed closed trade {trade_id} for {symbol} from Active Trades log.")
    except Exception as e:
        log_general(f"Error removing closed trade {trade_id} for {symbol}: {e}", level='ERROR')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='ERROR')

# Main trading logic
def main_trading(csv_file, ib, app_instance, stop_event):
    log_general("Starting main trading function.")
    entries_df = read_daily_entries(csv_file)

    if entries_df.empty:
        log_general("No entries to process. Exiting trading function.", level='WARNING')
        print("No entries to process. Exiting trading function.")
        return

    placed_trades = []

    for index, row in entries_df.iterrows():
        if stop_event.is_set():
            log_general("Stop event set. Exiting main trading loop.")
            break

        symbol = row.get('Ticker')  # [4] Ticker
        position_type = row.get('PositionType')  # [5] Position Type (Long/Short)
        initial_stop_loss = float(row.get('InitialStopLoss'))  # [6] Stop Loss Price
        risk_amount = float(row.get('RiskAmount'))  # [1] Risk $ to calculate position size
        entry_percentage1 = float(row.get('EntryPriceRangePercentage1'))  # [2] % to calculate price range for entry 1
        entry_percentage2 = float(row.get('EntryPriceRangePercentage2'))  # [3] % to calculate price range for entry 2
        take_profit_share_percentage = float(row.get('TakeProfitSharePercentage', 0.5))  # [7] Take Profit Share %
        manual_stop_loss_adj = float(row.get('ManualStopLossAdjustment', 0.1))  # [8] Manual SL Adjustment %

        # Other parameters...
        ib_account = row.get('IBAccount', 'default_account')
        entry_time_in_force = row.get('EntryTimeInForce', 'GTC')
        entry_type_hours = row.get('EntryTypeHours', 'rth')
        stop_loss_hours = row.get('StopLossHours', 'rth')
        take_profit_hours = row.get('TakeProfitHours', 'rth')
        take_profit_type = row.get('TakeProfitType', 'partial')

        if pd.isna(symbol):
            log_general(f"Skipping entry at Index: {index} due to missing 'Ticker'.", level='WARNING')
            print(f"Skipping entry at Index: {index} due to missing 'Ticker'.")
            continue

        log_general(f"Processing entry for {symbol} (Index: {index}).")
        print(f"Processing entry for {symbol} (Index: {index}).")

        # [9] Get Previous Day Price (High for Long, Low for Short)
        price_type = 'high' if position_type.lower() == 'long' else 'low'
        prev_price = get_previous_day_price(symbol, price_type, ib)  # [9] Previous day's high/low price

        if prev_price is None:
            log_general(f"Couldn't retrieve previous day's price for {symbol}. Skipping entry.", level='WARNING')
            print(f"Couldn't retrieve previous day's price for {symbol}. Skipping entry.")
            continue

        try:
            if position_type.lower() == 'long':
                # [10] Risk per Contract = Previous Day High - Stop Loss Price
                risk_per_unit = prev_price - initial_stop_loss  # [10] = [9] - [6]
                # [11] Entry Price 1 = ([2] * [10]) + [9]
                entry_price1 = (entry_percentage1 * risk_per_unit) + prev_price  # [11]
                # [12] Entry Price 2 = ([3] * [10]) + [9]
                entry_price2 = (entry_percentage2 * risk_per_unit) + prev_price  # [12]
            else:
                # For Short positions
                risk_per_unit = initial_stop_loss - prev_price  # [10] = [6] - [9]
                # [11] Entry Price 1 = [9] - ([2] * [10])
                entry_price1 = prev_price - (entry_percentage1 * risk_per_unit)  # [11]
                # [12] Entry Price 2 = [9] - ([3] * [10])
                entry_price2 = prev_price - (entry_percentage2 * risk_per_unit)  # [12]

            log_general(f"Calculated risk_per_unit: {risk_per_unit}, EntryPrice1: {entry_price1}, EntryPrice2: {entry_price2} for {symbol}.")
            print(f"Calculated risk_per_unit: {risk_per_unit}, EntryPrice1: {entry_price1}, EntryPrice2: {entry_price2} for {symbol}.")

            # [13] Contracts to Buy = RoundDown([1] / [10])
            if risk_per_unit == 0:
                log_general(f"Risk per unit is zero for {symbol}. Skipping entry.", level='ERROR')
                print(f"Risk per unit is zero for {symbol}. Skipping entry.")
                continue

            adj_risk_per_unit = abs(entry_price1 - initial_stop_loss)  # [17]
            
            # Contracts to buy
            quantity = int(risk_amount / adj_risk_per_unit)  # [13]

            if quantity <= 0:
                log_general(f"Calculated quantity {quantity} for {symbol} is not positive. Skipping entry.", level='WARNING')
                print(f"Calculated quantity {quantity} for {symbol} is not positive. Skipping entry.")
                continue

        except ZeroDivisionError:
            log_general(f"Risk per unit is zero for {symbol}. Skipping entry.", level='ERROR')
            print(f"Risk per unit is zero for {symbol}. Skipping entry.")
            continue
        except TypeError:
            log_general(f"Invalid data type for 'RiskAmount' or 'RiskPerUnit' for {symbol}. Skipping entry.", level='ERROR')
            print(f"Invalid data type for 'RiskAmount' or 'RiskPerUnit' for {symbol}. Skipping entry.")
            continue

        log_general(f"Calculated quantity: {quantity} for {symbol} based on risk_amount: {risk_amount} and risk_per_unit: {risk_per_unit}.")
        print(f"Calculated quantity: {quantity} for {symbol} based on risk_amount: {risk_amount} and risk_per_unit: {risk_per_unit}.")

        # Store calculated values back into the DataFrame
        entries_df.at[index, 'RiskPerUnit'] = adj_risk_per_unit  # [10] #changing to adj version
        entries_df.at[index, 'EntryPrice1'] = entry_price1  # [11]
        entries_df.at[index, 'EntryPrice2'] = entry_price2  # [12]
        entries_df.at[index, 'Quantity'] = quantity  # [13]
        entries_df.at[index, 'TakeProfitSharePercentage'] = take_profit_share_percentage  # [7]
        entries_df.at[index, 'ManualStopLossAdjustment'] = manual_stop_loss_adj  # [8]
        entries_df.at[index, 'IBAccount'] = ib_account
        entries_df.at[index, 'EntryTypeHours'] = entry_type_hours
        entries_df.at[index, 'StopLossHours'] = stop_loss_hours
        entries_df.at[index, 'TakeProfitHours'] = take_profit_hours
        entries_df.at[index, 'TakeProfitType'] = take_profit_type

        print(f"Updated Row at Index {index}:")
        print(entries_df.loc[index])
        log_general(f"Updated Row at Index {index}:\n{entries_df.loc[index].to_string()}")

        if pd.notna(entry_price1):
            trade, contract = place_order(entries_df.loc[index], ib, app_instance)
            if trade and contract:
                placed_trades.append(trade)
                log_general(f"Order placed and callback assigned for {symbol} at Index: {index}.")
                print(f"Order placed and callback assigned for {symbol} at Index: {index}.")
        else:
            log_general(f"Skipping order placement for {symbol} at Index: {index} due to missing EntryPrice1.", level='WARNING')
            print(f"Skipping order placement for {symbol} at Index: {index} due to missing EntryPrice1.")

    log_general("All entry orders placed. Awaiting fills...")
    print("All entry orders placed. Awaiting fills...")

class TradingApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Trading Interface")
        self.geometry("1400x800")
        self.ib_instance = None  # Initialize ib_instance as None
        self.ib_lock = threading.Lock()  # Initialize a lock for thread safety
        self.stop_event = threading.Event()  # Event to signal threads to stop
        log_general("Initialized TradingApp GUI.")

        self.notebook = ttk.Notebook(self)
        self.notebook.pack(expand=1, fill='both')

        # Define tabs
        self.active_orders_tab = ttk.Frame(self.notebook)
        self.active_trades_tab = ttk.Frame(self.notebook)
        self.active_risk_tab = ttk.Frame(self.notebook)
        self.closed_trades_tab = ttk.Frame(self.notebook)
        self.settings_tab = ttk.Frame(self.notebook)
        self.logs_tab = ttk.Frame(self.notebook)

        # Add tabs to notebook
        self.notebook.add(self.active_orders_tab, text='Active Orders')
        self.notebook.add(self.active_trades_tab, text='Active Trades')
        self.notebook.add(self.active_risk_tab, text='Active Risk Summary')
        self.notebook.add(self.closed_trades_tab, text='Closed Trades')
        self.notebook.add(self.settings_tab, text='Settings')
        self.notebook.add(self.logs_tab, text='Logs')

        # Initialize each tab
        self.init_active_orders_tab()
        self.init_active_trades_tab()
        self.init_active_risk_tab()
        self.init_closed_trades_tab()
        self.init_settings_tab()
        self.init_logs_tab()

        # Import CSV button
        self.import_button = ttk.Button(self, text="Import CSV", command=self.import_csv)
        self.import_button.pack(pady=10)
        log_general("Added Import CSV button to GUI.")

        # Start worker threads for log updates
        self.start_worker_threads()

        # Start processing the UI queue and updating the UI from logs
        self.after(1000, self.process_queue)
        # Start the periodic UI update loop
        self.after(1000, self.update_ui_from_logs_and_queues)

        # Handle window close to ensure threads are terminated
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

        # Initialize a queue for handling trade fills
        self.trade_fill_queue = queue.Queue()

        # Start a background thread to process trade fills
        threading.Thread(target=self.process_trade_fills, daemon=True).start()

    # Function to start worker threads
    def start_worker_threads(self):
        # Worker for Active Orders
        threading.Thread(target=self.active_orders_worker, daemon=True).start()
        # Worker for Active Trades
        threading.Thread(target=self.active_trades_worker, daemon=True).start()
        # Worker for Closed Trades
        threading.Thread(target=self.closed_trades_worker, daemon=True).start()
        # Worker for Active Risk Summary
        threading.Thread(target=self.active_risk_worker, daemon=True).start()
        log_general("Started all worker threads for log updates.")

    # Worker functions to read CSV logs and enqueue data
    def active_orders_worker(self):
        while not self.stop_event.is_set():
            try:
                if os.path.exists(ACTIVE_ORDERS_LOG):
                    df = pd.read_csv(ACTIVE_ORDERS_LOG, header=None)
                    df.columns = ['Order ID', 'Symbol', 'Action', 'Quantity', 'Price', 'Status']
                    active_orders_queue.put(df)
                else:
                    log_general(f"Active orders log file not found: {ACTIVE_ORDERS_LOG}", level='WARNING')
            except Exception as e:
                log_general(f"Error reading active orders: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')
            finally:
                # Wait for 2 seconds before next read
                time.sleep(2)

    def active_trades_worker(self):
        while not self.stop_event.is_set():
            try:
                if os.path.exists(ACTIVE_TRADES_LOG):
                    df = pd.read_csv(ACTIVE_TRADES_LOG, header=None)
                    df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status']
                    active_trades_queue.put(df)
                else:
                    log_general(f"Active trades log file not found: {ACTIVE_TRADES_LOG}", level='WARNING')
            except Exception as e:
                log_general(f"Error reading active trades: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')
            finally:
                time.sleep(2)

    def closed_trades_worker(self):
        while not self.stop_event.is_set():
            try:
                if os.path.exists(CLOSED_TRADES_LOG):
                    df = pd.read_csv(CLOSED_TRADES_LOG, header=None)
                    df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Exit Price', 'Profit/Loss', 'Exit Time']
                    closed_trades_queue.put(df)
                else:
                    log_general(f"Closed trades log file not found: {CLOSED_TRADES_LOG}", level='WARNING')
            except Exception as e:
                log_general(f"Error reading closed trades: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')
            finally:
                time.sleep(2)

    def active_risk_worker(self):
        while not self.stop_event.is_set():
            try:
                if os.path.exists(ACTIVE_TRADES_LOG):
                    df = pd.read_csv(ACTIVE_TRADES_LOG, header=None)
                    df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status']
                    active_risk_queue.put(df)
                else:
                    log_general(f"Active trades log file not found for risk summary: {ACTIVE_TRADES_LOG}", level='WARNING')
            except Exception as e:
                log_general(f"Error reading active risk summary: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')
            finally:
                time.sleep(2)

    # Initialize Active Orders Tab
    def init_active_orders_tab(self):
        try:
            columns = ('Order ID', 'Symbol', 'Action', 'Quantity', 'Price', 'Status')
            self.active_orders_tree = ttk.Treeview(self.active_orders_tab, columns=columns, show='headings')
            for col in columns:
                self.active_orders_tree.heading(col, text=col)
                self.active_orders_tree.column(col, width=200, anchor='center')
            self.active_orders_tree.pack(expand=True, fill='both', padx=10, pady=10)

            vsb = ttk.Scrollbar(self.active_orders_tab, orient="vertical", command=self.active_orders_tree.yview)
            self.active_orders_tree.configure(yscrollcommand=vsb.set)
            vsb.pack(side='right', fill='y')

            log_general("Initialized Active Orders tab.")
        except Exception as e:
            log_general(f"Error initializing Active Orders tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize Active Trades Tab
    def init_active_trades_tab(self):
        try:
            columns = ('Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status', 'Entry Time', 'Current Price', 'Unrealized P/L')
            self.active_trades_tree = ttk.Treeview(self.active_trades_tab, columns=columns, show='headings')
            for col in columns:
                self.active_trades_tree.heading(col, text=col)
                self.active_trades_tree.column(col, width=150, anchor='center')
            self.active_trades_tree.pack(expand=True, fill='both', padx=10, pady=10)

            vsb = ttk.Scrollbar(self.active_trades_tab, orient="vertical", command=self.active_trades_tree.yview)
            self.active_trades_tree.configure(yscrollcommand=vsb.set)
            vsb.pack(side='right', fill='y')

            log_general("Initialized Active Trades tab.")
        except Exception as e:
            log_general(f"Error initializing Active Trades tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize Active Risk Summary Tab
    def init_active_risk_tab(self):
        try:
            summary_frame = ttk.Frame(self.active_risk_tab)
            summary_frame.pack(fill='both', expand=True, padx=20, pady=20)

            columns = ('Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Risk', 'Status')
            self.active_risk_tree = ttk.Treeview(summary_frame, columns=columns, show='headings')
            for col in columns:
                self.active_risk_tree.heading(col, text=col)
                self.active_risk_tree.column(col, width=150, anchor='center')
            self.active_risk_tree.pack(expand=True, fill='both', padx=10, pady=10)

            vsb = ttk.Scrollbar(summary_frame, orient="vertical", command=self.active_risk_tree.yview)
            self.active_risk_tree.configure(yscrollcommand=vsb.set)
            vsb.pack(side='right', fill='y')

            self.refresh_risk_button = ttk.Button(summary_frame, text="Refresh", command=self.enqueue_risk_summary_update)
            self.refresh_risk_button.pack(pady=10)

            # Label to display total risk
            self.total_risk_label = ttk.Label(summary_frame, text="Total Risk Exposure: $0.00", font=("Helvetica", 14, "bold"))
            self.total_risk_label.pack(pady=10)

            log_general("Initialized Active Risk Summary tab.")
        except Exception as e:
            log_general(f"Error initializing Active Risk Summary tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize Closed Trades Tab
    def init_closed_trades_tab(self):
        try:
            columns = ('Trade ID', 'Symbol', 'Entry Price', 'Exit Price', 'Profit/Loss', 'Exit Time')
            self.closed_trades_tree = ttk.Treeview(self.closed_trades_tab, columns=columns, show='headings')
            for col in columns:
                self.closed_trades_tree.heading(col, text=col)
                self.closed_trades_tree.column(col, width=150, anchor='center')
            self.closed_trades_tree.pack(expand=True, fill='both', padx=10, pady=10)

            vsb = ttk.Scrollbar(self.closed_trades_tab, orient="vertical", command=self.closed_trades_tree.yview)
            self.closed_trades_tree.configure(yscrollcommand=vsb.set)
            vsb.pack(side='right', fill='y')

            log_general("Initialized Closed Trades tab.")
        except Exception as e:
            log_general(f"Error initializing Closed Trades tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize Settings Tab
    def init_settings_tab(self):
        try:
            default_frame = ttk.LabelFrame(self.settings_tab, text="Default Trade Settings")
            default_frame.pack(fill='x', padx=10, pady=10)

            ttk.Label(default_frame, text="Default Risk Amount:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
            self.default_risk_amount = ttk.Entry(default_frame)
            self.default_risk_amount.grid(row=0, column=1, padx=5, pady=5)
            self.default_risk_amount.insert(0, '2000')  # [1] Default Risk $

            log_general("Initialized Default Trade Settings frame.")

            ib_frame = ttk.LabelFrame(self.settings_tab, text="IB Connection")
            ib_frame.pack(fill='x', padx=10, pady=10)

            ttk.Label(ib_frame, text="Host:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
            self.ib_host = ttk.Entry(ib_frame)
            self.ib_host.grid(row=0, column=1, padx=5, pady=5)
            self.ib_host.insert(0, '127.0.0.1')

            ttk.Label(ib_frame, text="Port:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
            self.ib_port = ttk.Entry(ib_frame)
            self.ib_port.grid(row=1, column=1, padx=5, pady=5)
            self.ib_port.insert(0, '4002')  # Default IB port

            ttk.Label(ib_frame, text="Client ID:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
            self.ib_client_id = ttk.Entry(ib_frame)
            self.ib_client_id.grid(row=2, column=1, padx=5, pady=5)
            self.ib_client_id.insert(0, '1')

            self.connect_button = ttk.Button(ib_frame, text="Connect", command=self.connect_ib)
            self.connect_button.grid(row=3, column=0, columnspan=2, pady=10)

            log_general("Initialized IB Connection frame.")

            comm_frame = ttk.LabelFrame(self.settings_tab, text="Communication Setup")
            comm_frame.pack(fill='x', padx=10, pady=10)

            ttk.Label(comm_frame, text="Email:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
            self.comm_email = ttk.Entry(comm_frame)
            self.comm_email.grid(row=0, column=1, padx=5, pady=5)

            ttk.Label(comm_frame, text="Text:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
            self.comm_text = ttk.Entry(comm_frame)
            self.comm_text.grid(row=1, column=1, padx=5, pady=5)

            log_general("Initialized Communication Setup frame.")

            logs_frame = ttk.LabelFrame(self.settings_tab, text="Logs")
            logs_frame.pack(fill='x', padx=10, pady=10)

            self.settings_logs = tk.Text(logs_frame, height=10)
            self.settings_logs.pack(fill='x', padx=5, pady=5)

            log_general("Initialized Logs frame in Settings tab.")
        except Exception as e:
            log_general(f"Error initializing Settings tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize Logs Tab
    def init_logs_tab(self):
        try:
            self.logs_text = tk.Text(self.logs_tab, state='disabled')
            self.logs_text.pack(expand=True, fill='both', padx=10, pady=10)
            log_general("Initialized Logs tab.")
        except Exception as e:
            log_general(f"Error initializing Logs tab: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Connect to Interactive Brokers
    def connect_ib(self):
        try:
            host = self.ib_host.get()
            port = int(self.ib_port.get())
            client_id = int(self.ib_client_id.get())

            log_general(f"Attempting to set connection parameters: {host}:{port} Client ID {client_id}.")
            messagebox.showinfo("Connection", "Connect via Import CSV to start trading thread.")
        except Exception as e:
            messagebox.showerror("Connection Error", str(e))
            log_general(f"Failed to set connection parameters: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Import CSV and start trading thread
    def import_csv(self):
        try:
            file_path = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")])
            if file_path:
                log_general(f"Imported CSV file: {file_path}")
                trading_thread = threading.Thread(target=self.start_trading, args=(file_path,), daemon=True)
                trading_thread.start()
                log_general("Started trading thread.")
        except Exception as e:
            log_general(f"Error importing CSV file: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Start trading logic in a separate thread
    def start_trading(self, csv_file):
        try:
            log_general("Starting trading thread.")
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)

            ib = IB()
            host = self.ib_host.get()
            port = int(self.ib_port.get())
            client_id = int(self.ib_client_id.get())

            log_general(f"Connecting to IB at {host}:{port} with Client ID {client_id}.")
            ib.connect(host, port, clientId=client_id)

            with self.ib_lock:
                if ib.isConnected():
                    log_general("Successfully connected to IB.")
                    self.ib_instance = ib  # Assign ib_instance here
                else:
                    log_general("Failed to connect to IB.", level='ERROR')
                    return

            # Start trading logic
            main_trading(csv_file, ib, self, self.stop_event)

            # Start the event loop
            ib.run()
        except Exception as e:
            log_general(f"Error in trading thread: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Function to enqueue trade fills for background processing
    def enqueue_trade_fill(self, fill, trade, contract, row, ib):
        self.trade_fill_queue.put((fill, trade, contract, row, ib))

    # Background thread to process trade fills
    def process_trade_fills(self):
        while not self.stop_event.is_set():
            try:
                fill, trade, contract, row, ib = self.trade_fill_queue.get(timeout=1)
                threading.Thread(target=self.handle_trade_fill, args=(fill, trade, contract, row, ib), daemon=True).start()
            except queue.Empty:
                continue
            except Exception as e:
                log_general(f"Error processing trade fills: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Handle trade fill in a separate thread
    def handle_trade_fill(self, fill, trade, contract, row, ib):
        try:
            log_general(f"Trade fill detected for Order ID {trade.order.orderId} - {contract.symbol}")
            # Start profit management only once
            if not hasattr(trade, 'profit_managed'):
                trade.profit_managed = True
                # Enqueue profit management task
                self.trade_fill_queue.put(('profit_management', fill, trade, contract, row, ib))
        except Exception as e:
            log_general(f"Error in handle_trade_fill for Order ID {trade.order.orderId}: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Handle profit management from the queue
    def handle_profit_management(self, fill, trade, contract, row, ib):
        symbol = row['Ticker']
        position_type = row['PositionType']
        take_profit_share_percentage = row['TakeProfitSharePercentage']
        manual_stop_loss_adj = row['ManualStopLossAdjustment']
        risk_amount = float(row['RiskAmount'])
        account = str(row['IBAccount'])
        take_profit_hours = row['TakeProfitHours']
        take_profit_type = row['TakeProfitType']

        log_general(f"Starting profit management for {symbol}.")

        filled_price = float(trade.orderStatus.avgFillPrice)
        total_quantity = int(trade.orderStatus.filled)

        log_general(f"Order {trade.order.orderId} filled at ${filled_price:.2f} for {total_quantity} contracts.")

        initial_stop_loss = float(row['InitialStopLoss'])

        # Calculate Risk After Entry
        if position_type.lower() == 'long':
            risk_after_entry = filled_price - initial_stop_loss
        else:
            risk_after_entry = initial_stop_loss - filled_price

        # Calculate Stop Loss Price
        if position_type.lower() == 'long':
            stop_loss_price = filled_price - (manual_stop_loss_adj * risk_after_entry)
        else:
            stop_loss_price = filled_price + (manual_stop_loss_adj * risk_after_entry)

        log_general(f"Calculated risk_after_entry: {risk_after_entry}, Stop Loss Price: {stop_loss_price} for {symbol}.")

        log_active_trade(
            trade_id=trade.order.orderId,
            symbol=symbol,
            entry_price=filled_price,
            stop_loss_price=stop_loss_price,
            quantity=total_quantity,
            status=trade.orderStatus.status
        )

        # Define stop loss order
        if position_type.lower() == 'long':
            stop_action = 'SELL'
        else:
            stop_action = 'BUY'

        sl_outside_rth = False if row['StopLossHours'].lower() == 'rth' else True

        # Retrieve minTick for stop loss price adjustment
        tick_size = contract.minTick
        if tick_size is None or tick_size <= 0:
            log_general(f"Invalid or undefined minTick for {symbol}. Using default tick size of 0.01.", level='WARNING')
            tick_size = 0.01  # Default tick size if undefined

        # Round stop_loss_price to conform to minTick
        rounded_sl_price = round_to_tick(stop_loss_price, tick_size)
        log_general(f"Rounded Stop Loss Price: {rounded_sl_price} (Original: {stop_loss_price}) for {symbol}.")

        sl_order = StopOrder(stop_action, total_quantity, rounded_sl_price, tif='GTC', outsideRth=sl_outside_rth)
        ib.placeOrder(contract, sl_order)
        log_general(f"Placed Stop Loss order at ${rounded_sl_price:.2f}, Outside RTH: {sl_outside_rth}")

        log_active_order(
            order_id=sl_order.orderId,
            symbol=symbol,
            action=stop_action,
            quantity=total_quantity,
            price=rounded_sl_price,
            status="Submitted"
        )

        remaining_quantity = total_quantity
        R_level = 1  # Assuming 1R for risk-reward ratio
        entry_price = filled_price
        tp_outside_rth = False if take_profit_hours.lower() == 'rth' else True

        if take_profit_type == 'partial':
            log_general(f"Using partial Take Profit strategy for {symbol}.")

            tp_quantity = max(int(remaining_quantity * take_profit_share_percentage), 1)  # Adjust as needed
            tp_price_raw = filled_price + (R_level * risk_after_entry) if position_type.lower() == 'long' else filled_price - (R_level * risk_after_entry)

            # Round tp_price to conform to minTick
            tp_price = round_to_tick(tp_price_raw, tick_size)
            log_general(f"Calculated Take Profit price: ${tp_price:.2f} (Raw: {tp_price_raw:.2f}), Quantity: {tp_quantity} for {symbol}.")

            tp_order = LimitOrder(self.take_profit_action(position_type), tp_quantity, tp_price, tif='GTC', outsideRth=tp_outside_rth)
            tp_trade = ib.placeOrder(contract, tp_order)
            log_general(f"Placed Take Profit order at ${tp_price:.2f} for {tp_quantity} contracts, Outside RTH: {tp_outside_rth}")

            log_active_order(
                order_id=tp_trade.order.orderId,
                symbol=symbol,
                action=self.take_profit_action(position_type),
                quantity=tp_quantity,
                price=tp_price,
                status=tp_trade.orderStatus.status
            )

            # Monitor the TP order via callback
            tp_trade.filledEvent += lambda fill: self.enqueue_tp_fill(fill, contract, position_type, rounded_sl_price, remaining_quantity, sl_order, tp_trade, ib)
        else:
            log_general(f"Using full Take Profit strategy for {symbol}.")

            desired_R = 1  # 1R risk-reward
            tp_price_raw = filled_price + (desired_R * risk_after_entry) if position_type.lower() == 'long' else filled_price - (desired_R * risk_after_entry)

            # Round tp_price to conform to minTick
            tp_price = round_to_tick(tp_price_raw, tick_size)
            log_general(f"Calculated Take Profit price: ${tp_price:.2f} (Raw: {tp_price_raw:.2f}) for full quantity.")

            tp_order = LimitOrder(self.take_profit_action(position_type), remaining_quantity, tp_price, tif='GTC', outsideRth=tp_outside_rth)
            tp_trade = ib.placeOrder(contract, tp_order)
            log_general(f"Placed Take Profit order at ${tp_price:.2f} for {remaining_quantity} contracts, Outside RTH: {tp_outside_rth}")

            log_active_order(
                order_id=tp_trade.order.orderId,
                symbol=symbol,
                action=self.take_profit_action(position_type),
                quantity=remaining_quantity,
                price=tp_price,
                status=tp_trade.orderStatus.status
            )

            # Monitor the TP order via callback
            tp_trade.filledEvent += lambda fill: self.enqueue_tp_fill(fill, contract, position_type, rounded_sl_price, remaining_quantity, sl_order, tp_trade, ib)

    # Determine action based on position type
    def take_profit_action(self, position_type):
        return 'SELL' if position_type.lower() == 'long' else 'BUY'

    # Enqueue Take Profit fill for background processing
    def enqueue_tp_fill(self, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib):
        self.trade_fill_queue.put(('tp_fill', fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib))

    # Handle take profit fill in background
    def handle_tp_fill(self, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib):
        try:
            symbol = contract.symbol
            filled_quantity = fill.size
            log_general(f"Take Profit order {tp_trade.order.orderId} filled at ${tp_trade.lmtPrice:.2f} for {symbol}.")

            # Retrieve the entry price from active trades log
            entry_price = get_entry_price(tp_trade.order.orderId, symbol, self)
            if entry_price is None:
                log_general(f"Cannot log closed trade for Order ID {tp_trade.order.orderId} due to missing entry price.", level='ERROR')
                return

            exit_price = float(tp_trade.lmtPrice)  # Ensure it's a float
            profit_loss = (exit_price - entry_price) * filled_quantity if position_type.lower() == 'long' else (entry_price - exit_price) * filled_quantity
            exit_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            log_closed_trade(tp_trade.order.orderId, symbol, entry_price, exit_price, profit_loss, exit_time)

            remaining_quantity -= filled_quantity

            ib.cancelOrder(sl_order)
            log_general(f"Canceled Stop Loss order {sl_order.orderId} for {symbol}.")

            if remaining_quantity > 0:
                # Adjust stop loss for remaining contracts
                new_sl_order = StopOrder(sl_order.action, remaining_quantity, stop_loss_price, tif='GTC', outsideRth=sl_order.outsideRth)
                ib.placeOrder(contract, new_sl_order)
                log_general(f"Placed new Stop Loss order at ${stop_loss_price:.2f} for remaining {remaining_quantity} contracts.")

                log_active_order(
                    order_id=new_sl_order.orderId,
                    symbol=symbol,
                    action=new_sl_order.action,
                    quantity=remaining_quantity,
                    price=stop_loss_price,
                    status=new_sl_order.orderStatus.status
                )
            else:
                log_general(f"Position for {symbol} fully closed.")
                # Remove the trade from active trades log
                remove_closed_trade(tp_trade.order.orderId, symbol)
        except Exception as e:
            log_general(f"Error in handle_tp_fill callback for Order ID {tp_trade.order.orderId}: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Process trade fills from the queue
    def process_trade_fills_from_queue(self):
        while not self.trade_fill_queue.empty():
            try:
                task = self.trade_fill_queue.get_nowait()
                if task[0] == 'profit_management':
                    _, fill, trade, contract, row, ib = task
                    self.handle_profit_management(fill, trade, contract, row, ib)
                elif task[0] == 'tp_fill':
                    _, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib = task
                    self.handle_tp_fill(fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib)
            except queue.Empty:
                break
            except Exception as e:
                log_general(f"Error processing trade fills from queue: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Function to enqueue risk summary update
    def enqueue_risk_summary_update(self):
        self.active_risk_queue.put('update_risk')

    # Background thread to handle profit management
    def handle_profit_management(self, fill, trade, contract, row, ib):
        # This function can be expanded if additional processing is needed
        self.profit_management(trade, contract, row, ib, self)

    # Profit management after a trade is filled
    def profit_management(self, trade, contract, row, ib, app_instance):
        # [Implementation remains the same as in the original code]
        symbol = row['Ticker']
        position_type = row['PositionType']
        take_profit_share_percentage = row['TakeProfitSharePercentage']
        manual_stop_loss_adj = row['ManualStopLossAdjustment']
        risk_amount = float(row['RiskAmount'])
        account = str(row['IBAccount'])
        take_profit_hours = row['TakeProfitHours']
        take_profit_type = row['TakeProfitType']

        log_general(f"Starting profit management for {symbol}.")

        filled_price = float(trade.orderStatus.avgFillPrice)
        total_quantity = int(trade.orderStatus.filled)

        log_general(f"Order {trade.order.orderId} filled at ${filled_price:.2f} for {total_quantity} contracts.")

        initial_stop_loss = float(row['InitialStopLoss'])

        # Calculate Risk After Entry
        if position_type.lower() == 'long':
            risk_after_entry = filled_price - initial_stop_loss
        else:
            risk_after_entry = initial_stop_loss - filled_price

        # Calculate Stop Loss Price
        if position_type.lower() == 'long':
            stop_loss_price = filled_price - (manual_stop_loss_adj * risk_after_entry)
        else:
            stop_loss_price = filled_price + (manual_stop_loss_adj * risk_after_entry)

        log_general(f"Calculated risk_after_entry: {risk_after_entry}, Stop Loss Price: {stop_loss_price} for {symbol}.")

        log_active_trade(
            trade_id=trade.order.orderId,
            symbol=symbol,
            entry_price=filled_price,
            stop_loss_price=stop_loss_price,
            quantity=total_quantity,
            status=trade.orderStatus.status
        )

        # Define stop loss order
        if position_type.lower() == 'long':
            stop_action = 'SELL'
        else:
            stop_action = 'BUY'

        sl_outside_rth = False if row['StopLossHours'].lower() == 'rth' else True

        # Retrieve minTick for stop loss price adjustment
        tick_size = contract.minTick
        if tick_size is None or tick_size <= 0:
            log_general(f"Invalid or undefined minTick for {symbol}. Using default tick size of 0.01.", level='WARNING')
            tick_size = 0.01  # Default tick size if undefined

        # Round stop_loss_price to conform to minTick
        rounded_sl_price = round_to_tick(stop_loss_price, tick_size)
        log_general(f"Rounded Stop Loss Price: {rounded_sl_price} (Original: {stop_loss_price}) for {symbol}.")

        sl_order = StopOrder(stop_action, total_quantity, rounded_sl_price, tif='GTC', outsideRth=sl_outside_rth)
        ib.placeOrder(contract, sl_order)
        log_general(f"Placed Stop Loss order at ${rounded_sl_price:.2f}, Outside RTH: {sl_outside_rth}")

        log_active_order(
            order_id=sl_order.orderId,
            symbol=symbol,
            action=stop_action,
            quantity=total_quantity,
            price=rounded_sl_price,
            status="Submitted"
        )

        remaining_quantity = total_quantity
        R_level = 1  # Assuming 1R for risk-reward ratio
        entry_price = filled_price
        tp_outside_rth = False if take_profit_hours.lower() == 'rth' else True

        if take_profit_type == 'partial':
            log_general(f"Using partial Take Profit strategy for {symbol}.")

            tp_quantity = max(int(remaining_quantity * take_profit_share_percentage), 1)  # Adjust as needed
            tp_price_raw = filled_price + (R_level * risk_after_entry) if position_type.lower() == 'long' else filled_price - (R_level * risk_after_entry)

            # Round tp_price to conform to minTick
            tp_price = round_to_tick(tp_price_raw, tick_size)
            log_general(f"Calculated Take Profit price: ${tp_price:.2f} (Raw: {tp_price_raw:.2f}), Quantity: {tp_quantity} for {symbol}.")

            tp_order = LimitOrder(self.take_profit_action(position_type), tp_quantity, tp_price, tif='GTC', outsideRth=tp_outside_rth)
            tp_trade = ib.placeOrder(contract, tp_order)
            log_general(f"Placed Take Profit order at ${tp_price:.2f} for {tp_quantity} contracts, Outside RTH: {tp_outside_rth}")

            log_active_order(
                order_id=tp_trade.order.orderId,
                symbol=symbol,
                action=self.take_profit_action(position_type),
                quantity=tp_quantity,
                price=tp_price,
                status=tp_trade.orderStatus.status
            )

            # Monitor the TP order via callback
            tp_trade.filledEvent += lambda fill: self.enqueue_tp_fill(fill, contract, position_type, rounded_sl_price, remaining_quantity, sl_order, tp_trade, ib)

        else:
            log_general(f"Using full Take Profit strategy for {symbol}.")

            desired_R = 1  # 1R risk-reward
            tp_price_raw = filled_price + (desired_R * risk_after_entry) if position_type.lower() == 'long' else filled_price - (desired_R * risk_after_entry)

            # Round tp_price to conform to minTick
            tp_price = round_to_tick(tp_price_raw, tick_size)
            log_general(f"Calculated Take Profit price: ${tp_price:.2f} (Raw: {tp_price_raw:.2f}) for full quantity.")

            tp_order = LimitOrder(self.take_profit_action(position_type), remaining_quantity, tp_price, tif='GTC', outsideRth=tp_outside_rth)
            tp_trade = ib.placeOrder(contract, tp_order)
            log_general(f"Placed Take Profit order at ${tp_price:.2f} for {remaining_quantity} contracts, Outside RTH: {tp_outside_rth}")

            log_active_order(
                order_id=tp_trade.order.orderId,
                symbol=symbol,
                action=self.take_profit_action(position_type),
                quantity=remaining_quantity,
                price=tp_price,
                status=tp_trade.orderStatus.status
            )

            # Monitor the TP order via callback
            tp_trade.filledEvent += lambda fill: self.enqueue_tp_fill(fill, contract, position_type, rounded_sl_price, remaining_quantity, sl_order, tp_trade, ib)

    # Determine action based on position type
    def take_profit_action(self, position_type):
        return 'SELL' if position_type.lower() == 'long' else 'BUY'

    # Enqueue Take Profit fill for background processing
    def enqueue_tp_fill(self, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib):
        self.trade_fill_queue.put(('tp_fill', fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib))

    # Handle take profit fill in background
    def handle_tp_fill(self, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib):
        try:
            symbol = contract.symbol
            filled_quantity = fill.size
            log_general(f"Take Profit order {tp_trade.order.orderId} filled at ${tp_trade.lmtPrice:.2f} for {symbol}.")

            # Retrieve the entry price from active trades log
            entry_price = get_entry_price(tp_trade.order.orderId, symbol, self)
            if entry_price is None:
                log_general(f"Cannot log closed trade for Order ID {tp_trade.order.orderId} due to missing entry price.", level='ERROR')
                return

            exit_price = float(tp_trade.lmtPrice)  # Ensure it's a float
            profit_loss = (exit_price - entry_price) * filled_quantity if position_type.lower() == 'long' else (entry_price - exit_price) * filled_quantity
            exit_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            log_closed_trade(tp_trade.order.orderId, symbol, entry_price, exit_price, profit_loss, exit_time)

            remaining_quantity -= filled_quantity

            ib.cancelOrder(sl_order)
            log_general(f"Canceled Stop Loss order {sl_order.orderId} for {symbol}.")

            if remaining_quantity > 0:
                # Adjust stop loss for remaining contracts
                new_sl_order = StopOrder(sl_order.action, remaining_quantity, stop_loss_price, tif='GTC', outsideRth=sl_order.outsideRth)
                ib.placeOrder(contract, new_sl_order)
                log_general(f"Placed new Stop Loss order at ${stop_loss_price:.2f} for remaining {remaining_quantity} contracts.")

                log_active_order(
                    order_id=new_sl_order.orderId,
                    symbol=symbol,
                    action=new_sl_order.action,
                    quantity=remaining_quantity,
                    price=stop_loss_price,
                    status=new_sl_order.orderStatus.status
                )
            else:
                log_general(f"Position for {symbol} fully closed.")
                # Remove the trade from active trades log
                remove_closed_trade(tp_trade.order.orderId, symbol)
        except Exception as e:
            log_general(f"Error in handle_tp_fill callback for Order ID {tp_trade.order.orderId}: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Process trade fills from the queue
    def process_trade_fills_from_queue(self):
        while not self.trade_fill_queue.empty():
            try:
                task = self.trade_fill_queue.get_nowait()
                if task[0] == 'profit_management':
                    _, fill, trade, contract, row, ib = task
                    self.handle_profit_management(fill, trade, contract, row, ib)
                elif task[0] == 'tp_fill':
                    _, fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib = task
                    self.handle_tp_fill(fill, contract, position_type, stop_loss_price, remaining_quantity, sl_order, tp_trade, ib)
            except queue.Empty:
                break
            except Exception as e:
                log_general(f"Error processing trade fills from queue: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Function to enqueue risk summary update
    def enqueue_risk_summary_update(self):
        self.active_risk_queue.put('update_risk')

    # Function to update Active Risk Summary from queue
    def update_active_risk_summary_from_queue(self):
        try:
            while not active_risk_queue.empty():
                task = active_risk_queue.get_nowait()
                if task == 'update_risk':
                    self.update_active_risk_summary()
                else:
                    df = task
                    self.active_risk_tree.delete(*self.active_risk_tree.get_children())

                    total_risk = 0.0
                    for index, row in df.iterrows():
                        try:
                            trade_id = int(row['Trade ID'])
                            symbol = str(row['Symbol'])
                            entry_price = float(row['Entry Price'])
                            stop_loss_price = float(row['Stop Loss Price'])
                            quantity = int(row['Quantity'])
                            status = str(row['Status'])

                            risk = self.calculate_risk(entry_price, stop_loss_price, 'long' if 'long' in status.lower() else 'short')  # Assuming status indicates position type
                            total_risk += risk

                            self.active_risk_tree.insert('', 'end', iid=trade_id, values=(
                                trade_id,
                                symbol,
                                f"${entry_price:.2f}",
                                f"${stop_loss_price:.2f}",
                                quantity,
                                f"${risk:.2f}",
                                status
                            ))
                        except Exception as e:
                            log_general(f"Error processing Active Risk Summary row {index}: {e}", level='ERROR')

                    self.total_risk_label.config(text=f"Total Risk Exposure: ${total_risk:.2f}")

                    log_general("Updated Active Risk Summary tab.")
        except Exception as e:
            log_general(f"Error updating Active Risk Summary from queue: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Initialize and start background threads
    def run(self):
        while True:
            self.process_trade_fills_from_queue()
            self.after(1000, self.run)
            break  # Prevent multiple calls

    # Update Active Orders from queue
    def update_active_orders_from_queue(self):
        try:
            while not active_orders_queue.empty():
                df = active_orders_queue.get_nowait()
                self.active_orders_tree.delete(*self.active_orders_tree.get_children())
                for index, row in df.iterrows():
                    try:
                        order_id = int(row['Order ID'])
                        symbol = str(row['Symbol'])
                        action = str(row['Action'])
                        quantity = int(row['Quantity'])
                        price = float(row['Price'])
                        status = str(row['Status'])

                        self.active_orders_tree.insert('', 'end', iid=order_id, values=(
                            order_id,
                            symbol,
                            action,
                            quantity,
                            f"${price:.2f}",
                            status
                        ))
                    except Exception as e:
                        log_general(f"Error processing Active Orders row {index}: {e}", level='ERROR')
        except Exception as e:
            log_general(f"Error updating Active Orders from queue: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Update Active Trades from queue
    def update_active_trades_from_queue(self):
        try:
            while not active_trades_queue.empty():
                df = active_trades_queue.get_nowait()
                self.active_trades_tree.delete(*self.active_trades_tree.get_children())
                for index, row in df.iterrows():
                    try:
                        trade_id = int(row['Trade ID'])
                        symbol = str(row['Symbol'])
                        entry_price = float(row['Entry Price'])
                        stop_loss_price = float(row['Stop Loss Price'])
                        quantity = int(row['Quantity'])
                        status = str(row['Status'])

                        # Correctly call the class methods using self.
                        current_price = self.get_current_price(symbol)
                        unrealized_pl = self.calculate_unrealized_pl(
                            entry_price, current_price, quantity, 
                            'long' if 'long' in status.lower() else 'short'
                        ) if current_price else "N/A"

                        self.active_trades_tree.insert('', 'end', iid=trade_id, values=(
                            trade_id,
                            symbol,
                            f"${entry_price:.2f}",
                            f"${stop_loss_price:.2f}",
                            quantity,
                            status,
                            "N/A",  # Entry Time can be added if tracked
                            f"${current_price:.2f}" if current_price else "N/A",
                            f"${unrealized_pl:.2f}" if isinstance(unrealized_pl, float) else "N/A"
                        ))
                    except Exception as e:
                        log_general(f"Error processing Active Trades row {index}: {e}", level='ERROR')
        except Exception as e:
            log_general(f"Error updating Active Trades from queue: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Update Closed Trades from queue
    def update_closed_trades_from_queue(self):
        try:
            while not closed_trades_queue.empty():
                df = closed_trades_queue.get_nowait()
                self.closed_trades_tree.delete(*self.closed_trades_tree.get_children())
                for index, row in df.iterrows():
                    try:
                        trade_id = int(row['Trade ID'])
                        symbol = str(row['Symbol'])
                        entry_price = float(row['Entry Price'])
                        exit_price = float(row['Exit Price'])
                        profit_loss = float(row['Profit/Loss'])
                        exit_time = str(row['Exit Time'])

                        self.closed_trades_tree.insert('', 'end', iid=trade_id, values=(
                            trade_id,
                            symbol,
                            f"${entry_price:.2f}",
                            f"${exit_price:.2f}",
                            f"${profit_loss:.2f}",
                            exit_time
                        ))
                    except Exception as e:
                        log_general(f"Error processing Closed Trades row {index}: {e}", level='ERROR')
        except Exception as e:
            log_general(f"Error updating Closed Trades from queue: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Calculate unrealized P/L
    def calculate_unrealized_pl(self, entry_price, current_price, quantity, position_type):
        try:
            entry_price = float(entry_price)
            current_price = float(current_price)
            quantity = float(quantity)
            if position_type.lower() == 'long':
                pl = (current_price - entry_price) * quantity
            elif position_type.lower() == 'short':
                pl = (entry_price - current_price) * quantity
            else:
                pl = 0.0
                log_general(f"Unknown position type '{position_type}' for P/L calculation.", level='WARNING')
            return pl
        except Exception as e:
            log_general(f"Error calculating Unrealized P/L: {e}", level='ERROR')
            return 0.0

    # Get current price
    def get_current_price(self, symbol):
        """
        Retrieves the current market price for a given symbol using the IB API.

        :param symbol: The ticker symbol to retrieve the price for.
        :return: The current market price as a float. Returns None if unable to retrieve.
        """
        if self.ib_instance is None:
            log_general(f"IB instance is not connected. Cannot fetch current price for {symbol}.", level='ERROR')
            return None

        with self.ib_lock:
            try:
                # Define the contract
                contract = Stock(symbol, 'SMART', 'USD')
                self.ib_instance.qualifyContracts(contract)

                # Request market data
                market_data = self.ib_instance.reqMktData(contract, '', False, False)

                # Allow some time for market data to be received
                time.sleep(1)

                # Retrieve the last price
                current_price = market_data.last

                # Cancel market data subscription
                self.ib_instance.cancelMktData(contract)

                if current_price is None:
                    log_general(f"Market data for {symbol} not available.", level='WARNING')
                    return None

                log_general(f"Fetched current price for {symbol}: ${current_price:.2f}")
                return float(current_price)
            except Exception as e:
                log_general(f"Error fetching current price for {symbol}: {e}", level='ERROR')
                traceback_str = traceback.format_exc()
                log_general(f"Traceback: {traceback_str}", level='ERROR')
                return None

    # Callback function to handle trade fills
    def on_trade_fill(self, fill, trade, contract, row, ib, app_instance):
        # This method is now obsolete due to the new enqueue_trade_fill method
        pass  # All trade fills are handled via enqueue_trade_fill

    # Update Active Risk Summary
    def update_active_risk_summary(self):
        try:
            if not os.path.exists(ACTIVE_TRADES_LOG):
                self.active_risk_tree.delete(*self.active_risk_tree.get_children())
                self.total_risk_label.config(text="Total Risk Exposure: $0.00")
                return

            df = pd.read_csv(ACTIVE_TRADES_LOG, header=None)
            df.columns = ['Trade ID', 'Symbol', 'Entry Price', 'Stop Loss Price', 'Quantity', 'Status']

            # Convert numerical columns
            numeric_columns = ['Entry Price', 'Stop Loss Price', 'Quantity']
            for col in numeric_columns:
                df[col] = pd.to_numeric(df[col], errors='coerce')

            self.active_risk_tree.delete(*self.active_risk_tree.get_children())

            total_risk = 0.0
            for index, row in df.iterrows():
                try:
                    trade_id = int(row['Trade ID'])
                    symbol = str(row['Symbol'])
                    entry_price = float(row['Entry Price'])
                    stop_loss_price = float(row['Stop Loss Price'])
                    quantity = int(row['Quantity'])
                    status = str(row['Status'])

                    risk = self.calculate_risk(entry_price, stop_loss_price, 'long' if 'long' in status.lower() else 'short')  # Assuming status indicates position type
                    total_risk += risk

                    self.active_risk_tree.insert('', 'end', iid=trade_id, values=(
                        trade_id,
                        symbol,
                        f"${entry_price:.2f}",
                        f"${stop_loss_price:.2f}",
                        quantity,
                        f"${risk:.2f}",
                        status
                    ))
                except Exception as e:
                    log_general(f"Error processing Active Risk Summary row {index}: {e}", level='ERROR')

            self.total_risk_label.config(text=f"Total Risk Exposure: ${total_risk:.2f}")

            log_general("Updated Active Risk Summary tab.")
        except Exception as e:
            log_general(f"Error updating Active Risk Summary: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Periodically update the UI from queues and logs
    def update_ui_from_logs_and_queues(self):
        self.update_active_orders_from_queue()
        self.update_active_trades_from_queue()
        self.update_closed_trades_from_queue()
        self.update_active_risk_summary_from_queue()
        self.update_logs_from_general()
        self.after(2000, self.update_ui_from_logs_and_queues)

    # Process messages from the queue
    def process_queue(self):
        try:
            while not ui_queue.empty():
                try:
                    item = ui_queue.get_nowait()
                    if item[0] == 'log':
                        self.update_logs(item[1])
                except queue.Empty:
                    break
            self.after(1000, self.process_queue)
        except Exception as e:
            log_general(f"Error processing UI queue: {e}", level='ERROR')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='ERROR')

    # Update the Logs tab
    def update_logs(self, message):
        try:
            self.logs_text.configure(state='normal')
            self.logs_text.insert('end', f"{message}\n")
            self.logs_text.configure(state='disabled')
            self.logs_text.see('end')
        except Exception as e:
            print(f"Error updating Logs tab: {e}")

    # Handle window closing to ensure graceful shutdown
    def on_closing(self):
        if messagebox.askokcancel("Quit", "Do you want to quit?"):
            log_general("Shutting down TradingApp.")
            self.stop_event.set()  # Signal all threads to stop
            # Optionally, wait for threads to finish if not daemon
            time.sleep(1)  # Give threads time to terminate
            if self.ib_instance and self.ib_instance.isConnected():
                self.ib_instance.disconnect()
                log_general("Disconnected from IB.")
            self.destroy()

    # Run the application
    def run_app(self):
        try:
            self.mainloop()
        except Exception as e:
            log_general(f"Unhandled exception in main thread: {e}", level='CRITICAL')
            traceback_str = traceback.format_exc()
            log_general(f"Traceback: {traceback_str}", level='CRITICAL')

if __name__ == "__main__":
    try:
        app = TradingApp()
        log_general("Starting TradingApp main loop.")
        app.run_app()
    except Exception as e:
        log_general(f"Unhandled exception in main thread: {e}", level='CRITICAL')
        traceback_str = traceback.format_exc()
        log_general(f"Traceback: {traceback_str}", level='CRITICAL')