import json
import logging
import uuid
import datetime as dt
from typing import Any

from Users import Users

# Configure logging
logger = logging.getLogger(__name__)

# Debug file logger for trade updates
_debug_logger = logging.getLogger('trade_debug')
_debug_logger.setLevel(logging.DEBUG)
_debug_handler = logging.FileHandler('/home/rob/PycharmProjects/BrighterTrading/trade_debug.log', mode='w')
_debug_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
_debug_logger.addHandler(_debug_handler)


class Trade:
    def __init__(self, target: str, symbol: str, side: str, order_price: float, base_order_qty: float,
                 order_type: str = 'MARKET', time_in_force: str = 'GTC', unique_id: str | None = None,
                 status: str | None = None, stats: dict[str, Any] | None = None,
                 order: Any | None = None, fee: float = 0.001, strategy_id: str | None = None,
                 is_paper: bool = False, creator: int | None = None, created_at: str | None = None,
                 tbl_key: str | None = None, testnet: bool = False, exchange: str | None = None,
                 broker_kind: str | None = None, broker_mode: str | None = None,
                 broker_exchange: str | None = None, broker_order_id: str | None = None,
                 exchange_order_id: str | None = None,
                 stop_loss: float | None = None, take_profit: float | None = None):
        """
        Initializes a Trade instance with all necessary attributes.
        """
        self.unique_id = unique_id or uuid.uuid4().hex
        self.tbl_key = tbl_key or self.unique_id
        self.target = target
        # exchange: for live trades, the actual exchange; for paper trades, use 'paper' (single synthetic market)
        if target in ['test_exchange', 'paper', 'Paper Trade']:
            self.exchange = 'paper'  # Paper trades use a single synthetic market
        else:
            self.exchange = exchange or target
        self.symbol = symbol
        self.side = side.upper()
        self.order_price = order_price
        self.order_type = order_type.upper()
        self.time_in_force = time_in_force.upper()
        self.base_order_qty = base_order_qty
        self.fee = fee
        self.strategy_id = strategy_id
        self.is_paper = is_paper
        self.testnet = testnet
        self.creator = creator
        self.created_at = created_at or dt.datetime.now(dt.timezone.utc).isoformat()

        # Broker integration fields
        self.broker_kind = broker_kind  # 'paper' or 'live'
        self.broker_mode = broker_mode  # 'testnet', 'production', or 'paper'
        self.broker_exchange = broker_exchange  # Exchange name (for live)
        self.broker_order_id = broker_order_id  # Local broker order ID
        self.exchange_order_id = exchange_order_id  # Live exchange order ID

        # Stop Loss / Take Profit (triggers auto-close when price crosses threshold)
        self.stop_loss = stop_loss
        self.take_profit = take_profit

        if status is None:
            self.status = 'inactive'
            self.stats = {
                'opening_price': order_price,
                'opening_value': self.base_order_qty * order_price,
                'current_price': order_price,
                'current_value': self.base_order_qty * order_price,
                'settled_price': 0.0,
                'settled_value': 0.0,
                'qty_filled': 0.0,
                'qty_settled': 0.0,
                'profit': 0.0,
                'profit_pct': 0.0,
                'fee_paid': 0.0
            }
            self.order = None
        else:
            self.status = status
            self.stats = stats
            self.order = order

    def to_json(self) -> dict[str, Any]:
        """
        Serializes the Trade object to a JSON-compatible dictionary.
        """
        return {
            'unique_id': self.unique_id,
            'tbl_key': self.tbl_key,
            'strategy_id': self.strategy_id,
            'target': self.target,
            'exchange': self.exchange,
            'symbol': self.symbol,
            'side': self.side,
            'order_price': self.order_price,
            'base_order_qty': self.base_order_qty,
            'order_type': self.order_type,
            'time_in_force': self.time_in_force,
            'status': self.status,
            'stats': self.stats,
            'order': self.order,
            'is_paper': self.is_paper,
            'testnet': self.testnet,
            'creator': self.creator,
            'created_at': self.created_at,
            'broker_kind': self.broker_kind,
            'broker_mode': self.broker_mode,
            'broker_exchange': self.broker_exchange,
            'broker_order_id': self.broker_order_id,
            'exchange_order_id': self.exchange_order_id,
            'stop_loss': self.stop_loss,
            'take_profit': self.take_profit
        }

    def get_position_size(self) -> float:
        """
        Position_size is the value of the trade in the quote currency.
        """
        return self.stats['current_value']

    def get_pl(self) -> float:
        """
        Returns a positive profit or a negative loss.
        """
        return self.stats['profit']

    def get_pl_pct(self) -> float:
        """
        Returns the profit/loss percentage.
        """
        return self.stats['profit_pct']

    def get_status(self) -> str:
        """
        Returns the current status of the trade.
        """
        return self.status

    def update_values(self, current_price: float) -> None:
        """
        Updates the P/L values and percentages based on the current price.
        """

        def percent(part: float, whole: float) -> float:
            if whole == 0:
                return 0.0
            return 100.0 * float(part) / float(whole)

        self.stats['current_price'] = current_price
        initial_value = self.stats['opening_value']
        self.stats['current_value'] = self.base_order_qty * current_price
        logger.debug(f"Trade {self.unique_id}: Updated current value to {self.stats['current_value']}")

        self.stats['profit'] = self.stats['current_value'] - initial_value

        if self.side == 'SELL':
            self.stats['profit'] *= -1

        projected_fees = (self.stats['current_value'] * self.fee) + (self.stats['opening_value'] * self.fee)
        self.stats['profit'] -= projected_fees

        self.stats['profit_pct'] = percent(self.stats['profit'], initial_value)
        logger.debug(f"Trade {self.unique_id}: Profit updated to {self.stats['profit']} ({self.stats['profit_pct']}%)")

    def update(self, current_price: float) -> str:
        """
        Updates the trade based on the current price and returns the updated status.
        """
        if self.status == 'closed':
            return self.status

        self.update_values(current_price)

        # Determine if the trade has been filled or partially filled
        if self.status in ['filled', 'part-filled']:
            return self.status
        else:
            return 'updated'  # Indicates that the trade has been updated but not filled

    def order_placed(self, order: Any) -> None:
        """
        Updates the trade status and stores the order after it has been placed on the exchange.
        """
        self.status = 'unfilled'
        self.order = order

    def trade_filled(self, qty: float, price: float) -> None:
        """
        Records the quantity filled, updates trade statistics, and sets the status.
        """
        if self.status == 'inactive':
            self.status = 'unfilled'

        if self.status == 'unfilled':
            self.stats['qty_filled'] = qty
            self.stats['opening_price'] = price
        else:
            sum_of_values = (qty * price) + self.stats['opening_value']
            t_qty = self.stats['qty_filled'] + qty
            weighted_average = sum_of_values / t_qty if t_qty != 0 else 0.0
            self.stats['opening_price'] = weighted_average
            self.stats['qty_filled'] += qty

        self.stats['opening_value'] = self.stats['qty_filled'] * self.stats['opening_price']
        self.stats['current_value'] = self.stats['qty_filled'] * self.stats['current_price']

        if self.stats['qty_filled'] >= self.base_order_qty:
            self.status = 'filled'
        else:
            self.status = 'part-filled'

    def settle(self, qty: float, price: float) -> None:
        """
        Settles all or part of the trade based on the provided quantity and price.
        """
        if self.stats['qty_settled'] == 0:
            self.stats['settled_price'] = price
            self.stats['settled_value'] = qty * price
            self.stats['qty_settled'] = qty
        else:
            sum_of_values = (qty * price) + self.stats['settled_value']
            t_qty = self.stats['qty_settled'] + qty
            weighted_average = sum_of_values / t_qty if t_qty != 0 else 0.0
            self.stats['settled_price'] = weighted_average
            self.stats['qty_settled'] += qty

        self.stats['settled_value'] = self.stats['qty_settled'] * self.stats['settled_price']

        if self.stats['qty_settled'] >= self.base_order_qty:
            self.status = 'closed'


