# pip install pandas numpy sqlalchemy mysql-connector-python ta lightgbm scikit-learn python-dotenv joblib
import os
import warnings
import pandas as pd
import numpy as np
import joblib
import ta
from dotenv import load_dotenv
from sqlalchemy import create_engine, text
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import accuracy_score, precision_score, f1_score
import lightgbm as lgb
from datetime import datetime

warnings.filterwarnings('ignore')
load_dotenv()

# 🔐 Безопасная конфигурация (используйте .env!)
DB_CONFIG = {
    'host': os.getenv('DB_HOST', 'nlbotinterface.ru'),
    'port': int(os.getenv('DB_PORT', 3306)),
    'database': os.getenv('DB_NAME', 'bitcoin_tickers'),
    'user': os.getenv('DB_USER', 'bitcoin'),
    'password': 'g49020007'
}

def get_engine():
    url = f"mysql+mysqlconnector://{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}"
    return create_engine(url)

def fetch_ohlcv(engine, table='ASTR_H1', limit=150000):
    """Загрузка данных с учётом вашей схемы. Автоматическое определение формата timestamp."""
    query = text(f"""
        SELECT `timestamp`, `Open`, `High`, `Low`, `Close`, `Volume` 
        FROM `{table}` 
        ORDER BY `timestamp` ASC 
        LIMIT :limit
    """)
    df = pd.read_sql(query, engine, params={'limit': limit})
    
    # Безопасное преобразование Unix timestamp (секунды или миллисекунды)
    unit = 'ms' if df['timestamp'].max() > 1e12 else 's'
    df['datetime'] = pd.to_datetime(df['timestamp'], unit=unit)
    df.set_index('datetime', inplace=True)
    df.drop(columns=['timestamp'], inplace=True)
    
    # Приведение к нижнему регистру для удобства
    df.columns = [c.lower() for c in df.columns]
    df = df.astype({'open': float, 'high': float, 'low': float, 'close': float, 'volume': float})
    return df

def engineer_features(df):
    """Инжиниринг признаков для H1 без заглядывания в будущее."""
    df = df.copy()
    
    # 🎯 Target: 1 если следующая свеча закроется выше текущей
    df['target'] = (df['close'].shift(-1) > df['close']).astype(int)
    
    # ⏰ Временные признаки (сильно работают на H1)
    df['hour'] = df.index.hour
    df['day_of_week'] = df.index.dayofweek
    df['is_weekend'] = (df.index.dayofweek >= 5).astype(int)
    df['is_asian_session'] = df['hour'].between(0, 7).astype(int)
    
    # 📈 Технические индикаторы
    df['sma_20'] = ta.trend.sma_indicator(df['close'], window=20)
    df['ema_12'] = ta.trend.ema_indicator(df['close'], window=12)
    df['rsi_14'] = ta.momentum.rsi(df['close'], window=14)
    
    macd = ta.trend.MACD(df['close'], window_slow=26, window_fast=12, window_sign=9)
    df['macd_hist'] = macd.macd_diff()
    df['macd_signal'] = macd.macd_signal()
    
    bb = ta.volatility.BollingerBands(df['close'], window=20, window_dev=2)
    df['bb_width'] = (bb.bollinger_hband() - bb.bollinger_lband()) / df['sma_20']
    df['close_vs_bb_upper'] = (df['close'] - bb.bollinger_hband()) / df['sma_20']
    df['close_vs_bb_lower'] = (df['close'] - bb.bollinger_lband()) / df['sma_20']
    
    df['atr_14'] = ta.volatility.average_true_range(df['high'], df['low'], df['close'], window=14)
    df['obv'] = ta.volume.on_balance_volume(df['close'], df['volume'])
    
    # 📉 Возвраты, волатильность и объём
    df['log_ret'] = np.log(df['close'] / df['close'].shift(1))
    df['volatility_20'] = df['log_ret'].rolling(20).std()
    df['volume_ma_ratio'] = df['volume'] / df['volume'].rolling(20).mean()
    df['close_vs_sma20'] = (df['close'] - df['sma_20']) / df['sma_20']
    
    # 🕰️ Лаги (H1 → актуальны лаги 1, 3, 6, 12, 24 часа)
    for lag in [1, 3, 6, 12, 24]:
        df[f'ret_lag_{lag}'] = df['log_ret'].shift(lag)
        df[f'vol_lag_{lag}'] = df['volume'].shift(lag)
        df[f'rsi_lag_{lag}'] = df['rsi_14'].shift(lag)
        
    df.dropna(inplace=True)
    return df

