make html  this scriptbot_code = "="
import json
import os
import time
import threading
import asyncio
import signal
import sys
from collections import deque
import logging
from logging.handlers import RotatingFileHandler

import ccxt
import pandas as pd
import requests
import websockets
from dotenv import load_dotenv
from rich.console import Console
from rich.live import Live
from rich.table import Table
from rich.theme import Theme
from rich.layout import Layout
from rich.panel import Panel
from rich.text import Text
import datetime 

# ---------------------------
# Setup logging and console
# ---------------------------

logger = logging.getLogger("AutoTrader")
logger.setLevel(logging.INFO)
file_handler = RotatingFileHandler("bot.log", maxBytes=5_000_000, backupCount=3)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

custom_theme = Theme({
    "success": "bold green",
    "error": "bold red",
    "warning": "bold yellow",
    "info": "bold cyan",
    "header": "bold magenta"
})
console = Console(theme=custom_theme)

# ---------------------------
# Load environment variables
# ---------------------------

load_dotenv()
API_KEY = os.getenv("BINANCE_API_KEY")
SECRET_KEY = os.getenv("BINANCE_SECRET_KEY")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
USE_TESTNET = os.getenv("USE_TESTNET", "false").lower() == "true"

# ---------------------------
# In-memory log handler for dashboard system console panel
# ---------------------------

class InMemoryLogHandler(logging.Handler):
    def __init__(self, max_messages=50):
        super().__init__()
        self.max_messages = max_messages
        self.messages = []

    def emit(self, record):
        msg = self.format(record)
        timestamp = datetime.datetime.fromtimestamp(record.created).strftime("%H:%M:%S")
        self.messages.append(f"[{timestamp}] {msg}")
        if len(self.messages) > self.max_messages:
            self.messages.pop(0)

in_memory_log_handler = InMemoryLogHandler()
in_memory_log_handler.setFormatter(formatter)
logger.addHandler(in_memory_log_handler)

# ---------------------------
# Dashboard helper functions
# ---------------------------

def create_header():
    now = datetime.datetime.now().strftime("%a, %b %d, %Y %H:%M:%S")
    text = Text()
    text.append("CYBOT v2.1.0\\n", style="bold magenta")
    text.append("BINANCE FUTURES • AI-POWERED ALGORITHM\\n")
    text.append(f"CONNECTED • BINANCE\\n{now}\\n", style="bold green")
    return Panel(text, style="white on black")

def create_status_panel(bot):
    positions_count = sum(len(pos_list) for pos_list in bot.open_positions.values())
    max_drawdown = getattr(bot, 'max_drawdown_pct', -4.2)
    win_rate = getattr(bot, 'win_rate_pct', 78.6)
    total_trades = sum(len(hist) for hist in bot.position_history.values())
    table = Table.grid(expand=True)
    table.add_column(justify="center")
    table.add_column(justify="center")
    table.add_column(justify="center")
    table.add_column(justify="center")
    table.add_column(justify="center")
    table.add_row(
        f"[bold]BALANCE[/bold]\\n${bot.account_balance:,.2f}",
        f"[bold]LEVERAGE[/bold]\\n{bot.leverage}x",
        f"[bold]RISK PER TRADE[/bold]\\n{bot.risk_pct:.2f}%",
        f"[bold]POSITIONS[/bold]\\n{positions_count}",
        f"[bold]MAX DRAWDOWN[/bold]\\n{max_drawdown:.2f}%",
    )
    table.add_row(
        f"[bold]WIN RATE[/bold]\\n{win_rate:.1f}%",
        f"[bold]TOTAL TRADES[/bold]\\n{total_trades}",
        "", "", ""
    )
    return Panel(table, title="STATUS", style="cyan")