class Trades:
    def __init__(self, users: Users, data_cache: Any = None):
        """
        Initializes the Trades class with necessary attributes.

        :param users: Users instance for user lookups.
        :param data_cache: DataCache instance for persistence.
        """
        self.users = users
        self.data_cache = data_cache
        self.exchange_interface: Any | None = None  # Define the type based on your exchange interface
        self.manual_broker_manager: Any | None = None  # ManualTradingBrokerManager for broker-based trading
        self.exchange_fees = {'maker': 0.001, 'taker': 0.001}
        self.hedge_mode = False
        self.side: str | None = None
        self.active_trades: dict[str, Trade] = {}  # Keyed by trade.unique_id
        self.settled_trades: dict[str, Trade] = {}
        self.stats = {'num_trades': 0, 'total_position': 0, 'total_position_value': 0}
        self.balances: dict[str, float] = {}  # Track balances per strategy
        self.locked_funds: dict[str, float] = {}  # Track locked funds per strategy

        # Initialize database persistence if data_cache is available
        if self.data_cache:
            self._ensure_table_exists()
            self._create_cache()
            self._load_trades_from_db()

    def _ensure_table_exists(self) -> None:
        """Create the trades table in the database if it doesn't exist."""
        try:
            if not self.data_cache.db.table_exists('trades'):
                create_sql = """
                    CREATE TABLE IF NOT EXISTS trades (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        creator INTEGER,
                        unique_id TEXT UNIQUE,
                        target TEXT NOT NULL,
                        symbol TEXT NOT NULL,
                        side TEXT NOT NULL,
                        order_type TEXT NOT NULL,
                        order_price REAL,
                        base_order_qty REAL NOT NULL,
                        time_in_force TEXT DEFAULT 'GTC',
                        fee REAL DEFAULT 0.001,
                        status TEXT DEFAULT 'inactive',
                        stats_json TEXT,
                        strategy_id TEXT,
                        is_paper INTEGER DEFAULT 0,
                        testnet INTEGER DEFAULT 0,
                        created_at TEXT,
                        tbl_key TEXT UNIQUE,
                        broker_kind TEXT,
                        broker_mode TEXT,
                        broker_exchange TEXT,
                        broker_order_id TEXT,
                        exchange_order_id TEXT
                    )
                """
                self.data_cache.db.execute_sql(create_sql, params=[])
                logger.info("Created trades table in database")
            else:
                # Ensure testnet column exists for existing databases
                self._ensure_testnet_column()
                # Ensure broker columns exist for existing databases
                self._ensure_broker_columns()
        except Exception as e:
            logger.error(f"Error ensuring trades table exists: {e}", exc_info=True)

    def _ensure_testnet_column(self) -> None:
        """Add testnet column to trades table if it doesn't exist."""
        try:
            # Check if testnet column exists
            result = self.data_cache.db.execute_sql(
                "PRAGMA table_info(trades)", params=[]
            )
            columns = {row[1] for row in result} if result else set()
            if 'testnet' not in columns:
                self.data_cache.db.execute_sql(
                    "ALTER TABLE trades ADD COLUMN testnet INTEGER DEFAULT 0", params=[]
                )
                logger.info("Added testnet column to trades table")
        except Exception as e:
            logger.debug(f"Could not add testnet column: {e}")

    def _ensure_broker_columns(self) -> None:
        """Add broker tracking columns to trades table if they don't exist."""
        broker_columns = [
            ('broker_kind', 'TEXT'),
            ('broker_mode', 'TEXT'),
            ('broker_exchange', 'TEXT'),
            ('broker_order_id', 'TEXT'),
            ('exchange_order_id', 'TEXT'),
            ('stop_loss', 'REAL'),
            ('take_profit', 'REAL'),
        ]
        try:
            result = self.data_cache.db.execute_sql(
                "PRAGMA table_info(trades)", params=[]
            )
            existing_columns = {row[1] for row in result} if result else set()

            for col_name, col_type in broker_columns:
                if col_name not in existing_columns:
                    try:
                        self.data_cache.db.execute_sql(
                            f"ALTER TABLE trades ADD COLUMN {col_name} {col_type}", params=[]
                        )
                        logger.info(f"Added {col_name} column to trades table")
                    except Exception as e:
                        logger.debug(f"Could not add {col_name} column: {e}")
        except Exception as e:
            logger.debug(f"Could not ensure broker columns: {e}")

    def _create_cache(self) -> None:
        """Create the trades cache in DataCache."""
        try:
            self.data_cache.create_cache(
                name='trades',
                cache_type='table',
                size_limit=1000,
                eviction_policy='deny',
                default_expiration=dt.timedelta(hours=24),
                columns=[
                    "creator",
                    "unique_id",
                    "target",
                    "symbol",
                    "side",
                    "order_type",
                    "order_price",
                    "base_order_qty",
                    "time_in_force",
                    "fee",
                    "status",
                    "stats_json",
                    "strategy_id",
                    "is_paper",
                    "testnet",
                    "created_at",
                    "tbl_key",
                    "broker_kind",
                    "broker_mode",
                    "broker_exchange",
                    "broker_order_id",
                    "exchange_order_id"
                ]
            )
        except Exception as e:
            logger.debug(f"Cache 'trades' may already exist: {e}")

    def _load_trades_from_db(self) -> None:
        """Load all trades from database into memory (active + settled)."""
        try:
            trades_df = self.data_cache.get_all_rows_from_datacache(cache_name='trades')
            if trades_df is not None and not trades_df.empty:
                active_count = 0
                settled_count = 0
                for _, row in trades_df.iterrows():
                    status = row.get('status', 'inactive')

                    # Parse stats JSON
                    stats_json = row.get('stats_json', '{}')
                    try:
                        stats = json.loads(stats_json) if stats_json else {}
                    except (json.JSONDecodeError, TypeError):
                        stats = {}

                    trade = Trade(
                        target=row.get('target', ''),
                        symbol=row.get('symbol', ''),
                        side=row.get('side', 'BUY'),
                        order_price=float(row.get('order_price', 0)),
                        base_order_qty=float(row.get('base_order_qty', 0)),
                        order_type=row.get('order_type', 'MARKET'),
                        time_in_force=row.get('time_in_force', 'GTC'),
                        unique_id=row.get('unique_id'),
                        status=status,
                        stats=stats if stats else None,
                        fee=float(row.get('fee', 0.001)),
                        strategy_id=row.get('strategy_id'),
                        is_paper=bool(row.get('is_paper', 0)),
                        testnet=bool(row.get('testnet', 0)),
                        creator=row.get('creator'),
                        created_at=row.get('created_at'),
                        tbl_key=row.get('tbl_key'),
                        broker_kind=row.get('broker_kind'),
                        broker_mode=row.get('broker_mode'),
                        broker_exchange=row.get('broker_exchange'),
                        broker_order_id=row.get('broker_order_id'),
                        exchange_order_id=row.get('exchange_order_id'),
                        stop_loss=row.get('stop_loss'),
                        take_profit=row.get('take_profit')
                    )

                    # Route to appropriate collection based on status
                    if status in ['closed', 'cancelled']:
                        self.settled_trades[trade.unique_id] = trade
                        settled_count += 1
                    else:
                        self.active_trades[trade.unique_id] = trade
                        self.stats['num_trades'] += 1
                        active_count += 1

                logger.info(f"Loaded {active_count} active trades and {settled_count} settled trades from database")
        except Exception as e:
            logger.error(f"Error loading trades from database: {e}", exc_info=True)

    def _save_trade(self, trade: Trade) -> bool:
        """
        Save a trade to the database.

        :param trade: Trade object to save.
        :return: True if successful, False otherwise.
        """
        if not self.data_cache:
            return True  # No persistence, just return success

        try:
            columns = (
                "creator", "unique_id", "target", "symbol", "side", "order_type",
                "order_price", "base_order_qty", "time_in_force", "fee", "status",
                "stats_json", "strategy_id", "is_paper", "testnet", "created_at", "tbl_key",
                "broker_kind", "broker_mode", "broker_exchange", "broker_order_id", "exchange_order_id",
                "stop_loss", "take_profit"
            )

            stats_json = json.dumps(trade.stats) if trade.stats else '{}'

            values = (
                trade.creator,
                trade.unique_id,
                trade.target,
                trade.symbol,
                trade.side,
                trade.order_type,
                trade.order_price,
                trade.base_order_qty,
                trade.time_in_force,
                trade.fee,
                trade.status,
                stats_json,
                trade.strategy_id,
                int(trade.is_paper),
                int(trade.testnet),
                trade.created_at,
                trade.tbl_key,
                trade.broker_kind,
                trade.broker_mode,
                trade.broker_exchange,
                trade.broker_order_id,
                trade.exchange_order_id,
                trade.stop_loss,
                trade.take_profit
            )

            # Check if trade already exists
            existing = self.data_cache.get_rows_from_datacache(
                cache_name='trades',
                filter_vals=[('tbl_key', trade.tbl_key)],
                include_tbl_key=True
            )

            if existing.empty:
                # Insert new trade
                self.data_cache.insert_row_into_datacache(
                    cache_name='trades',
                    columns=columns,
                    values=values
                )
            else:
                # Update existing trade
                self.data_cache.modify_datacache_item(
                    cache_name='trades',
                    filter_vals=[('tbl_key', trade.tbl_key)],
                    field_names=columns,
                    new_values=values,
                    key=trade.tbl_key,
                    overwrite='tbl_key'
                )

            return True
        except Exception as e:
            logger.error(f"Failed to save trade {trade.unique_id}: {e}", exc_info=True)
            return False

    def _update_trade_in_db(self, trade: Trade) -> bool:
        """
        Update a trade's stats in the database.

        :param trade: Trade object to update.
        :return: True if successful, False otherwise.
        """
        return self._save_trade(trade)

    def _delete_trade_from_db(self, trade_id: str) -> bool:
        """
        Delete a trade from the database.

        :param trade_id: The unique ID of the trade to delete.
        :return: True if successful, False otherwise.
        """
        if not self.data_cache:
            return True

        try:
            self.data_cache.remove_row_from_datacache(
                cache_name='trades',
                filter_vals=[('unique_id', trade_id)]
            )
            return True
        except Exception as e:
            logger.error(f"Failed to delete trade {trade_id}: {e}", exc_info=True)
            return False

    def new_trade(self, target: str, symbol: str, price: float, side: str,
                  order_type: str, qty: float, user_id: int = None,
                  strategy_id: str = None, testnet: bool = False, exchange: str = None,
                  stop_loss: float = None, take_profit: float = None) -> tuple[str, str | None]:
        """
        Creates a new trade (paper or live).

        :param target: The exchange target ('test_exchange' for paper, exchange name for live).
        :param symbol: The trading pair symbol.
        :param price: The price to trade at (ignored for market orders).
        :param side: 'BUY' or 'SELL'.
        :param order_type: 'MARKET' or 'LIMIT'.
        :param qty: The quantity to trade.
        :param user_id: The user creating the trade.
        :param strategy_id: Optional strategy ID if from a strategy.
        :param testnet: Whether to use testnet/sandbox mode for live trades.
        :param exchange: The actual exchange for price data (for paper trades).
        :return: Tuple of (status, trade_id or error message).
        """
        from brokers.base_broker import OrderSide, OrderType, OrderStatus

        # Determine if this is a paper trade
        is_paper = target in ['test_exchange', 'paper', 'Paper Trade']

        # === PRODUCTION SAFETY GATE (BEFORE any broker/exchange creation) ===
        if not is_paper and not testnet:
            import config
            if not getattr(config, 'ALLOW_LIVE_PRODUCTION', False):
                logger.warning(
                    f"Production trading blocked: ALLOW_LIVE_PRODUCTION not set. "
                    f"User {user_id} attempted production trade on {target}."
                )
                return 'Error', 'Production trading is disabled. Set BRIGHTER_ALLOW_LIVE_PROD=true to enable.'

        # For live trades, validate exchange is configured BEFORE creating trade
        if not is_paper:
            if not self.exchange_connected():
                return 'Error', 'No exchange interface connected. Cannot place live trades.'

            # Check if user has this exchange configured
            user_name = self._get_user_name(user_id) if user_id else None
            if not user_name:
                return 'Error', 'You must be logged in to place live trades.'

            try:
                exchange_obj = self.exchange_interface.get_exchange(ename=target, uname=user_name)
                if not exchange_obj or not exchange_obj.configured:
                    return 'Error', f'Exchange "{target}" is not configured with API keys. Please configure it in the Exchanges panel first.'
            except ValueError:
                return 'Error', f'Exchange "{target}" is not connected. Please add it in the Exchanges panel first.'

        # For market orders, fetch the current price from exchange
        effective_price = float(price) if price else 0.0
        if order_type and order_type.upper() == 'MARKET' and self.exchange_interface:
            try:
                current_price = self.exchange_interface.get_price(symbol)
                if current_price:
                    effective_price = float(current_price)
                    logger.debug(f"Market order: using current price {effective_price} for {symbol}")
            except Exception as e:
                logger.warning(f"Could not fetch current price for {symbol}: {e}, using provided price {price}")

        # Fetch trading fees from exchange (falls back to defaults if unavailable)
        effective_fee = self.exchange_fees.get('taker', 0.001)  # Default to taker fee
        if self.exchange_interface:
            try:
                user_name = self._get_user_name(user_id) if user_id else None
                fee_info = self.exchange_interface.get_trading_fees(
                    symbol=symbol,
                    user_name=user_name,
                    exchange_name=target if not is_paper else None
                )
                # Use taker fee for market orders, maker fee for limit orders
                if order_type and order_type.upper() == 'LIMIT':
                    effective_fee = fee_info.get('maker', 0.001)
                else:
                    effective_fee = fee_info.get('taker', 0.001)
                logger.debug(f"Trade fee for {symbol}: {effective_fee} (source: {fee_info.get('source', 'unknown')})")
            except Exception as e:
                logger.warning(f"Could not fetch trading fees for {symbol}: {e}, using default {effective_fee}")

        try:
            # === BROKER-BASED ORDER PLACEMENT ===
            if self.manual_broker_manager and user_id:
                # Get appropriate broker FIRST (need it for SELL validation)
                if is_paper:
                    broker = self.manual_broker_manager.get_paper_broker(user_id)
                    broker_kind = 'paper'
                    broker_mode = 'paper'
                    broker_exchange = None
                    broker_key = 'paper'
                else:
                    user_name = self._get_user_name(user_id)
                    broker = self.manual_broker_manager.get_live_broker(
                        user_id, target, testnet, user_name
                    )
                    if not broker:
                        return 'Error', f'Could not create broker for exchange "{target}".'
                    broker_kind = 'live'
                    broker_mode = 'testnet' if testnet else 'production'
                    broker_exchange = target
                    broker_key = f"{target}_{broker_mode}"

                # Inventory-only SELL (applies to BOTH paper and live)
                if side.upper() == 'SELL':
                    position = broker.get_position(symbol)
                    if not position or position.size <= 0:
                        return 'Error', 'Cannot sell: no position in this symbol. Buy first.'

                # Place order through broker
                order_side = OrderSide.BUY if side.upper() == 'BUY' else OrderSide.SELL
                order_type_enum = OrderType.MARKET if order_type.upper() == 'MARKET' else OrderType.LIMIT

                result = broker.place_order(
                    symbol=symbol,
                    side=order_side,
                    order_type=order_type_enum,
                    size=float(qty),
                    price=effective_price if order_type.upper() == 'LIMIT' else None
                )

                if not result.success:
                    return 'Error', result.message or 'Order placement failed'

                # Map OrderStatus to trade status string
                status_map = {
                    OrderStatus.PENDING: 'pending',
                    OrderStatus.OPEN: 'open',
                    OrderStatus.FILLED: 'filled',
                    OrderStatus.PARTIALLY_FILLED: 'part-filled',
                    OrderStatus.CANCELLED: 'cancelled',
                    OrderStatus.REJECTED: 'rejected',
                    OrderStatus.EXPIRED: 'expired',
                }
                trade_status = status_map.get(result.status, 'pending')

                # Create Trade with full broker tracking
                trade = Trade(
                    target=target,
                    exchange=exchange or (target if not is_paper else 'binance'),
                    symbol=symbol,
                    side=side.upper(),
                    order_price=effective_price,
                    base_order_qty=float(qty),
                    order_type=order_type.upper() if order_type else 'MARKET',
                    strategy_id=strategy_id,
                    is_paper=is_paper,
                    testnet=testnet,
                    creator=user_id,
                    fee=effective_fee,
                    broker_kind=broker_kind,
                    broker_mode=broker_mode,
                    broker_exchange=broker_exchange,
                    broker_order_id=result.order_id,
                    exchange_order_id=result.exchange_order_id,
                    stop_loss=stop_loss,
                    take_profit=take_profit
                )
                trade.status = trade_status

                # Update stats if order was filled immediately (market orders)
                if result.status == OrderStatus.FILLED:
                    trade.stats['qty_filled'] = result.filled_qty or float(qty)
                    trade.stats['opening_price'] = result.filled_price or effective_price
                    trade.stats['opening_value'] = trade.stats['qty_filled'] * trade.stats['opening_price']
                    trade.stats['current_value'] = trade.stats['opening_value']

                logger.info(f"Broker trade created: {trade.unique_id} {side} {qty} {symbol} @ {effective_price} "
                           f"(broker_kind={broker_kind}, status={trade_status})")

            else:
                # === LEGACY PATH (no broker manager) ===
                trade = Trade(
                    target=target,
                    exchange=exchange,
                    symbol=symbol,
                    side=side.upper(),
                    order_price=effective_price,
                    base_order_qty=float(qty),
                    order_type=order_type.upper() if order_type else 'MARKET',
                    strategy_id=strategy_id,
                    is_paper=is_paper,
                    testnet=testnet,
                    creator=user_id,
                    fee=effective_fee,
                    stop_loss=stop_loss,
                    take_profit=take_profit
                )

                if is_paper:
                    # Paper trade: simulate immediate fill
                    trade.status = 'filled'
                    trade.stats['qty_filled'] = trade.base_order_qty
                    trade.stats['opening_price'] = trade.order_price
                    trade.stats['opening_value'] = trade.base_order_qty * trade.order_price
                    trade.stats['current_value'] = trade.stats['opening_value']
                    logger.info(f"Paper trade created: {trade.unique_id} {side} {qty} {symbol} @ {effective_price}")
                else:
                    # Live trade: place order on exchange (legacy path)
                    mode_str = "testnet" if testnet else "production"
                    logger.info(f"Live trade ({mode_str}): {trade.unique_id} {side} {qty} {symbol} @ {effective_price}")

                    if not self.exchange_connected():
                        return 'Error', 'No exchange connected'

                    user_name = self._get_user_name(user_id) if user_id else 'unknown'
                    status, msg = self.place_order(trade, user_name=user_name)
                    if status != 'success':
                        return 'Error', msg

            # Add to active trades
            self.active_trades[trade.unique_id] = trade
            self.stats['num_trades'] += 1

            # Persist to database
            self._save_trade(trade)

            return 'Success', trade.unique_id

        except Exception as e:
            logger.error(f"Error creating new trade: {e}", exc_info=True)
            return 'Error', str(e)

    def get_trades_for_user(self, user_id: int, form: str = 'json') -> list:
        """
        Returns trades visible to a specific user.

        :param user_id: The user ID to filter trades for.
        :param form: Output format ('json', 'obj', 'dict').
        :return: List of trades.
        """
        user_trades = [
            trade for trade in self.active_trades.values()
            if trade.creator == user_id or trade.creator is None
        ]

        if form == 'obj':
            return user_trades
        elif form == 'json':
            return [trade.to_json() for trade in user_trades]
        elif form == 'dict':
            return [trade.__dict__ for trade in user_trades]
        else:
            return [trade.to_json() for trade in user_trades]

    def get_trade_history(self, user_id: int, limit: int = 50) -> list[dict]:
        """
        Get settled/cancelled trade history for a user.

        Only includes trades that are truly finished (status='closed' or 'cancelled').
        Active positions (status='filled') belong in the Positions panel, not history.

        :param user_id: The user ID.
        :param limit: Maximum number of trades to return.
        :return: List of trade dicts, most recent first.
        """
        history = []

        # From in-memory settled trades (these are truly closed)
        for trade_id, trade in self.settled_trades.items():
            if trade.creator == user_id:
                history.append(trade.to_json())

        # Also check active trades for 'cancelled' status only
        # Note: 'filled' = open position (not history), 'closed' should be in settled_trades
        for trade_id, trade in self.active_trades.items():
            if trade.creator == user_id and trade.status == 'cancelled':
                history.append(trade.to_json())

        # Sort by timestamp descending (most recent first)
        history.sort(key=lambda t: t.get('created_at', 0), reverse=True)

        return history[:limit]

    def buy(self, order_data: dict[str, Any], user_id: int) -> tuple[str, str | None]:
        """
        Executes a buy order.
        """
        return self._execute_order(order_data, user_id, side='BUY')

    def sell(self, order_data: dict[str, Any], user_id: int) -> tuple[str, str | None]:
        """
        Executes a sell order.
        """
        return self._execute_order(order_data, user_id, side='SELL')

    def _execute_order(self, order_data: dict[str, Any], user_id: int, side: str) -> tuple[str, str | None]:
        """
        Internal method to handle order execution.
        """
        try:
            # Create a new trade
            trade = Trade(
                target=order_data.get('target', 'exchange_interface'),
                symbol=order_data['symbol'],
                side=side,
                order_price=order_data.get('price', 0.0),
                base_order_qty=order_data['size'],
                order_type=order_data.get('order_type', 'MARKET'),
                time_in_force=order_data.get('tif', 'GTC'),
                strategy_id=order_data.get('strategy_id')
            )

            # Add the trade to active trades
            self.active_trades[trade.unique_id] = trade
            self.stats['num_trades'] += 1

            # Place the order
            status, msg = self.place_order(trade, user_name=self._get_user_name(user_id))
            if status == 'success':
                logger.info(f"Order placed successfully: Trade ID {trade.unique_id}")
                return 'success', None
            else:
                # Remove the trade from active_trades if placement failed
                del self.active_trades[trade.unique_id]
                self.stats['num_trades'] -= 1
                logger.error(f"Order placement failed for Trade ID {trade.unique_id}: {msg}")
                return 'fail', msg

        except Exception as e:
            logger.error(f"Exception during {side} order execution: {e}", exc_info=True)
            return 'fail', str(e)

    def _get_user_name(self, user_id: int) -> str:
        """
        Retrieves the username based on user_id.
        """
        try:
            return self.users.get_username(user_id)
        except Exception as e:
            logger.error(f"Failed to retrieve username for user ID {user_id}: {e}", exc_info=True)
            return "unknown_user"

    def exit_strategy_all(self, strategy_id: str) -> tuple[str, str | None]:
        """
        Exits all trades associated with a specific strategy.
        """
        try:
            trades_to_exit = [trade for trade in self.active_trades.values() if trade.strategy_id == strategy_id]
            for trade in trades_to_exit:
                self.close_trade(trade.unique_id)
            logger.info(f"All trades for strategy {strategy_id} have been exited.")
            return 'success', None
        except Exception as e:
            logger.error(f"Error exiting all trades for strategy '{strategy_id}': {e}", exc_info=True)
            return 'fail', str(e)

    def exit_strategy_in_profit(self, strategy_id: str) -> tuple[str, str | None]:
        """
        Exits trades that are in profit for a specific strategy.
        """
        try:
            trades_to_exit = [trade for trade in self.active_trades.values()
                              if trade.strategy_id == strategy_id and trade.stats.get('profit', 0.0) > 0]
            for trade in trades_to_exit:
                self.close_trade(trade.unique_id)
            logger.info(f"Profitable trades for strategy {strategy_id} have been exited.")
            return 'success', None
        except Exception as e:
            logger.error(f"Error exiting profitable trades for strategy '{strategy_id}': {e}", exc_info=True)
            return 'fail', str(e)

    def exit_strategy_in_loss(self, strategy_id: str) -> tuple[str, str | None]:
        """
        Exits trades that are in loss for a specific strategy.
        """
        try:
            trades_to_exit = [trade for trade in self.active_trades.values()
                              if trade.strategy_id == strategy_id and trade.stats.get('profit', 0.0) < 0]
            for trade in trades_to_exit:
                self.close_trade(trade.unique_id)
            logger.info(f"Losing trades for strategy {strategy_id} have been exited.")
            return 'success', None
        except Exception as e:
            logger.error(f"Error exiting losing trades for strategy '{strategy_id}': {e}", exc_info=True)
            return 'fail', str(e)

    def all_trades_closed(self, strategy_id: str) -> bool:
        """
        Checks if all trades for the given strategy are closed.

        :param strategy_id: Identifier of the strategy.
        :return: True if all trades are closed, False otherwise.
        """
        try:
            trades = self.active_trades.get(strategy_id, [])
            return len(trades) == 0
        except Exception as e:
            logger.error(f"Error checking trades for strategy '{strategy_id}': {e}", exc_info=True)
            return False

    def notify_user(self, user_id: int, message: str) -> tuple[str, str | None]:
        """
        Sends a notification to the specified user.
        """
        try:
            self.users.notify_user(user_id, message)
            return 'success', None
        except Exception as e:
            logger.error(f"Error notifying user '{user_id}': {e}", exc_info=True)
            return 'fail', str(e)

    def get_profit(self, strategy_id: str) -> float:
        """
        Calculates the total profit for a specific strategy.
        """
        total_profit = sum(trade.stats.get('profit', 0.0) for trade in self.active_trades.values()
                           if trade.strategy_id == strategy_id)
        logger.info(f"Total profit for strategy {strategy_id}: {total_profit}")
        return total_profit

    def get_current_balance(self, user_id: int) -> float:
        """
        Retrieves the current balance for a user.
        """
        try:
            # Implement logic to fetch the user's current balance.
            # This could involve querying the exchange interface or a database.
            balance = self.exchange_interface.get_user_balance(user_id)
            logger.info(f"Current balance for user {user_id}: {balance}")
            return balance
        except Exception as e:
            logger.error(f"Error fetching current balance for user '{user_id}': {e}", exc_info=True)
            return 0.0

    def connect_exchanges(self, exchanges: Any) -> None:
        """
        Connects an exchange interface.
        """
        self.exchange_interface = exchanges
        logger.info("Exchange interface connected.")

    def exchange_connected(self) -> bool:
        """
        Reports if an exchange interface has been connected.
        """
        return self.exchange_interface is not None

    def get_trades(self, form: str) -> Any | None:
        """
        Returns stored trades in various formats.
        """
        if form == 'obj':
            return list(self.active_trades.values())
        elif form == 'json':
            return [trade.to_json() for trade in self.active_trades.values()]
        elif form == 'dict':
            return [trade.__dict__ for trade in self.active_trades.values()]
        else:
            logger.error(f"Invalid form '{form}' requested for get_trades.")
            return None

    def get_trades_by_status(self, status: str) -> list[Trade]:
        """
        Returns a list of active trades with the specified status.
        """
        return [trade for trade in self.active_trades.values() if trade.status == status]

    def is_valid_trade_id(self, trade_id: str) -> bool:
        """
        Validates if a trade ID exists among active trades.
        """
        return trade_id in self.active_trades

    def get_trade_by_id(self, trade_id: str) -> Trade | None:
        """
        Retrieves a trade by its unique ID.
        """
        return self.active_trades.get(trade_id, None)

    def load_trades(self, trades: list[dict[str, Any]]) -> None:
        """
        Loads trades from a list of trade dictionaries.
        """
        for trade_data in trades:
            trade = Trade(**trade_data)
            self.active_trades[trade.unique_id] = trade
            self.stats['num_trades'] += 1
        logger.info(f"Loaded {len(trades)} trades.")

    def get_filled_orders_count(self, strategy_id: str) -> int:
        """
        Returns the number of filled orders for the given strategy.
        """
        try:
            filled_orders = [order for order in self.orders if
                             order['strategy_id'] == strategy_id and order['status'] == 'filled']
            return len(filled_orders)
        except Exception as e:
            logger.error(f"Error retrieving filled orders for strategy '{strategy_id}': {e}", exc_info=True)
            return 0

    def get_available_balance(self, strategy_id: str) -> float:
        """
        Returns the available balance for the given strategy.
        """
        try:
            # Implement logic to calculate available balance
            # This is a placeholder and should be replaced with actual implementation
            return self.balances.get(strategy_id, 0.0) - self.locked_funds.get(strategy_id, 0.0)
        except Exception as e:
            logger.error(f"Error retrieving available balance for strategy '{strategy_id}': {e}", exc_info=True)
            return 0.0

    def get_total_filled_order_volume(self, strategy_id: str) -> float:
        """
        Returns the total volume of filled orders for the given strategy.
        """
        try:
            filled_orders = [order for order in self.orders if
                             order['strategy_id'] == strategy_id and order['status'] == 'filled']
            total_volume = sum(order['size'] for order in filled_orders)
            return total_volume
        except Exception as e:
            logger.error(f"Error retrieving filled order volume for strategy '{strategy_id}': {e}", exc_info=True)
            return 0.0

    def get_total_unfilled_order_volume(self, strategy_id: str) -> float:
        """
        Returns the total volume of unfilled orders for the given strategy.
        """
        try:
            unfilled_orders = [order for order in self.orders if
                               order['strategy_id'] == strategy_id and order['status'] in ['pending', 'open']]
            total_volume = sum(order['size'] for order in unfilled_orders)
            return total_volume
        except Exception as e:
            logger.error(f"Error retrieving unfilled order volume for strategy '{strategy_id}': {e}", exc_info=True)
            return 0.0

    def get_unfilled_orders_count(self, strategy_id: str) -> int:
        """
        Returns the number of unfilled orders for the given strategy.
        """
        try:
            unfilled_orders = [order for order in self.orders if
                               order['strategy_id'] == strategy_id and order['status'] in ['pending', 'open']]
            return len(unfilled_orders)
        except Exception as e:
            logger.error(f"Error retrieving unfilled orders for strategy '{strategy_id}': {e}", exc_info=True)
            return 0

    def place_order(self, trade: Trade, user_name: str) -> tuple[str, str | None]:
        """
        Executes a place order command on the exchange interface.
        """
        if not self.exchange_connected():
            error_msg = 'No exchange_interface connected.'
            logger.error(f"Trades:place_order(): {error_msg}")
            return 'fail', error_msg

        exchange = self.exchange_interface.get_exchange(ename=trade.target, uname=user_name)

        try:
            if trade.order_type in ['MARKET', 'LIMIT']:
                order = exchange.place_order(
                    symbol=trade.symbol,
                    side=trade.side,
                    type=trade.order_type,
                    timeInForce=trade.time_in_force,
                    quantity=trade.base_order_qty,
                    price=trade.order_price if trade.order_type == 'LIMIT' else None
                )
            elif trade.order_type == 'CHASE':
                error_msg = 'Trades:place_order(): CHASE orders not implemented yet.'
                logger.error(error_msg)
                return 'fail', error_msg
            else:
                error_msg = f"Trades:place_order(): No implementation for order type: {trade.order_type}"
                logger.error(error_msg)
                return 'fail', error_msg
        except Exception as e:
            error_msg = f"Trades:place_order(): {e}"
            logger.error(error_msg, exc_info=True)
            return 'fail', error_msg

        # Assign the exchange's order ID to the trade
        if hasattr(order, 'orderId'):
            trade.unique_id = order.orderId
        else:
            logger.warning(f"Order object does not have 'orderId'. Using existing unique_id: {trade.unique_id}")

        # Update the trade status and store the order
        trade.order_placed(order)
        logger.info(f"Trade {trade.unique_id} placed: {trade.side} {trade.symbol} at {trade.order_price}")
        return 'success', None

    def update(self, price_updates: dict[str, float]) -> list[dict[str, Any]]:
        """
        Updates the price for all active trades based on provided price updates.
        :param price_updates: Dictionary mapping (exchange:symbol) or symbol to prices.
                             Keys can be "binance:BTC/USDT" for exchange-specific prices,
                             or just "BTC/USDT" for generic prices.
        :return: List of dictionaries containing updated trade data.
        """
        _debug_logger.debug(f"=== Trades.update() called ===")
        _debug_logger.debug(f"price_updates: {price_updates}")
        _debug_logger.debug(f"active_trades count: {len(self.active_trades)}")
        _debug_logger.debug(f"active_trades keys: {list(self.active_trades.keys())}")

        r_update = []

        for trade_id, trade in list(self.active_trades.items()):
            symbol = trade.symbol
            exchange = getattr(trade, 'exchange', None) or trade.target
            _debug_logger.debug(f"Processing trade_id={trade_id}, symbol={symbol}, exchange={exchange}, status={trade.status}")

            # First try exchange-specific price key
            exchange_key = f"{exchange.lower()}:{symbol}" if exchange else None
            current_price = price_updates.get(exchange_key) if exchange_key else None
            _debug_logger.debug(f"Tried exchange_key '{exchange_key}': {current_price}")

            # Fall back to symbol-only lookup
            if current_price is None:
                current_price = price_updates.get(symbol)
                _debug_logger.debug(f"Tried symbol '{symbol}': {current_price}")

            if current_price is None:
                # Try to find a matching symbol (handle format differences like BTC/USD vs BTC/USDT)
                for price_key, price in price_updates.items():
                    # Extract symbol part if key is exchange:symbol format
                    price_symbol = price_key.split(':')[-1] if ':' in price_key else price_key
                    # Normalize both symbols for comparison
                    norm_trade = symbol.upper().replace('/', '')
                    norm_price = price_symbol.upper().replace('/', '')
                    if norm_trade == norm_price or norm_trade.rstrip('T') == norm_price.rstrip('T'):
                        current_price = price
                        logger.debug(f"Matched trade symbol '{symbol}' to price key '{price_key}'")
                        break

            if current_price is None:
                _debug_logger.debug(f"current_price is None after matching, skipping trade {trade_id}")
                logger.warning(f"No price update for symbol '{symbol}' (exchange: {exchange}). Available: {list(price_updates.keys())}. Skipping trade {trade_id}.")
                continue

            _debug_logger.debug(f"current_price resolved to: {current_price}")
            _debug_logger.debug(f"Checking trade.status: '{trade.status}' in ['unfilled', 'part-filled']")
            if trade.status in ['unfilled', 'part-filled']:
                status = self.exchange_interface.get_trade_status(trade)
                if status in ['FILLED', 'PARTIALLY_FILLED']:
                    executed_qty = self.exchange_interface.get_trade_executed_qty(trade)
                    try:
                        executed_price = self.exchange_interface.get_trade_executed_price(
                            trade, fallback_price=current_price
                        )
                    except Exception as e:
                        logger.error(
                            f"Trades:update() unable to resolve executed price for trade {trade_id}: {e}",
                            exc_info=True
                        )
                        continue

                    if executed_price <= 0:
                        logger.error(
                            f"Trades:update() received non-positive executed price for trade {trade_id}: "
                            f"{executed_price}"
                        )
                        continue

                    trade.trade_filled(qty=executed_qty, price=executed_price)
                elif status in ['CANCELED', 'EXPIRED', 'REJECTED']:
                    logger.warning(f"Trade {trade_id} status: {status}")
                    # Handle according to your business logic, e.g., mark as closed or retry
                    trade.status = status.lower()
                    continue  # Skip further processing for this trade

            _debug_logger.debug(f"Checking if trade.status == 'inactive': {trade.status == 'inactive'}")
            if trade.status == 'inactive':
                _debug_logger.debug(f"Trade {trade_id} is inactive, skipping")
                logger.error(f"Trades:update() - inactive trade encountered: {trade_id}")
                continue  # Skip processing for inactive trades

            _debug_logger.debug(f"Calling trade.update({current_price})")
            trade.update(current_price)
            trade_status = trade.status
            _debug_logger.debug(f"After trade.update(), trade_status={trade_status}, trade.stats={trade.stats}")

            if trade_status in ['updated', 'filled', 'part-filled']:
                update_data = {
                    'status': trade_status,
                    'id': trade.unique_id,
                    'pl': trade.stats.get('profit', 0.0),
                    'pl_pct': trade.stats.get('profit_pct', 0.0)
                }
                r_update.append(update_data)
                _debug_logger.debug(f"Appended update_data: {update_data}")
                logger.info(f"Trade {trade_id} updated: price={current_price}, P/L={update_data['pl']:.2f} ({update_data['pl_pct']:.2f}%)")
            else:
                _debug_logger.debug(f"trade_status '{trade_status}' not in update list, appending minimal data")
                r_update.append({'id': trade.unique_id, 'status': trade_status})

        _debug_logger.debug(f"=== Trades.update() returning: {r_update} ===")
        return r_update

    def update_prices_only(self, price_updates: dict[str, float]) -> list[dict[str, Any]]:
        """
        Update current prices and P/L for active trades.

        This method ONLY updates prices and P/L calculations. It does NOT poll brokers
        for order status - that happens in strategy_execution_loop via ManualTradingBrokerManager.

        :param price_updates: Dictionary mapping (exchange:symbol) or symbol to prices.
        :return: List of dictionaries containing updated trade data.
        """
        r_update = []

        for trade_id, trade in list(self.active_trades.items()):
            symbol = trade.symbol
            exchange = getattr(trade, 'exchange', None) or trade.target

            # Resolve price from updates
            exchange_key = f"{exchange.lower()}:{symbol}" if exchange else None
            current_price = price_updates.get(exchange_key) if exchange_key else None

            if current_price is None:
                current_price = price_updates.get(symbol)

            if current_price is None:
                # Try to find a matching symbol
                for price_key, price in price_updates.items():
                    price_symbol = price_key.split(':')[-1] if ':' in price_key else price_key
                    norm_trade = symbol.upper().replace('/', '')
                    norm_price = price_symbol.upper().replace('/', '')
                    if norm_trade == norm_price or norm_trade.rstrip('T') == norm_price.rstrip('T'):
                        current_price = price
                        break

            if current_price is None:
                continue  # No price available for this trade

            # Skip inactive or closed trades
            if trade.status in ['inactive', 'closed']:
                continue

            # Update P/L values (no broker polling)
            trade.update(current_price)
            trade_status = trade.status

            if trade_status in ['updated', 'filled', 'part-filled']:
                # Compute broker_key for frontend position matching
                broker_key = 'paper' if trade.broker_kind == 'paper' else f"{trade.broker_exchange}_{trade.broker_mode}"
                update_data = {
                    'status': trade_status,
                    'id': trade.unique_id,
                    'symbol': trade.symbol,
                    'broker_key': broker_key,
                    'pl': trade.stats.get('profit', 0.0),
                    'pl_pct': trade.stats.get('profit_pct', 0.0),
                    'current_price': current_price
                }
                r_update.append(update_data)
            else:
                broker_key = 'paper' if trade.broker_kind == 'paper' else f"{trade.broker_exchange}_{trade.broker_mode}"
                r_update.append({'id': trade.unique_id, 'status': trade_status, 'symbol': trade.symbol, 'broker_key': broker_key})

        return r_update

    def close_trade(self, trade_id: str, current_price: float = None) -> dict:
        """
        Closes a specific trade by settling it.

        :param trade_id: The unique ID of the trade.
        :param current_price: Optional current price (used for paper trades).
        :return: Dict with success status and trade info.
        """
        trade = self.get_trade_by_id(trade_id)
        if not trade:
            logger.error(f"close_trade(): Trade ID {trade_id} not found.")
            return {"success": False, "message": f"Trade {trade_id} not found."}

        if trade.status == 'closed':
            logger.warning(f"close_trade(): Trade ID {trade_id} is already closed.")
            return {"success": False, "message": f"Trade {trade_id} is already closed."}

        try:
            # Get current price
            if current_price is None:
                if trade.is_paper:
                    # For paper trades without a price, use the last known current price
                    current_price = trade.stats.get('current_price', trade.order_price)
                elif self.exchange_interface:
                    current_price = self.exchange_interface.get_price(trade.symbol)
                else:
                    current_price = trade.stats.get('current_price', trade.order_price)

            # Settle the trade
            trade.settle(qty=trade.base_order_qty, price=current_price)

            # Calculate final P/L
            final_pl = trade.stats.get('profit', 0.0)
            final_pl_pct = trade.stats.get('profit_pct', 0.0)

            # Move from active to settled
            if trade.status == 'closed':
                del self.active_trades[trade_id]
                self.settled_trades[trade_id] = trade
                self.stats['num_trades'] -= 1

                # Update database - either delete or mark as closed
                if self.data_cache:
                    self._save_trade(trade)

                logger.info(f"Trade {trade_id} closed. P/L: {final_pl:.2f} ({final_pl_pct:.2f}%)")

                return {
                    "success": True,
                    "message": "Trade closed successfully.",
                    "trade_id": trade_id,
                    "final_pl": final_pl,
                    "final_pl_pct": final_pl_pct,
                    "settled_price": current_price
                }
            else:
                # Partial settlement
                self._save_trade(trade)
                logger.info(f"Trade {trade_id} partially settled.")
                return {
                    "success": True,
                    "message": "Trade partially settled.",
                    "trade_id": trade_id
                }

        except Exception as e:
            logger.error(f"Error closing trade '{trade_id}': {e}", exc_info=True)
            return {"success": False, "message": f"Error closing trade: {str(e)}"}

    def close_position(self, user_id: int, symbol: str, broker_key: str) -> dict:
        """
        Close filled exposure for a symbol (position-first operation).

        This only affects filled/part-filled trades:
        - Fully filled trades: settle entire qty with P/L calculation
        - Part-filled trades: settle filled portion, cancel unfilled remainder
        - Pending/open orders: NOT affected (use cancel_orders_for_symbol for those)

        :param user_id: The user ID.
        :param symbol: Trading symbol to close.
        :param broker_key: The broker key ('paper' or 'exchange_production').
        :return: Dict with success status and details.
        """
        if not self.manual_broker_manager:
            return {"success": False, "message": "Broker manager not configured"}

        result = self.manual_broker_manager.close_position(user_id, symbol, broker_key)

        if result.get('success'):
            close_price = result.get('filled_price', 0.0)
            trades_closed = 0

            for trade_id, trade in list(self.active_trades.items()):
                # Check if this trade belongs to the same broker
                if broker_key == 'paper':
                    matches_broker = trade.broker_kind == 'paper'
                else:
                    trade_broker_key = f"{trade.broker_exchange}_{trade.broker_mode}"
                    matches_broker = (trade.broker_kind == 'live' and trade_broker_key == broker_key)

                if not (trade.symbol == symbol and matches_broker and trade.creator == user_id):
                    continue

                # Handle based on trade status
                if trade.status == 'filled':
                    # Fully filled - settle entire qty
                    if close_price <= 0:
                        close_price = self._get_close_price(trade)
                    trade.settle(qty=trade.stats.get('qty_filled', trade.base_order_qty), price=close_price)
                    trade.status = 'closed'
                    trades_closed += 1

                elif trade.status == 'part-filled':
                    # Part filled - settle only the filled portion, cancel the rest
                    if close_price <= 0:
                        close_price = self._get_close_price(trade)

                    filled_qty = trade.stats.get('qty_filled', 0)
                    if filled_qty > 0:
                        trade.settle(qty=filled_qty, price=close_price)

                    # Cancel the unfilled remainder through broker
                    if trade.broker_order_id:
                        self.manual_broker_manager.cancel_order(
                            user_id, trade.broker_order_id, broker_key
                        )

                    trade.status = 'closed'
                    unfilled = trade.base_order_qty - filled_qty
                    logger.info(f"Part-filled trade {trade_id}: settled {filled_qty}, "
                               f"cancelled remainder of {unfilled}")
                    trades_closed += 1

                elif trade.status in ['pending', 'open', 'unfilled']:
                    # No fills - skip (these are just resting orders, not positions)
                    continue

                else:
                    # Already closed/cancelled - skip
                    continue

                # Save and move to settled
                self._save_trade(trade)
                del self.active_trades[trade_id]
                self.settled_trades[trade_id] = trade
                self.stats['num_trades'] -= 1

                final_pl = trade.stats.get('profit', 0.0)
                logger.info(f"Trade {trade_id} closed via position close. P/L: {final_pl:.2f}")

            result['trades_closed'] = trades_closed

        return result

    def _get_close_price(self, trade) -> float:
        """Get current price for settlement."""
        if self.exchange_interface:
            try:
                return self.exchange_interface.get_price(trade.symbol)
            except Exception:
                pass
        return trade.stats.get('current_price', trade.order_price)

    def cancel_order(self, trade_id: str) -> dict:
        """
        Cancel a specific unfilled order.

        :param trade_id: The unique ID of the trade/order to cancel.
        :return: Dict with success status and message.
        """
        trade = self.get_trade_by_id(trade_id)
        if not trade:
            return {"success": False, "message": "Trade not found"}

        if trade.status not in ['open', 'pending', 'unfilled']:
            return {"success": False, "message": "Cannot cancel: order already filled or closed"}

        # If using broker manager, cancel through it
        if self.manual_broker_manager and trade.broker_order_id:
            broker_key = 'paper' if trade.broker_kind == 'paper' else f"{trade.broker_exchange}_{trade.broker_mode}"
            result = self.manual_broker_manager.cancel_order(
                trade.creator, trade.broker_order_id, broker_key
            )
            if not result.get('success'):
                return result

        trade.status = 'cancelled'
        self._save_trade(trade)

        # Move from active trades to settled trades (so it appears in history)
        if trade_id in self.active_trades:
            del self.active_trades[trade_id]
            self.settled_trades[trade_id] = trade
            self.stats['num_trades'] -= 1

        logger.info(f"Order {trade_id} cancelled")
        return {"success": True, "message": "Order cancelled"}

    def cancel_orders_for_symbol(self, user_id: int, symbol: str, broker_key: str) -> dict:
        """
        Cancel all open/pending orders for a symbol.

        This is a separate action from close_position() - user must explicitly choose this.
        Does NOT affect filled positions.

        :param user_id: The user ID.
        :param symbol: Trading symbol.
        :param broker_key: The broker key ('paper' or 'exchange_mode').
        :return: Dict with success status, message, and count.
        """
        cancelled = 0
        errors = []

        for trade_id, trade in list(self.active_trades.items()):
            # Check broker match
            if broker_key == 'paper':
                matches_broker = trade.broker_kind == 'paper'
            else:
                trade_broker_key = f"{trade.broker_exchange}_{trade.broker_mode}"
                matches_broker = (trade.broker_kind == 'live' and trade_broker_key == broker_key)

            if not (trade.symbol == symbol and matches_broker and trade.creator == user_id):
                continue

            # Only cancel unfilled orders
            if trade.status not in ['pending', 'open', 'unfilled']:
                continue

            result = self.cancel_order(trade_id)
            if result.get('success'):
                cancelled += 1
            else:
                errors.append(f"{trade_id}: {result.get('message')}")

        if errors:
            return {
                "success": False,
                "message": f"Cancelled {cancelled}, errors: {'; '.join(errors)}",
                "count": cancelled
            }
        return {"success": True, "message": f"Cancelled {cancelled} orders", "count": cancelled}

    def reduce_trade(self, user_id: int, trade_id: str, qty: float) -> float | None:
        """
        Reduces the position of a trade.
        :param user_id: ID of the user.
        :param trade_id: The unique ID of the trade.
        :param qty: The quantity to reduce the trade by.
        :return: The remaining quantity after reduction or None if unsuccessful.
        """
        trade = self.get_trade_by_id(trade_id)
        if not trade:
            logger.error(f"reduce_trade(): Trade ID {trade_id} not found.")
            return None

        if trade.status == 'closed':
            logger.warning(f"reduce_trade(): Cannot reduce a closed trade {trade_id}.")
            return None

        if trade.status in ['inactive', 'unfilled']:
            if trade.status == 'unfilled' and trade.order:
                try:
                    self.exchange_interface.cancel_order(symbol=trade.symbol, orderId=trade.order.orderId)
                    logger.info(f"Cancelled unfilled order {trade.unique_id} for trade {trade_id}.")
                except Exception as e:
                    logger.error(f"Failed to cancel order {trade.unique_id} for trade {trade_id}: {e}", exc_info=True)
                    return None

            # Reduce the order quantity
            if qty > trade.base_order_qty:
                qty = trade.base_order_qty

            trade.base_order_qty -= qty
            trade.stats['opening_value'] = trade.base_order_qty * trade.stats['opening_price']
            trade.update_values(trade.stats['opening_price'])

            logger.info(f"Trade {trade_id} reduced by {qty}. New quantity: {trade.base_order_qty}")

            # If trade was unfilled, attempt to place a new order with the reduced quantity
            if trade.status == 'unfilled':
                try:
                    status, msg = self.place_order(trade, user_name=self._get_user_name(user_id))
                    if status != 'success':
                        logger.error(f"Failed to place reduced order for trade {trade_id}: {msg}")
                        return None
                except Exception as e:
                    logger.error(f"Exception while placing reduced order for trade {trade_id}: {e}", exc_info=True)
                    return None

            return trade.base_order_qty

        # Settling more than owned is not allowed
        if qty > trade.stats.get('qty_filled', 0.0):
            qty = trade.stats.get('qty_filled', 0.0)

        try:
            current_price = self.exchange_interface.get_price(trade.symbol)
            trade.settle(qty=qty, price=current_price)

            if trade.status == 'closed':
                del self.active_trades[trade_id]
                self.settled_trades[trade_id] = trade
                self.stats['num_trades'] -= 1
                logger.info(f"Trade {trade_id} has been closed after reduction.")
                return 0.0
            else:
                left = trade.stats['qty_filled'] - trade.stats['qty_settled']
                logger.info(f"Trade {trade_id} partially settled. Remaining quantity: {left}")
                return float(f"{left:.3f}")
        except Exception as e:
            logger.error(f"Error settling trade '{trade_id}': {e}", exc_info=True)
            return None

    def find_trade_by_broker_order_id(self, broker_order_id: str) -> Trade | None:
        """
        Find a trade by its broker order ID.

        :param broker_order_id: The broker order ID to search for.
        :return: Trade object if found, None otherwise.
        """
        for trade in self.active_trades.values():
            if trade.broker_order_id == broker_order_id:
                return trade
        return None

    def recover_brokers(self) -> int:
        """
        Recover brokers for broker-managed trades after restart.

        This should be called after manual_broker_manager is wired up.
        It ensures that persisted broker-managed trades have their brokers
        recreated so they can be polled and tracked.

        :return: Number of brokers recovered.
        """
        if not self.manual_broker_manager:
            logger.debug("No broker manager configured, skipping broker recovery")
            return 0

        # Get trades that have broker_order_id (broker-managed)
        broker_trades = [
            trade for trade in self.active_trades.values()
            if trade.broker_order_id
        ]

        if not broker_trades:
            return 0

        # Use users to get username from user_id
        def get_username(user_id):
            return self._get_user_name(user_id)

        recovered = self.manual_broker_manager.recover_brokers_for_trades(
            trades=broker_trades,
            get_username_func=get_username
        )

        logger.info(f"Recovered {recovered} brokers for {len(broker_trades)} broker-managed trades")
        return recovered
