#!/usr/bin/env python3
"""
V9 Paper Trading Bot
====================

Connects to V9 Inference Server and executes paper trades.
Uses 4-hour timeframe with Dual Model architecture.

Features:
- Volatility Gate filtering
- Trend detection for entry
- Exit Optimizer for optimal exit timing
- Saves trades to database
"""

import time
import json
import signal
import sys
import requests
from datetime import datetime, timedelta
from pathlib import Path
import psycopg2

# Configuration
CONFIG = {
    'inference_url': 'http://localhost:3060',
    'pair': 'BTC/EUR',
    'initial_capital': 10000,
    'position_size': 0.20,  # 20% per trade
    'check_interval': 60,   # Check every minute
    'candle_interval': 240, # 4h candles (minutes)
    'stop_loss': 0.02,      # 2% stop loss
    'take_profit': 0.03,    # 3% take profit
    'fee_rate': 0.005,      # 0.5% fee per side
}

DB_CONFIG = {
    'host': 'localhost',
    'port': 5432,
    'dbname': 'bestrading',
    'user': 'bestrading',
    'password': 'UQyvjfZIvUtpqlksPfKeq2MmXgGiG3y5'
}

STATE_FILE = Path('/var/www/html/bestrading.cuttalo.com/models/btc_v9/paper_state.json')
LOG_FILE = Path('/var/www/html/bestrading.cuttalo.com/models/btc_v9/paper_trades.log')

# Global state
running = True
state = {
    'capital': CONFIG['initial_capital'],
    'position': 0,  # 0=flat, 1=long, -1=short
    'btc_amount': 0.0,
    'entry_price': 0.0,
    'entry_time': None,
    'trades': [],
    'last_candle_time': None,
}


def signal_handler(sig, frame):
    global running
    print("\n[!] Shutting down paper trader...")
    save_state()
    running = False


def log(msg):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    log_line = f"[{timestamp}] {msg}"
    print(log_line)
    with open(LOG_FILE, 'a') as f:
        f.write(log_line + '\n')


def save_state():
    with open(STATE_FILE, 'w') as f:
        json.dump(state, f, indent=2, default=str)
    log(f"State saved. Capital: €{state['capital']:.2f}")


def load_state():
    global state
    if STATE_FILE.exists():
        try:
            with open(STATE_FILE) as f:
                loaded = json.load(f)
                state.update(loaded)
                log(f"State loaded. Capital: €{state['capital']:.2f}, Position: {state['position']}")
        except Exception as e:
            log(f"Error loading state: {e}")


def get_current_price():
    """Get current BTC/EUR price."""
    try:
        resp = requests.get('https://api.binance.com/api/v3/ticker/price?symbol=BTCEUR', timeout=5)
        if resp.ok:
            return float(resp.json()['price'])
    except:
        pass
    return 0


def update_inference_price(price: float):
    """Send price to inference server."""
    try:
        resp = requests.post(
            f"{CONFIG['inference_url']}/update-price",
            json={'price': price},
            timeout=5
        )
        return resp.json()
    except Exception as e:
        log(f"Error updating price: {e}")
        return None


def get_entry_signal():
    """Get entry signal from inference server."""
    try:
        resp = requests.get(f"{CONFIG['inference_url']}/entry-signal", timeout=10)
        return resp.json()
    except Exception as e:
        log(f"Error getting entry signal: {e}")
        return None


def check_exit(current_price: float):
    """Check exit signal from inference server."""
    try:
        resp = requests.post(
            f"{CONFIG['inference_url']}/check-exit",
            json={'current_price': current_price},
            timeout=10
        )
        return resp.json()
    except Exception as e:
        log(f"Error checking exit: {e}")
        return None


def notify_open_position(direction: int, price: float):
    """Notify inference server of position open."""
    try:
        requests.post(
            f"{CONFIG['inference_url']}/open-position",
            json={'direction': direction, 'price': price},
            timeout=5
        )
    except:
        pass


def notify_close_position():
    """Notify inference server of position close."""
    try:
        requests.post(f"{CONFIG['inference_url']}/close-position", timeout=5)
    except:
        pass


