#!/usr/bin/env python3
"""
V6 Paper Trading Bot - Regime-Based Strategy
Connects to live market data and executes simulated trades.

Strategy: Long-only regime trading based on optimized thresholds from backtesting.
Results from backtest: 7 trades, 42.9% win rate, +15.35% alpha vs BTC.
"""

import time
import json
import signal
import sys
from datetime import datetime
from pathlib import Path
import psycopg2
import redis
import requests

# Configuration - VERY AGGRESSIVE for active trading
CONFIG = {
    'pair': 'BTC/EUR',
    'initial_capital': 10000,
    'position_size': 0.25,      # 25% per trade (bigger positions)
    'stop_loss': 0.012,         # 1.2% stop loss (tight)
    'take_profit': 0.02,        # 2.0% take profit
    'fee_rate': 0.002,          # 0.2%
    'slippage': 0.0003,         # 0.03%
    # Short-term thresholds (20-60 min)
    'bullish_ret20': 0.0008,    # 0.08% (very sensitive)
    'bullish_ret60': 0.0005,    # 0.05%
    'bearish_ret20': -0.0008,   # -0.08%
    'bearish_ret60': -0.0005,   # -0.05%
    # Longer-term thresholds (4h, 24h)
    'bullish_ret4h': 0.005,     # 0.5% 4-hour return
    'bearish_ret4h': -0.005,    # -0.5%
    'bullish_ret24h': 0.01,     # 1% 24-hour return
    'bearish_ret24h': -0.01,    # -1%
    'min_confirm': 10,          # 10 minutes (faster)
    'min_hold': 10,             # 10 minutes
    'cooldown': 20,             # 20 minutes
}

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

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

# Global state
running = True
state = {
    'capital': CONFIG['initial_capital'],
    'btc_held': 0.0,
    'entry_price': 0.0,
    'entry_time': None,
    'in_position': False,
    'position_type': None,  # 'long' or 'short'
    'margin_held': 0,       # For short positions
    'current_regime': 'neutral',
    'regime_start': None,
    'last_trade_time': None,
    'trades': [],
    'price_history': [],  # Last 120 prices for feature calculation
}

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():
    """Save state to file for persistence."""
    state_copy = state.copy()
    state_copy['price_history'] = state_copy['price_history'][-60:]  # Only keep last 60
    with open(STATE_FILE, 'w') as f:
        json.dump(state_copy, f, indent=2, default=str)
    log(f"State saved. Capital: €{state['capital']:.2f}, BTC: {state['btc_held']:.6f}")

def load_state():
    """Load state from file if exists."""
    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}, BTC: {state['btc_held']:.6f}")
        except Exception as e:
            log(f"Error loading state: {e}")

def get_current_price():
    """Get current BTC/EUR price from Redis or API."""
    try:
        # Try Redis first (fastest)
        r = redis.Redis(host='localhost', port=6379, decode_responses=True)
        market_data = r.get('market:BTC/EUR')
        if market_data:
            data = json.loads(market_data)
            return float(data.get('mid', 0))
    except:
        pass

    # Fallback: Try state-engine API
    try:
        resp = requests.get('http://localhost:3054/state/BTC/EUR', timeout=5)
        if resp.ok:
            data = resp.json()
            return float(data.get('mid', 0))
    except:
        pass

    # Final fallback: Binance API
    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 get_historical_returns():
    """Get 4h and 24h returns from Binance klines."""
    try:
        # Get 24h of hourly candles
        resp = requests.get(
            'https://api.binance.com/api/v3/klines',
            params={'symbol': 'BTCEUR', 'interval': '1h', 'limit': 25},
            timeout=10
        )
        if resp.ok:
            klines = resp.json()
            if len(klines) >= 25:
                current = float(klines[-1][4])  # Close price
                price_4h = float(klines[-5][4])  # 4 hours ago
                price_24h = float(klines[0][4])  # 24 hours ago

                ret4h = (current - price_4h) / price_4h
                ret24h = (current - price_24h) / price_24h

                return {'ret4h': ret4h, 'ret24h': ret24h, 'price_4h': price_4h, 'price_24h': price_24h}
    except Exception as e:
        log(f"Error getting historical returns: {e}")

    return None