def optimize_confidence_threshold(y_true, y_prob, payout=0.80, min_trades=50):
    """Находит порог уверенности, максимизирующий матожидание бинарных опционов."""
    thresholds = np.arange(0.52, 0.90, 0.01)
    best_ev, best_thresh, best_wr, best_n = -np.inf, 0.5, 0.0, 0
    
    for t in thresholds:
        # Торгуют только если уверенность >= t
        # CALL: prob >= t | PUT: prob <= (1 - t)
        mask = (y_prob >= t) | (y_prob <= (1 - t))
        n_trades = mask.sum()
        
        if n_trades < min_trades:
            continue
            
        # Направление предсказываем по порогу 0.5
        preds = (y_prob >= 0.5).astype(int)
        acc = accuracy_score(y_true[mask], preds[mask])
        wr = acc
        
        # Матожидание на 1 сделку (в долях от ставки)
        ev = (wr * payout) - (1 - wr)
        
        if ev > best_ev:
            best_ev, best_thresh, best_wr, best_n = ev, t, wr, n_trades
            
    return best_thresh, best_ev, best_wr, best_n

def train_and_backtest(df, payout=0.80):
    features = [c for c in df.columns if c not in ['target', 'open', 'high', 'low', 'close', 'volume']]
    X, y = df[features], df['target']
    
    # Хронологическое разделение 70/15/15
    n = len(df)
    train_end, val_end = int(n * 0.70), int(n * 0.85)
    
    X_train, X_val, X_test = X.iloc[:train_end], X.iloc[train_end:val_end], X.iloc[val_end:]
    y_train, y_val, y_test = y.iloc[:train_end], y.iloc[train_end:val_end], y.iloc[val_end:]
    
    # 🤖 LightGBM
    model = lgb.LGBMClassifier(
        n_estimators=800, learning_rate=0.01, max_depth=5,
        subsample=0.8, colsample_bytree=0.8, min_child_samples=20,
        reg_alpha=0.2, reg_lambda=0.5, random_state=42, verbose=-1
    )
    model.fit(
        X_train, y_train,
        eval_set=[(X_val, y_val)],
        callbacks=[lgb.early_stopping(50, verbose=False)]
    )
    
    probs_test = model.predict_proba(X_test)[:, 1]
    
    # Оптимизация порога уверенности
    opt_thresh, ev, wr, n_trades = optimize_confidence_threshold(y_test, probs_test, payout)
    
    metrics = {
        'raw_accuracy': accuracy_score(y_test, (probs_test >= 0.5).astype(int)),
        'optimized_threshold': opt_thresh,
        'trades_per_month_est': n_trades / (len(df) * 0.15) * (30 * 24), # H1
        'win_rate_filtered': wr,
        'expected_value_per_trade': ev,
        'is_profitable': ev > 0
    }
    
    return model, features, metrics, probs_test, y_test

def predict_next(model, features, df):
    """Прогноз для следующей свечи."""
    last = df[features].iloc[[-1]]
    prob = model.predict_proba(last)[0, 1]
    direction = 'BULLISH' if prob > 0.5 else 'BEARISH'
    confidence = max(prob, 1 - prob)
    return direction, prob, confidence

def main():
    print("🔌 Подключение к БД...")
    engine = get_engine()
    
    print("📥 Загрузка H1 данных...")
    df = fetch_ohlcv(engine, table='BITCOIN_H1', limit=100000)
    if df.empty:
        raise ValueError("Нет данных. Проверьте доступ и имя таблицы.")
    print(f"✅ Загружено {len(df)} свечей. Период: {df.index.min()} -> {df.index.max()}")
    
    print("🔧 Инжиниринг признаков...")
    df_feat = engineer_features(df)
    print(f"✅ Готово: {len(df_feat)} записей, {df_feat.shape[1]-1} признаков")
    
    print("🤖 Обучение и оптимизация...")
    model, features, metrics, probs, y_test = train_and_backtest(df_feat, payout=0.80)
    
    print("\n📊 РЕЗУЛЬТАТЫ (Test Set):")
    for k, v in metrics.items():
        suffix = "%" if isinstance(v, float) and v < 1.5 else ""
        print(f"  {k}: {v:.4f}{suffix}" if isinstance(v, float) else f"  {k}: {v}")
        
    print(f"\n💡 Рекомендация: торговать только при уверенности >= {metrics['optimized_threshold']:.0%}")
    print(f"📈 Стратегия {'ПРИБЫЛЬНА' if metrics['is_profitable'] else 'УБЫТОЧНА'} на тестовом периоде")
    
    # Сохранение
    joblib.dump((model, features, metrics['optimized_threshold']), 'btc_h1_binary_model.pkl')
    print("\n💾 Модель сохранена: btc_h1_binary_model.pkl")
    
    # Прогноз
    direction, prob, conf = predict_next(model, features, df_feat)
    action = "ТОРГОВАТЬ" if conf >= metrics['optimized_threshold'] else "ПРОПУСТИТЬ"
    print(f"\n🔮 СЛЕДУЮЩАЯ СВЕЧА: {direction} (вероятность: {prob:.2%} | уверенность: {conf:.2%}) → {action}")

if __name__ == '__main__':
    main()