import os            # WAJIB UNTUK os.system('clear')

os.environ['OPENBLAS_NUM_THREADS'] = '1'
os.environ['MKL_NUM_THREADS'] = '1'
os.environ['NUMEXPR_NUM_THREADS'] = '1'
os.environ['OMP_NUM_THREADS'] = '1'
os.environ['VECLIB_MAXIMUM_THREADS'] = '1'

from binance.client import Client
import time
import threading
import pandas as pd  # WAJIB UNTUK ANALISA
import matplotlib
matplotlib.use('Agg')  # WAJIB: Agar bisa menggambar di server tanpa monitor
import matplotlib.pyplot as plt
import io
import requests
import json
import datetime
import sys
import pandas as pd
from datetime import datetime


USE_DEMO = True # Ubah ke False jika ingin pakai saldo asli
MY_API_KEY = 'mCP71w1Gwn1t4liBFICcmfUEmWyY1mWfLHspARwkFgDTd6gdrjrxikrKhFmTfOtw' 
MY_API_SECRET = '5azgMFdiTegdgoKHaRhe6LaEFmcKbhRzEPhk9ULRkRClRISpS566qkUj01zTFmdf'

API_KEY_DEMO = "UYIg5lk40EAYIFSrJwBSQZuINZpV6ybyjLZt4r0g6xnasD5Bjol8M1zvRK1EkW8v"
API_SECRET_DEMO = "h09kWddMZKizJXUBFnkToSdUIkesgzVrCIvszlhAO96wIBQBWLz8LUD80J449Dcd"

DISCORD_WEBHOOK = "https://discord.com/api/webhooks/1480252883998998661/Z3X5WwGSBaQqvRlgbZTnKtVgWwGqiMMC26xeoiFfYsqlI8n2Lwzc42e_5tyVhg18jw9k"
PNL_WEBHOOK = "https://discord.com/api/webhooks/1480636636235829381/zpbOtgDz55eykVqduiu6_s_gWW2sELyjEzcJqYxv07rw1QObWiQwwZJg85JDV_1nW5NH"
MAX_SLOTS = 3
WHITELIST_COINS = ["POWERUSDT", "NAORIUSDT", "BANANAUSDT", "BTCUSDT", "ETHUSDT", "BNBUSDT", "XRPUSDT", "ADAUSDT", "SOLUSDT", "DOGEUSDT", "MATICUSDT"]

if USE_DEMO:
    MY_API_KEY = API_KEY_DEMO
    MY_API_SECRET = API_SECRET_DEMO