def compute_features():
    """Compute regime detection features from price history."""
    prices = state['price_history']
    if len(prices) < 20:
        return None

    current = prices[-1]
    features = {}

    # Returns - use available history
    if len(prices) >= 20:
        features['ret20'] = (current - prices[-20]) / prices[-20]
    else:
        features['ret20'] = (current - prices[0]) / prices[0]

    if len(prices) >= 60:
        features['ret60'] = (current - prices[-60]) / prices[-60]
    else:
        # Use what we have
        features['ret60'] = (current - prices[0]) / prices[0]

    return features

def detect_regime(features, hist_returns=None):
    """Detect current market regime using short + long term signals.

    Priority: Strong long-term signals override short-term noise.
    """
    if features is None:
        return 'neutral'

    ret20 = features['ret20']
    ret60 = features['ret60']

    # Long-term signals (from Binance historical data) - checked FIRST
    if hist_returns:
        ret4h = hist_returns.get('ret4h', 0)
        ret24h = hist_returns.get('ret24h', 0)

        # Strong 24h move dominates (over 1.5%)
        if ret24h < -0.015:  # -1.5% or more in 24h = strong bearish
            return 'bearish'
        if ret24h > 0.015:   # +1.5% or more in 24h = strong bullish
            return 'bullish'

        # Strong 4h move (over 0.8%)
        if ret4h < -0.008:   # -0.8% in 4h = bearish
            return 'bearish'
        if ret4h > 0.008:    # +0.8% in 4h = bullish
            return 'bullish'

    # Short-term signals (only if long-term is not decisive)
    short_bullish = ret20 > CONFIG['bullish_ret20'] or ret60 > CONFIG['bullish_ret60']
    short_bearish = ret20 < CONFIG['bearish_ret20'] or ret60 < CONFIG['bearish_ret60']

    if short_bullish:
        return 'bullish'
    elif short_bearish:
        return 'bearish'

    return 'neutral'

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

        cur.execute("""
            INSERT INTO paper_trades (
                pair, action, price, btc_amount, pnl, regime, capital_after, timestamp
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
        """, (
            CONFIG['pair'],
            trade['action'],
            trade['price'],
            trade.get('btc', 0),
            trade.get('pnl', 0),
            trade.get('regime', ''),
            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 table if not exists."""
    try:
        conn = psycopg2.connect(**DB_CONFIG)
        cur = conn.cursor()

        cur.execute("""
            CREATE TABLE IF NOT EXISTS paper_trades (
                id SERIAL PRIMARY KEY,
                pair VARCHAR(20),
                action VARCHAR(20),
                price DECIMAL(20, 8),
                btc_amount DECIMAL(20, 8),
                pnl DECIMAL(20, 8),
                regime VARCHAR(20),
                capital_after DECIMAL(20, 2),
                timestamp TIMESTAMP DEFAULT NOW()
            )
        """)

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

def execute_long(price, regime):
    """Execute simulated LONG (buy)."""
    trade_value = state['capital'] * CONFIG['position_size']
    fee = trade_value * CONFIG['fee_rate']
    exec_price = price * (1 + CONFIG['slippage'])
    btc_amount = (trade_value - fee) / exec_price

    state['capital'] -= trade_value
    state['btc_held'] = btc_amount
    state['entry_price'] = exec_price
    state['entry_time'] = datetime.now().isoformat()
    state['in_position'] = True
    state['position_type'] = 'long'
    state['last_trade_time'] = datetime.now().isoformat()

    trade = {
        'action': 'LONG',
        'price': price,
        'btc': btc_amount,
        'regime': regime,
        'time': datetime.now().isoformat()
    }
    state['trades'].append(trade)
    save_trade_to_db(trade)

    log(f"📈 LONG {btc_amount:.6f} BTC @ €{price:,.2f} | Capital: €{state['capital']:,.2f} | Regime: {regime}")

def execute_short(price, regime):
    """Execute simulated SHORT (sell first, buy back later)."""
    trade_value = state['capital'] * CONFIG['position_size']
    fee = trade_value * CONFIG['fee_rate']
    exec_price = price * (1 - CONFIG['slippage'])  # Worse price for short entry
    btc_amount = trade_value / exec_price

    state['capital'] -= (trade_value + fee)  # Lock margin + pay fee
    state['btc_held'] = btc_amount  # Amount we're short
    state['entry_price'] = exec_price
    state['entry_time'] = datetime.now().isoformat()
    state['in_position'] = True
    state['position_type'] = 'short'
    state['margin_held'] = trade_value  # Margin for the short
    state['last_trade_time'] = datetime.now().isoformat()

    trade = {
        'action': 'SHORT',
        'price': price,
        'btc': btc_amount,
        'regime': regime,
        'time': datetime.now().isoformat()
    }
    state['trades'].append(trade)
    save_trade_to_db(trade)

    log(f"📉 SHORT {btc_amount:.6f} BTC @ €{price:,.2f} | Margin: €{trade_value:,.2f} | Regime: {regime}")

def execute_close(price, reason, regime):
    """Close position (works for both LONG and SHORT)."""
    position_type = state.get('position_type', 'long')

    if position_type == 'long':
        # Close LONG: sell BTC
        exec_price = price * (1 - CONFIG['slippage'])
        proceeds = state['btc_held'] * exec_price
        fee = proceeds * CONFIG['fee_rate']
        net_proceeds = proceeds - fee
        pnl = net_proceeds - (state['btc_held'] * state['entry_price'])
        state['capital'] += net_proceeds
    else:
        # Close SHORT: buy back BTC
        exec_price = price * (1 + CONFIG['slippage'])  # Worse price to close
        cost_to_close = state['btc_held'] * exec_price
        fee = cost_to_close * CONFIG['fee_rate']
        # PnL = entry_price - exit_price (per BTC) * amount
        pnl = (state['entry_price'] - exec_price) * state['btc_held'] - fee
        # Return margin + PnL
        state['capital'] += state.get('margin_held', 0) + pnl

    trade = {
        'action': f"CLOSE_{position_type.upper()} ({reason})",
        'price': price,
        'btc': state['btc_held'],
        'pnl': pnl,
        'regime': regime,
        'time': datetime.now().isoformat()
    }
    state['trades'].append(trade)
    save_trade_to_db(trade)

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

    state['btc_held'] = 0
    state['entry_price'] = 0
    state['entry_time'] = None
    state['in_position'] = False
    state['position_type'] = None
    state['margin_held'] = 0
    state['last_trade_time'] = datetime.now().isoformat()

# Legacy functions for compatibility
def execute_buy(price, regime):
    execute_long(price, regime)

def execute_sell(price, reason, regime):
    execute_close(price, reason, regime)

def check_trading_signals(price, regime):
    """Check if we should enter or exit a trade. Supports LONG and SHORT."""
    now = datetime.now()

    # Calculate time-based conditions
    if state['regime_start']:
        time_in_regime = (now - datetime.fromisoformat(str(state['regime_start']))).total_seconds() / 60
    else:
        time_in_regime = 0

    if state['last_trade_time']:
        time_since_trade = (now - datetime.fromisoformat(str(state['last_trade_time']))).total_seconds() / 60
    else:
        time_since_trade = CONFIG['cooldown'] + 1  # Allow first trade

    # Get current position type
    position_type = state.get('position_type', None)  # 'long', 'short', or None

    # Entry logic - no position
    if not state['in_position'] and time_since_trade >= CONFIG['cooldown']:
        # LONG on bullish
        if regime == 'bullish' and time_in_regime >= CONFIG['min_confirm']:
            execute_long(price, regime)
        # SHORT on bearish
        elif regime == 'bearish' and time_in_regime >= CONFIG['min_confirm']:
            execute_short(price, regime)

    # Exit logic - has position
    elif state['in_position']:
        if state['entry_time']:
            time_held = (now - datetime.fromisoformat(str(state['entry_time']))).total_seconds() / 60
        else:
            time_held = 0

        # Calculate PnL based on position type
        if position_type == 'long':
            pnl_pct = (price - state['entry_price']) / state['entry_price']
        else:  # short
            pnl_pct = (state['entry_price'] - price) / state['entry_price']

        # Stop loss
        if pnl_pct < -CONFIG['stop_loss']:
            execute_close(price, 'STOP_LOSS', regime)

        # Take profit
        elif pnl_pct > CONFIG['take_profit']:
            execute_close(price, 'TAKE_PROFIT', regime)

        # Regime exit for LONG
        elif position_type == 'long' and time_held >= CONFIG['min_hold']:
            if regime == 'bearish' and time_in_regime >= 10:
                execute_close(price, 'REGIME_EXIT', regime)

        # Regime exit for SHORT
        elif position_type == 'short' and time_held >= CONFIG['min_hold']:
            if regime == 'bullish' and time_in_regime >= 10:
                execute_close(price, 'REGIME_EXIT', regime)

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

    log("=" * 60)
    log("V6 PAPER TRADING BOT - REGIME STRATEGY")
    log("=" * 60)
    log(f"Pair: {CONFIG['pair']}")
    log(f"Capital: €{state['capital']:,.2f}")
    log(f"Position Size: {CONFIG['position_size']*100:.0f}%")
    log(f"Stop Loss: {CONFIG['stop_loss']*100:.1f}%")
    log(f"Take Profit: {CONFIG['take_profit']*100:.1f}%")
    log("=" * 60)

    cycle = 0
    while running:
        try:
            # Get current price
            price = get_current_price()
            if price <= 0:
                log("Could not get price, retrying...")
                time.sleep(10)
                continue

            # Update price history
            state['price_history'].append(price)
            if len(state['price_history']) > 120:  # Keep max 2 hours
                state['price_history'] = state['price_history'][-120:]

            # Compute features and detect regime
            features = compute_features()
            hist_returns = get_historical_returns()  # 4h and 24h returns
            regime = detect_regime(features, hist_returns)

            # Track regime changes
            if regime != state['current_regime']:
                log(f"📊 Regime change: {state['current_regime']} → {regime}")
                state['current_regime'] = regime
                state['regime_start'] = datetime.now().isoformat()

            # Check trading signals
            check_trading_signals(price, regime)

            # Calculate equity (different for long vs short positions)
            if state['position_type'] == 'short':
                # For shorts: capital + margin + unrealized PnL
                unrealized_pnl = (state['entry_price'] - price) * state['btc_held']
                equity = state['capital'] + state.get('margin_held', 0) + unrealized_pnl
            else:
                # For longs or flat: capital + BTC value
                equity = state['capital'] + state['btc_held'] * price
            pnl_pct = (equity - CONFIG['initial_capital']) / CONFIG['initial_capital'] * 100

            # Periodic status log (every 5 minutes)
            if cycle % 5 == 0:
                pos_str = f"BTC: {state['btc_held']:.6f}" if state['in_position'] else "FLAT"
                ret_info = ""
                if hist_returns:
                    ret_info = f" | 4h:{hist_returns['ret4h']*100:+.2f}% 24h:{hist_returns['ret24h']*100:+.2f}%"
                log(f"[STATUS] Price: €{price:,.2f} | Regime: {regime}{ret_info} | {pos_str} | Equity: €{equity:,.2f} ({pnl_pct:+.2f}%)")

            # Save state periodically (every 10 minutes)
            if cycle % 10 == 0:
                save_state()

            cycle += 1
            time.sleep(60)  # Check every minute

        except KeyboardInterrupt:
            break
        except Exception as e:
            log(f"Error in main loop: {e}")
            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()
