import requests
import json
import os
import time
from datetime import datetime

LEGACY_AUTOTRADER_DISABLED_MESSAGE = "Legacy AutoTrader disabled; use src/tools/preflight.py and v76 executors"
if os.getenv("CTB_ALLOW_LEGACY_AUTOTRADER", "").lower() != "true":
    print(LEGACY_AUTOTRADER_DISABLED_MESSAGE)
    raise SystemExit(2)

from dotenv import load_dotenv
import eth_account
from hyperliquid.utils import constants
from hyperliquid.exchange import Exchange

# --- DEINE NEUEN CIO-PARAMETER (V2) ---
STATE_FILE = "portfolio_state.json"
COOLDOWN_FILE = "cooldowns.json" # Speichert die 6h-Sperren
LOG_FILE = "trading_log.txt"

MAX_TRADES = 5
TRADE_AMOUNT_USD = 30.0
TRAILING_STOP_PCT = 0.020  # CIO-Update: Von 1.5% auf 3.5% erhöht (Gegen Zersägen)
LOOP_INTERVAL = 10 

# Sniper-Parameter (Kurzzeit-Gedächtnis)
MEMORY_SECONDS = 300       # Beobachtungsfenster: 5 Minuten
SNIPER_TRIGGER_PCT = 0.015 # CIO-Update: Von 1.0% auf 1.6% erhöht (Härterer Filter)

# NEU: Die Rüstung gegen Illiquidität
MIN_VOLUME_24H = 2000000   # Min. 2 Mio. USD Tagesvolumen
BLACKLIST = ["FTT", "HEMI", "LUNA", "USTC"] # Toxische/Illiquide Coins ignorieren
COOLDOWN_HOURS = 6         # Sperrzeit nach einem Stop-Loss

# --- BÖRSE INITIALISIEREN ---
load_dotenv()
secret_key = os.getenv("HL_API_PRIVATE_KEY")
if not secret_key:
    print("FEHLER: HL_API_PRIVATE_KEY nicht gefunden.")
    exit()

account = eth_account.Account.from_key(secret_key)
exchange = Exchange(account, constants.MAINNET_API_URL)

def log_action(msg):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(line + "\n")

def load_json(file_path):
    if os.path.exists(file_path):
        try:
            with open(file_path, "r") as f: return json.load(f)
        except Exception: return {}
    return {}

def save_json(file_path, data):
    with open(file_path, "w") as f: json.dump(data, f, indent=4)

def get_market_data():
    url = "https://api.hyperliquid.xyz/info"
    try:
        r = requests.post(url, headers={"Content-Type": "application/json"}, json={"type": "metaAndAssetCtxs"})
        r.raise_for_status()
        return r.json()
    except Exception as e:
        log_action(f"❌ API-Fehler: {e}")
        return None