def save_trade_to_db(trade: dict):
    """Save trade to database."""
    try:
        conn = psycopg2.connect(**DB_CONFIG)
        cur = conn.cursor()

        cur.execute("""
            INSERT INTO paper_trades_v9 (
                pair, action, direction, entry_price, exit_price,
                btc_amount, pnl, capital_after, timestamp
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
        """, (
            CONFIG['pair'],
            trade['action'],
            trade.get('direction', ''),
            trade.get('entry_price', 0),
            trade.get('exit_price', 0),
            trade.get('btc_amount', 0),
            trade.get('pnl', 0),
            state['capital'],
            datetime.now()
        ))

        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        log(f"DB Error: {e}")


def ensure_db_table():
    """Create paper_trades_v9 table if not exists."""
    try:
        conn = psycopg2.connect(**DB_CONFIG)
        cur = conn.cursor()

        cur.execute("""
            CREATE TABLE IF NOT EXISTS paper_trades_v9 (
                id SERIAL PRIMARY KEY,
                pair VARCHAR(20),
                action VARCHAR(20),
                direction VARCHAR(10),
                entry_price DECIMAL(20, 8),
                exit_price DECIMAL(20, 8),
                btc_amount DECIMAL(20, 8),
                pnl DECIMAL(20, 8),
                capital_after DECIMAL(20, 2),
                timestamp TIMESTAMP DEFAULT NOW()
            )
        """)

        conn.commit()
        cur.close()
        conn.close()
        log("Database table paper_trades_v9 ready")
    except Exception as e:
        log(f"DB table error: {e}")


def execute_entry(direction: int, price: float, confidence: float):
    """Execute entry trade."""
    trade_value = state['capital'] * CONFIG['position_size']
    fee = trade_value * 0.0025  # Half of 0.5% (entry only)

    if direction == 1:  # Long
        slippage = price * 0.0003
        exec_price = price + slippage
        btc_amount = (trade_value - fee) / exec_price
        state['btc_amount'] = btc_amount
        dir_str = 'LONG'
    else:  # Short
        slippage = price * 0.0003
        exec_price = price - slippage
        btc_amount = trade_value / exec_price
        state['btc_amount'] = btc_amount
        dir_str = 'SHORT'

    state['capital'] -= trade_value
    state['position'] = direction
    state['entry_price'] = exec_price
    state['entry_time'] = datetime.now().isoformat()

    notify_open_position(direction, exec_price)

    trade = {
        'action': 'OPEN',
        'direction': dir_str,
        'entry_price': exec_price,
        'btc_amount': btc_amount,
    }
    state['trades'].append(trade)
    save_trade_to_db(trade)

    log(f"{'📈' if direction == 1 else '📉'} {dir_str} {btc_amount:.6f} BTC @ €{price:,.2f} | Confidence: {confidence:.1%} | Capital: €{state['capital']:,.2f}")


def execute_exit(price: float, reason: str):
    """Execute exit trade."""
    direction = state['position']
    fee = state['btc_amount'] * price * 0.0025  # Half of 0.5% (exit only)

    if direction == 1:  # Close long
        slippage = price * 0.0003
        exec_price = price - slippage
        proceeds = state['btc_amount'] * exec_price - fee
        pnl = proceeds - (state['btc_amount'] * state['entry_price'])
        state['capital'] += proceeds
    else:  # Close short
        slippage = price * 0.0003
        exec_price = price + slippage
        cost = state['btc_amount'] * exec_price + fee
        original_value = state['btc_amount'] * state['entry_price']
        pnl = original_value - cost
        state['capital'] += original_value + pnl

    notify_close_position()

    trade = {
        'action': 'CLOSE',
        'direction': 'LONG' if direction == 1 else 'SHORT',
        'entry_price': state['entry_price'],
        'exit_price': exec_price,
        'btc_amount': state['btc_amount'],
        'pnl': pnl,
        'reason': reason,
    }
    state['trades'].append(trade)
    save_trade_to_db(trade)

    emoji = '✅' if pnl > 0 else '❌'
    log(f"{emoji} CLOSE {'LONG' if direction == 1 else 'SHORT'} ({reason}) @ €{price:,.2f} | PnL: €{pnl:+.2f} | Capital: €{state['capital']:,.2f}")

    # Reset state
    state['position'] = 0
    state['btc_amount'] = 0
    state['entry_price'] = 0
    state['entry_time'] = None