class InafaBot:
    def __init__(self, api_key, api_secret, is_demo=True):
        self.is_demo = is_demo
        self.whitelist = WHITELIST_COINS 
        self.active_watch = list(self.whitelist)
        self.max_slots = MAX_SLOTS 
        self.rotation_slots = ["", "", ""] # 3 Slot kosong awal
        self.last_rotated_coins = [] # Daftar koin yang baru saja tampil (Anti-Loop)
        self.active_positions = [] 
        self.history_pnl = {} # Kolom baru untuk mencatat hasil trading terakhir
        
        # --- TAMBAHKAN INI AGAR SIMULASI TIDAK ERROR ---
        self.simulated_trades = {}  # Buku catatan posisi virtual
        self.running = True        
        self.current_tf = '1m'     
        self.last_msg = "Ready"    

        self.discord = self 
        self.webhook_url = DISCORD_WEBHOOK
        
        self.client = Client(api_key, api_secret, testnet=is_demo, requests_params={'timeout': 20})

        self.is_backtest_mode = False  # Ubah ke False untuk Trading Real-time
        self.last_backtest_signal = {}

        # --- VIRTUAL BROKER SETTINGS ---
        self.virtual_balance = 1000.0  # Saldo awal simulasi $1,0x00
        self.initial_balance = 1000.0
        self.is_mc = False             # Status Margin Call

        self.backtest_results = {
            "total_trades": 0,
            "win": 0,
            "loss": 0,
            "total_pnl": 0.0
        }
        self.enabled_strategies = {
            "BULL-FLAG": True,   # <--- Fokus testing yang ini dulu
            "BEAR-FLAG": False,
            "DBL-TOP": False,
            "DBL-BOTTOM": False,
            "H&S-REV": False,
            "INV-H&S": False,
            "SAUCER": False,
            "INV-SAUCER": False,
            "SCALP-CONT": False,
            "SCALP-REV": False
        }
        print(f"🚀 INAFATRADE {'DEMO' if is_demo else 'REAL'} Started")
    

    def run_backtest(self, symbol):
        start_time_bench = time.time()
        cache_file = f"data_backtest_{symbol}_{self.current_tf}.csv"
        
        def update_loader(step, progress=0, info="", data_time=""):
            elapsed = time.time() - start_time_bench
            bar = '█' * int(20 * progress / 100) + '-' * (20 - int(20 * progress / 100))
            status_line = f"\r[{data_time}] Status: [{step:10}] |{bar}| {progress:3.0f}% | {info:65} | {elapsed:.1f}s"
            sys.stdout.write(status_line)
            sys.stdout.flush()

        try:
            # --- 1. DATA ACQUISITION ---
            if os.path.exists(cache_file):
                df_full = pd.read_csv(cache_file)
            else:
                update_loader("DOWNLOAD", 15, "Fetching 1 Year Historical Data...")
                klines = self.client.futures_historical_klines(symbol=symbol, interval=self.current_tf, start_str="1 year ago UTC")
                df_full = pd.DataFrame(klines, columns=['time','Open','High','Low','Close','Volume','ct','qa','tc','tb','tq','i'])
                df_full.to_csv(cache_file, index=False)
            
            df_full = df_full.apply(pd.to_numeric)
            df_full['human_time'] = pd.to_datetime(df_full['time'], unit='ms').dt.strftime('%Y-%m-%d %H:%M')

            # --- 2. INDICATORS CALCULATION (GLOBAL) ---
            update_loader("STANDBY", 45, "Calculating Global Indicators...")
            df_full['ema9'] = df_full['Close'].ewm(span=9, adjust=False).mean()
            df_full['ema21'] = df_full['Close'].ewm(span=21, adjust=False).mean()
            
            delta = df_full['Close'].diff()
            gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
            loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
            df_full['rsi'] = 100 - (100 / (1 + (gain / loss.replace(0, 1e-9))))

            # --- 3. BACKTEST ENGINE LOOP ---
            print(f"\n🚀 STARTING SIMULATION: {symbol} | Initial Bal: ${self.initial_balance}")
            
            for i in range(50, len(df_full)):
                if self.is_mc: break # Berhenti jika saldo habis

                row = df_full.iloc[i]
                df_slice = df_full.iloc[max(0, i-100):i+1].copy()
                
                mock_data = {
                    'price': row['Close'],
                    'df': df_slice,
                    'rsi': row['rsi'],
                    'backtest_sec': 0 # Bypass filter detik
                }

                # Urutan: Pantau Exit dulu baru cari Entry baru
                self.monitor_active_order(symbol, mock_data)
                
                if symbol not in self.simulated_trades:
                    self.update_simulation(symbol, mock_data)

                # Update UI tiap 100 candle
                if i % 100 == 0 or i == len(df_full)-1:
                    prog = (i / len(df_full)) * 100
                    t_trades = self.backtest_results['total_trades']
                    wr = (self.backtest_results['win'] / t_trades * 100) if t_trades > 0 else 0
                    pnl_usd = self.virtual_balance - self.initial_balance
                    
                    info_box = f"T:{t_trades} | WR:{wr:.1f}% | PnL: ${pnl_usd:+.2f} | Bal: ${self.virtual_balance:.2f}"
                    update_loader("BACKTEST", prog, info_box, data_time=row['human_time'])

            print(f"\n\n🏁 BACKTEST COMPLETED | Final Balance: ${self.virtual_balance:.2f}")

        except Exception as e:
            print(f"\n❌ CRITICAL ERROR IN RUN: {str(e)}")

    def process_virtual_trade_with_status(self, symbol, start_idx, df_full, loader_func):
        s = self.last_backtest_signal
        self.backtest_results['total_trades'] += 1
        
        for j in range(start_idx + 1, min(start_idx + 200, len(df_full))):
            f = df_full.iloc[j]
            prog_inner = (j / len(df_full)) * 100
            
            # Animasi saat menunggu candle (Standby/Monitoring)
            loader_func("MONITORING", prog_inner, f"In Trade: {s['pola']} {s['side']}")

            if s['side'] == "BUY":
                if f['Low'] <= s['sl']:
                    loader_func("EMERGENCY", prog_inner, "Stop Loss Hit!")
                    self.record_result(s, "LOSS", s['sl'])
                    return "SL"
                elif f['High'] >= s['tp']:
                    loader_func("CLOSE", prog_inner, "Take Profit Hit!")
                    self.record_result(s, "WIN", s['tp'])
                    return "TP"
            else: # SELL
                if f['High'] >= s['sl']:
                    loader_func("EMERGENCY", prog_inner, "Stop Loss Hit!")
                    self.record_result(s, "LOSS", s['sl'])
                    return "SL"
                elif f['Low'] <= s['tp']:
                    loader_func("CLOSE", prog_inner, "Take Profit Hit!")
                    self.record_result(s, "WIN", s['tp'])
                    return "TP"
        return "EXPIRED"

    def process_virtual_trade(self, symbol, start_idx, df_full):
        s = self.last_backtest_signal
        self.backtest_results['total_trades'] += 1
        
        # Scan 100 candle ke depan dari start_idx
        for j in range(start_idx + 1, min(start_idx + 101, len(df_full))):
            f = df_full.iloc[j]
            
            if s['side'] == "BUY":
                if f['Low'] <= s['sl']: # Kena SL
                    self.record_result(s, "LOSS", s['sl'])
                    break
                elif f['High'] >= s['tp']: # Kena TP
                    self.record_result(s, "WIN", s['tp'])
                    break
            else: # SELL
                if f['High'] <= s['sl']:
                    self.record_result(s, "LOSS", s['sl'])
                    break
                elif f['Low'] >= s['tp']:
                    self.record_result(s, "WIN", s['tp'])
                    break

    def record_result(self, s, res, exit_p):
        pnl = ((exit_p - s['entry']) / s['entry'] * 100) if s['side'] == "BUY" else ((s['entry'] - exit_p) / s['entry'] * 100)
        if res == "WIN": self.backtest_results['win'] += 1
        else: self.backtest_results['loss'] += 1
        self.backtest_results['total_pnl'] += pnl
        
        print(f"\n[BACKTEST RESULT] {s['pola']} {s['side']} | PnL: {pnl:+.2f}% | Status: {res}")

    def get_current_winrate(self):
        if self.backtest_results['total_trades'] == 0: return 0
        return (self.backtest_results['win'] / self.backtest_results['total_trades']) * 100

    def check_rotation(self):
        """Mencari 3 koin paling panas untuk rotasi cepat"""
        try:
            tickers = self.client.futures_ticker()
            # Sortir berdasarkan persentase perubahan harga (Volatilitas)
            sorted_tickers = sorted(tickers, key=lambda x: abs(float(x['priceChangePercent'])), reverse=True)
            
            new_rotations = []
            for t in sorted_tickers:
                symbol = t['symbol']
                # Hindari koin favorit dan koin non-USDT
                if symbol.endswith("USDT") and symbol not in self.whitelist:
                    new_rotations.append(symbol)
                if len(new_rotations) >= 3: # Kita kunci 3 baris saja sesuai perintah
                    break
            
            # Update daftar pantau: Favorit Tetap + 3 Rotasi Baru
            self.active_watch = list(self.whitelist) + new_rotations
        except:
            pass

    def get_candle_data(self, symbol, interval='1m', limit=5):
        """Mengambil data candle terakhir tanpa membebani API"""
        try:
            # futures_klines memiliki weight yang kecil, aman untuk rotasi
            candles = self.client.futures_klines(symbol=symbol, interval=interval, limit=limit)
            
            # Format: [Open Time, Open, High, Low, Close, Volume, ...]
            # Kita ambil harga Close dari candle terakhir yang sudah SELESAI (index -2)
            # Karena index -1 adalah candle yang sedang berjalan (masih berubah-ubah)
            last_closed_price = float(candles[-2][4]) 
            current_candle_price = float(candles[-1][4])
            
            return last_closed_price, current_candle_price
        except Exception as e:
            # print(f"❌ Gagal ambil candle {symbol}: {e}")
            return None, None

    def analyze_coin(self, symbol, is_favorite):
        """Analisa Teknikal dasar: Membandingkan harga saat ini dengan candle sebelumnya"""
        last_close, current_price = self.get_candle_data(symbol)
        
        if last_close and current_price:
            # Hitung perubahan sederhana dalam persen
            diff_percent = ((current_price - last_close) / last_close) * 100
            
            label = "⭐ FAV" if is_favorite else "🌐 GEN"
            status = "📈 NAIK" if diff_percent > 0 else "📉 TURUN"
            
            print(f"[{label}] {symbol:10} | Price: {current_price:<10} | {status}: {diff_percent:>6.3f}% | Slots: {len(self.active_positions)}/{self.max_slots}      ", end="\r")


    def get_candle_chart(self, symbol):
        """Mengambil data 10 candle terakhir dan mengubahnya jadi grafik ASCII"""
        try:
            # Ambil 10 candle terakhir (interval 1m)
            candles = self.client.futures_klines(symbol=symbol, interval='1m', limit=10)
            chart_visual = ""
            
            for c in candles:
                open_p = float(c[1])
                close_p = float(c[4])
                
                # ANSI Color Codes
                GREEN = "\033[92m"
                RED = "\033[91m"
                RESET = "\033[0m"
                
                # Jika harga naik atau tetap = Hijau, jika turun = Merah
                color = GREEN if close_p >= open_p else RED
                
                # Karakter blok tebal untuk candle
                chart_visual += f"{color}█{RESET}"
            
            return chart_visual, float(candles[-2][4]), float(candles[-1][4])
        except:
            return "----------", 0, 0

    def get_market_structure(self, df):
        """Mendeteksi High, Low, dan SnR Sejati dari data historis"""
        # Harga tertinggi dan terendah termasuk wick (High & Low)
        highest_price = df['High'].max()
        lowest_price = df['Low'].min()
        
        # Mencari Resistance & Support Sejati (Area yang sering dipantul)
        # Sederhananya, kita ambil titik puncak (Peak) dan dasar (Trough) yang signifikan
        resistance = df['High'].iloc[-20:].max() 
        support = df['Low'].iloc[-20:].min()
        
        return highest_price, lowest_price, resistance, support

    def calculate_rsi(self, df, period=14):
        delta = df['Close'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
        rs = gain / loss
        return 100 - (100 / (1 + rs))

    def get_market_data(self, symbol):
        try:
            ticker = self.client.futures_symbol_ticker(symbol=symbol)
            current_price = float(ticker['price'])

            klines = self.client.futures_klines(symbol=symbol, interval=self.current_tf, limit=100)
            df = pd.DataFrame(klines, columns=['time', 'Open', 'High', 'Low', 'Close', 'Volume', 'ct', 'qa', 'tc', 'tb', 'tq', 'i'])
            
            # Konversi kolom ke numeric (Gunakan nama kolom dari kode awal kamu)
            df[['Open', 'High', 'Low', 'Close', 'Volume']] = df[['Open', 'High', 'Low', 'Close', 'Volume']].apply(pd.to_numeric)
            
            # Tambahkan Indikator ke DF agar dibaca update_simulation
            df['ema9'] = df['Close'].ewm(span=9, adjust=False).mean()
            df['ema21'] = df['Close'].ewm(span=21, adjust=False).mean()
            
            # --- ANALISA CANDLE CLOSE ---
            closed_df = df.iloc[:-1].copy()
            
            delta = closed_df['Close'].diff()
            gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
            loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
            rsi_val = (100 - (100 / (1 + (gain / loss.replace(0, 1e-9))))).iloc[-1]
            
            is_bull = closed_df['ema9'].iloc[-1] > closed_df['ema21'].iloc[-1]

            return {
                'price': current_price,
                'rsi': rsi_val,
                'trend': "BULL" if is_bull else "BEAR",
                'trend_col': "\033[92m" if is_bull else "\033[91m",
                'df': closed_df # <--- INI WAJIB ADA AGAR update_simulation JALAN
            }
        except: return None

    def get_trend_duration(self, df):
        """Menghitung sudah berapa lama (candle) tren aktif saat ini berlangsung"""
        df['ema9'] = self.calculate_ema(df, 9)
        df['ema21'] = self.calculate_ema(df, 21)
        
        # Tentukan kondisi tren saat ini (Bullish = True, Bearish = False)
        current_is_bull = df['ema9'].iloc[-1] > df['ema21'].iloc[-1]
        
        # Hitung mundur ke belakang sampai kondisi berubah
        duration = 0
        for i in range(len(df)-1, -1, -1):
            is_bull = df['ema9'].iloc[i] > df['ema21'].iloc[i]
            if is_bull == current_is_bull:
                duration += 1
            else:
                break
        
        trend_label = "BULLISH 📈" if current_is_bull else "BEARISH 📉"
        color = "\033[92m" if current_is_bull else "\033[91m"
        
        return trend_label, duration, color

    def setup_futures_account(self, symbol):
        """Mengatur Leverage 20x dan Margin Isolated"""
        try:
            self.client.futures_change_leverage(symbol=symbol, leverage=20)
            self.client.futures_change_margin_type(symbol=symbol, marginType='ISOLATED')
        except:
            # Diabaikan jika settingan sudah sesuai (Binance kirim error jika tidak ada perubahan)
            pass

    def sync_existing_orders(self):
        """Mendeteksi posisi yang sudah ada di Binance (Antisipasi PC Mati)"""
        try:
            # Mengambil informasi posisi dari akun Futures
            positions = self.client.futures_position_information()
            found_count = 0
            
            for pos in positions:
                symbol = pos['symbol']
                amt = float(pos['positionAmt'])
                
                # Jika jumlah (amount) tidak 0, berarti ada posisi aktif
                if amt != 0: 
                    side = 'BUY' if amt > 0 else 'SELL'
                    entry = float(pos['entryPrice'])
                    
                    # Masukkan ke memori pantau bot agar muncul di dashboard
                    # Karena TP/SL real ada di bursa, kita beri nilai estimasi di dashboard
                    self.simulated_trades[symbol] = {
                        'type': side, 
                        'entry': entry, 
                        'tp': entry * (1.015 if side == 'BUY' else 0.985), # Estimasi tampilan
                        'sl': entry * (0.993 if side == 'BUY' else 1.007), 
                        'pola': "SYNC-REAL", 
                        'state': "RECOVERED"
                    }
                    found_count += 1
            
            if found_count > 0:
                self.last_msg = f"✅ Berhasil Sinkron: {found_count} Posisi Terdeteksi"
                print(f"\n[INFO] Menemukan {found_count} posisi aktif di Binance. Melanjutkan pemantauan...")
            else:
                self.last_msg = "ℹ️ Tidak ada posisi aktif untuk disinkronkan."
        except Exception as e:
            print(f"❌ Gagal Sinkron Posisi: {e}")

    def calculate_smart_margin(self, is_long_tp):
        """Menghitung margin berdasarkan sisa saldo & panjang prediksi"""
        try:
            balance = self.get_balance()
            # Sisakan 10% saldo cadangan
            useable_balance = balance * 0.9
            # Bagi rata ke jumlah slot maksimal (3)
            slot_budget = useable_balance / self.max_slots
            
            # Jika TP Panjang (Up/Down panjang), gunakan 80% dari budget slot
            # Jika TP Pendek, gunakan 100% budget slot
            final_margin = slot_budget * 0.8 if is_long_tp else slot_budget
            return max(final_margin, 5.0) # Minimal margin $5
        except:
            return 10.0

    def get_balance(self):
        """Menghitung Total Harta secara otomatis dari SEMUA aset yang ada"""
        try:
            # 1. Ambil semua saldo yang tidak nol
            balances = self.client.futures_account_balance()
            total_usd = 0.0
            usdt_only = 0.0

            for a in balances:
                amt = float(a['balance'])
                asset = a['asset']
                
                if amt > 0:
                    # Simpan saldo USDT murni untuk peluru trading
                    if asset == 'USDT':
                        usdt_only = amt
                    
                    # Logika Total: Jika stablecoin (USD), langsung tambah. 
                    # Jika koin (BTC/BNB/ETH), cari harga pasarnya dulu.
                    if asset in ['USDT', 'USDC', 'BUSD', 'DAI']:
                        total_usd += amt
                    else:
                        try:
                            # Cari harga koin tersebut terhadap USDT secara otomatis
                            ticker = self.client.futures_symbol_ticker(symbol=f"{asset}USDT")
                            price = float(ticker['price'])
                            total_usd += (amt * price)
                        except:
                            # Jika koin tidak punya pair USDT, lewati atau anggap 0
                            total_usd += 0 
            
            return total_usd, usdt_only
        except Exception as e:
            self.last_msg = f"⚠️ Balance Error: {str(e)[:15]}"
            return 0.0, 0.0

    def update_simulation(self, symbol, data):
        try:
            df = data.get('df')
            if df is None or len(df) < 50: return "🔍 SCANNING..."
            if symbol in self.simulated_trades: return self.monitor_active_order(symbol, data)
            if len(self.simulated_trades) >= self.max_slots: return "👀 FULL"

            # Hitung ATR Global untuk semua pola
            atr = (df['High'] - df['Low']).tail(14).mean()
            
            pola, side, tp, sl = "", "", 0.0, 0.0

            # --- MODULE TESTING LOOP ---
            # 1. Testing Pola Power Flag
            if not pola and self.enabled_strategies.get("BULL-FLAG"):
                pola, side, tp, sl = self.pattern_power_flag(df, data, atr)

            # 2. Testing Pola Liquidity Trap (Reversal)
            if not pola and (self.enabled_strategies.get("DBL-TOP")):
                pola, side, tp, sl = self.pattern_liquidity_trap(df, data, atr)

            # 3. Testing Pola VSA Squeeze (Scalping)
            if not pola and self.enabled_strategies.get("SCALP-CONT"):
                pola, side, tp, sl = self.pattern_vsa_squeeze(df, data, atr)

            # 4. Testing Pola Head & Shoulders Expert
            if not pola and self.enabled_strategies.get("H&S-REV"):
                pola, side, tp, sl = self.pattern_hs_expert(df, data, atr)

            # --- EXECUTION ---
            if pola != "" and data.get('backtest_sec', 10) <= 1:
                p_data = {'tp': tp, 'sl': sl, 'pola': pola, 'price': float(data['price']), 'start_bar': df.index[-1]}
                self.execute_real_order(symbol, side, p_data)
                return f"🔥 {side} {pola} EXEC"

            return "🔍 SCANNING..."
        except Exception as e:
            return f"Err Sim: {str(e)[:15]}"

    def pattern_panic_breakout(self, df, data, atr):
        try:
            c_closed = df.iloc[-2]
            vol_now, vol_avg = float(c_closed['Volume']), df['Volume'].iloc[-15:-2].mean()
            
            # Deteksi Konsolidasi (Squeeze)
            low_10 = df['Low'].iloc[-12:-2].min()
            high_10 = df['High'].iloc[-12:-2].max()
            is_squeeze = (high_10 - low_10) < (atr * 2.5)

            if is_squeeze and vol_now > (vol_avg * 2.2): # Ledakan Volume harus > 2.2x
                curr_p = float(data['price'])
                
                # PANIC SELL: Harga jebol dasar konsolidasi dengan volume raksasa
                if c_closed['Close'] < low_10:
                    pola, side = "PANIC-SELL", "SELL"
                    sl = c_closed['High'] + (atr * 0.5) # SL di atas candle panik
                    tp = curr_p - (atr * 4.0) # Target jauh karena panik biasanya berlanjut
                    return pola, side, tp, sl
                
                # PANIC BUY: Harga jebol atap konsolidasi
                elif c_closed['Close'] > high_10:
                    pola, side = "PANIC-BUY", "BUY"
                    sl = c_closed['Low'] - (atr * 0.5)
                    tp = curr_p + (atr * 4.0)
                    return pola, side, tp, sl
        except: pass
        return "", "", 0, 0

    def pattern_rubber_band(self, df, data, atr):
        try:
            c_closed = df.iloc[-2]
            ema21 = float(c_closed['ema21'])
            curr_p = float(data['price'])
            rsi = float(data.get('rsi', 50))
            
            # Jarak harga sekarang ke EMA21 (Overextended)
            dist = (curr_p - ema21) / ema21 * 100
            
            # OVERBOUGHT TRAP: Harga melenting jauh di atas EMA21 + RSI Pucuk
            if dist > 0.8 and rsi > 75: # 0.8% dlm 1 menit itu sangat jauh
                if c_closed['Close'] < c_closed['Open']: # Tunggu candle konfirmasi merah
                    return "RUBBER-SHORT", "SELL", curr_p - (dist * 0.5), c_closed['High'] + (atr * 0.2)
            
            # OVERSOLD TRAP: Harga jatuh terlalu dalam di bawah EMA21
            elif dist < -0.8 and rsi < 25:
                if c_closed['Close'] > c_closed['Open']: # Tunggu candle hijau
                    return "RUBBER-LONG", "BUY", curr_p + (abs(dist) * 0.5), c_closed['Low'] - (atr * 0.2)
        except: pass
        return "", "", 0, 0

    def pattern_power_flag(self, df, data, atr):
        try:
            c_closed = df.iloc[-2]
            curr_p = float(data['price'])
            ema9, ema21 = float(c_closed['ema9']), float(c_closed['ema21'])
            vol_now, vol_avg = float(c_closed['Volume']), df['Volume'].iloc[-15:-2].mean()
            
            # Hitung Slope EMA9 (4 candle terakhir)
            ema9_prev = float(df['ema9'].iloc[-5])
            slope = (ema9 - ema9_prev) / ema9_prev * 100
            
            # Area Flag (8 candle terakhir)
            flag_max = df['High'].iloc[-10:-2].max()
            flag_min = df['Low'].iloc[-10:-2].min()

            # --- A. BULL FLAG ---
            if ema9 > ema21 and slope > 0.02:
                if c_closed['Close'] > flag_max and vol_now > (vol_avg * 1.3):
                    return "PWR-BULL", "BUY", curr_p + (atr * 4.0), c_closed['Low'] - (atr * 0.5)

            # --- B. BEAR FLAG ---
            if ema9 < ema21 and slope < -0.02:
                if c_closed['Close'] < flag_min and vol_now > (vol_avg * 1.3):
                    return "PWR-BEAR", "SELL", curr_p - (atr * 4.0), c_closed['High'] + (atr * 0.5)

        except: pass
        return "", "", 0, 0
        
    def pattern_liquidity_trap(self, df, data, atr):
        c_closed = df.iloc[-2]
        rsi = float(data.get('rsi', 50))
        high_20 = df['High'].iloc[-25:-5].max()
        rsi_prev = df['rsi'].iloc[-10:-2].max()
        
        # Fakeout High: Harga bikin New High, tapi RSI melemah (Divergence)
        if float(data['price']) > high_20 and rsi < rsi_prev and c_closed['Close'] < c_closed['Open']:
            return "LIQ-TRAP", "SELL", float(data['price']) - (atr * 3.5), df['High'].iloc[-5:].max() + (atr * 0.2)
        return "", "", 0, 0

    def pattern_vsa_squeeze(self, df, data, atr):
        c_closed = df.iloc[-2]
        vol_now, vol_avg = float(c_closed['Volume']), df['Volume'].iloc[-15:-2].mean()
        # Deteksi Squeeze (5 candle terakhir range-nya sempit)
        squeeze = (df['High'].iloc[-6:-1].max() - df['Low'].iloc[-6:-1].min()) < (atr * 1.2)
        
        if squeeze and vol_now > (vol_avg * 2.0):
            body = c_closed['Close'] - c_closed['Open']
            if body > (atr * 0.8): # Breakout atas
                return "VSA-SQLZ", "BUY", float(data['price']) + (atr * 3.0), c_closed['Open'] - (atr * 0.5)
            elif body < -(atr * 0.8): # Breakout bawah
                return "VSA-SQLZ", "SELL", float(data['price']) - (atr * 3.0), c_closed['Open'] + (atr * 0.5)
        return "", "", 0, 0

    def pattern_inverse_hs(self, df, data, atr):
        try:
            # 1. AMBIL DATA LEMBAH
            l_shoulder_l = df['Low'].iloc[-40:-25].min()
            head_l = df['Low'].iloc[-25:-12].min()
            r_shoulder_l = df['Low'].iloc[-12:-2].min()
            
            # Neckline (Resistance Line)
            neckline_h = df['High'].iloc[-25:-5].max()
            
            c_closed = df.iloc[-2]
            curr_p = float(data['price'])
            vol_now = float(c_closed['Volume'])
            vol_avg = df['Volume'].iloc[-15:-2].mean()

            # 2. VALIDASI
            is_valid_shape = head_l < l_shoulder_l and head_l < r_shoulder_l and \
                             abs(l_shoulder_l - r_shoulder_l) < (atr * 2.5)
            
            is_volume_ok = vol_now > (vol_avg * 1.4)

            # 3. TRIGGER ENTRY
            if is_valid_shape and c_closed['Close'] > neckline_h:
                if is_volume_ok:
                    pola = "INV-H&S-EXPERT"
                    side = "BUY"
                    sl = r_shoulder_l - (atr * 0.3)
                    tp = curr_p + (neckline_h - head_l)
                    return pola, side, tp, sl
                    
        except Exception: pass
        return "", "", 0, 0

    def pattern_hs_expert(self, df, data, atr):
        try:
            # 1. AMBIL DATA STRUKTUR (Lebih sensitif/sempit)
            l_shoulder_h = df['High'].iloc[-25:-15].max()
            head_h = df['High'].iloc[-15:-8].max()
            r_shoulder_h = df['High'].iloc[-8:-2].max()
            
            # Neckline (Titik terendah di lembah head)
            neckline_l = df['Low'].iloc[-15:-5].min()
            
            c_closed = df.iloc[-2]
            curr_p = float(data['price'])
            vol_now = float(c_closed['Volume'])
            vol_avg = df['Volume'].iloc[-10:-2].mean()

            # 2. VALIDASI MINIMALIS
            # Head harus lebih tinggi dari bahu, bahu kanan tidak boleh balapan sama head
            if head_h > l_shoulder_h and head_h > r_shoulder_h:
                # Trigger: Close di bawah neckline ATAU close di bawah EMA9 (Early Entry)
                if c_closed['Close'] < neckline_l:
                    # Filter Volume Longgar: Cukup 1.1x rata-rata (Pecah Telur mode)
                    if vol_now > (vol_avg * 1.1):
                        pola = "H&S-SCALP"
                        side = "SELL"
                        # SL Ketat: Di atas bahu kanan saja
                        sl = r_shoulder_h + (atr * 0.2)
                        # TP 1:1.5 RR (Scalping)
                        tp = curr_p - (head_h - neckline_l) * 0.8
                        return pola, side, tp, sl
                    
        except Exception: pass
        return "", "", 0, 0
    

    def get_analysis_data(self, symbol):
        """Mendeteksi Tren, Durasi, High/Low (Wick), dan SnR Sejati"""
        try:
            # Ambil 50 candle untuk kalkulasi EMA dan SnR yang akurat
            candles = self.client.futures_klines(symbol=symbol, interval='1m', limit=50)
            df = pd.DataFrame(candles, columns=['time', 'Open', 'High', 'Low', 'Close', 'Vol', 'ct', 'qa', 'tc', 'tb', 'tq', 'i'])
            df[['Open', 'High', 'Low', 'Close']] = df[['Open', 'High', 'Low', 'Close']].apply(pd.to_numeric)

            # 1. Deteksi High/Low Sejati (Wick)
            highest_wick = df['High'].max()
            lowest_wick = df['Low'].min()

            # 2. Support & Resistance Statis (Area pantulan 20 candle terakhir)
            resistance = df['High'].iloc[-20:].max()
            support = df['Low'].iloc[-20:].min()

            # 3. Hitung EMA 9 & 21 untuk Tren
            ema9 = df['Close'].ewm(span=9, adjust=False).mean()
            ema21 = df['Close'].ewm(span=21, adjust=False).mean()
            
            # Tentukan Tren & Durasi
            is_bullish = ema9.iloc[-1] > ema21.iloc[-1]
            duration = 0
            for i in range(len(df)-1, -1, -1):
                if (ema9.iloc[i] > ema21.iloc[i]) == is_bullish:
                    duration += 1
                else:
                    break
            
            trend_label = "BULL" if is_bullish else "BEAR"
            trend_col = "\033[92m" if is_bullish else "\033[91m"
            
            return trend_label, trend_col, duration, highest_wick, lowest_wick, resistance, support
        except:
            return "Wait", "\033[0m", 0, 0, 0, 0, 0

    def display_loop(self):
        # os.system('clear' if os.name == 'posix' else 'cls') 
        while self.running:
            # --- TAMBAHKAN BARIS INI: Cek status real setiap detik ---
            self.refresh_real_positions() 
            
            print("\033[H", end="")
            now = time.localtime()
            
            # Update data dasar
            if now.tm_sec % 3 == 0:
                self.check_rotation()
            
            # --- HEADER DASHBOARD ---
            print(f"🚀 \033[1mINAFATRADE PRO-AI (DEMO API)\033[0m | TF: {self.current_tf} | {time.strftime('%H:%M:%S', now)}")
            
            # --- LIST POLA YANG DICARI (STRATEGY LIST) ---
            print("\033[94m[ STRATEGY ACTIVE ]\033[0m")
            print("1. Bull/Bear Flag (Strict)   2. Double Top/Bottom   3. Head & Shoulders")
            print("4. Inverse H&S               5. Saucer (Rounding)   6. Inverse Saucer")
            print("-" * 165)
            
            # --- TABEL KONTEN ---
            print(f"{'COIN':<10} | {'PRICE':<10} | {'TREND':<10} | {'RSI':<5} | {'ACTION / PATTERN / PNL / TARGET':<75} | {'TYPE'}")
            print("-" * 165)
            
            for coin in self.active_watch:
                data = self.get_market_data(coin)
                if data:
                    # Di sini update_simulation akan mendeteksi salah satu dari 6 pola di atas
                    status_col = self.update_simulation(coin, data)
                    c_type = "⭐ FAV" if coin in self.whitelist else "🔄 ROT"
                    
                    print(f"{coin:<10} | {data['price']:<10.4f} | {data['trend_col']}{data['trend']:<8}\033[0m | "
                          f"{data['rsi']:>5.1f} | {status_col:<75} | {c_type}")
            
            print("-" * 165)
            # Menampilkan pesan terakhir dari API (Error atau Success)
            print(f"Last Status: {self.last_msg}")
            
            time.sleep(1)

    def analyze_signal(self, symbol):
        """Logika Penentu Rekomendasi berdasarkan Pola & Tren"""
        data = self.get_market_data(symbol)
        if not data: return "WAIT", "\033[0m"

        # Ambil 3 candle terakhir untuk deteksi pola
        klines = self.client.futures_klines(symbol=symbol, interval=self.current_tf, limit=3)
        c1, c2, c3 = klines[0], klines[1], klines[2] # c3 adalah candle running

        # Definisi Bullish/Bearish Candle (Body)
        c1_bull = float(c1[4]) > float(c1[1])
        c2_bull = float(c2[4]) > float(c2[1])
        
        # 1. POLA PEMBALIKAN (Reversal - Pin Bar / Hammer)
        # Jika trend BEAR tapi muncul candle dengan ekor bawah panjang
        low_tail = float(c2[1]) - float(c2[3]) if c2_bull else float(c2[4]) - float(c2[3])
        body_size = abs(float(c2[4]) - float(c2[1]))
        
        if "BEAR" in data['trend'] and low_tail > (body_size * 2):
            return "BUY (REVERSAL)", "\033[92m"

        # 2. POLA KELANJUTAN (Continuation - Engulfing)
        # Jika candle sekarang memakan body candle sebelumnya searah trend
        if "BULL" in data['trend'] and c2_bull and float(c2[4]) > float(c1[2]):
            return "BUY (CONTINUE)", "\033[92m"
            
        if "BEAR" in data['trend'] and not c2_bull and float(c2[4]) < float(c1[3]):
            return "SELL (CONTINUE)", "\033[91m"

        return "WAITING", "\033[90m"

    def get_volatile_coins(self):
        """Mencari koin yang sedang volatil di pasar Futures"""
        try:
            # Ambil data ticker 24 jam untuk semua koin
            tickers = self.client.futures_ticker()
            # Filter koin yang punya perubahan harga (volatilitas) tinggi
            # Kita urutkan berdasarkan priceChangePercent
            sorted_tickers = sorted(tickers, key=lambda x: abs(float(x['priceChangePercent'])), reverse=True)
            
            volatile_list = []
            for t in sorted_tickers:
                symbol = t['symbol']
                # Pastikan koin berakhiran USDT dan bukan koin favorit yang sudah ada
                if symbol.endswith("USDT") and symbol not in self.whitelist:
                    volatile_list.append(symbol)
                if len(volatile_list) >= 5: # Ambil 5 koin volatil untuk dirotasi
                    break
            return volatile_list
        except:
            return []

    def place_futures_order(self, symbol, side, price):
        """Eksekusi Order Real ke Binance Futures (MODE DEMO/TESTNET)"""
        try:
            # Menggunakan Market Order untuk menjamin eksekusi di detik :00
            order = self.client.futures_create_order(
                symbol=symbol,
                side=side,
                type='MARKET',
                quantity=self.calculate_quantity(symbol) # Fungsi pembantu hitung lot
            )
            return order
        except Exception as e:
            self.last_msg = f"Order Error: {str(e)}"
            return None

    def execute_real_order(self, symbol, side, p_data):
        signal = {
            'type': side, 
            'entry': p_data['price'], 
            'tp': p_data['tp'], 
            'sl': p_data['sl'], 
            'pola': p_data['pola'], # Pastikan pola tercatat!
            'start_bar': p_data.get('start_bar', 0),
            'qty': 100 / p_data['price'] # Contoh qty simple
        }
        
        if self.is_backtest_mode:
            self.simulated_trades[symbol] = signal
            return True, p_data['price']

        # Proteksi: Jangan percaya memori lokal saja, tanya Binance
        try:
            positions = self.client.futures_position_information()
            real_active_count = sum(1 for p in positions if float(p['positionAmt']) != 0)
            
            if real_active_count >= self.max_slots:
                self.last_msg = "🚫 SLOT PENUH (BINANCE CHECK)"
                return None, 0
        except: pass

        try:
            # 1. SETUP & BERSIHKAN ORDER LAMA
            self.setup_futures_account(symbol)
            try:
                self.client.futures_cancel_all_open_orders(symbol=symbol)
                time.sleep(0.2)
            except: pass

            # --- LOCK 2: RE-CHECK ---
            if len(self.simulated_trades) >= self.max_slots:
                self.last_msg = f"🛑 Slot Full ({len(self.simulated_trades)})"
                return None, 0

            s_info = self.get_symbol_info(symbol)
            ticker = self.client.futures_symbol_ticker(symbol=symbol)
            curr_p = float(ticker['price'])
            
            # Filter Presisi
            step_size, tick_size = "0.001", "0.001"
            for f in s_info['filters']:
                if f['filterType'] == 'LOT_SIZE': step_size = f['stepSize']
                if f['filterType'] == 'PRICE_FILTER': tick_size = f['tickSize']

            # 2. HITUNG QUANTITY & CEK LIMIT BURSA
            brackets = self.client.futures_leverage_bracket(symbol=symbol)
            max_notional = float(brackets[0]['brackets'][0]['notionalCap'])
            
            # --- FIX ERROR DI SINI: Unpacking Tuple ---
            # Kita hanya ambil total_h (index 0), usdt_h diabaikan dengan _
            total_h, _ = self.get_balance() 
            
            slot_budget = (total_h * 0.9) / self.max_slots
            req_notional = min(slot_budget * 20, max_notional * 0.95)
            
            qty = self.round_step_size(req_notional / curr_p, step_size)

            # 3. EKSEKUSI MARKET ORDER
            res = self.client.futures_create_order(
                symbol=symbol, side=side, type='MARKET', quantity=format(qty, 'f')
            )

            # --- LOCK 3: ATOMIC MEMORY UPDATE ---
            self.simulated_trades[symbol] = {
                'type': side, 'entry': curr_p, 'pola': p_data['pola'], 
                'qty': qty, 'start_time': time.time(), 'state': 'OPENING'
            }

            # 4. AMBIL HARGA ENTRY ASLI & KIRIM DISCORD
            time.sleep(0.4)
            try:
                order_details = self.client.futures_get_order(symbol=symbol, orderId=res['orderId'])
                entry_p = float(order_details.get('avgPrice', curr_p))
                self.simulated_trades[symbol]['entry'] = entry_p 
                
                df_v = self.get_latest_df(symbol)
                self.send_discord_report(symbol, side, p_data['pola'], entry_p, p_data['tp'], p_data['sl'], df_v)
            except: 
                entry_p = curr_p

            # 5. PASANG TP & SL DENGAN EMERGENCY EXIT
            final_tp = self.round_step_size(float(p_data['tp']), tick_size)
            final_sl = self.round_step_size(float(p_data['sl']), tick_size)
            opp_side = 'SELL' if side == 'BUY' else 'BUY'

            # Pasang TP
            try:
                self.client.futures_create_order(
                    symbol=symbol, side=opp_side, type='TAKE_PROFIT_MARKET',
                    stopPrice=format(final_tp, 'f'), closePosition=True
                )
            except: pass

            # Pasang SL (Vital)
            try:
                self.client.futures_create_order(
                    symbol=symbol, side=opp_side, type='STOP_MARKET',
                    stopPrice=format(final_sl, 'f'), closePosition=True
                )
            except Exception as e:
                # JIKA SL GAGAL: Panic Close Market!
                self.client.futures_create_order(
                    symbol=symbol, side=opp_side, type='MARKET', 
                    quantity=format(qty, 'f'), reduceOnly=True
                )
                if symbol in self.simulated_trades:
                    del self.simulated_trades[symbol]
                self.last_msg = f"❌ SL Fail: Emergency Closed {symbol}"
                return None, 0

            # Update data final di memori
            self.simulated_trades[symbol].update({
                'tp': final_tp, 'sl': final_sl, 'state': 'MONITORING'
            })
            
            return res, entry_p

        except Exception as e:
            self.last_msg = f"❌ API ERROR: {str(e)[:40]}"
            return None, 0

    def force_close_position(self, symbol):
        """Fungsi Penyelamat: Pastikan posisi benar-benar hilang dari bursa"""
        try:
            # 1. Bersihkan semua antrian TP/SL agar tidak menghalangi Market Close
            self.client.futures_cancel_all_open_orders(symbol=symbol)
            time.sleep(0.3) 

            # 2. Cek jumlah lot yang benar-benar ada di bursa sekarang
            qty = self.get_active_qty(symbol)
            
            if qty <= 0:
                return True # Sudah tutup (mungkin kena TP/SL bursa duluan)

            # 3. Tentukan arah penutupan
            positions = self.client.futures_position_information(symbol=symbol)
            side_to_close = ""
            for p in positions:
                amt = float(p['positionAmt'])
                if amt > 0: side_to_close = "SELL"
                elif amt < 0: side_to_close = "BUY"

            if side_to_close:
                self.client.futures_create_order(
                    symbol=symbol,
                    side=side_to_close,
                    type='MARKET',
                    quantity=format(qty, 'f'),
                    reduceOnly=True
                )
                self.last_msg = f"🆘 EMERGENCY CLOSED: {symbol}"
                return True # Berhasil tutup
            
            return False
        except Exception as e:
            self.last_msg = f"❌ API ERROR CLOSE: {str(e)[:30]}"
            return False # GAGAL TUTUP: Slot jangan dilepas!

    def get_latest_df(self, symbol):
        """Helper untuk mengambil data kline terbaru 60 candle"""
        klines = self.client.futures_klines(symbol=symbol, interval=self.current_tf, limit=60)
        df = pd.DataFrame(klines, columns=['t','o','h','l','c','v','ct','qa','tc','tb','tq','i'])
        return df.apply(pd.to_numeric, axis=1)

    def send_exit_report(self, symbol, side, pnl, reason):
        try:
            current_time = time.strftime('%Y-%m-%d %H:%M:%S')
            status_emoji = "🟢 PROFIT" if pnl > 0 else "🔴 LOSS"
            embed_color = 3066993 if pnl > 0 else 15158332
            
            if "MANUAL" in reason:
                status_emoji = "⚠️ MANUAL CLOSE"
                embed_color = 15844367 # Kuning

            payload = {
                "embeds": [{
                    "title": f"🏁 POSITION CLOSED: {symbol}",
                    "description": f"Bot telah menutup posisi pada koin **{symbol}**.",
                    "color": embed_color,
                    "fields": [
                        {"name": "📝 Reason", "value": f"**{reason}**", "inline": True},
                        {"name": "↕️ Side", "value": f"`{side}`", "inline": True},
                        {"name": "💹 Result PnL", "value": f"**{pnl:+.2f}%**", "inline": True},
                        {"name": "💰 Status", "value": f"**{status_emoji}**", "inline": True}
                    ],
                    "footer": { "text": f"🕒 {current_time} | InafaBot Exit Monitor" }
                }]
            }
            requests.post(DISCORD_WEBHOOK, json=payload)
        except:
            pass

    def round_step_size(self, quantity, step_size):
        """Membulatkan angka sesuai presisi bursa menggunakan Decimal (Fix Float Error)"""
        from decimal import Decimal, ROUND_FLOOR
        try:
            # Ubah ke string dulu agar Decimal tidak salah baca floating point
            q = Decimal(str(quantity))
            s = Decimal(str(step_size))
            
            # Lakukan pembulatan ke bawah (Floor) sesuai step bursa
            result = q.quantize(s, rounding=ROUND_FLOOR)
            
            # Kembalikan sebagai float, namun jika nilainya sangat kecil, 
            # pastikan diformat agar tidak menjadi scientific notation (e-07)
            return float(result)
        except:
            return float(quantity)

    def get_symbol_info(self, symbol):
        """Mengambil data presisi koin (Quantity & Price)"""
        info = self.client.futures_exchange_info()
        for s in info['symbols']:
            if s['symbol'] == symbol:
                return s
        return None

    def refresh_real_positions(self):
        """Sinkronisasi Total: Memasukkan posisi manual ke radar bot setiap detik"""
        try:
            positions = self.client.futures_position_information()
            active_on_binance = {}
            
            for pos in positions:
                amt = float(pos['positionAmt'])
                symbol = pos['symbol']
                if amt != 0:
                    side = 'BUY' if amt > 0 else 'SELL'
                    active_on_binance[symbol] = {
                        'amt': abs(amt),
                        'entry': float(pos['entryPrice']),
                        'side': side
                    }

            # A. DETEKSI CLOSE: Jika di memori ada tapi di Binance hilang (Manual Close)
            for symbol in list(self.simulated_trades.keys()):
                if symbol not in active_on_binance:
                    # Ambil data terakhir untuk laporan sebelum dihapus
                    t_data = self.simulated_trades[symbol]
                    # Kita panggil monitor satu kali terakhir untuk kirim report
                    self.monitor_active_order(symbol, "FORCE_REPORT_EXIT")
                    if symbol in self.simulated_trades:
                        del self.simulated_trades[symbol]

            # B. DETEKSI OPEN BARU: Jika di Binance ada tapi di memori belum ada (Manual Open)
            for symbol, data in active_on_binance.items():
                if symbol not in self.simulated_trades:
                    self.simulated_trades[symbol] = {
                        'type': data['side'],
                        'entry': data['entry'],
                        'tp': data['entry'] * (1.02 if data['side'] == 'BUY' else 0.98), 
                        'sl': data['entry'] * (0.985 if data['side'] == 'BUY' else 1.015),
                        'pola': "MANUAL/HP-TRADE",
                        'start_time': time.time()
                    }
                    self.last_msg = f"📩 Radar: {symbol} Sync Active"
        except Exception as e:
            self.last_msg = f"Sync Error: {str(e)[:20]}"

    def record_virtual_trade(self, symbol, trade, exit_price, reason):
        # Hitung PnL
        pnl_pct = (exit_price - trade['entry']) / trade['entry'] * 100 if trade['type'] == 'BUY' else (trade['entry'] - exit_price) / trade['entry'] * 100
        
        # Hitung Nominal USD (Contoh: Risk $100 per trade)
        risk_amount = 100 
        pnl_usd = risk_amount * (pnl_pct / 100)
        self.virtual_balance += pnl_usd
        
        # Styling Log Riwayat
        color = "\033[92m" if pnl_usd >= 0 else "\033[91m"
        reset = "\033[0m"
        icon = "💰" if pnl_usd >= 0 else "💸"
        
        # PRINT RIWAYAT KE TERMINAL
        print(f"\n{icon} [CLOSED] {symbol} | {trade['pola']} | {trade['type']}")
        print(f"   Entry: {trade['entry']:.5f} -> Exit: {exit_price:.5f}")
        print(f"   Result: {color}{pnl_pct:+.2f}% ({pnl_usd:+.2f} USD){reset} | Reason: {reason}")
        print(f"   New Balance: ${self.virtual_balance:.2f}")
        print("-" * 50)

    def monitor_active_order(self, symbol, data):
        try:
            t = self.simulated_trades.get(symbol)
            if not t: return "🔍 SCANNING..."

            curr_p = float(data['price'])
            df = data.get('df')
            
            pnl_pct = (curr_p - t['entry']) / t['entry'] * 100 if t['type'] == 'BUY' else (t['entry'] - curr_p) / t['entry'] * 100
            
            # Deteksi Otomatis Mode
            is_bt = getattr(self, 'is_backtest_mode', False)
            # Jika backtest, paksa sec=0 agar cek setiap candle close
            sec = data.get('backtest_sec', time.localtime().tm_sec)
            
            pola_asal = t.get('pola', 'UNKNOWN')
            
            # Hitung PnL Berjalan (%)
            
            # =========================================================
            # TAHAP 1: EMERGENCY EXIT (SPESIFIK PER POLA)
            # =========================================================
            # Cek hanya saat candle baru saja Close (Awal menit)
            if sec <= 1 and df is not None and len(df) >= 3:
                last_c = df.iloc[-1]      # Candle yang baru saja Close
                ema9_now = float(last_c.get('ema9', 0))
                ema21_now = float(last_c.get('ema21', 0))
                close_p = float(last_c['Close'])
                
                exit_now = False
                reason = ""

                # --- 1. LOGIKA EXIT BULL/BEAR FLAG ---
                if "FLAG" in pola_asal:
                    # Exit jika candle close di bawah EMA9 (Momentum Patah)
                    if t['type'] == 'BUY' and last_c['Close'] < last_c['ema9']:
                        exit_now, reason = True, "PWR-FLAG FAIL: Momentum Lost (EMA9)"

                # --- 2. LOGIKA EXIT DOUBLE TOP / BOTTOM ---
                elif "DBL" in pola_asal:
                    # Gagal jika harga balik ke area entry (Fakeout)
                    if pola_asal == "DBL-TOP" and close_p > t['entry'] * 1.005:
                        exit_now, reason = True, "DBL-TOP FAIL: Upper Fakeout"
                    elif pola_asal == "DBL-BOTTOM" and close_p < t['entry'] * 0.995:
                        exit_now, reason = True, "DBL-BOT FAIL: Lower Fakeout"

                # --- 3. LOGIKA EXIT HEAD & SHOULDERS ---
                elif "H&S" in pola_asal:
                    # 1. TIME DECAY: Jika sudah 10 candle (10 menit) tapi PnL masih minus atau < 0.1%
                    # Cabut segera! Scalping H&S harus cepat meledak.
                    if bars_held > 10 and pnl_pct < 0.1:
                        self.record_virtual_trade(symbol, t, curr_p, "H&S FAIL: No Momentum")
                        if symbol in self.simulated_trades: del self.simulated_trades[symbol]
                        return "🏁 CUT: STAGNANT"

                    # 2. REJECTION EXIT: Jika candle close balik menembus Neckline (SL Mental)
                    if sec <= 1:
                        # Untuk SELL, neckline_l biasanya adalah entry - sedikit jarak
                        if t['type'] == 'SELL' and last_c['Close'] > t['entry'] * 1.002:
                            self.record_virtual_trade(symbol, t, last_c['Close'], "H&S FAIL: Neckline Re-entry")
                            if symbol in self.simulated_trades: del self.simulated_trades[symbol]
                            return "🏁 EMG: RE-ENTRY"

                # --- 4. LOGIKA EXIT SCALPING (CONT/REV) ---
                elif "CONT" in pola_asal or "REV" in pola_asal:
                    # Scalping butuh momentum, jika EMA9 ditembus lawan arah, cabut!
                    if t['type'] == 'BUY' and close_p < ema9_now:
                        exit_now, reason = True, "SCALP FAIL: Momemtum Lost"
                    elif t['type'] == 'SELL' and close_p > ema9_now:
                        exit_now, reason = True, "SCALP FAIL: Momentum Lost"

                # --- EKSEKUSI EMERGENCY ---
                if exit_now:
                    if is_bt:
                        # Di Backtest, gunakan harga Close candle tersebut sebagai exit_p
                        self.record_virtual_trade(symbol, t, close_p, reason)
                        if symbol in self.simulated_trades: del self.simulated_trades[symbol]
                    else:
                        self.force_close_position(symbol)
                    return f"🏁 {reason}"

            # =========================================================
            # TAHAP 2: REAL-TIME WICK PROTECTION (TP / SL JAUH)
            # =========================================================
            is_tp = (t['type'] == 'BUY' and curr_p >= t['tp']) or (t['type'] == 'SELL' and curr_p <= t['tp'])
            is_sl = (t['type'] == 'BUY' and curr_p <= t['sl']) or (t['type'] == 'SELL' and curr_p >= t['sl'])

            if is_tp or is_sl:
                f_reason = "TAKE PROFIT 🎯" if is_tp else "STOP LOSS (WICK) ❌"
                if is_bt:
                    self.record_virtual_trade(symbol, t, curr_p, f_reason)
                    if symbol in self.simulated_trades: del self.simulated_trades[symbol]
                else:
                    self.force_close_position(symbol)
                return f"🏁 {f_reason}"

            # =========================================================
            # TAHAP 3: BREAKEVEN & TRAILING STATUS
            # =========================================================
            # Geser SL ke entry + sedikit profit jika harga sudah naik 0.3%
            if pnl_pct > 0.30 and t.get('state') != "SECURED 🛡️":
                t['sl'] = t['entry'] * (1.0002 if t['type'] == 'BUY' else 0.9998)
                t['state'] = "SECURED 🛡️"

            # Dashboard Display
            color = "\033[92m" if pnl_pct >= 0 else "\033[91m"
            reset = "\033[0m"
            
            # Loader sekarang menampilkan: SIMBOL | POLA | SIDE | PNL%
            return f"{symbol} [{t['pola']}] {t['type']} | {color}{pnl_pct:+.2f}%{reset}"

        except Exception as e:
            return f"Err Mon: {str(e)[:15]}"

    def send_main_exit_report(self, symbol, trade_data, exit_p, pnl_calc, reason):
        """Laporan Detail Penutupan untuk Webhook Utama"""
        try:
            is_profit = pnl_calc >= 0
            color = 3066993 if is_profit else 15158332
            
            payload = {
                "embeds": [{
                    "title": f"🏁 CLOSED: {symbol}",
                    "description": f"Posisi telah ditutup sepenuhnya.",
                    "color": color,
                    "fields": [
                        {"name": "🚪 Alasan Keluar", "value": f"**{reason}**", "inline": True},
                        {"name": "↕️ Side", "value": f"`{trade_data['type']}`", "inline": True},
                        {"name": "📊 Result", "value": f"**{pnl_calc:+.2f}%**", "inline": True},
                        {"name": "💵 Entry Price", "value": f"`{trade_data['entry']:.6f}`", "inline": True},
                        {"name": "🚪 Exit Price", "value": f"`{exit_p:.6f}`", "inline": True},
                        {"name": "🤖 Strategy", "value": f"`{trade_data.get('pola', 'UNKNOWN')}`", "inline": True}
                    ],
                    "footer": {"text": f"InafaBot Main Monitor | {time.strftime('%H:%M:%S')}"}
                }]
            }
            requests.post(self.webhook_url, json=payload)
        except:
            pass

    def send_emergency_exit_report(self, symbol, trade_data, exit_p, pnl_calc, reason):
        try:
            payload = {
                "embeds": [{
                    "title": f"⚠️ EMERGENCY CLOSE: {symbol}",
                    "description": f"Bot mendeteksi pola/struktur gagal. Posisi ditutup manual untuk keamanan.",
                    "color": 16753920, # Oranye
                    "fields": [
                        {"name": "📝 Alasan Gagal", "value": f"**{reason}**", "inline": False},
                        {"name": "📊 PnL Terakhir", "value": f"**{pnl_calc:+.2f}%**", "inline": True},
                        {"name": "💡 Strategi", "value": f"`{trade_data.get('pola')}`", "inline": True},
                        {"name": "🚪 Exit Price", "value": f"`{exit_p:.6f}`", "inline": True}
                    ],
                    "footer": {"text": f"InafaBot Risk Guard | {time.strftime('%H:%M:%S')}"}
                }]
            }
            requests.post(self.webhook_url, json=payload)
        except: pass

    def get_active_qty(self, symbol):
        """Helper untuk mendapatkan jumlah lot yang benar saat mau close force"""
        try:
            positions = self.client.futures_position_information(symbol=symbol)
            for pos in positions:
                if pos['symbol'] == symbol:
                    return abs(float(pos['positionAmt']))
            return 0
        except:
            return 0

    def send_close_report(self, symbol, trade_data, exit_p, pnl_calc, reason):
        try:
            time.sleep(2) # Jeda sinkronisasi bursa
            total_h, usdt_h = self.get_balance()
            
            # Ambil Realized PnL (Angka pasti dari Binance)
            trades = self.client.futures_account_trades(symbol=symbol, limit=5)
            real_pnl = next((float(t['realizedPnl']) for t in reversed(trades) if float(t['realizedPnl']) != 0), 0.0)

            # --- LOGIKA PENANDA TPSL ---
            pnl_pct = (real_pnl / total_h) * 100 if total_h > 0 else 0
            abs_pnl = abs(pnl_pct)
            
            if abs_pnl < 0.5:
                size_label = "🔹 KECIL"
            elif 0.5 <= abs_pnl < 2.0:
                size_label = "🔸 SEDANG"
            else:
                size_label = "🔥 BESAR"

            # Setup Visual
            is_profit = real_pnl >= 0
            color = 3066993 if is_profit else 15158332
            emoji = "🟢" if is_profit else "🔴"
            pola = trade_data.get('pola', "MANUAL")

            payload = {
                "embeds": [{
                    "title": f"{emoji} {real_pnl:+.4f} USDT | {symbol}",
                    "description": f"Skala: **{size_label}** ({pnl_pct:+.2f}%)",
                    "color": color,
                    "fields": [
                        {"name": "📊 Strategy & Side", "value": f"`{pola}` • **{trade_data['type']}**", "inline": False},
                        {"name": "🚪 Reason", "value": f"**{reason}**", "inline": True},
                        {"name": "🕒 Time", "value": f"`{time.strftime('%H:%M:%S')}`", "inline": True},
                        {"name": "📏 Price Levels", "value": f"Entry: `{trade_data['entry']:.5f}`\nExit: `{exit_p:.5f}`", "inline": False},
                        {"name": "🏦 Balance Snapshot", "value": f"Total: **${total_h:,.2f}**\nAvail: **${usdt_h:,.2f}**", "inline": False}
                    ],
                    "footer": {"text": "InafaBot Tracker"},
                    "timestamp": datetime.datetime.utcnow().isoformat()
                }]
            }
            requests.post(PNL_WEBHOOK, json=payload)
        except Exception as e:
            print(f"Error: {e}")


    def get_active_qty(self, symbol):
        """Mendapatkan jumlah lot posisi yang sedang berjalan di Binance secara Real-time"""
        try:
            positions = self.client.futures_position_information(symbol=symbol)
            for pos in positions:
                if pos['symbol'] == symbol:
                    # Ambil angka absolut karena short position nilainya negatif
                    return abs(float(pos['positionAmt']))
            return 0
        except Exception as e:
            self.last_msg = f"⚠️ Gagal ambil Qty: {str(e)[:20]}"
            return 0

    def send_discord_report(self, symbol, side, pola, entry, tp, sl, df):
        try:
            # 1. Pastikan Data adalah Float Murni (Mencegah Error Matplotlib)
            entry, tp, sl = float(entry), float(tp), float(sl)
            
            if df is None or len(df) < 5:
                # Jika DF kosong, kirim teks saja tanpa chart
                self.send_text_only_discord(symbol, side, pola, entry, tp, sl)
                return

            # 2. Setup Grafik (Dark Mode)
            plt.figure(figsize=(10, 6))
            plt.style.use('dark_background')
            
            # Ambil 30 candle terakhir
            plot_df = df.tail(30).reset_index()
            
            # Gambar Candlestick Manual
            for i in range(len(plot_df)):
                color = '#26a69a' if plot_df.loc[i, 'c'] >= plot_df.loc[i, 'o'] else '#ef5350'
                plt.vlines(i, plot_df.loc[i, 'l'], plot_df.loc[i, 'h'], color=color, linewidth=1)
                plt.vlines(i, min(plot_df.loc[i, 'o'], plot_df.loc[i, 'c']), 
                           max(plot_df.loc[i, 'o'], plot_df.loc[i, 'c']), color=color, linewidth=6)

            # Garis Harga
            plt.axhline(y=entry, color='white', linestyle='--', alpha=0.6)
            plt.axhline(y=tp, color='#26a69a', linestyle='-', alpha=0.8)
            plt.axhline(y=sl, color='#ef5350', linestyle='-', alpha=0.8)
            
            plt.title(f"{symbol} | {pola} | {side}", color='white', fontsize=12)
            
            # Simpan ke Buffer
            buf = io.BytesIO()
            plt.savefig(buf, format='png', bbox_inches='tight')
            buf.seek(0)
            plt.close()

            # 3. Payload Discord
            payload = {
                "embeds": [{
                    "title": f"🚀 NEW SIGNAL: {symbol}",
                    "color": 3066993 if side == "BUY" else 15158332,
                    "fields": [
                        {"name": "📈 Pattern", "value": f"`{pola}`", "inline": True},
                        {"name": "↕️ Side", "value": f"**{side}**", "inline": True},
                        {"name": "💰 Entry", "value": f"`{entry:.6f}`", "inline": True},
                        {"name": "🎯 Target TP", "value": f"`{tp:.6f}`", "inline": True},
                        {"name": "🛑 Stop Loss", "value": f"`{sl:.6f}`", "inline": True}
                    ],
                    "footer": { "text": f"InafaBot Pro Visualizer | {time.strftime('%H:%M:%S')}" }
                }]
            }
            
            files = {'file': ('chart.png', buf, 'image/png')}
            requests.post(self.webhook_url, data={'payload_json': json.dumps(payload)}, files=files)
            
        except Exception as e:
            # BACKUP: Jika chart gagal, kirim teks saja agar Hajir tetap tahu ada order
            self.last_msg = f"⚠️ Discord Visual Error: {str(e)[:20]}"
            self.send_text_only_discord(symbol, side, pola, entry, tp, sl)

    def send_text_only_discord(self, symbol, side, pola, entry, tp, sl):
        """Fungsi cadangan jika Matplotlib/Gambar error"""
        try:
            msg = f"🚀 **{symbol} {side}**\nPattern: `{pola}`\nEntry: `{entry}`\nTP: `{tp}`\nSL: `{sl}`"
            requests.post(self.webhook_url, json={"content": msg})
        except:
            pass

    def check_rotation(self):
        """
        Sinkronisasi Tampilan Dashboard:
        1. Prioritas Utama: Koin yang sedang OPEN POSITION (Bot/Manual HP)
        2. Prioritas Kedua: Koin Whitelist (Favorit)
        3. Prioritas Ketiga: Koin Volatil (Paling Panas di Market)
        """
        try:
            # --- 1. AMBIL KOIN YANG SEDANG ADA POSISI (REAL-TIME) ---
            # Kita ambil dari memori simulated_trades yang sudah diupdate oleh refresh_real_positions
            active_symbols = list(self.simulated_trades.keys())
            
            # --- 2. SIAPKAN LIST DASHBOARD ---
            # Mulai dengan koin yang ada posisi agar SELALU terpantau di baris atas
            final_display_list = active_symbols.copy()
            
            # Tambahkan koin Whitelist (Favorit) jika belum ada di list
            for fav in self.whitelist:
                if fav not in final_display_list:
                    final_display_list.append(fav)
            
            # --- 3. ISI SLOT KOSONG DENGAN KOIN VOLATIL (ROTASI) ---
            # Kita targetkan dashboard menampilkan maksimal 8-10 koin agar tidak berantakan
            max_dashboard_rows = 10
            
            if len(final_display_list) < max_dashboard_rows:
                # Ambil data ticker dari Binance untuk mencari koin "Gainer/Loser" terbesar
                tickers = self.client.futures_ticker()
                # Sortir berdasarkan persentase perubahan harga absolut (volatilitas)
                sorted_tickers = sorted(tickers, key=lambda x: abs(float(x['priceChangePercent'])), reverse=True)
                
                for t in sorted_tickers:
                    symbol = t['symbol']
                    # Syarat: Berakhiran USDT, bukan koin yang sudah tampil, dan bukan koin 'aneh'
                    if symbol.endswith("USDT") and symbol not in final_display_list:
                        # Hindari koin yang baru saja di-rotate (opsional, untuk stabilitas mata)
                        if symbol not in self.last_rotated_coins:
                            final_display_list.append(symbol)
                    
                    # Jika sudah mencapai batas baris dashboard, stop mencari
                    if len(final_display_list) >= max_dashboard_rows:
                        break

            # --- 4. UPDATE LIST PANTUAN AKTIF ---
            self.active_watch = final_display_list
            
            # Simpan koin rotasi saat ini ke history agar rotasi berikutnya ganti koin lain
            # (Hanya simpan koin yang bukan Whitelist dan bukan Posisi Aktif)
            current_rotations = [s for s in final_display_list if s not in self.whitelist and s not in active_symbols]
            if current_rotations:
                self.last_rotated_coins = current_rotations[-5:] # Simpan 5 terakhir

        except Exception as e:
            # Jika API Binance lemot/error, jangan biarkan dashboard kosong
            if not self.active_watch:
                self.active_watch = self.whitelist
            self.last_msg = f"⚠️ Rotation Delay: {str(e)[:20]}"

    def check_position_limit(self, symbol, quantity, price):
        """Memastikan order tidak melebihi batas Notional atau Max Position koin"""
        try:
            # Ambil data bracket leverage untuk koin spesifik
            brackets = self.client.futures_leverage_bracket(symbol=symbol)
            # Biasanya bracket[0] adalah tier tertinggi (leverage max)
            max_notional = float(brackets[0]['brackets'][0]['notionalCap'])
            
            order_notional = quantity * price
            if order_notional > (max_notional * 0.9): # Beri buffer 10%
                self.last_msg = f"⚠️ Order {symbol} ditolak: Melebihi limit bursa."
                return False
            return True
        except:
            return True # Jika API error, asumsikan aman (default)

    def run(self):
        # Tambahkan import pandas di paling atas file: import pandas as pd
        print(f"--- 🧠 INAFATRADE ANALYZER ACTIVE ---")
        try:
            while True:
                output_lines = []
                for coin in self.whitelist:
                    chart, _, current_price = self.get_candle_chart(coin)
                    trend, t_col, dur, hi, lo, res, sup = self.get_analysis_data(coin)
                    
                    if current_price > 0:
                        # Tampilan Baris Tunggal yang Sangat Lengkap
                        line = (f"{coin:10} | {current_price:<9} | {t_col}{trend} ({dur}m)\033[0m | "
                                f"R:{res:<8} S:{sup:<8} | {chart}")
                        output_lines.append(line)
                
                print("\033[H", end="") 
                print(f"🚀 INAFATRADE {'DEMO' if self.is_demo else 'REAL'} | Slots: {len(self.active_positions)}/{self.max_slots}")
                print("-" * 80)
                for l in output_lines:
                    print(l + "      ")
                
                time.sleep(2)
        except KeyboardInterrupt:
            print("\n\n👋 Analisa berhenti. Sampai jumpa, Hajir!")

if __name__ == "__main__":
    # 1. Inisialisasi Bot
    bot = InafaBot(MY_API_KEY, MY_API_SECRET, is_demo=USE_DEMO)
    
    # --- PENGATURAN MODE ---
    # Ubah ke True jika ingin testing data 1 tahun
    # Ubah ke False jika ingin trading real-time (Live)
    bot.is_backtest_mode = False 

    if bot.is_backtest_mode:
        print("🛠️ MODE BACKTEST AKTIF - Memulai simulasi historis...")
        # Pilih koin yang mau di-test (misal koin pertama di whitelist)
        target_coin = WHITELIST_COINS[0] 
        bot.run_backtest(target_coin)
    else:
        print("🚀 MODE LIVE AKTIF - Sinkronisasi posisi...")
        bot.sync_existing_orders()
        bot.display_loop()