#!/usr/bin/env python3
"""
RSI Mean Reversion Trading Engine
=================================
Strategia vincente: RSI 30/70
- Buy quando RSI < 30 (oversold)
- Sell quando RSI > 70 (overbought)
"""

import asyncio
import json
import signal
import time
from datetime import datetime
from typing import Dict, Any, Optional
import requests
import psycopg2
from psycopg2.extras import RealDictCursor
import numpy as np

# Configuration
DB_CONFIG = {
    'host': 'localhost',
    'port': 5432,
    'dbname': 'pippo',
    'user': 'pippo',
    'password': 'pippo_trading_2026'
}

BINANCE_API = 'https://api.binance.com/api/v3'
CHECK_INTERVAL = 60  # seconds

# Strategy configs per trader
TRADER_CONFIGS = {
    'Conservative': {
        'rsi_oversold': 25,
        'rsi_overbought': 75,
        'position_size': 0.5,  # Use half capital per trade
    },
    'Balanced': {
        'rsi_oversold': 30,
        'rsi_overbought': 70,
        'position_size': 0.5,
    },
    'Aggressive': {
        'rsi_oversold': 35,
        'rsi_overbought': 65,
        'position_size': 0.5,
    },
}

# Global state
running = True
traders: Dict[int, Dict[str, Any]] = {}
price_history = []


def log(msg: str, trader_id: Optional[int] = None):
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    prefix = f"[Trader {trader_id}]" if trader_id else "[Engine]"
    print(f"[{timestamp}] {prefix} {msg}")


def get_db_connection():
    return psycopg2.connect(**DB_CONFIG, cursor_factory=RealDictCursor)


def fetch_prices(limit=100):
    """Fetch recent prices from Binance."""
    global price_history
    try:
        resp = requests.get(f'{BINANCE_API}/klines', params={
            'symbol': 'BTCEUR',
            'interval': '1m',
            'limit': limit
        }, timeout=10)
        if resp.ok:
            data = resp.json()
            price_history = [float(k[4]) for k in data]  # Close prices
            return price_history[-1] if price_history else 0
    except Exception as e:
        log(f"Error fetching prices: {e}")
    return 0


def calculate_rsi(prices, period=14):
    """Calculate RSI."""
    if len(prices) < period + 1:
        return 50  # Neutral

    deltas = np.diff(prices[-period-1:])
    gains = np.where(deltas > 0, deltas, 0)
    losses = np.where(deltas < 0, -deltas, 0)

    avg_gain = np.mean(gains)
    avg_loss = np.mean(losses)

    if avg_loss == 0:
        return 100
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))


def load_active_traders():
    """Load all active traders from database."""
    global traders

    try:
        conn = get_db_connection()
        cur = conn.cursor()

        cur.execute("""
            SELECT t.*, ts.position, ts.position_amount, ts.entry_price, ts.entry_time
            FROM traders t
            LEFT JOIN trader_states ts ON t.id = ts.trader_id
            WHERE t.status IN ('paper', 'live')
        """)

        rows = cur.fetchall()
        cur.close()
        conn.close()

        for row in rows:
            trader_id = row['id']
            name = row['name']

            # Get config based on trader name
            config_key = None
            for key in TRADER_CONFIGS.keys():
                if key in name:
                    config_key = key
                    break

            if config_key is None:
                config_key = 'Balanced'

            config = TRADER_CONFIGS[config_key]

            if trader_id not in traders:
                traders[trader_id] = {
                    'id': trader_id,
                    'name': name,
                    'status': row['status'],
                    'initial_capital': float(row['initial_capital']),
                    'current_capital': float(row['current_capital']),
                    'pair': row['pair'],
                    'fee_rate': float(row['fee_rate']),
                    'position': row['position'] or 0,
                    'position_amount': float(row['position_amount'] or 0),
                    'entry_price': float(row['entry_price'] or 0),
                    'entry_time': row['entry_time'],
                    'config': config,
                }
                log(f"Loaded: {name} | RSI {config['rsi_oversold']}/{config['rsi_overbought']}", trader_id)
            else:
                # Update capital
                traders[trader_id]['current_capital'] = float(row['current_capital'])

        # Remove stopped traders
        active_ids = {row['id'] for row in rows}
        for tid in list(traders.keys()):
            if tid not in active_ids:
                log(f"Trader stopped, removing", tid)
                del traders[tid]

    except Exception as e:
        log(f"Error loading traders: {e}")


def save_trader_state(trader_id: int, state: Dict[str, Any]):
    """Save trader state to database."""
    try:
        conn = get_db_connection()
        cur = conn.cursor()

        cur.execute("""
            INSERT INTO trader_states (trader_id, position, position_amount, entry_price, entry_time, updated_at)
            VALUES (%s, %s, %s, %s, %s, NOW())
            ON CONFLICT (trader_id) DO UPDATE SET
                position = EXCLUDED.position,
                position_amount = EXCLUDED.position_amount,
                entry_price = EXCLUDED.entry_price,
                entry_time = EXCLUDED.entry_time,
                updated_at = NOW()
        """, (
            trader_id,
            state['position'],
            state['position_amount'],
            state['entry_price'],
            state['entry_time']
        ))

        cur.execute("""
            UPDATE traders SET current_capital = %s, updated_at = NOW() WHERE id = %s
        """, (state['current_capital'], trader_id))

        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        log(f"Error saving state: {e}", trader_id)