def main_loop():
    """Main trading loop."""
    global running

    log("=" * 60)
    log("V9 PAPER TRADING BOT - DUAL MODEL SYSTEM")
    log("=" * 60)
    log(f"Pair: {CONFIG['pair']}")
    log(f"Capital: €{state['capital']:,.2f}")
    log(f"Position Size: {CONFIG['position_size']*100:.0f}%")
    log(f"Timeframe: 4 hours")
    log(f"Stop Loss: {CONFIG['stop_loss']*100:.0f}% | Take Profit: {CONFIG['take_profit']*100:.0f}%")
    log(f"Inference: {CONFIG['inference_url']}")
    log("=" * 60)

    # Wait for inference server
    log("Waiting for inference server...")
    while running:
        try:
            resp = requests.get(f"{CONFIG['inference_url']}/health", timeout=5)
            if resp.ok and resp.json().get('models_loaded'):
                log("Inference server ready!")
                break
        except:
            pass
        time.sleep(5)

    last_price_update = datetime.now()
    price_update_interval = 60  # Update price every minute

    while running:
        try:
            price = get_current_price()
            if price <= 0:
                log("Could not get price, retrying...")
                time.sleep(30)
                continue

            # Update inference server with price
            now = datetime.now()
            if (now - last_price_update).total_seconds() >= price_update_interval:
                update_result = update_inference_price(price)
                last_price_update = now

                if update_result and update_result.get('candle_completed'):
                    log(f"4h candle completed. Total: {update_result.get('total_candles', 0)}")

            # If no position, check for entry
            if state['position'] == 0:
                signal = get_entry_signal()

                if signal and signal.get('signal') in ['LONG', 'SHORT']:
                    direction = 1 if signal['signal'] == 'LONG' else -1
                    confidence = signal.get('confidence', 0)
                    execute_entry(direction, price, confidence)

                elif signal and signal.get('signal') == 'WAIT':
                    reason = signal.get('reason', '')
                    if 'Accumulating' in reason:
                        # Log progress periodically
                        candles = signal.get('candles', 0)
                        if candles % 10 == 0:
                            log(f"Building history: {candles}/50 candles")

            # If in position, check for exit
            else:
                exit_signal = check_exit(price)

                if exit_signal and exit_signal.get('action') == 'EXIT':
                    reason = exit_signal.get('reason', 'model_decision')
                    execute_exit(price, reason)

                else:
                    # Log position status periodically
                    if state['entry_price'] > 0:
                        if state['position'] == 1:
                            pnl_pct = (price - state['entry_price']) / state['entry_price'] * 100
                        else:
                            pnl_pct = (state['entry_price'] - price) / state['entry_price'] * 100

                        steps = exit_signal.get('steps_in_position', 0) if exit_signal else 0
                        max_profit = exit_signal.get('max_profit_seen', 0) * 100 if exit_signal else 0

                        # Log every 15 minutes
                        if now.minute % 15 == 0 and now.second < 60:
                            log(f"[POSITION] {'LONG' if state['position'] == 1 else 'SHORT'} | P&L: {pnl_pct:+.2f}% | Max: {max_profit:.2f}% | Steps: {steps}/12")

            # Calculate equity
            if state['position'] == 1:
                equity = state['capital'] + state['btc_amount'] * price
            elif state['position'] == -1:
                unrealized = (state['entry_price'] - price) * state['btc_amount']
                equity = state['capital'] + state['btc_amount'] * state['entry_price'] + unrealized
            else:
                equity = state['capital']

            pnl_pct = (equity - CONFIG['initial_capital']) / CONFIG['initial_capital'] * 100

            # Save state periodically
            if now.minute % 30 == 0 and now.second < 60:
                save_state()
                pos_str = 'FLAT' if state['position'] == 0 else ('LONG' if state['position'] == 1 else 'SHORT')
                log(f"[STATUS] Price: €{price:,.2f} | Position: {pos_str} | Equity: €{equity:,.2f} ({pnl_pct:+.2f}%)")

            time.sleep(CONFIG['check_interval'])

        except KeyboardInterrupt:
            break
        except Exception as e:
            log(f"Error in main loop: {e}")
            import traceback
            traceback.print_exc()
            time.sleep(30)

    log("Paper trader stopped.")
    save_state()


if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    load_state()
    ensure_db_table()
    main_loop()
