#!/usr/bin/env python3
"""
AutoTradeManager_v2_class.py
Single-file class-based conversion of AutoTradeManager_v2.py

Keep configuration at top of file and edit variables directly there.
"""
# ==================== CONFIGURATION ==================== #
DB_FILE = "trade_manager.db"

DEFAULT_CAPITAL = 10000.0
DEFAULT_RISK_PCT = 1.0             # Percent of capital risked per trade (1%)

# Auto-stop defaults (percent of initial capital)
DEFAULT_MAX_DRAWDOWN_PCT = 3.0      # -3% daily drawdown
DEFAULT_MAX_PROFIT_PCT = 5.0        # +5% daily profit cap (optional)

MAX_OPEN_TRADES = 3
MAX_LOSS_TRADES = 2
MAX_TARGET_TRADES = 3

# Hardcoded startup option
HARD_CODED_MODE_ON_START = False

HARDCODED_TRADES = [
    {"symbol": "NIFTY", "entry": 25000, "stop_loss": 24800, "target": 25200, "lot_size": 50},
    {"symbol": "BANKNIFTY", "entry": 61000, "stop_loss": 60500, "target": 62000, "lot_size": 25},
]
# ======================================================= #

# Standard imports
import os
import math
import sqlite3
import traceback
from datetime import datetime, date

# UI / plotting / data
import pandas as pd
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.prompt import Prompt
from rich import box

