#Orakel Viewer
import numpy as np
import pandas as pd
import MetaTrader5 as mt5
import plotly.graph_objects as go
from datetime import datetime
import warnings

warnings.filterwarnings('ignore')

# ========== PARAMETER ==========
# Zeitraum für die Analyse
START_DATE = "2024-01-01 00:00:00"
END_DATE = "2025-05-01 00:00:00"

# MT5 Symbol
SYMBOL = "EURUSD"  # MT5 Symbol (ohne =X)

# Timeframe (MT5 Konstanten)
TIMEFRAME = mt5.TIMEFRAME_H1  # 1 Stunde
# Andere Optionen: mt5.TIMEFRAME_M1, mt5.TIMEFRAME_M5, mt5.TIMEFRAME_M15,
#                  mt5.TIMEFRAME_M30, mt5.TIMEFRAME_H4, mt5.TIMEFRAME_D1

# Reversal Indikator Parameter
MIN_PRICE_DISTANCE_PIPS = 50  # Mindest-Preisdistanz in Pips für eine Umkehr

# 100pips Momentum Parameter
INDICATOR_PERIOD = 15

# MT5 Login Daten (optional, falls automatischer Login gewünscht)
MT5_LOGIN = None  # Ihre MT5 Kontonummer
MT5_PASSWORD = None  # Ihr MT5 Passwort
MT5_SERVER = None  # Ihr MT5 Server


# ========== MT5 FUNKTIONEN ==========

def initialize_mt5():
    """Initialisiert die Verbindung zu MetaTrader 5"""
    if not mt5.initialize():
        print("MetaTrader 5 Initialisierung fehlgeschlagen")
        print(f"Fehler: {mt5.last_error()}")
        return False

    # Optional: Automatischer Login
    if MT5_LOGIN and MT5_PASSWORD and MT5_SERVER:
        authorized = mt5.login(MT5_LOGIN, password=MT5_PASSWORD, server=MT5_SERVER)
        if not authorized:
            print("Login fehlgeschlagen")
            mt5.shutdown()
            return False

    print(f"MetaTrader 5 Version: {mt5.version()}")

    # Prüfe ob Symbol verfügbar ist
    symbol_info = mt5.symbol_info(SYMBOL)
    if symbol_info is None:
        print(f"Symbol {SYMBOL} nicht gefunden")
        mt5.shutdown()
        return False

    # Aktiviere Symbol für Market Watch
    if not symbol_info.visible:
        if not mt5.symbol_select(SYMBOL, True):
            print(f"Fehler beim Aktivieren von {SYMBOL}")
            mt5.shutdown()
            return False

    return True


def get_mt5_data(symbol, timeframe, start_date, end_date):
    """Lädt historische Daten von MetaTrader 5"""
    # Konvertiere Datetime zu Timestamp
    start_dt = pd.to_datetime(start_date)
    end_dt = pd.to_datetime(end_date)

    # Hole Daten von MT5
    rates = mt5.copy_rates_range(symbol, timeframe, start_dt, end_dt)

    if rates is None or len(rates) == 0:
        print("Keine Daten erhalten")
        return None

    # Konvertiere zu DataFrame
    df = pd.DataFrame(rates)

    # Konvertiere Zeit zu datetime
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time', inplace=True)

    # Benenne Spalten um für Konsistenz
    df.rename(columns={
        'open': 'Open',
        'high': 'High',
        'low': 'Low',
        'close': 'Close',
        'tick_volume': 'Volume'
    }, inplace=True)

    # Entferne unnötige Spalten
    if 'real_volume' in df.columns:
        df.drop('real_volume', axis=1, inplace=True)
    if 'spread' in df.columns:
        df.drop('spread', axis=1, inplace=True)

    return df


# ========== HELPER FUNCTIONS ==========

def calculate_lwma(data, period):
    """Berechnet Linear Weighted Moving Average"""
    weights = np.arange(1, period + 1)
    weights = weights / weights.sum()

    result = np.full_like(data, np.nan)
    for i in range(period - 1, len(data)):
        result[i] = np.sum(data[i - period + 1:i + 1] * weights)

    return result


