import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
from sklearn.preprocessing import MinMaxScaler
import talib
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.utils import to_categorical
import warnings
warnings.filterwarnings('ignore')

# --------------------------------------------
# Fetch Data with Adjustable Timeframe
# --------------------------------------------
def fetch_price_data(symbol, interval, start_date, end_date):
    """Fetch cryptocurrency price data from Yahoo Finance."""
    data = yf.download(symbol, interval=interval, start=start_date, end=end_date)
    data = data[['Close', 'High', 'Low', 'Open', 'Volume']].dropna()  # Ensure no missing data
    return data

# --------------------------------------------
# Add Technical Indicators
# --------------------------------------------
def calculate_technical_indicators(data):
    """Add RSI, MACD, Bollinger Bands to data."""
    data['RSI'] = talib.RSI(data['Close'], timeperiod=14)
    data['MACD'], data['MACD_signal'], _ = talib.MACD(data['Close'], fastperiod=12, slowperiod=26, signalperiod=9)
    data['Upper_BB'], data['Middle_BB'], data['Lower_BB'] = talib.BBANDS(
        data['Close'], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
    return data

# --------------------------------------------
# Detect Elliott Waves (Peaks and Troughs)
# --------------------------------------------
def classify_elliott_wave(data):
    """Classify Elliott Waves using peaks and valleys."""
    data['wave_label'] = 0
    highs, _ = find_peaks(data['Close'], distance=5)
    lows, _ = find_peaks(-data['Close'], distance=5)

    # Label peaks (impulse waves) and troughs (corrective waves)
    data['wave_label'][highs] = 1  # Peaks: Waves 1, 3, 5
    data['wave_label'][lows] = -1 # Troughs: Waves 2, 4, A/B/C corrections
    return highs, lows, data

# --------------------------------------------
# Calculate Fibonacci Levels (Dynamic)
# --------------------------------------------
def calculate_fibonacci_levels(data, start_idx, end_idx):
    """Calculate Fibonacci retracement and extension levels."""
    high = data['Close'][start_idx]
    low = data['Close'][end_idx]
    
    retracements = {f"{level*100:.1f}%": high - (high - low) * level for level in [0.236, 0.382, 0.5, 0.618, 0.786]}
    extensions = {f"{level*100:.1f}%": high + (high - low) * level for level in [1.618, 2.618, 4.236]}
    return retracements, extensions

# --------------------------------------------
# Prepare LSTM Dataset
# --------------------------------------------
def create_lstm_dataset(data, feature_columns, target_column, window_size=60):
    """Prepare dataset for LSTM training."""
    X, y = [], []
    for i in range(window_size, len(data)):
        X.append(data[i-window_size:i, feature_columns])
        y.append(data[i, target_column])  # Predict Close Price
    return np.array(X), np.array(y)

# --------------------------------------------
# Build and Train the LSTM Model
# --------------------------------------------
def train_lstm_model(symbol, interval, start_date, end_date):
    """Train the LSTM model to predict price movement."""
    # Fetch data
    data = fetch_price_data(symbol, interval, start_date, end_date)
    data = calculate_technical_indicators(data)

    # Drop NaN values (caused by technical indicators)
    data = data.dropna()

    # Normalize data
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_data = scaler.fit_transform(data)

    # Prepare LSTM dataset (multivariate inputs)
    target_column = list(data.columns).index('Close')
    feature_columns = list(range(data.shape[1]))  # All features
    X, y = create_lstm_dataset(scaled_data, feature_columns, target_column)

    # Reshape for LSTM input
    X = np.reshape(X, (X.shape[0], X.shape[1], len(feature_columns)))

    # LSTM model
    model = Sequential([
        LSTM(units=50, return_sequences=True, input_shape=(X.shape[1], X.shape[2])),
        LSTM(units=50, return_sequences=False),
        Dropout(0.2),
        Dense(units=1)  # Output: Predicted Close Price
    ])
    model.compile(optimizer='adam', loss='mean_squared_error')
    model.fit(X, y, epochs=10, batch_size=32)

    return model, scaler, data, X, y

# --------------------------------------------
# Predict and Visualize Waves & Fibonacci Levels
# --------------------------------------------
def predict_and_visualize(symbol, interval, start_date, end_date, model, scaler):
    """Make price predictions and overlay Elliott Wave labels and Fibonacci levels on the chart."""
    # Fetch data
    data = fetch_price_data(symbol, interval, start_date, end_date)
    data = calculate_technical_indicators(data)

    # Detect Elliott Waves
    highs, lows, data = classify_elliott_wave(data)

    # Make predictions
    scaled_data = scaler.transform(data)
    feature_columns = list(range(data.shape[1]))  # All features
    X, _ = create_lstm_dataset(scaled_data, feature_columns, target_column=0)
    predictions = model.predict(X)
    predicted_prices = scaler.inverse_transform(predictions)  # Reverse normalization

    # Calculate Fibonacci retracement/extensions
    retracements, extensions = calculate_fibonacci_levels(data, start_idx=highs[-1], end_idx=lows[-1])

    # Plot the data
    plt.figure(figsize=(12, 8))
    plt.plot(data['Close'], label='Actual Price', linewidth=1, color='blue')
    plt.plot(range(len(data) - len(predicted_prices), len(data)), predicted_prices, label='Predicted Price', linestyle='--', color='red')
    for retrace, value in retracements.items():
        plt.axhline(value, linestyle='--', color='purple', label=f'Fibo {retrace}')
    plt.scatter(highs, data['Close'][highs], color='green', label='Impulsive Waves (Peaks)')
    plt.scatter(lows, data['Close'][lows], color='orange', label='Corrective Waves (Troughs)')

    plt.title(f'{symbol} Price Prediction + Elliott Waves + Fibonacci Levels ({interval})')
    plt.xlabel('Time')
    plt.ylabel('Price')
    plt.legend()
    plt.show()

# --------------------------------------------
# Main Function to Run the Program
# --------------------------------------------
def main():
    # Take inputs
    symbol = input("Enter the symbol (e.g., 'BTC-USD' for Bitcoin or 'ETH-USD'): ")
    interval = input("Enter the timeframe (e.g., '1h' for hourly, '1d' for daily, '1wk' for weekly): ")
    start_date = input("Enter the start date (YYYY-MM-DD): ")
    end_date = input("Enter the end date (YYYY-MM-DD): ")

    # Train the model
    model, scaler, data, X, y = train_lstm_model(symbol, interval, start_date, end_date)
    
    # Predict and visualize
    predict_and_visualize(symbol, interval, start_date, end_date, model, scaler)

# Run the program
if __name__ == "__main__":
    main()