# The single unified class
class AutoTradeManager:
    def __init__(
        self,
        db_file: str = DB_FILE,
        default_capital: float = DEFAULT_CAPITAL,
        default_risk_pct: float = DEFAULT_RISK_PCT,
        max_drawdown_pct: float = DEFAULT_MAX_DRAWDOWN_PCT,
        max_profit_pct: float = DEFAULT_MAX_PROFIT_PCT,
        max_open_trades: int = MAX_OPEN_TRADES,
        max_loss_trades: int = MAX_LOSS_TRADES,
        max_target_trades: int = MAX_TARGET_TRADES,
        hardcoded_mode: bool = HARD_CODED_MODE_ON_START,
        hardcoded_trades: list = None
    ):
        """
        Initialize AutoTradeManager as a single unified class.
        Config values default to the top-of-file configuration which you may edit.
        """
        # console
        self.console = Console()

        # configuration values (can be overridden by params)
        self.db_file = db_file
        self.default_capital = float(default_capital)
        self.default_risk_pct = float(default_risk_pct)
        self.max_drawdown_pct = float(max_drawdown_pct)
        self.max_profit_pct = float(max_profit_pct)
        self.max_open_trades = int(max_open_trades)
        self.max_loss_trades = int(max_loss_trades)
        self.max_target_trades = int(max_target_trades)
        self.hardcoded_mode = bool(hardcoded_mode)
        self.hardcoded_trades = hardcoded_trades or HARDCODED_TRADES

        # db connection
        self.db = self.init_db(self.db_file)

        # load or ensure today's row
        self.ensure_today_row()

        # load stats into runtime fields
        stats = self.load_today_stats()
        self.initial_capital = float(stats["initial_capital"])
        self.capital = float(stats["capital"])
        self.daily_trades = int(stats["daily_trades"])
        self.daily_gain_trades = int(stats["daily_gain_trades"])
        self.daily_loss_trades = int(stats["daily_loss_trades"])
        self.daily_gain_amount = float(stats["daily_gain_amount"])
        self.daily_loss_amount = float(stats["daily_loss_amount"])
        self.trading_halted = bool(stats["trading_halted"])
        self.carry_forward = bool(stats["carry_forward"])
        # thresholds
        self.max_drawdown_pct = float(stats.get("max_drawdown_pct", self.max_drawdown_pct))
        self.max_profit_pct = float(stats.get("max_profit_pct", self.max_profit_pct))

        # runtime editable configs
        self.risk_pct = float(self.default_risk_pct)
        # keep original names for compatibility
        self.max_open_trades = int(self.max_open_trades)
        self.max_loss_trades = int(self.max_loss_trades)
        self.max_target_trades = int(self.max_target_trades)

    # ----------------- Utility helpers (module functions converted) -----------------
    def today_date(self):
        return datetime.now().strftime("%Y-%m-%d")

    def money(self, x):
        return f"₹{x:,.2f}"

    def pct(self, x):
        return f"{x:.2f}%"

    # ========================= DB: init & helpers =========================
    def init_db(self, db_file=None):
        db_file = db_file or self.db_file
        conn = sqlite3.connect(db_file, check_same_thread=False)
        cur = conn.cursor()

        # open trades
        cur.execute("""
            CREATE TABLE IF NOT EXISTS open_trades (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                symbol TEXT,
                entry REAL,
                qty INTEGER,
                stop REAL,
                target REAL,
                lot_size INTEGER,
                direction TEXT,
                note TEXT,
                opened_at TEXT
            )
        """)

        # trade log (closed trades)
        cur.execute("""
        CREATE TABLE IF NOT EXISTS trade_log (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            symbol TEXT,
            entry REAL,
            exit REAL,
            qty INTEGER,
            pnl REAL,
            pnl_percent REAL,
            direction TEXT,
            note TEXT,
            closed_at TEXT,
            timestamp TEXT,
            stop_loss REAL,
            target REAL,
            lot_size INTEGER,
            position_size INTEGER,
            rr REAL,
            outcome REAL,
            capital_after REAL,
            daily_gain_loss REAL,
            daily_gain_loss_percent REAL
        )
        """)
        # daily stats
        cur.execute(f"""
            CREATE TABLE IF NOT EXISTS daily_stats (
                date TEXT PRIMARY KEY,
                initial_capital REAL,
                capital REAL,
                daily_trades INTEGER,
                daily_gain_trades INTEGER,
                daily_loss_trades INTEGER,
                daily_gain_amount REAL,
                daily_loss_amount REAL,
                trading_halted INTEGER DEFAULT 0,
                carry_forward INTEGER DEFAULT 1,
                max_drawdown_pct REAL DEFAULT {self.max_drawdown_pct},
                max_profit_pct REAL DEFAULT {self.max_profit_pct}
            )
        """)
        conn.commit()
        return conn

    def ensure_today_row(self):
        cur = self.db.cursor()
        td = self.today_date()
        cur.execute("SELECT date FROM daily_stats WHERE date = ?", (td,))
        row = cur.fetchone()
        if row:
            return
        # see if previous date exists
        cur.execute("SELECT date, capital, carry_forward FROM daily_stats ORDER BY date DESC LIMIT 1")
        prev = cur.fetchone()
        # default carry forward ON (1)
        carry_forward = 1
        initial = self.default_capital
        if prev:
            prev_cap = prev[1] if prev[1] is not None else self.default_capital
            if carry_forward == 1:
                initial = float(prev_cap)
                self.console.print(f"[cyan]Carry-forward ON: rolling previous capital {self.money(initial)} to today's initial capital[/cyan]")
        else:
            self.console.print(f"[yellow]No previous daily record found. Using default capital {self.money(initial)}[/yellow]")

        cur.execute("""
            INSERT OR REPLACE INTO daily_stats
            (date, initial_capital, capital, daily_trades, daily_gain_trades, daily_loss_trades,
             daily_gain_amount, daily_loss_amount, trading_halted, carry_forward, max_drawdown_pct, max_profit_pct)
            VALUES (?, ?, ?, 0, 0, 0, 0.0, 0.0, 0, 1, ?, ?)
        """, (td, initial, initial, self.max_drawdown_pct, self.max_profit_pct))
        self.db.commit()

    def load_today_stats(self):
        self.ensure_today_row()
        cur = self.db.cursor()
        td = self.today_date()
        cur.execute("SELECT * FROM daily_stats WHERE date = ?", (td,))
        row = cur.fetchone()
        cols = [d[0] for d in cur.description]
        stats = dict(zip(cols, row))
        # normalize types
        stats["initial_capital"] = float(stats.get("initial_capital", self.default_capital))
        stats["capital"] = float(stats.get("capital", stats["initial_capital"]))
        stats["daily_trades"] = int(stats.get("daily_trades", 0))
        stats["daily_gain_trades"] = int(stats.get("daily_gain_trades", 0))
        stats["daily_loss_trades"] = int(stats.get("daily_loss_trades", 0))
        stats["daily_gain_amount"] = float(stats.get("daily_gain_amount", 0.0))
        stats["daily_loss_amount"] = float(stats.get("daily_loss_amount", 0.0))
        stats["trading_halted"] = int(stats.get("trading_halted", 0))
        stats["carry_forward"] = int(stats.get("carry_forward", 1))
        stats["max_drawdown_pct"] = float(stats.get("max_drawdown_pct", self.max_drawdown_pct))
        stats["max_profit_pct"] = float(stats.get("max_profit_pct", self.max_profit_pct))
        return stats

    def update_today_stats(self, updates: dict):
        cur = self.db.cursor()
        cur.execute("PRAGMA table_info(daily_stats)")
        cols = [r[1] for r in cur.fetchall()]
        td = self.today_date()
        to_upd = {k: updates[k] for k in updates.keys() if k in cols and k != "date"}
        if not to_upd:
            return
        clause = ", ".join([f"{k} = ?" for k in to_upd.keys()])
        params = list(to_upd.values()) + [td]
        sql = f"UPDATE daily_stats SET {clause} WHERE date = ?"
        cur.execute(sql, params)
        self.db.commit()

    def reset_db(self):
        cur = self.db.cursor()
        cur.execute("DROP TABLE IF EXISTS open_trades")
        cur.execute("DROP TABLE IF EXISTS trade_log")
        cur.execute("DROP TABLE IF EXISTS daily_stats")
        self.db.commit()
        self.db.close()
        self.db = self.init_db(self.db_file)
        self.console.print("[green]Database reset complete.[/green]")
        return self.db

    # ASCII bar helper (kept as original)
    def ascii_bar(self, value, max_abs, width=40):
        RED = "\033[31m"
        GREEN = "\033[32m"
        RESET = "\033[0m"
        WHITE = "\033[37m"

        if max_abs == 0:
            return f"{WHITE}|{RESET}"

        half = width // 2
        scaled = int(abs(value) / max_abs * half) if max_abs else 0

        left = " " * half
        right = " " * half

        if value > 0:
            right = f"{GREEN}{'█' * scaled}{RESET}" + " " * (half - scaled)
        elif value < 0:
            left = " " * (half - scaled) + f"{RED}{'█' * scaled}{RESET}"

        return f"{left}{WHITE}|{RESET}{right}"

    # ========================= Core manager methods =========================
    def count_open_trades(self):
        cur = self.db.cursor()
        cur.execute("SELECT COUNT(*) FROM open_trades")
        return int(cur.fetchone()[0])

    def calc_qty(self, entry, stop):
        risk_amount = self.capital * (self.risk_pct / 100.0)
        tick_risk = abs(entry - stop)
        if tick_risk <= 0:
            return 0
        qty = int(risk_amount // tick_risk)
        return max(qty, 0)

    def can_add_trade(self):
        open_trades = self.count_open_trades()
        wins = self.daily_gain_trades
        losses = self.daily_loss_trades

        if open_trades >= self.max_open_trades:
            return False, f"⛔ Max open trades reached ({open_trades}/{self.max_open_trades})", "block"

        if losses + open_trades >= self.max_loss_trades:
            return False, f"❌ Possible daily loss breach ({losses}+{open_trades} ≥ {self.max_loss_trades})", "block"

        if wins + open_trades >= self.max_target_trades:
            return False, f"✅ Daily target reached ({wins}+{open_trades} ≥ {self.max_target_trades})", "block"

        return True, f"✅ OK — Open: {open_trades}, Wins: {wins}, Losses: {losses}", "ok"

    def add_trade(self, symbol, entry, stop, target, lot_size=None, note=""):
        if self.trading_halted:
            self.console.print("[red]Trading is halted today — no new trades allowed.[/red]")
            return False

        allowed, msg, level = self.can_add_trade()
        if not allowed:
            self.console.print(f"[red]🚫 {msg}[/red]")
            return False
        if level == "warn":
            self.console.print(f"[yellow]⚠️ {msg}[/yellow]")

        qty = self.calc_qty(entry, stop)
        if qty <= 0:
            self.console.print("[red]Quantity calculated as 0 — risk too small or stop==entry. Trade not added.[/red]")
            return False

        capital_used = round(entry * qty, 2)
        target_profit = round(target - entry, 2)
        max_loss = round(entry - stop, 2)
        rr_ratio = abs(target_profit / max_loss) if max_loss else 0
        target_pct = round((target_profit / entry) * 100, 2) if entry else 0.0
        loss_pct = round((max_loss / entry) * 100, 2) if entry else 0.0

        table = Table(title="📋 Order Summary", box=box.ROUNDED, expand=False)
        table.add_column("Field", style="bold cyan", justify="left", no_wrap=True)
        table.add_column("Details", style="bold white", justify="left")

        table.add_row("💰 Available", f"₹{self.capital:.2f}")
        table.add_row("💸 Required", f"₹{capital_used:.2f}")
        table.add_row("", "")

        table.add_row("📈 Symbol", symbol.upper())
        table.add_row("🎯 Entry", f"₹{entry:.2f}")
        table.add_row("🎯 Target", f"₹{target:.2f}")
        table.add_row("🛑 SL Type", f"₹{stop:.2f}")
        table.add_row("📦 Quantity", str(qty))
        table.add_row("", "")

        table.add_row("💼 Capital Used", f"₹{capital_used:.2f}")
        table.add_row("🏁 Target Profit", f"[green]₹{target_profit:.2f} ({target_pct}%) [/green]")
        table.add_row("⚠️ Max Loss", f"[red]₹{max_loss:.2f} ({loss_pct}%) [/red]")
        table.add_row("", "")

        table.add_row("⚖️ Risk:Reward", f"[yellow]{rr_ratio:.1f} : 1[/yellow]")

        self.console.print(Panel(table, border_style="cyan", title="ORDER REVIEW"))

        while True:
            confirm = Prompt.ask("Proceed with order? (Y=Yes / E=Edit / N=Cancel)", choices=["y", "n", "e"], default="y")
            if confirm.lower() == "n":
                self.console.print("[red]❌ Trade cancelled by user.[/red]")
                return False
            elif confirm.lower() == "e":
                entry = float(Prompt.ask("New Entry Price", default=str(entry)))
                stop = float(Prompt.ask("New Stoploss", default=str(stop)))
                target = float(Prompt.ask("New Target", default=str(target)))
                self.console.print("[yellow]🔁 Updated order details shown below...[/yellow]")
                return self.add_trade(symbol, entry, stop, target, lot_size, note)
            elif confirm.lower() == "y":
                break

        opened_at = datetime.now().isoformat(sep=' ', timespec='seconds')
        cur = self.db.cursor()
        cur.execute("""
            INSERT INTO open_trades (symbol, entry, qty, stop, target, lot_size, direction, note, opened_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (symbol.upper(), float(entry), int(qty), float(stop), float(target),
            int(lot_size) if lot_size else None, "LONG", note, opened_at))
        self.db.commit()

        self.daily_trades += 1
        self.update_today_stats({
            "capital": self.capital,
            "daily_trades": self.daily_trades,
            "daily_gain_trades": self.daily_gain_trades,
            "daily_loss_trades": self.daily_loss_trades,
            "daily_gain_amount": self.daily_gain_amount,
            "daily_loss_amount": self.daily_loss_amount
        })

        self.console.print(f"[green]✅ Trade added successfully: {symbol.upper()} | Entry ₹{entry} | SL ₹{stop} | Target ₹{target} | Qty {qty}[/green]")
        return True

    def list_open_trades(self):
        try:
            df = pd.read_sql_query("SELECT * FROM open_trades ORDER BY id", self.db)
            return df
        except Exception:
            return None

    def close_trade(self, trade_id, exit_price):
        try:
            df = pd.read_sql_query("SELECT * FROM open_trades WHERE id = ?", self.db, params=(trade_id,))
        except Exception:
            self.console.print(f"[red]DB read error while closing trade {trade_id}[/red]")
            return False

        if df.empty:
            self.console.print(f"[red]Trade id {trade_id} not found.[/red]")
            return False

        row = df.iloc[0]
        entry = float(row["entry"])
        qty = int(row["qty"])
        direction = row.get("direction", "LONG").upper()

        exit_price = float(exit_price)
        if direction == "LONG":
            pnl = (exit_price - entry) * qty
        else:
            pnl = (entry - exit_price) * qty

        prev_cap = float(self.capital)
        self.capital = round(self.capital + pnl, 2)
        pnl_percent = round((pnl / prev_cap * 100), 2) if prev_cap else 0.0

        self.daily_trades += 1
        if pnl > 0:
            self.daily_gain_trades += 1
            self.daily_gain_amount += pnl
        elif pnl < 0:
            self.daily_loss_trades += 1
            self.daily_loss_amount += abs(pnl)

        closed_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        def safe_float(x, default=0.0):
            try:
                return float(x) if x not in (None, "", "None") else default
            except Exception:
                return default

        stop_loss = safe_float(row.get("stop"), 0.0)
        target = safe_float(row.get("target"), 0.0)
        lot_size = int(row.get("lot_size", 1) or 1)
        position_size = int(row.get("position_size", qty * entry)) if "position_size" in row.index else int(qty * entry)

        rr = 0.0
        if stop_loss and target:
            try:
                rr = round(abs(exit_price - entry) / abs(entry - stop_loss), 2)
            except ZeroDivisionError:
                rr = 0.0

        daily_gain_loss = round(self.daily_gain_amount - self.daily_loss_amount, 2)
        daily_gain_loss_percent = round((daily_gain_loss / self.initial_capital) * 100, 2) if self.initial_capital else 0.0

        if pnl > 0:
            outcome = "✅ WIN"
        elif pnl < 0:
            outcome = "❌ LOSS"
        else:
            outcome = "⚖️ BE"

        cur = self.db.cursor()
        cur.execute("""
            INSERT INTO trade_log (
                symbol, entry, exit, qty, pnl, pnl_percent, direction, note,
                closed_at, timestamp, stop_loss, target, lot_size, position_size,
                rr, outcome, capital_after, daily_gain_loss, daily_gain_loss_percent
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            row["symbol"],
            entry,
            exit_price,
            qty,
            round(pnl, 2),
            round(pnl_percent, 2),
            direction,
            row.get("note", ""),
            closed_at,
            datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            stop_loss,
            target,
            lot_size,
            position_size,
            rr,
            outcome,
            round(self.capital, 2),
            daily_gain_loss,
            daily_gain_loss_percent
        ))

        cur.execute("DELETE FROM open_trades WHERE id = ?", (trade_id,))
        self.db.commit()

        self.update_today_stats({
            "capital": self.capital,
            "daily_trades": self.daily_trades,
            "daily_gain_trades": self.daily_gain_trades,
            "daily_loss_trades": self.daily_loss_trades,
            "daily_gain_amount": self.daily_gain_amount,
            "daily_loss_amount": self.daily_loss_amount
        })

        pnl_color = "green" if pnl > 0 else "red" if pnl < 0 else "yellow"
        net_col = f"[{pnl_color}]{pnl:.2f}[/{pnl_color}]"
        cap_col = f"[green]{self.capital:,.2f}[/green]" if self.capital >= self.initial_capital else f"[red]{self.capital:,.2f}[/red]"
        self.console.print(f"🔒 Trade closed: [cyan]{row['symbol']}[/cyan] | Outcome: {outcome} | P&L: {net_col} | Capital → {cap_col} | R:R = {rr:.2f}")

        self.evaluate_auto_stop()
        return True

    def evaluate_auto_stop(self):
        net = self.daily_gain_amount - self.daily_loss_amount
        net_pct = (net / self.initial_capital * 100) if self.initial_capital else 0.0
        drawdown_pct = ((self.initial_capital - self.capital) / self.initial_capital * 100) if self.initial_capital else 0.0

        halted = False
        reason = ""
        if drawdown_pct >= self.max_drawdown_pct and self.max_drawdown_pct > 0:
            halted = True
            reason = f"Drawdown {drawdown_pct:.2f}% >= {self.max_drawdown_pct:.2f}%"
        elif net_pct >= self.max_profit_pct and self.max_profit_pct > 0:
            halted = True
            reason = f"Profit {net_pct:.2f}% >= {self.max_profit_pct:.2f}%"

        self.trading_halted = halted
        self.update_today_stats({
            "trading_halted": int(self.trading_halted),
            "capital": self.capital,
            "daily_gain_amount": self.daily_gain_amount,
            "daily_loss_amount": self.daily_loss_amount
        })
        if halted:
            self.console.print(f"[red]⚠️ Auto-stop triggered: {reason} — trading halted for today.[/red]")

    def print_pnl_summary(self):
        net = self.daily_gain_amount - self.daily_loss_amount
        net_pct = (net / self.initial_capital * 100) if self.initial_capital else 0.0
        gains = f"[green]{self.money(self.daily_gain_amount)}[/green]"
        losses = f"[red]{self.money(self.daily_loss_amount)}[/red]"
        net_col = f"[green]{self.money(net)}[/green]" if net >= 0 else f"[red]{self.money(net)}[/red]"
        cap_col = f"[green]{self.money(self.capital)}[/green]" if self.capital >= self.initial_capital else f"[red]{self.money(self.capital)}[/red]"

        self.console.print("─" * 48)
        self.console.print("📊 DAILY PERFORMANCE (Minimal)")
        self.console.print(f"Initial Capital: {self.money(self.initial_capital)}")
        self.console.print(f"Trades: {self.daily_trades}  |  Wins: {self.daily_gain_trades}  |  Losses: {self.daily_loss_trades}")
        self.console.print(f"Gains: {gains}   Losses: {losses}")
        self.console.print(f"Net: {net_col}   ({net_pct:+.2f}%)")
        self.console.print(f"Capital: {cap_col}")
        if self.trading_halted:
            self.console.print("[red]🔒 Trading halted by auto-stop rules for today.[/red]")
        self.console.print("─" * 48)

    def show_trade_history_ascii(self):
        try:
            df = pd.read_sql_query(
                "SELECT date(closed_at) AS d, SUM(pnl) AS pnl FROM trade_log GROUP BY d ORDER BY d",
                self.db
            )
        except Exception as e:
            self.console.print(f"[red]⚠️ Database error: {e}[/red]")
            return

        if df is None or df.empty:
            self.console.print("[yellow]No trade history yet — start closing trades to view performance.[/yellow]")
            return

        self.console.print("\n📈 [bold cyan]Trade History (ASCII)[/bold cyan]")
        max_abs = df["pnl"].abs().max()
        scale = max_abs / 50 if max_abs > 0 else 1

        for _, r in df.iterrows():
            bar_len = int(abs(r.pnl) / scale)
            bar = "█" * bar_len
            prefix = "📈" if r.pnl > 0 else "📉"
            color = "green" if r.pnl > 0 else "red"
            self.console.print(f"[{color}]{r.d} | {prefix} {bar} ₹{r.pnl:,.0f}[/{color}]")

        total_pnl = df["pnl"].sum()
        self.console.print(f"\n[bold cyan]Total P&L: ₹{total_pnl:,.0f}[/bold cyan]")

    def monthly_report_and_equity(self):
        try:
            df_trades = pd.read_sql_query("SELECT * FROM trade_log", self.db)
        except Exception:
            df_trades = None
        try:
            df_daily = pd.read_sql_query("SELECT * FROM daily_stats ORDER BY date", self.db)
        except Exception:
            df_daily = None

        if (df_trades is None or df_trades.empty) and (df_daily is None or df_daily.empty):
            self.console.print("[yellow]No data to report.[/yellow]")
            return

        if df_trades is not None and not df_trades.empty:
            df_trades["closed_at"] = pd.to_datetime(df_trades["closed_at"])
            df_trades["month"] = df_trades["closed_at"].dt.to_period("M")
            grp = df_trades.groupby("month").agg(
                trades=("id", "count"),
                net_pnl=("pnl", "sum"),
                wins=("pnl", lambda x: (x > 0).sum()),
                losses=("pnl", lambda x: (x <= 0).sum())
            ).reset_index()
            self.console.print(Panel(grp.to_string(index=False), title="Monthly Trade Summary"))
        if df_daily is not None and not df_daily.empty:
            df_daily["date_dt"] = pd.to_datetime(df_daily["date"])
            df_daily = df_daily.sort_values("date_dt")
            try:
                import matplotlib.pyplot as plt
                plt.figure(figsize=(8, 3))
                plt.plot(df_daily["date_dt"], df_daily["capital"], marker='o', linewidth=1)
                plt.title("Equity Curve")
                plt.tight_layout()
                fname = f"equity_curve_{self.today_date()}.png"
                plt.savefig(fname)
                self.console.print(f"[green]Equity curve saved: {fname}[/green]")
            except Exception as e:
                self.console.print(f"[yellow]Could not plot equity curve: {e}[/yellow]")

    def show_reports(self):
        print("\n📊 Report Menu")
        print("1️⃣ Intraday Stock-wise Report (Today or Custom Date)")
        print("2️⃣ Weekly Performance Summary (Last 4 Weeks)")
        print("3️⃣ Monthly Performance Summary")

        choice = input("Select report type (1/2/3): ").strip() or "1"

        try:
            df = pd.read_sql_query("SELECT * FROM trade_log", self.db)
        except Exception as e:
            print(f"⚠️ Database error: {e}")
            return

        if df.empty:
            print("No trade history yet — close some trades first.")
            return

        df["closed_at"] = pd.to_datetime(df["closed_at"])
        df["date"] = df["closed_at"].dt.date
        df["week_start"] = df["closed_at"].dt.to_period("W").apply(lambda r: r.start_time.date())
        df["week_end"] = df["closed_at"].dt.to_period("W").apply(lambda r: r.end_time.date())
        df["month"] = df["closed_at"].dt.to_period("M")

        if choice == "1":
            print("\n🟩 Intraday Stock-wise Report")
            ask_date = input("Enter date (YYYY-MM-DD) or leave blank for today: ").strip()
            if ask_date:
                try:
                    report_date = datetime.strptime(ask_date, "%Y-%m-%d").date()
                except Exception:
                    print("❌ Invalid date format. Use YYYY-MM-DD.")
                    return
            else:
                report_date = date.today()

            df_day = df[df["date"] == report_date]
            if df_day.empty:
                print(f"No trades found for {report_date}")
                return

            grouped = df_day.groupby("symbol")["pnl"].sum().reset_index()
            max_abs = grouped["pnl"].abs().max()

            print(f"\n📅 Date: {report_date}")
            print("-" * 70)
            print(f"{'Symbol':<10} {'P&L (₹)':>10}   Chart")
            print("-" * 70)

            win_count = loss_count = 0
            for _, r in grouped.iterrows():
                if r.pnl > 0:
                    win_count += 1
                else:
                    loss_count += 1
                bar = self.ascii_bar(r.pnl, max_abs)
                print(f"{r.symbol:<10} {r.pnl:>10,.0f}   {bar}")

            total = df_day["pnl"].sum()
            print("-" * 70)
            print(f"Day P&L: ₹{total:,.0f} (W:{win_count}/L:{loss_count})")

        elif choice == "2":
            print("\n📅 Weekly Performance Summary (Last 4 Weeks)")
            df_week = (
                df.groupby(["week_start", "week_end"])
                .agg(net_pnl=("pnl", "sum"),
                    wins=("pnl", lambda x: (x > 0).sum()),
                    losses=("pnl", lambda x: (x <= 0).sum()))
                .reset_index()
                .sort_values("week_start", ascending=False)
                .head(4)
                .iloc[::-1]
            )

            max_abs = df_week["net_pnl"].abs().max()

            print("-" * 80)
            print(f"{'Week Range':<26} {'Net P&L (₹)':>10}   {'Performance Chart':<40} {'W/L'}")
            print("-" * 80)

            for _, r in df_week.iterrows():
                bar = self.ascii_bar(r.net_pnl, max_abs)
                label = f"{r.week_start}→{r.week_end}"
                print(f"{label:<26} {r.net_pnl:>10,.0f}   {bar}   ({r.wins}/{r.losses})")

            total = df_week["net_pnl"].sum()
            print("-" * 80)
            print(f"Total 4-Week P&L: ₹{total:,.0f}")

        elif choice == "3":
            print("\n📆 Monthly Performance Summary")
            grp = df.groupby("month").agg(
                trades=("id", "count"),
                net_pnl=("pnl", "sum"),
                wins=("pnl", lambda x: (x > 0).sum()),
                losses=("pnl", lambda x: (x <= 0).sum())
            ).reset_index()

            max_abs = grp["net_pnl"].abs().max()

            print("-" * 80)
            print(f"{'Month':<10} {'Net P&L (₹)':>10}   {'Performance Chart':<40} {'W/L'}")
            print("-" * 80)

            for _, r in grp.iterrows():
                bar = self.ascii_bar(r.net_pnl, max_abs)
                label = f"{r.month.strftime('%b\'%y')}"
                print(f"{label:<10} {r.net_pnl:>10,.0f}   {bar}   ({r.wins}/{r.losses})")

            total = grp["net_pnl"].sum()
            print("-" * 80)
            print(f"Total P&L: ₹{total:,.0f}")

    def show_trade_log(self):
        cur = self.db.cursor()
        cur.execute("SELECT * FROM trade_log ORDER BY id DESC")
        rows = cur.fetchall()

        if not rows:
            self.console.print(Panel("[yellow]No trade logs found yet![/yellow]", title="🧾 Trade Log"))
            return

        headers = [
            "ID", "Symbol", "Direction", "Entry", "Exit", "SL", "Target",
            "Qty", "R:R", "Outcome", "P&L", "P&L %", 
            "Capital →", "Daily %", "Closed", "Note"
        ]
        table = Table(title="🧾 TRADE LOG — Historical Performance", expand=True, show_lines=True)

        for h in headers:
            justify = (
                "right" if h in ("ID", "Entry", "Exit", "SL", "Target", "Qty", "R:R", "P&L", "P&L %", "Capital →", "Daily %")
                else "center" if h in ("Symbol", "Direction", "Outcome", "Closed")
                else "left"
            )
            table.add_column(h, justify=justify)

        for row in rows:
            (
                id_, symbol, entry, exit_price, qty, pnl, pnl_percent,
                direction, note, closed_at, timestamp, stop_loss, target,
                lot_size, position_size, rr, outcome, capital_after,
                daily_gain_loss, daily_gain_loss_percent
            ) = row

            def to_float(v):
                try:
                    return float(v) if v not in (None, "", "None") else 0.0
                except:
                    return 0.0

            entry = to_float(entry)
            exit_price = to_float(exit_price)
            stop_loss = to_float(stop_loss)
            target = to_float(target)
            pnl = to_float(pnl)
            pnl_percent = to_float(pnl_percent)
            rr = to_float(rr)
            capital_after = to_float(capital_after)
            daily_gain_loss_percent = to_float(daily_gain_loss_percent)

            outcome_str = str(outcome or "")
            if "WIN" in outcome_str:
                outcome_col = f"[green]{outcome_str}[/green]"
            elif "LOSS" in outcome_str:
                outcome_col = f"[red]{outcome_str}[/red]"
            elif "BE" in outcome_str:
                outcome_col = f"[yellow]{outcome_str}[/yellow]"
            else:
                outcome_col = f"[cyan]{outcome_str or '⏳ Open'}[/cyan]"

            pnl_str = f"[green]{pnl:.2f}[/green]" if pnl > 0 else f"[red]{pnl:.2f}[/red]" if pnl < 0 else f"[yellow]{pnl:.2f}[/yellow]"
            pnl_pct_str = f"[green]{pnl_percent:.2f}%[/green]" if pnl_percent > 0 else f"[red]{pnl_percent:.2f}%[/red]" if pnl_percent < 0 else f"[yellow]{pnl_percent:.2f}%[/yellow]"
            daily_pct_str = f"[green]{daily_gain_loss_percent:.2f}%[/green]" if daily_gain_loss_percent > 0 else f"[red]{daily_gain_loss_percent:.2f}%[/red]" if daily_gain_loss_percent < 0 else f"{daily_gain_loss_percent:.2f}%"

            table.add_row(
                str(id_),
                symbol or "",
                direction or "",
                f"{entry:.2f}" if entry else "",
                f"{exit_price:.2f}" if exit_price else "",
                f"{stop_loss:.2f}" if stop_loss else "",
                f"{target:.2f}" if target else "",
                str(qty or ""),
                f"{rr:.2f}" if rr else "",
                outcome_col,
                pnl_str,
                pnl_pct_str,
                f"{capital_after:,.2f}" if capital_after else "",
                daily_pct_str,
                closed_at or "",
                note or ""
            )

        self.console.print(table)

    # ----------------- CLI helpers & main loop -----------------
    def prompt_trade_input(self):
        symbol = Prompt.ask("Symbol").strip().upper()
        while True:
            try:
                entry = float(Prompt.ask("Entry price"))
                break
            except Exception:
                self.console.print("[red]Invalid entry price. Try again.[/red]")
        while True:
            try:
                sl = float(Prompt.ask("Stop-loss"))
                break
            except Exception:
                self.console.print("[red]Invalid stop-loss. Try again.[/red]")
        while True:
            try:
                target = float(Prompt.ask("Target price"))
                break
            except Exception:
                self.console.print("[red]Invalid target. Try again.[/red]")
        lot = Prompt.ask("Lot size (optional)", default="")
        lot = int(lot) if lot else None
        note = Prompt.ask("Optional note", default="")
        return symbol, entry, sl, target, lot, note

    def show_table_from_df(self, df, title="Table"):
        if df is None or df.empty:
            self.console.print("[bold red]No records found.[/bold red]")
            return
        table = Table(show_header=True, header_style="bold magenta")
        for col in df.columns:
            table.add_column(str(col))
        for _, row in df.iterrows():
            table.add_row(*[str(x) for x in row.values])
        self.console.print(Panel(table, title=title))

    def export_csvs(self):
        folder = "exports"
        os.makedirs(folder, exist_ok=True)
        try:
            df_open = pd.read_sql_query("SELECT * FROM open_trades", self.db)
            df_open.to_csv(os.path.join(folder, f"open_trades_{self.today_date()}.csv"), index=False)
            df_log = pd.read_sql_query("SELECT * FROM trade_log", self.db)
            df_log.to_csv(os.path.join(folder, f"trade_log_{self.today_date()}.csv"), index=False)
            df_daily = pd.read_sql_query("SELECT * FROM daily_stats", self.db)
            df_daily.to_csv(os.path.join(folder, f"daily_stats_{self.today_date()}.csv"), index=False)
            self.console.print(f"[green]Exported CSVs to {folder}[/green]")
        except Exception as e:
            self.console.print(f"[red]Export error: {e}[/red]")

    def apply_hardcoded_trades(self):
        if not self.hardcoded_mode:
            return
        for t in self.hardcoded_trades:
            try:
                symbol = t.get("symbol")
                entry = float(t.get("entry"))
                stop = float(t.get("stop_loss"))
                target = float(t.get("target"))
                lot = int(t.get("lot_size")) if t.get("lot_size") else None
                self.add_trade(symbol, entry, stop, target, lot_size=lot, note="hardcoded")
            except Exception as e:
                self.console.print(f"[yellow]Warning: could not add hardcoded trade {t}: {e}[/yellow]")

    def run(self):
        # explicit ensure row & reload stats
        self.ensure_today_row()
        # reload runtime stats
        stats = self.load_today_stats()
        self.initial_capital = float(stats["initial_capital"])
        self.capital = float(stats["capital"])
        self.daily_trades = int(stats["daily_trades"])
        self.daily_gain_trades = int(stats["daily_gain_trades"])
        self.daily_loss_trades = int(stats["daily_loss_trades"])
        self.daily_gain_amount = float(stats["daily_gain_amount"])
        self.daily_loss_amount = float(stats["daily_loss_amount"])
        self.trading_halted = bool(stats["trading_halted"])
        self.carry_forward = bool(stats["carry_forward"])

        self.console.print(Panel("🤖 AutoTradeManager v2 — Minimal UI", title="STARTUP", border_style="blue"))

        # Apply hardcoded trades if configured
        self.apply_hardcoded_trades()

        try:
            while True:
                self.console.print(Panel.fit(
                    "1) Add Trade  |  2) List Open Trades  |  3) Close Trade  |  4) Trade Log |  5) Status  |  6) Report  |  7) Export CSVs  |  8) Settings  |  9) DB reset |  0) Exit",
                    title="MAIN MENU", border_style="magenta"))
                cmd = Prompt.ask("Choose option").strip()

                if cmd in ("0", "b", "B", ""):
                    sure = Prompt.ask("Type 'C' to confirm exit", default="")
                    if sure.upper() == "C":
                        self.console.print("[green]Goodbye.[/green]")
                        break
                    continue

                if cmd == "1":
                    allowed, msg, level = self.can_add_trade()
                    if not allowed:
                        self.console.print(f"[red]🚫 {msg}[/red]")
                        continue

                    self.console.print(f"[green]{msg}[/green]")
                    symbol, entry, sl, target, lot, note = self.prompt_trade_input()
                    self.add_trade(symbol, entry, sl, target, lot_size=lot, note=note)

                elif cmd == "2":
                    try:
                        df = pd.read_sql_query("SELECT * FROM open_trades ORDER BY id", self.db)
                    except Exception:
                        df = None
                    self.show_table_from_df(df, "Open Trades")

                elif cmd == "3":
                    try:
                        df = pd.read_sql_query("SELECT * FROM open_trades ORDER BY id", self.db)
                    except Exception:
                        df = None
                    if df is None or df.empty:
                        self.console.print("[yellow]No open trades to close.[/yellow]")
                        continue
                    self.show_table_from_df(df, "Open Trades")
                    tid = Prompt.ask("Enter trade id to close").strip()
                    try:
                        tid_int = int(tid)
                    except Exception:
                        self.console.print("[red]Invalid trade id.[/red]")
                        continue
                    outp = Prompt.ask("Enter exit price").strip()
                    try:
                        outp_f = float(outp)
                    except Exception:
                        self.console.print("[red]Invalid price.[/red]")
                        continue
                    self.close_trade(tid_int, outp_f)

                elif cmd == "4":
                    self.show_trade_log()

                elif cmd == "5":
                    self.print_pnl_summary()

                elif cmd == "6":
                    self.show_reports()

                elif cmd == "7":
                    self.export_csvs()

                elif cmd == "8":
                    self.console.print(Panel(f"Carry-forward: {'ON' if self.carry_forward else 'OFF'} | Risk%: {self.risk_pct:.2f} | Max open: {self.max_open_trades}\nDrawdown cap: {self.max_drawdown_pct:.2f}% | Profit cap: {self.max_profit_pct:.2f}%", title="SETTINGS"))
                    self.console.print("1) Toggle Carry-forward  2) Set Risk%  3) Set Max open trades  4) Set Drawdown/Profit caps  5) Edit Capital  b) Back")
                    s = Prompt.ask("Choose", default="b").strip()
                    if s == "1":
                        new = 0 if self.carry_forward else 1
                        self.update_today_stats({"carry_forward": new})
                        self.carry_forward = bool(new)
                        self.console.print(f"[green]Carry-forward is now {'ON' if self.carry_forward else 'OFF'}[/green]")
                    elif s == "2":
                        v = Prompt.ask("Risk % (e.g. 1 for 1%)", default=str(self.risk_pct)).strip()
                        try:
                            self.risk_pct = float(v)
                            self.console.print(f"[green]Risk set to {self.risk_pct:.2f}%[/green]")
                        except Exception:
                            self.console.print("[red]Invalid risk value.[/red]")
                    elif s == "3":
                        v = Prompt.ask("Max open trades", default=str(self.max_open_trades)).strip()
                        try:
                            self.max_open_trades = int(v)
                            self.console.print(f"[green]Max open trades = {self.max_open_trades}[/green]")
                        except Exception:
                            self.console.print("[red]Invalid value.[/red]")
                    elif s == "4":
                        dd = Prompt.ask(f"Max drawdown pct (current {self.max_drawdown_pct:.2f})", default=str(self.max_drawdown_pct)).strip()
                        pp = Prompt.ask(f"Max profit pct (current {self.max_profit_pct:.2f})", default=str(self.max_profit_pct)).strip()
                        try:
                            self.max_drawdown_pct = float(dd)
                            self.max_profit_pct = float(pp)
                            self.update_today_stats({"max_drawdown_pct": self.max_drawdown_pct, "max_profit_pct": self.max_profit_pct})
                            self.console.print("[green]Thresholds saved.[/green]")
                        except Exception:
                            self.console.print("[red]Invalid input.[/red]")
                    elif s == "5":
                        new_cap = Prompt.ask("Set new capital (will update today's capital & initial if you confirm)", default=str(self.capital)).strip()
                        try:
                            v = float(new_cap)
                            confirm = Prompt.ask(f"Type YES to set initial_capital and capital to {self.money(v)}", default="NO")
                            if confirm.upper() == "YES":
                                self.update_today_stats({"initial_capital": v, "capital": v})
                                # reload stats object
                                stats = self.load_today_stats()
                                self.initial_capital = float(stats["initial_capital"])
                                self.capital = float(stats["capital"])
                                self.console.print(f"[green]Capital updated to {self.money(v)}[/green]")
                            else:
                                self.console.print("[yellow]Cancelled.[/yellow]")
                        except Exception:
                            self.console.print("[red]Invalid number.[/red]")
                    else:
                        continue

                elif cmd == "9":
                    c = Prompt.ask("Type RESET to confirm", default="")
                    if c == "RESET":
                        self.reset_db()
                        # reload object state
                        stats = self.load_today_stats()
                        self.initial_capital = float(stats["initial_capital"])
                        self.capital = float(stats["capital"])
                        self.daily_trades = int(stats["daily_trades"])
                        self.daily_gain_trades = int(stats["daily_gain_trades"])
                        self.daily_loss_trades = int(stats["daily_loss_trades"])
                        self.daily_gain_amount = float(stats["daily_gain_amount"])
                        self.daily_loss_amount = float(stats["daily_loss_amount"])
                        # self.console.print("[green]DB reset complete.[/green]")
                    else:
                        self.console.print("[yellow]DB reset cancelled.[/yellow]")

                else:
                    self.console.print("[red]Invalid choice. Try again.[/red]")

        except KeyboardInterrupt:
            self.console.print("\n[yellow]Interrupted — exiting...[/yellow]")
        except Exception as e:
            self.console.print(f"[red]Unexpected error: {e}[/red]")
            self.console.print(traceback.format_exc())
        finally:
            try:
                self.db.close()
            except Exception:
                pass

# ----------------- module-run launcher -----------------
if __name__ == "__main__":
    atm = AutoTradeManager()
    atm.run()