def pips_to_price(pips, symbol_info):
    """Konvertiert Pips in Preis basierend auf dem Symbol"""
    # Hole Symbol-Informationen von MT5
    point = symbol_info.point
    return pips * point * 10  # 1 Pip = 10 Points für die meisten Forex-Paare


# ========== REVERSAL INDIKATOR ==========

def calculate_reversal_points(df, min_pips, symbol_info):
    """
    Implementierung des Reversal (ZigZag) Indikators
    Findet signifikante Hoch- und Tiefpunkte
    """
    high = df['High'].values
    low = df['Low'].values

    # Konvertiere Pips in Preisdistanz
    min_dist = pips_to_price(min_pips, symbol_info)

    # Arrays für Hoch- und Tiefpunkte
    high_points = np.full(len(df), np.nan)
    low_points = np.full(len(df), np.nan)

    # ZigZag Zustandsvariablen
    last_pivot_type = 0  # 0=unbestimmt, 1=Hoch, -1=Tief

    candidate_high_idx = -1
    candidate_high_val = 0
    candidate_low_idx = -1
    candidate_low_val = 0

    # Schleife durch alle Datenpunkte
    for i in range(len(df)):
        # 1. Finde den ersten Startpunkt
        if last_pivot_type == 0:
            if candidate_high_idx < 0:
                candidate_high_idx = i
                candidate_high_val = high[i]
            if candidate_low_idx < 0:
                candidate_low_idx = i
                candidate_low_val = low[i]

            # Prüfe ob genug Bewegung für erstes Pivot
            if candidate_high_val - low[i] >= min_dist:
                low_points[candidate_low_idx] = candidate_low_val
                last_pivot_type = -1
                candidate_high_idx = i
                candidate_high_val = high[i]
            elif high[i] - candidate_low_val >= min_dist:
                high_points[candidate_high_idx] = candidate_high_val
                last_pivot_type = 1
                candidate_low_idx = i
                candidate_low_val = low[i]

        # 2. Letztes Pivot war ein Tief, suche Hoch
        elif last_pivot_type == -1:
            if high[i] > candidate_high_val:
                candidate_high_idx = i
                candidate_high_val = high[i]
            elif candidate_high_val - low[i] >= min_dist:
                high_points[candidate_high_idx] = candidate_high_val
                last_pivot_type = 1
                candidate_low_idx = i
                candidate_low_val = low[i]

        # 3. Letztes Pivot war ein Hoch, suche Tief
        elif last_pivot_type == 1:
            if low[i] < candidate_low_val:
                candidate_low_idx = i
                candidate_low_val = low[i]
            elif high[i] - candidate_low_val >= min_dist:
                low_points[candidate_low_idx] = candidate_low_val
                last_pivot_type = -1
                candidate_high_idx = i
                candidate_high_val = high[i]

    # Letzten unbestätigten Punkt hinzufügen
    if last_pivot_type == -1 and candidate_high_idx >= 0:
        high_points[candidate_high_idx] = candidate_high_val
    elif last_pivot_type == 1 and candidate_low_idx >= 0:
        low_points[candidate_low_idx] = candidate_low_val

    return high_points, low_points


# ========== 100PIPS MOMENTUM INDIKATOR ==========

def calculate_momentum_indicator(df, period):
    """
    Implementierung des 100pips Momentum Indikators
    """
    close = df['Close'].values

    # Berechne die zwei gleitenden Durchschnitte
    ma1_period = int(period / 2)
    ma1 = calculate_lwma(close, ma1_period)
    ma2 = calculate_lwma(close, period)

    # Hull Moving Average ähnliche Berechnung
    hull_ma = 2 * ma1 - ma2

    # Weighted MA auf Hull MA
    sqrt_period = int(np.sqrt(period))
    final_ma = calculate_lwma(hull_ma, sqrt_period)

    return hull_ma, final_ma


# ========== ORACLE SIGNALE ==========