def main():
    log_action("🚀 Sniper V2 gestartet (Big-Player-Filter & Cooldowns aktiv)...")
    price_history = {} 
    
    while True:
        try:
            current_ts = time.time()
            data = get_market_data()
            
            if not data: 
                time.sleep(LOOP_INTERVAL)
                continue

            universe = data[0]["universe"]
            asset_ctxs = data[1]
            
            prices, volumes = {}, {}
            sniper_signals = {}
            
            for i, coin_info in enumerate(universe):
                name = coin_info["name"]
                ctx = asset_ctxs[i]
                curr_px = float(ctx["markPx"])
                vol_24h = float(ctx["dayNtlVlm"])
                
                prices[name] = curr_px
                volumes[name] = vol_24h
                
                if name not in price_history:
                    price_history[name] = []
                
                price_history[name] = [x for x in price_history[name] if current_ts - x[0] <= MEMORY_SECONDS]
                price_history[name].append((current_ts, curr_px))
                
                if len(price_history[name]) > 0:
                    oldest_px = price_history[name][0][1]
                    if oldest_px > 0:
                        st_change = (curr_px - oldest_px) / oldest_px
                        sniper_signals[name] = st_change
                    else:
                        sniper_signals[name] = 0

            state = load_json(STATE_FILE)
            cooldowns = load_json(COOLDOWN_FILE)
            
            # --- TRAILING STOP ÜBERWACHEN ---
            closed_coins = []
            for coin, t_data in state.items():
                curr_px = prices.get(coin, 0)
                if curr_px == 0: continue
                
                direction = t_data.get("direction", "long")
                entry_px = t_data["entry_px"]
                size = t_data["size"]
                
                if direction == "long":
                    if curr_px > t_data["high_px"]: state[coin]["high_px"] = curr_px
                    stop_price = state[coin]["high_px"] * (1 - TRAILING_STOP_PCT)
                    if curr_px <= stop_price:
                        try:
                            exchange.market_open(coin, False, size, None, 0.05)
                            profit_pct = ((curr_px - entry_px) / entry_px) * 100
                            log_action(f"🛑 VERKAUF LONG: {coin} bei ${curr_px:.4f} | G/V: {profit_pct:+.2f}%")
                            closed_coins.append(coin)
                            cooldowns[coin] = current_ts # Coin in den Cooldown schicken
                        except Exception as e:
                            log_action(f"❌ Fehler beim Verkauf von {coin}: {e}")
                            
                elif direction == "short":
                    if curr_px < t_data["low_px"]: state[coin]["low_px"] = curr_px
                    stop_price = state[coin]["low_px"] * (1 + TRAILING_STOP_PCT)
                    if curr_px >= stop_price:
                        try:
                            exchange.market_open(coin, True, size, None, 0.05)
                            profit_pct = ((entry_px - curr_px) / entry_px) * 100
                            log_action(f"🛑 COVER SHORT: {coin} bei ${curr_px:.4f} | G/V: {profit_pct:+.2f}%")
                            closed_coins.append(coin)
                            cooldowns[coin] = current_ts # Coin in den Cooldown schicken
                        except Exception as e:
                            log_action(f"❌ Fehler beim Covern von {coin}: {e}")
            
            for c in closed_coins: 
                del state[c]
                
            save_json(STATE_FILE, state)
            save_json(COOLDOWN_FILE, cooldowns)

            # --- NEUE TRADES SUCHEN (SNIPER V2 MODUS) ---
            open_slots = MAX_TRADES - len(state)
            if open_slots > 0:
                opportunities = []
                for coin, st_change in sniper_signals.items():
                    # NEUE FILTER: Bereits offen? Zu wenig Volumen? Auf Blacklist?
                    if coin in state or volumes[coin] < MIN_VOLUME_24H or coin in BLACKLIST: 
                        continue
                    
                    # COOLDOWN CHECK: Ist der Coin noch gesperrt?
                    if coin in cooldowns:
                        time_passed = current_ts - cooldowns[coin]
                        if time_passed < (COOLDOWN_HOURS * 3600):
                            continue # Sperre noch aktiv, überspringen!
                    
                    if len(price_history[coin]) < (60 / LOOP_INTERVAL): continue 
                    
                    if st_change >= SNIPER_TRIGGER_PCT:
                        opportunities.append({"coin": coin, "change": st_change, "vol": volumes[coin], "px": prices[coin], "dir": "long"})
                    elif st_change <= -SNIPER_TRIGGER_PCT:
                        opportunities.append({"coin": coin, "change": st_change, "vol": volumes[coin], "px": prices[coin], "dir": "short"})
                
                opportunities = sorted(opportunities, key=lambda x: abs(x["change"]), reverse=True)
                
                for opp in opportunities[:open_slots]:
                    coin = opp["coin"]
                    px = opp["px"]
                    direction = opp["dir"]
                    size = max(1, int(TRADE_AMOUNT_USD / px))
                    
                    try:
                        if direction == "long":
                            exchange.market_open(coin, True, size, None, 0.05)
                            log_action(f"🎯 V2 KAUF LONG: {coin} | Preis: ${px:.4f} | Vol: ${opp['vol']/1000000:.1f}M | Zündung: +{opp['change']*100:.2f}%")
                            state[coin] = {"entry_px": px, "high_px": px, "low_px": px, "size": size, "direction": "long"}
                        else:
                            exchange.market_open(coin, False, size, None, 0.05)
                            log_action(f"🎯 V2 SELL SHORT: {coin} | Preis: ${px:.4f} | Vol: ${opp['vol']/1000000:.1f}M | Absturz: {opp['change']*100:.2f}%")
                            state[coin] = {"entry_px": px, "high_px": px, "low_px": px, "size": size, "direction": "short"}
                    except Exception as e:
                        log_action(f"❌ API Order-Fehler bei {coin}: {e}")
            
            save_json(STATE_FILE, state)
            
        except Exception as e:
            log_action(f"⚠️ Warnung in der Hauptschleife: {e}")
        
        time.sleep(LOOP_INTERVAL)

if __name__ == "__main__":
    main()