def create_market_overview(bot, symbol):
    interval = "5m"
    df = bot.fetch_ohlcv_for_symbol(symbol, interval)
    df_processed = bot.strategies[symbol].get_processed_dataframe(df)
    if df_processed.empty:
        return Panel("No data", title="MARKET OVERVIEW", style="green")
    last = df_processed.iloc[-1]
    ema9 = last['EMA_FAST']
    ema21 = last['EMA_SLOW']
    rsi14 = last['RSI']
    macd = last['MACD_HIST']

    # Get order flow features
    ofc = bot.order_flow_collectors.get(symbol)
    order_flow_features = ofc.get_features() if ofc else {}
    bid_volume = order_flow_features.get('bid_volume', 0)
    ask_volume = order_flow_features.get('ask_volume', 0)

    grid = Table.grid(expand=True)
    grid.add_row(
        f"[bold]Symbol:[/bold] {symbol}",
        f"[bold]Interval:[/bold] {interval}",
        f"EMA(9): {ema9:.2f}",
        f"EMA(21): {ema21:.2f}",
        f"RSI(14): {rsi14:.2f}",
        f"MACD: {'+' if macd > 0 else ''}{macd:.4f}"
    )
    grid.add_row(
        f"[bold]Bid Vol:[/bold] [green]{bid_volume:.2f}[/green]",
        f"[bold]Ask Vol:[/bold] [red]{ask_volume:.2f}[/red]",
        "", "", "", "" # Fill empty columns for alignment
    )
    return Panel(grid, title="MARKET OVERVIEW", style="green")

def create_positions_table(bot):
    table = Table(title="OPEN POSITIONS")
    table.add_column("SYMBOL")
    table.add_column("SIDE")
    table.add_column("SIZE", justify="right")
    table.add_column("ENTRY", justify="right")
    table.add_column("MARK", justify="right")
    table.add_column("P&L", justify="right")
    for symbol, pos_list in bot.open_positions.items():
        for pos in pos_list:
            try:
                ticker = bot.exchange.fetch_ticker(symbol)
                mark_price = ticker.get('last', 0)
            except Exception:
                mark_price = 0.0
            pnl = (mark_price - pos['entry_price']) * pos['size'] if pos['direction'] == 'buy' else (pos['entry_price'] - mark_price) * pos['size']
            pnl_style = "green" if pnl >= 0 else "red"
            table.add_row(
                symbol,
                pos['direction'].upper(),
                f"{pos['size']:.2f}",
                f"{pos['entry_price']:.5f}",
                f"{mark_price:.5f}",
                f"[{pnl_style}]{pnl:.2f}[/{pnl_style}]",
            )
    if len(table.rows) == 0:
        return Panel("No open positions", title="OPEN POSITIONS")
    return table

def create_trade_log(bot):
    table = Table(title="TRADING LOG")
    table.add_column("TIME", style="dim")
    table.add_column("ACTION")
    table.add_column("SYMBOL")
    table.add_column("PRICE", justify="right")
    table.add_column("P&L", justify="right")

    trades = []
    for symbol, hist in bot.position_history.items():
        for entry in hist:
            time_str = datetime.datetime.fromtimestamp(entry.get('timestamp', time.time())).strftime("%H:%M:%S")
            trades.append({
                'time': time_str,
                'action': entry.get('direction','').upper(),
                'symbol': symbol,
                'price': entry.get('exit', 0),
                'pnl': entry.get('pnl', 0),
            })
    trades = sorted(trades, key=lambda x: x['time'], reverse=True)
    for entry in trades[:10]:
        pnl_style = "green" if entry['pnl'] >= 0 else "red"
        table.add_row(
            entry['time'],
            entry['action'],
            entry['symbol'],
            f"{entry['price']:.5f}",
            f"[{pnl_style}]{entry['pnl']:.2f}[/{pnl_style}]",
        )
    if len(table.rows) == 0:
        return Panel("No trades yet", title="TRADING LOG")
    return table

def create_system_console(log_handler):
    lines = "\n".join(log_handler.messages[-10:])
    return Panel(lines, title="SYSTEM CONSOLE", style="yellow")

def build_layout(bot, log_handler):
    layout = Layout()
    layout.split(Layout(name="header", size=3), Layout(name="main"))
    layout["main"].split_row(Layout(name="left"), Layout(name="right"))
    layout["left"].split(Layout(name="status", size=7),
                         Layout(name="market", size=3),
                         Layout(name="positions"))
    layout["right"].split(Layout(name="trade_log", ratio=2),
                         Layout(name="system_console", ratio=1))

    layout["header"].update(create_header())
    layout["status"].update(create_status_panel(bot))
    first_symbol = bot.symbols[0] if bot.symbols else "XRPUSDT"
    layout["market"].update(create_market_overview(bot, first_symbol))
    layout["positions"].update(create_positions_table(bot))
    layout["trade_log"].update(create_trade_log(bot))
    layout["system_console"].update(create_system_console(log_handler))
    return layout