def generate_oracle_signals(df, high_points, low_points, hull_ma, final_ma, symbol_info):
    """
    Generiert perfekte Buy/Sell Signale basierend auf den Wendepunkten
    und dem Momentum-Indikator
    """
    buy_signals = []
    sell_signals = []

    # Finde alle Wendepunkte
    for i in range(len(df)):
        # Buy Signal: Bei einem Tiefpunkt, wenn Momentum bullish wird
        if not np.isnan(low_points[i]):
            # Suche nach dem frühesten Punkt nach dem Tiefpunkt,
            # wo hull_ma > final_ma (bullish crossover)
            for j in range(i, min(i + 50, len(df))):  # Schaue max 50 Kerzen voraus
                if j > 0 and hull_ma[j] > final_ma[j] and hull_ma[j - 1] <= final_ma[j - 1]:
                    buy_signals.append({
                        'index': j,
                        'price': df.iloc[j]['Low'] - pips_to_price(1, symbol_info),
                        'time': df.index[j],
                        'reversal_index': i,
                        'reversal_time': df.index[i]
                    })
                    break

        # Sell Signal: Bei einem Hochpunkt, wenn Momentum bearish wird
        if not np.isnan(high_points[i]):
            # Suche nach dem frühesten Punkt nach dem Hochpunkt,
            # wo hull_ma < final_ma (bearish crossover)
            for j in range(i, min(i + 50, len(df))):
                if j > 0 and hull_ma[j] < final_ma[j] and hull_ma[j - 1] >= final_ma[j - 1]:
                    sell_signals.append({
                        'index': j,
                        'price': df.iloc[j]['High'] + pips_to_price(1, symbol_info),
                        'time': df.index[j],
                        'reversal_index': i,
                        'reversal_time': df.index[i]
                    })
                    break

    return buy_signals, sell_signals


# ========== HAUPTPROGRAMM ==========