def save_trade(trader_id: int, trade: Dict[str, Any], mode: str):
    """Save trade to database."""
    try:
        conn = get_db_connection()
        cur = conn.cursor()

        cur.execute("""
            INSERT INTO trades (
                trader_id, mode, pair, direction, action,
                entry_price, exit_price, amount,
                fee_entry, fee_exit, gross_pnl, net_pnl, pnl_percent,
                exit_reason, entry_time, exit_time, duration_minutes,
                capital_before, capital_after
            ) VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
            )
        """, (
            trader_id,
            mode,
            trade.get('pair', 'BTC/EUR'),
            trade.get('direction'),
            trade.get('action'),
            trade.get('entry_price'),
            trade.get('exit_price'),
            trade.get('amount'),
            trade.get('fee_entry', 0),
            trade.get('fee_exit', 0),
            trade.get('gross_pnl'),
            trade.get('net_pnl'),
            trade.get('pnl_percent'),
            trade.get('exit_reason'),
            trade.get('entry_time'),
            trade.get('exit_time'),
            trade.get('duration_minutes'),
            trade.get('capital_before'),
            trade.get('capital_after'),
        ))

        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        log(f"Error saving trade: {e}", trader_id)


def execute_buy(trader: Dict[str, Any], price: float, rsi: float):
    """Execute buy order."""
    config = trader['config']
    trade_value = trader['current_capital'] * config['position_size']
    fee = trade_value * trader['fee_rate']

    slippage = price * 0.0003
    exec_price = price + slippage
    amount = (trade_value - fee) / exec_price

    capital_before = trader['current_capital']
    trader['current_capital'] -= trade_value
    trader['position'] = 1
    trader['position_amount'] = amount
    trader['entry_price'] = exec_price
    trader['entry_time'] = datetime.now()

    save_trade(trader['id'], {
        'pair': trader['pair'],
        'direction': 'LONG',
        'action': 'OPEN',
        'entry_price': exec_price,
        'amount': amount,
        'fee_entry': fee,
        'entry_time': trader['entry_time'],
        'capital_before': capital_before,
        'capital_after': trader['current_capital'],
    }, trader['status'])

    save_trader_state(trader['id'], trader)

    log(f"BUY {amount:.6f} BTC @ €{exec_price:,.2f} | RSI: {rsi:.1f}", trader['id'])


def execute_sell(trader: Dict[str, Any], price: float, rsi: float, reason: str):
    """Execute sell order."""
    amount = trader['position_amount']
    entry_price = trader['entry_price']
    fee = amount * price * trader['fee_rate']

    slippage = price * 0.0003
    exec_price = price - slippage
    gross_pnl = amount * (exec_price - entry_price)
    net_pnl = gross_pnl - fee
    pnl_pct = net_pnl / (amount * entry_price)

    capital_before = trader['current_capital']
    trader['current_capital'] += amount * entry_price + net_pnl

    duration = None
    if trader['entry_time']:
        duration = int((datetime.now() - trader['entry_time']).total_seconds() / 60)

    save_trade(trader['id'], {
        'pair': trader['pair'],
        'direction': 'LONG',
        'action': 'CLOSE',
        'entry_price': entry_price,
        'exit_price': exec_price,
        'amount': amount,
        'fee_exit': fee,
        'gross_pnl': gross_pnl,
        'net_pnl': net_pnl,
        'pnl_percent': pnl_pct * 100,
        'exit_reason': reason,
        'entry_time': trader['entry_time'],
        'exit_time': datetime.now(),
        'duration_minutes': duration,
        'capital_before': capital_before,
        'capital_after': trader['current_capital'],
    }, trader['status'])

    trader['position'] = 0
    trader['position_amount'] = 0
    trader['entry_price'] = 0
    trader['entry_time'] = None

    save_trader_state(trader['id'], trader)

    emoji = '+' if net_pnl > 0 else ''
    log(f"SELL @ €{exec_price:,.2f} | RSI: {rsi:.1f} | P&L: {emoji}€{net_pnl:.2f} ({pnl_pct*100:+.2f}%) | {reason}", trader['id'])


async def process_trader(trader: Dict[str, Any], price: float, rsi: float):
    """Process a single trader."""
    config = trader['config']

    # If in position, check exit
    if trader['position'] == 1:
        if rsi > config['rsi_overbought']:
            execute_sell(trader, price, rsi, 'rsi_overbought')
    # If no position, check entry
    elif trader['position'] == 0:
        if rsi < config['rsi_oversold']:
            execute_buy(trader, price, rsi)


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

    log("Starting RSI Mean Reversion Engine")
    log("=" * 50)
    log("Strategy: Buy RSI oversold, Sell RSI overbought")

    while running:
        try:
            load_active_traders()

            if not traders:
                await asyncio.sleep(10)
                continue

            price = fetch_prices(100)
            if price <= 0:
                log("Could not get price, retrying...")
                await asyncio.sleep(30)
                continue

            rsi = calculate_rsi(price_history, 14)

            # Process all traders
            for trader in traders.values():
                await process_trader(trader, price, rsi)

            # Log status periodically
            now = datetime.now()
            if now.minute % 5 == 0 and now.second < CHECK_INTERVAL:
                in_pos = sum(1 for t in traders.values() if t['position'] != 0)
                total_cap = sum(t['current_capital'] for t in traders.values())
                log(f"RSI: {rsi:.1f} | Price: €{price:,.2f} | {len(traders)} traders ({in_pos} in position) | Capital: €{total_cap:,.2f}")

            await asyncio.sleep(CHECK_INTERVAL)

        except asyncio.CancelledError:
            break
        except Exception as e:
            log(f"Error in main loop: {e}")
            await asyncio.sleep(30)

    log("Engine stopped")


def signal_handler(sig, frame):
    global running
    log("Shutting down...")
    running = False


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

    asyncio.run(main_loop())