# OrderFlowCollector
class OrderFlowCollector:
    def __init__(self, symbol, window_seconds=120):
        self.symbol = symbol.lower()
        self.window_seconds = window_seconds
        self.trade_volumes = deque()
        self.bid_volume = 0
        self.ask_volume = 0
        self.best_bid = 0
        self.best_ask = 0
        self._lock = threading.Lock()
        self._latest_features = {}
        self._stop_event = threading.Event()

    async def connect(self):
        streams = f"{self.symbol}@trade/{self.symbol}@depth20@100ms"
        url = f"wss://fstream.binance.com/stream?streams={streams}"
        retry_delay = 1
        max_delay = 60
        while not self._stop_event.is_set():
            try:
                async with websockets.connect(url) as ws:
                    retry_delay = 1
                    logger.info(f"[{self.symbol.upper()}] Connected to Binance Futures WebSocket.")
                    while not self._stop_event.is_set():
                        try:
                            message = await asyncio.wait_for(ws.recv(), timeout=60)
                            await self.process_message(message)
                        except asyncio.TimeoutError:
                            pass
                        except Exception as e:
                            logger.warning(f"[{self.symbol.upper()}] WebSocket recv error: {e}")
                            break
            except Exception as e:
                logger.warning(f"[{self.symbol.upper()}] WebSocket error: {e}. Reconnecting in {retry_delay}s.")
                await asyncio.sleep(retry_delay)
                retry_delay = min(max_delay, retry_delay * 2)

    async def process_message(self, message):
        try:
            data = json.loads(message)
            stream = data['stream']
            payload = data['data']
            with self._lock:
                if "@trade" in stream:
                    ts = payload['T'] / 1000
                    qty = float(payload['q'])
                    side = "sell" if payload['m'] else "buy"
                    self.trade_volumes.append((ts, qty, side))
                    self._cleanup_old_trades(ts)
                elif "@depth" in stream:
                    bids = payload.get('b', [])
                    asks = payload.get('a', [])

                    if not bids:
                        logger.warning(f"[{self.symbol.upper()}] Depth update received with empty bids.")
                    if not asks:
                        logger.warning(f"[{self.symbol.upper()}] Depth update received with empty asks.")

                    self.best_bid = float(bids[0][0]) if bids else 0
                    self.best_ask = float(asks[0][0]) if asks else 0
                    self.bid_volume = sum(float(bid[1]) for bid in bids[:10])
                    self.ask_volume = sum(float(ask[1]) for ask in asks[:10])
                    logger.debug(f"[{self.symbol.upper()}] Processed depth update. Bid: {self.best_bid}, Ask: {self.best_ask}")

                net_trade_vol = sum(q for (t, q, s) in self.trade_volumes if s == 'buy') - \
                                sum(q for (t, q, s) in self.trade_volumes if s == 'sell')
                depth_imbalance = self.bid_volume - self.ask_volume
                self._latest_features = {
                    'net_trade_volume': net_trade_vol,
                    'order_book_imbalance': depth_imbalance,
                    'best_bid': self.best_bid,
                    'best_ask': self.best_ask,
                    'timestamp': time.time()
                }
        except json.JSONDecodeError as e:
            logger.error(f"[{self.symbol.upper()}] JSON decoding error in WS message: {e}. Message: {message[:200]}...")
        except KeyError as e:
            logger.error(f"[{self.symbol.upper()}] Missing key in WS message payload: {e}. Payload: {payload}")
        except Exception as e:
            logger.error(f"[{self.symbol.upper()}] Generic error processing WS message: {e}. Data: {data}")

    def _cleanup_old_trades(self, current_ts):
        while self.trade_volumes and (current_ts - self.trade_volumes[0][0]) > self.window_seconds:
            self.trade_volumes.popleft()

    def get_features(self):
        with self._lock:
            return self._latest_features.copy()

    def start(self):
        def _run_loop():
            asyncio.run(self.connect())

        thread = threading.Thread(target=_run_loop, daemon=True)
        thread.start()
        return thread

    def stop(self):
        self._stop_event.set()