def main():
    print(f"Initialisiere MetaTrader 5...")

    # Initialisiere MT5
    if not initialize_mt5():
        print("MT5 Initialisierung fehlgeschlagen. Bitte stellen Sie sicher, dass MT5 läuft.")
        return

    try:
        # Hole Symbol-Informationen
        symbol_info = mt5.symbol_info(SYMBOL)
        if symbol_info is None:
            print(f"Symbol {SYMBOL} nicht gefunden")
            return

        print(f"\nSymbol-Informationen:")
        print(f"Symbol: {symbol_info.name}")
        print(f"Punkt: {symbol_info.point}")
        print(f"Digits: {symbol_info.digits}")
        print(f"Spread: {symbol_info.spread}")

        print(f"\nLade Daten für {SYMBOL} von {START_DATE} bis {END_DATE}...")

        # Lade Daten von MT5
        df = get_mt5_data(SYMBOL, TIMEFRAME, START_DATE, END_DATE)

        if df is None or df.empty:
            print("Keine Daten erhalten. Bitte überprüfen Sie Symbol und Zeitraum.")
            return

        print(f"Daten geladen: {len(df)} Datenpunkte")
        print(f"Zeitraum: {df.index[0]} bis {df.index[-1]}")

        # Berechne Indikatoren
        print("\nBerechne Reversal-Punkte...")
        high_points, low_points = calculate_reversal_points(df, MIN_PRICE_DISTANCE_PIPS, symbol_info)

        print("Berechne Momentum-Indikator...")
        hull_ma, final_ma = calculate_momentum_indicator(df, INDICATOR_PERIOD)

        print("Generiere Oracle-Signale...")
        buy_signals, sell_signals = generate_oracle_signals(df, high_points, low_points,
                                                            hull_ma, final_ma, symbol_info)

        # Erstelle Listen für valleys und peaks für die Analyse
        valleys = []
        peaks = []
        for i in range(len(low_points)):
            if not np.isnan(low_points[i]):
                valleys.append({'idx': i, 'price': low_points[i], 'time': df.index[i]})
        for i in range(len(high_points)):
            if not np.isnan(high_points[i]):
                peaks.append({'idx': i, 'price': high_points[i], 'time': df.index[i]})

        # Berechne ZigZag-Statistiken
        all_zigzag_points = []
        for i in range(len(high_points)):
            if not np.isnan(high_points[i]):
                all_zigzag_points.append({
                    'type': 'peak',
                    'idx': i,
                    'price': high_points[i]
                })
        for i in range(len(low_points)):
            if not np.isnan(low_points[i]):
                all_zigzag_points.append({
                    'type': 'valley',
                    'idx': i,
                    'price': low_points[i]
                })
        all_zigzag_points.sort(key=lambda x: x['idx'])

        total_zigzag = len(all_zigzag_points)
        peaks_count = len([p for p in all_zigzag_points if p['type'] == 'peak'])
        valleys_count = len([p for p in all_zigzag_points if p['type'] == 'valley'])

        print(f"\nGefundene Signale:")
        print(f"Buy Signale: {len(buy_signals)}")
        print(f"Sell Signale: {len(sell_signals)}")

        # ========== VISUALISIERUNG (Plotly Style) ==========

        print("\nErstelle Visualisierung...")

        # Figure erstellen (nur ein Panel wie im Beispielcode)
        fig = go.Figure()

        # Candlestick Chart mit Index statt Zeit für lückenlose Darstellung
        fig.add_trace(
            go.Candlestick(
                x=list(range(len(df))),
                open=df['Open'],
                high=df['High'],
                low=df['Low'],
                close=df['Close'],
                name='OHLC',
                increasing_line_color='#26a69a',
                decreasing_line_color='#ef5350'
            )
        )

        # ZigZag als verbundene Linie (verwende bereits berechnete all_zigzag_points)
        if all_zigzag_points:
            zigzag_x = [p['idx'] for p in all_zigzag_points]
            zigzag_y = [p['price'] for p in all_zigzag_points]

            fig.add_trace(
                go.Scatter(
                    x=zigzag_x,
                    y=zigzag_y,
                    mode='lines+markers',
                    name='ZigZag',
                    line=dict(color='yellow', width=2),
                    marker=dict(size=8, color='yellow'),
                    opacity=0.7
                )
            )

        # Oracle BUY Signale
        if buy_signals:
            buy_indices = []
            buy_prices = []
            buy_texts = []
            buy_hovers = []

            for s in buy_signals:
                idx = (df.index == s['time']).argmax()
                buy_indices.append(idx)
                buy_prices.append(df['Low'].iloc[idx] - pips_to_price(5, symbol_info))

                # Text für Signal
                text = "BUY"
                buy_texts.append(text)

                # Hover-Text
                hover = f"BUY Signal<br>Zeit: {s['time']}<br>"
                hover += f"Nach Reversal Low bei {s.get('reversal_time', 'N/A')}<br>"
                hover += f"Entry bei: {s['price']:.{symbol_info.digits}f}<br>"
                hover += f"Verzögerung: {s['index'] - s['reversal_index']} Kerzen"
                buy_hovers.append(hover)

            fig.add_trace(
                go.Scatter(
                    x=buy_indices,
                    y=buy_prices,
                    mode='markers+text',
                    name='Oracle BUY',
                    marker=dict(
                        symbol='triangle-up',
                        size=12,
                        color='lime',
                        line=dict(color='darkgreen', width=2)
                    ),
                    text=buy_texts,
                    textposition='bottom center',
                    textfont=dict(size=9),
                    hovertext=buy_hovers,
                    hoverinfo='text'
                )
            )

        # Oracle SELL Signale
        if sell_signals:
            sell_indices = []
            sell_prices = []
            sell_texts = []
            sell_hovers = []

            for s in sell_signals:
                idx = (df.index == s['time']).argmax()
                sell_indices.append(idx)
                sell_prices.append(df['High'].iloc[idx] + pips_to_price(5, symbol_info))

                # Text für Signal
                text = "SELL"
                sell_texts.append(text)

                # Hover-Text
                hover = f"SELL Signal<br>Zeit: {s['time']}<br>"
                hover += f"Nach Reversal High bei {s.get('reversal_time', 'N/A')}<br>"
                hover += f"Entry bei: {s['price']:.{symbol_info.digits}f}<br>"
                hover += f"Verzögerung: {s['index'] - s['reversal_index']} Kerzen"
                sell_hovers.append(hover)

            fig.add_trace(
                go.Scatter(
                    x=sell_indices,
                    y=sell_prices,
                    mode='markers+text',
                    name='Oracle SELL',
                    marker=dict(
                        symbol='triangle-down',
                        size=12,
                        color='red',
                        line=dict(color='darkred', width=2)
                    ),
                    text=sell_texts,
                    textposition='top center',
                    textfont=dict(size=9),
                    hovertext=sell_hovers,
                    hoverinfo='text'
                )
            )

        # Layout im dunklen Theme
        fig.update_layout(
            title=dict(
                text=f'{SYMBOL} Oracle Trading System (Reversal ZigZag)',
                x=0.5,
                xanchor='center'
            ),
            height=700,
            template='plotly_dark',
            xaxis_rangeslider_visible=False,
            hovermode='x unified',
            showlegend=True,
            legend=dict(
                yanchor="top",
                y=0.99,
                xanchor="left",
                x=0.01,
                bgcolor="rgba(0,0,0,0.5)",
                bordercolor="white",
                borderwidth=1,
                itemsizing='constant'
            )
        )

        # X-Achse ohne Lücken
        tick_spacing = max(1, len(df) // 10)
        tick_vals = list(range(0, len(df), tick_spacing))
        tick_texts = [df.index[i].strftime('%Y-%m-%d') for i in tick_vals if i < len(df)]

        fig.update_xaxes(
            title_text="Zeit",
            tickmode='array',
            tickvals=tick_vals,
            ticktext=tick_texts,
            tickangle=45
        )
        fig.update_yaxes(title_text="Preis")

        # Initial nur letzte Bars zeigen
        if len(df) > 2000:
            fig.update_xaxes(range=[len(df) - 2000, len(df) - 1])

        # Statistik-Box
        stats_text = f"""<b>🔮 Oracle Trading System</b>
<b>Symbol: {SYMBOL}</b>
<b>Reversal Min Pips: {MIN_PRICE_DISTANCE_PIPS}</b>
<b>Momentum Period: {INDICATOR_PERIOD}</b>

<b>ZigZag Statistik:</b>
Wendepunkte: {total_zigzag}
Peaks (High): {peaks_count}
Valleys (Low): {valleys_count}

<b>Oracle Signale:</b>
BUY Signale: {len(buy_signals)}
SELL Signale: {len(sell_signals)}
Total: {len(buy_signals) + len(sell_signals)}

<b>Zeitraum:</b>
Von: {df.index[0].strftime('%Y-%m-%d')}
Bis: {df.index[-1].strftime('%Y-%m-%d')}
Bars: {len(df):,}

<b>Oracle Regel:</b>
Nach Valley → BUY Signal
Nach Peak → SELL Signal"""

        fig.add_annotation(
            x=0.02, y=0.98,
            xref="paper", yref="paper",
            text=stats_text,
            showarrow=False,
            bgcolor="rgba(0,0,0,0.7)",
            bordercolor="white",
            borderwidth=1,
            font=dict(size=12, color="white"),
            align="left",
            xanchor="left",
            yanchor="top"
        )

        print("\n✅ Visualisierung wird geöffnet...")
        print("\n⚠️ ECHTES ORACLE:")
        print("   • Oracle generiert NUR Signale bei profitablen Wendepunkten")
        print("   • Nach jedem Valley → warte auf nächsten Momentum-BUY-Crossover")
        print("   • Nach jedem Peak → warte auf nächsten Momentum-SELL-Crossover")
        print("   • Dies garantiert optimale Einstiegspunkte")
        print("\n📌 REVERSAL ZIGZAG Logik (MQL5):")
        print("   • Mindest-Preisdistanz für Umkehr definiert")
        print("   • Robuste Wendepunkt-Erkennung")
        print("   • Nach Peak (High) → suche SELL-Signal")
        print("   • Nach Valley (Low) → suche BUY-Signal")

        fig.show()

        print("\n" + "=" * 60)
        print("Analyse abgeschlossen. Chart wird angezeigt.")
        print("=" * 60)

        # Drucke Signal-Details
        print("\n=== SIGNAL DETAILS ===")
        print("\nBuy Signale:")
        for i, signal in enumerate(buy_signals[:10], 1):
            print(f"{i}. {signal['time']} - Preis: {signal['price']:.{symbol_info.digits}f}")
        if len(buy_signals) > 10:
            print(f"... und {len(buy_signals) - 10} weitere Buy Signale")

        print("\nSell Signale:")
        for i, signal in enumerate(sell_signals[:10], 1):
            print(f"{i}. {signal['time']} - Preis: {signal['price']:.{symbol_info.digits}f}")
        if len(sell_signals) > 10:
            print(f"... und {len(sell_signals) - 10} weitere Sell Signale")

        # Performance-Statistiken
        if buy_signals and sell_signals:
            print("\n=== PERFORMANCE ÜBERSICHT ===")
            print(
                f"Erstes Signal: {min(buy_signals[0]['time'] if buy_signals else datetime.max, sell_signals[0]['time'] if sell_signals else datetime.max)}")
            print(
                f"Letztes Signal: {max(buy_signals[-1]['time'] if buy_signals else datetime.min, sell_signals[-1]['time'] if sell_signals else datetime.min)}")

            # Berechne durchschnittliche Anzahl von Kerzen zwischen Signal und Reversal
            buy_delays = []
            sell_delays = []

            for s in buy_signals:
                if 'reversal_index' in s and 'index' in s:
                    buy_delays.append(s['index'] - s['reversal_index'])

            for s in sell_signals:
                if 'reversal_index' in s and 'index' in s:
                    sell_delays.append(s['index'] - s['reversal_index'])

            if buy_delays:
                print(f"Durchschnittliche Verzögerung Buy-Signal: {np.mean(buy_delays):.1f} Kerzen")
            if sell_delays:
                print(f"Durchschnittliche Verzögerung Sell-Signal: {np.mean(sell_delays):.1f} Kerzen")

        print("\n📌 Oracle-Regeln:")
        print("   • Nach jedem Valley (Tiefpunkt) → warte auf BUY-Signal")
        print("   • Nach jedem Peak (Hochpunkt) → warte auf SELL-Signal")
        print("   • Dies garantiert perfekte Einstiegspunkte nach Wendepunkten")
        print(f"   • Reversal Min Distance: {MIN_PRICE_DISTANCE_PIPS} Pips")
        print(f"   • Momentum Period: {INDICATOR_PERIOD}")

        print("\n📊 ZigZag Analyse:")
        print(f"   • Gefundene Peaks: {peaks_count}")
        print(f"   • Gefundene Valleys: {valleys_count}")
        print(f"   • Total Wendepunkte: {total_zigzag}")

        # Berechne wie viele ZigZag-Punkte zu Signalen führten
        valleys_with_signals = 0
        peaks_with_signals = 0

        for v in valleys:
            if any(s.get('reversal_index') == v['idx'] for s in buy_signals):
                valleys_with_signals += 1

        for p in peaks:
            if any(s.get('reversal_index') == p['idx'] for s in sell_signals):
                peaks_with_signals += 1

        print(f"\n🎯 Signal-Effizienz:")
        if len(valleys) > 0:
            print(
                f"   • Valleys mit Buy-Signal: {valleys_with_signals}/{len(valleys)} ({valleys_with_signals / len(valleys) * 100:.1f}%)")
        if len(peaks) > 0:
            print(
                f"   • Peaks mit Sell-Signal: {peaks_with_signals}/{len(peaks)} ({peaks_with_signals / len(peaks) * 100:.1f}%)")

    finally:
        # MT5 Verbindung beenden
        mt5.shutdown()
        print("\nMT5 Verbindung beendet.")
        print("✅ Fertig!")


if __name__ == "__main__":
    main()