# TradingStrategy
class TradingStrategy:
    def __init__(self, params):
        self.fast_ema = params['fast_ema']
        self.slow_ema = params['slow_ema']
        self.rsi_period = params['rsi_period']
        self.macd_fast_period = params['macd_fast_period']
        self.macd_slow_period = params['macd_slow_period']
        self.macd_signal_period = params['macd_signal_period']
        self.stop_loss_pct = params['stop_loss_pct']
        self.risk_reward_ratio = params['risk_reward_ratio']
        self.breakeven_profit_pct = params['breakeven_profit_pct']
        self.trailing_stop_pct = params['trailing_stop_pct']
        self.partial_tp_levels = params['partial_tp_levels']

    def rsi(self, series):
        delta = series.diff()
        gain = delta.where(delta > 0, 0.0)
        loss = -delta.where(delta < 0, 0.0)
        avg_gain = gain.rolling(self.rsi_period).mean()
        avg_loss = loss.rolling(self.rsi_period).mean()
        rs = avg_gain / (avg_loss + 1e-8)
        return 100 - (100 / (1 + rs))

    def _detect_simple_divergence(self, df, indicator_col, divergence_type):
        if len(df) < 2:
            return False
        last = df.iloc[-1]
        prev = df.iloc[-2]
        price_current = last['close']
        price_prev = prev['close']
        indicator_current = last[indicator_col]
        indicator_prev = prev[indicator_col]
        if divergence_type == 'bullish':
            return price_current < price_prev and indicator_current > indicator_prev
        elif divergence_type == 'bearish':
            return price_current > price_prev and indicator_current < indicator_prev
        return False

    def get_processed_dataframe(self, ohlcv_df):
        df = ohlcv_df.copy()
        price = df['close']
        df['EMA_FAST'] = price.ewm(span=self.fast_ema, adjust=False).mean()
        df['EMA_SLOW'] = price.ewm(span=self.slow_ema, adjust=False).mean()
        df['RSI'] = self.rsi(price)

        ema_fast_macd = price.ewm(span=self.macd_fast_period, adjust=False).mean()
        ema_slow_macd = price.ewm(span=self.macd_slow_period, adjust=False).mean()
        macd_line = ema_fast_macd - ema_slow_macd
        signal_line = macd_line.ewm(span=self.macd_signal_period, adjust=False).mean()
        df['MACD_LINE'] = macd_line
        df['SIGNAL_LINE'] = signal_line
        df['MACD_HIST'] = macd_line - signal_line
        return df

    def check_signal(self, df, df_confirmation, open_positions):
        if len(df) < 3:
            return None, None

        last = df.iloc[-1]
        prev = df.iloc[-2]
        current_price = last['close']

        macd_conf = df_confirmation.iloc[-1]['MACD_HIST'] if len(df_confirmation) > 0 else 0
        macd_bullish_div = self._detect_simple_divergence(df, 'MACD_HIST', 'bullish')
        macd_bearish_div = self._detect_simple_divergence(df, 'MACD_HIST', 'bearish')
        rsi_bullish_div = self._detect_simple_divergence(df, 'RSI', 'bullish')
        rsi_bearish_div = self._detect_simple_divergence(df, 'RSI', 'bearish')

        if (prev['EMA_FAST'] < prev['EMA_SLOW'] and last['EMA_FAST'] > last['EMA_SLOW']
            and last['RSI'] > 50 and last['MACD_HIST'] > 0
            and last['close'] > prev['close']
            and macd_conf > 0
            and (macd_bullish_div or rsi_bullish_div)):
            if not any(p['direction'] == 'buy' for p in open_positions):
                return 'buy', current_price

        elif (prev['EMA_FAST'] > prev['EMA_SLOW'] and last['EMA_FAST'] < last['EMA_SLOW']
              and last['RSI'] < 50 and last['MACD_HIST'] < 0
              and last['close'] < prev['close']
              and macd_conf < 0
              and (macd_bearish_div or rsi_bearish_div)):
            if not any(p['direction'] == 'sell' for p in open_positions):
                return 'sell', current_price

        return None, None

    def calculate_sl_tp(self, current_price, signal, stop_loss_pct, risk_reward_ratio):
        if signal == 'buy':
            sl_price = current_price * (1 - stop_loss_pct / 100)
            tp_price = current_price * (1 + stop_loss_pct * risk_reward_ratio / 100)
        else:
            sl_price = current_price * (1 + stop_loss_pct / 100)
            tp_price = current_price * (1 - stop_loss_pct * risk_reward_ratio / 100)
        return sl_price, tp_price

    def monitor_position_updates(self, position, current_price):
        if position['direction'] == 'buy':
            profit_pct = ((current_price - position['entry_price']) / position['entry_price']) * 100
            if profit_pct >= self.breakeven_profit_pct and position['sl'] < position['entry_price']:
                position['sl'] = position['entry_price']
            new_sl = current_price * (1 - self.trailing_stop_pct / 100)
            if new_sl > position['sl']:
                position['sl'] = new_sl
        else:
            profit_pct = ((position['entry_price'] - current_price) / position['entry_price']) * 100
            if profit_pct >= self.breakeven_profit_pct and position['sl'] > position['entry_price']:
                position['sl'] = position['entry_price']
            new_sl = current_price * (1 + self.trailing_stop_pct / 100)
            if new_sl < position['sl']:
                position['sl'] = new_sl

        if 'taken_profit_levels' not in position:
            position['taken_profit_levels'] = []
        for tp_level in self.partial_tp_levels:
            if tp_level['profit_pct'] not in position['taken_profit_levels']:
                if (position['direction'] == 'buy' and current_price >= position['entry_price'] * (1 + tp_level['profit_pct'] / 100)) or \
                   (position['direction'] == 'sell' and current_price <= position['entry_price'] * (1 - tp_level['profit_pct'] / 100)):
                    position['taken_profit_levels'].append(tp_level['profit_pct'])
        return position


class AutoTrader:
    def __init__(self, api_key, secret_key, symbols=None, use_testnet=False):
        self.symbols = symbols if symbols else ["XRPUSDT"]
        self.exchange = ccxt.binance({
            'apiKey': api_key,
            'secret': secret_key,
            'enableRateLimit': True,
            'options': {'defaultType': 'future'}
        })
        if use_testnet:
            self.exchange.set_sandbox_mode(True)
            logger.info("Using Binance Futures Testnet environment.")
        else:
            logger.info("Using Binance Futures Mainnet environment.")

        self.exchange.load_markets()
        self.open_positions = {symbol: [] for symbol in self.symbols}
        self.position_history = {symbol: [] for symbol in self.symbols}
        self.account_balance = 0.0
        self.leverage = 10
        self.risk_pct = 1.0

        self.strategy_params = {
            'fast_ema': 10,
            'slow_ema': 21,
            'rsi_period': 14,
            'macd_fast_period': 12,
            'macd_slow_period': 26,
            'macd_signal_period': 9,
            'stop_loss_pct': 1.5,
            'risk_reward_ratio': 2.0,
            'breakeven_profit_pct': 1.0,
            'trailing_stop_pct': 0.5,
            'partial_tp_levels': [
                {'profit_pct': 1.0, 'percent_of_position': 50},
                {'profit_pct': 2.0, 'percent_of_position': 50}
            ],
        }

        self.strategies = {symbol: TradingStrategy(self.strategy_params) for symbol in self.symbols}
        self.last_signal_time = {symbol: 0 for symbol in self.symbols}
        self.cooldown_seconds = 300
        self.telegram_token = TELEGRAM_TOKEN
        self.telegram_chat_id = TELEGRAM_CHAT_ID

        self.order_flow_buy_threshold = 5.0
        self.order_flow_sell_threshold = 5.0
        self.order_book_spread_threshold = 0.05

        self.order_flow_collectors = {}
        for symbol in self.symbols:
            col = OrderFlowCollector(symbol)
            col.start()
            self.order_flow_collectors[symbol] = col

        self.console = console
        from binance.client import Client
        self.client = Client(api_key, )

        self.set_leverage()
        self.load_positions()
        self.update_account_balance()

        logger.info(f"Initialized AutoTrader for symbols: {', '.join(self.symbols)}")
        logger.info(f"Account balance: {self.account_balance:.2f}")

        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)

    def _signal_handler(self, sig, frame):
        logger.info("Received termination signal, shutting down...")
        self.save_positions()
        for collector in self.order_flow_collectors.values():
            collector.stop()
        sys.exit(0)

    def set_leverage(self):
        for symbol in self.symbols:
            try:
                self.exchange.set_leverage(self.leverage, symbol)
                logger.info(f"Set leverage {self.leverage}x for {symbol}")
            except Exception as e:
                logger.error(f"Failed to set leverage for {symbol}: {e}")

    def save_positions(self):
        try:
            with open('trading_data.json', 'w') as f:
                json.dump(self.open_positions, f, indent=4)
            logger.info("Open positions saved.")
        except Exception as e:
            logger.error(f"Error saving positions: {e}")

    def load_positions(self):
        try:
            if os.path.exists('trading_data.json'):
                with open('trading_data.json', 'r') as f:
                    loaded = json.load(f)
                for symbol in self.symbols:
                    self.open_positions[symbol] = loaded.get(symbol, [])
                logger.info("Open positions loaded.")
            else:
                logger.info("No open positions file found; starting empty.")
        except Exception as e:
            logger.error(f"Error loading positions: {e}")
            for symbol in self.symbols:
                self.open_positions[symbol] = []

    def update_account_balance(self):
        try:
            balance_info = self.exchange.fetch_balance()
            self.account_balance = balance_info.get('USDT', {}).get('free', 0)
        except Exception as e:
            logger.error(f"Error fetching balance: {e}")

    def fetch_ohlcv_for_symbol(self, symbol, timeframe):
        try:
            ohlcv = self.exchange.fetch_ohlcv(symbol, timeframe, limit=100)
            df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
            df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
            return df
        except Exception as e:
            logger.error(f"Error fetching OHLCV for {symbol}: {e}")
            return pd.DataFrame()

    def calculate_position_size_dynamic(self, symbol, current_price):
        try:
            position_risk = self.exchange.fapiPrivate_get_positionRisk()
            margin_available = 0.0
            for pos in position_risk:
                if pos['symbol'] == symbol:
                    margin_available = float(pos.get('isolatedMargin') or pos.get('maintMargin') or 0)
                    break

            balance_info = self.exchange.fetch_balance()
            usdt_balance = balance_info.get('USDT', {}).get('free', 0)

            margin_available = max(margin_available, usdt_balance)

            stop_loss_pct = self.strategy_params['stop_loss_pct']
            risk_pct = self.risk_pct / 100.0
            risk_amount = margin_available * risk_pct

            risk_per_contract = current_price * (stop_loss_pct / 100)
            if risk_per_contract == 0:
                return 0

            max_size = (margin_available * self.leverage) / current_price
            size = risk_amount / risk_per_contract
            position_size = min(size, max_size)
            min_size = 0.25

            return max(min_size, position_size)

        except Exception as e:
            self.console.print(f"[error]Error calculating dynamic position size: {e}[/error]")
            logger.error(f"Error calculating dynamic position size: {e}")
            return 0.25

    def open_trade(self, symbol, signal, current_price, size):
        sl_price, tp_price = self.strategies[symbol].calculate_sl_tp(
            current_price,
            signal,
            self.strategy_params['stop_loss_pct'],
            self.strategy_params['risk_reward_ratio']
        )
        try:
            side_closing = self.exchange.SIDE_SELL if signal == 'buy' else self.exchange.SIDE_BUY

            if signal == 'buy':
                order = self.exchange.create_market_buy_order(symbol, size)
            else:
                order = self.exchange.create_market_sell_order(symbol, size)

            executed_price = float(order.get('price') or current_price)
            executed_size = float(order.get('filled') or size)

            stop_order = self.exchange.fapiPrivate_post_order({
                'symbol': symbol,
                'side': side_closing,
                'type': 'STOP_MARKET',
                'stopPrice': round(sl_price, 5),
                'closePosition': True,
                'reduceOnly': True,
                'timeInForce': 'GTC',
            })

            tp_order = self.exchange.fapiPrivate_post_order({
                'symbol': symbol,
                'side': side_closing,
                'type': 'TAKE_PROFIT_MARKET',
                'stopPrice': round(tp_price, 5),
                'closePosition': True,
                'reduceOnly': True,
                'timeInForce': 'GTC',
            })

            position = {
                'symbol': symbol,
                'direction': signal,
                'entry_price': executed_price,
                'size': executed_size,
                'sl': sl_price,
                'tp': tp_price,
                'stop_loss_order_id': stop_order['orderId'],
                'take_profit_order_id': tp_order['orderId'],
                'taken_profit_levels': []
            }
            self.open_positions[symbol].append(position)
            msg = f"[{symbol}] Opened {signal.upper()} at {executed_price:.4f}. SL: {sl_price:.4f}, TP: {tp_price:.4f}"
            self.console.print(msg, style="success")
            logger.info(msg)
            self.send_telegram_message(msg)
            self.save_positions()

        except Exception as e:
            self.console.print(f"[error]Error opening trade for {symbol}: {e}[/error]")
            logger.error(f"Error opening trade for {symbol}: {e}")
            self.send_telegram_message(f"Order failed [{symbol}]: {e}")

    def close_position(self, symbol, position, exit_price, reason):
        try:
            if 'stop_loss_order_id' in position:
                self.exchange.cancel_order(position['stop_loss_order_id'], symbol)
            if 'take_profit_order_id' in position:
                self.exchange.cancel_order(position['take_profit_order_id'], symbol)
        except Exception as e:
            logger.warning(f"Failed to cancel SL/TP orders for {symbol}: {e}")

        pnl = 0
        if position['direction'] == 'buy':
            pnl = (exit_price - position['entry_price']) * position['size']
        else:
            pnl = (position['entry_price'] - exit_price) * position['size']

        self.account_balance += pnl
        self.position_history[symbol].append({
            'pnl': pnl,
            'direction': position['direction'],
            'entry': position['entry_price'],
            'exit': exit_price,
            'reason': reason,
            'timestamp': time.time()
        })

        self.open_positions[symbol].remove(position)
        msg = f"[{symbol}] Closed {position['direction'].upper()} at {exit_price:.4f} due to {reason}. PnL: ${pnl:.2f}"
        self.console.print(msg, style="success" if pnl >= 0 else "error")
        logger.info(msg)
        self.send_telegram_message(msg)
        self.save_positions()

    def monitor_positions(self, symbol, current_price):
        for pos in self.open_positions[symbol][:]:
            pos = self.strategies[symbol].monitor_position_updates(pos, current_price)
            if pos['direction'] == 'buy':
                if current_price <= pos['sl']:
                    self.close_position(symbol, pos, current_price, 'Stop Loss')
                elif current_price >= pos['tp']:
                    self.close_position(symbol, pos, current_price, 'Take Profit')
            else:
                if current_price >= pos['sl']:
                    self.close_position(symbol, pos, current_price, 'Stop Loss')
                elif current_price <= pos['tp']:
                    self.close_position(symbol, pos, current_price, 'Take Profit')

    def can_open_trade(self, symbol):
        return len(self.open_positions[symbol]) == 0

    def save_positions(self):
        try:
            with open('trading_data.json', 'w') as f:
                json.dump(self.open_positions, f, indent=4)
            logger.info("Open positions saved.")
        except Exception as e:
            logger.error(f"Error saving positions: {e}")

    def load_positions(self):
        try:
            if os.path.exists('trading_data.json'):
                with open('trading_data.json', 'r') as f:
                    loaded = json.load(f)
                for symbol in self.symbols:
                    self.open_positions[symbol] = loaded.get(symbol, [])
                logger.info("Open positions loaded.")
            else:
                logger.info("No open positions file found; starting empty.")
        except Exception as e:
            logger.error(f"Error loading positions: {e}")
            for symbol in self.symbols:
                self.open_positions[symbol] = []

    def update_account_balance(self):
        try:
            balance_info = self.exchange.fetch_balance()
            self.account_balance = balance_info.get('USDT', {}).get('free', 0)
        except Exception as e:
            logger.error(f"Error fetching balance: {e}")

    def fetch_ohlcv_for_symbol(self, symbol, timeframe):
        try:
            ohlcv = self.exchange.fetch_ohlcv(symbol, timeframe, limit=100)
            df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
            df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
            return df
        except Exception as e:
            logger.error(f"Error fetching OHLCV for {symbol}: {e}")
            return pd.DataFrame()

    def send_telegram_message(self, message):
        if self.telegram_token and self.telegram_chat_id:
            url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
            payload = {
                "chat_id": self.telegram_chat_id,
                "text": message,
                "parse_mode": "HTML"
            }
            try:
                requests.post(url, data=payload, timeout=5)
            except Exception as e:
                logger.error(f"Failed sending telegram message: {e}")

    def run(self):
        self.console.print("[info]Starting AutoTrader bot with dashboard...[/info]")
        with Live(console=self.console, screen=True, refresh_per_second=1) as live:
            while True:
                try:
                    self.update_account_balance()
                    current_prices = {}

                    for symbol in self.symbols:
                        df = self.fetch_ohlcv_for_symbol(symbol, '5m')
                        df_confirm = self.fetch_ohlcv_for_symbol(symbol, '15m')
                        df_processed = self.strategies[symbol].get_processed_dataframe(df)
                        df_confirm_processed = self.strategies[symbol].get_processed_dataframe(df_confirm)
                        signal, current_price = self.strategies[symbol].check_signal(
                            df_processed, df_confirm_processed, self.open_positions[symbol])

                        ofc = self.order_flow_collectors.get(symbol)
                        order_flow_features = ofc.get_features() if ofc else {}
                        net_volume = order_flow_features.get('net_trade_volume', 0)
                        best_bid = order_flow_features.get('best_bid', 10)
                        best_ask = order_flow_features.get('best_ask', 10)
                        spread = best_ask - best_bid

                        
                        self.console.print(
                                           f"[info]{symbol} - Signal: {signal}, Price: {current_price}, "
                                           f"NetVol: {net_volume:.4f}, "
                                           f"BidVol: [green]{order_flow_features.get('bid_volume', 10):.2f}[/green], "
                                           f"AskVol: [red]{order_flow_features.get('ask_volume', 10):.2f}[/red], "
                                           f"Spread: {spread:.5f}[/info]"
)
                        proceed = False
                        if spread > self.order_book_spread_threshold:
                            self.console.print(f"[warning]{symbol} skipped due to spread {spread:.5f} > threshold[/warning]")
                        else:
                            if signal == "buy" and net_volume > self.order_flow_buy_threshold:
                                proceed = True
                            elif signal == "buy":
                                self.console.print(f"[warning]{symbol} BUY signal rejected: net volume {net_volume:.4f} < threshold[/warning]")
                                
                            if signal == "sell" and net_volume < -self.order_flow_sell_threshold:
                                proceed = True
                            elif signal == "sell":
                                self.console.print(f"[warning]{symbol} SELL signal rejected: net volume {net_volume:.4f} > threshold[/warning]")

                        if proceed and current_price and self.can_open_trade(symbol) and (time.time() - self.last_signal_time[symbol] > self.cooldown_seconds):
                            size = self.calculate_position_size_dynamic(symbol, current_price)
                            self.console.print(f"[info]{symbol} - Calculated position size: {size:.4f} contracts[/info]")
                            self.open_trade(symbol, signal, current_price, size)
                            self.last_signal_time[symbol] = time.time()

                        if current_price:
                            self.monitor_positions(symbol, current_price)

                        current_prices[symbol] = current_price

                    layout = build_layout(self, in_memory_log_handler)
                    live.update(layout)
                except ccxt.NetworkError as e:
                    logger.error(f"Network error: {e}, retrying in 20s...")
                    time.sleep(20)
                except ccxt.ExchangeError as e:
                    logger.error(f"Exchange error: {e}, retrying in 60s...")
                    time.sleep(60)
                except Exception as e:
                    logger.exception(f"Unexpected error in main loop: {e}")
                time.sleep(1)

if __name__ == "__main__":
    if not API_KEY or not SECRET_KEY:
        console.print("[error]BINANCE_API_KEY and BINANCE_SECRET_KEY must be set in environment variables.[/error]")
        sys.exit(1)

    symbols_to_trade = ["TRBUSDT","XRPUSDT"]  # <--- Change this to your desired symbols
    bot = AutoTrader(
    API_KEY,
    SECRET_KEY,
    symbols=symbols_to_trade,
    use_testnet=False  # # <-- REAL funds on mainnet
)
bot.run()