import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

# 1. Load Data
file_path = r"C:\Users\jack8\OneDrive\stock cow ai\data\stock price.csv"
df = pd.read_csv(file_path, parse_dates=['Date'])

# 3. Sort and Indexing
df.sort_values('Date', inplace=True)
df.set_index('Date', inplace=True)

# 4. Handling Missing Values
print("Missing values before cleaning:\n", df.isnull().sum())
df.dropna(inplace=True)  # Or use df.fillna(method='ffill') for forward fill
print("Missing values after cleaning:\n", df.isnull().sum())

# 5. Feature Selection
# Include 'Closing Price' as an input feature for better forecasting
features = ['Opening Price', 'Highest Price', 'Lowest Price', 'Closing Price']
target = 'Closing Price'

X = df[features]
y = df[target]

# 6. Scaling the Data
scaler_X = MinMaxScaler(feature_range=(0, 1))
X_scaled = scaler_X.fit_transform(X)

scaler_y = MinMaxScaler(feature_range=(0, 1))
y_scaled = scaler_y.fit_transform(y.values.reshape(-1, 1))

# 7. Train-Test Split Based on Time
train_size = int(len(X_scaled) * 0.8)
test_size = len(X_scaled) - train_size

X_train_raw, X_test_raw = X_scaled[:train_size], X_scaled[train_size:]
y_train_raw, y_test_raw = y_scaled[:train_size], y_scaled[train_size:]

print(f"Training samples: {X_train_raw.shape[0]}")
print(f"Testing samples: {X_test_raw.shape[0]}")

# 8. Creating Sequences for LSTM
def create_sequences(X, y, time_steps=10):
    Xs, ys = [], []
    for i in range(len(X) - time_steps):
        Xs.append(X[i:(i + time_steps)])
        ys.append(y[i + time_steps])
    return np.array(Xs), np.array(ys)

time_steps = 10
X_train, y_train = create_sequences(X_train_raw, y_train_raw, time_steps)
X_test, y_test = create_sequences(X_test_raw, y_test_raw, time_steps)

print(f"Shape of X_train: {X_train.shape}")  # (num_train_samples, time_steps, num_features)
print(f"Shape of y_train: {y_train.shape}")  # (num_train_samples, 1)
print(f"Shape of X_test: {X_test.shape}")    # (num_test_samples, time_steps, num_features)
print(f"Shape of y_test: {y_test.shape}")    # (num_test_samples, 1)

# 9. Extract Corresponding Dates for y_test
# Extract dates starting from (time_steps) to align with y values
dates = df.index[time_steps:]

# Now, split the dates into train and test based on train_size
train_dates = dates[:train_size - time_steps]  # Adjusted to match the sequences
test_dates = dates[train_size:]  # Start from train_size

# 10. Building the LSTM Model with Enhanced Dropout
model = Sequential()
model.add(LSTM(
    units=50,
    return_sequences=True,
    input_shape=(X_train.shape[1], X_train.shape[2]),
    dropout=0.2,  # 增加Dropout率至0.3
    recurrent_dropout=0.2  # 添加遞歸Dropout
))
model.add(Dropout(0.2))  # 增加Dropout率至0.3
model.add(LSTM(
    units=50,
    return_sequences=False,
    dropout=0.2,  # 增加Dropout率至0.3
    recurrent_dropout=0.2  # 添加遞歸Dropout
))
model.add(Dropout(0.2))  # 增加Dropout率至0.3
model.add(Dense(units=25))
model.add(Dropout(0.2))  # 在全連接層之間添加Dropout
model.add(Dense(units=1))  # Output layer

model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()

# 11. Training the Model
epochs = 300
batch_size = 32

history = model.fit(
    X_train, y_train,
    epochs=epochs,
    batch_size=batch_size,
    validation_data=(X_test, y_test),
    verbose=1
)

##早停
from keras.callbacks import EarlyStopping, ReduceLROnPlateau

# 定義Early Stopping
early_stop = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)

# 定義學習率調度
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-6)

# 修改模型訓練部分
history = model.fit(
    X_train, y_train,
    epochs=epochs,
    batch_size=batch_size,
    validation_data=(X_test, y_test),
    callbacks=[early_stop, reduce_lr],
    verbose=1
)

# 12. Evaluating the Model
# Plot Training and Validation Loss
plt.figure(figsize=(12, 6))
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Loss Over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Mean Squared Error')
plt.legend()
plt.show()

# Making Predictions
y_pred_scaled = model.predict(X_test)
y_pred = scaler_y.inverse_transform(y_pred_scaled)
y_test_actual = scaler_y.inverse_transform(y_test)

# 13. Plot Actual vs Predicted with Dates on x-axis
plt.figure(figsize=(14,7))
plt.plot(test_dates, y_test_actual, label='Actual Closing Price', color='blue')
plt.plot(test_dates, y_pred, label='Predicted Closing Price', color='red')
plt.title('Actual vs Predicted Closing Prices')
plt.xlabel('Date')  # Changed from 'Time' to 'Date'
plt.ylabel('Closing Price')
plt.legend()
plt.show()

# 14. Calculating Performance Metrics
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

mse = mean_squared_error(y_test_actual, y_pred)
mae = mean_absolute_error(y_test_actual, y_pred)
# Calculate Mean Absolute Percentage Error (MAPE)
mape = np.mean(np.abs((y_test_actual - y_pred) / y_test_actual)) * 100
# Calculate R-squared
r2 = r2_score(y_test_actual, y_pred)

print(f"Mean Squared Error (MSE): {mse}")
print(f"Mean Absolute Error (MAE): {mae}")
print(f"Mean Absolute Percentage Error (MAPE): {mape:.2f}%")
print(f"R-squared (R2 ): {r2:.4f}")

# 15. Recursive Forecasting for Future Predictions
def forecast_multivariate(model, X_input, scaler_y, n_steps=20):
    """
    Predict the next n_steps closing prices based on X_input.
    
    Parameters:
    - model: Trained LSTM model
    - X_input: The initial input sequence (scaled), shape=(time_steps, num_features)
    - scaler_y: Scaler for the target variable
    - n_steps: Number of future steps to predict
    
    Returns:
    - List of predicted values (original scale)
    """
    predictions = []
    X_current = X_input.copy()
    
    for _ in range(n_steps):
        # Predict the next closing price
        pred_scaled = model.predict(X_current.reshape(1, X_current.shape[0], X_current.shape[1]))
        predictions.append(pred_scaled[0, 0])
        
        # Create a new feature vector for the next prediction
        new_feature = X_current[-1].copy()  # Copy the last time step
        
        # Update 'Closing Price' with the predicted value
        new_feature[-1] = pred_scaled  # Replace the 'Closing Price' with prediction
        
        # Append the new feature to the sequence and remove the first time step
        X_current = np.append(X_current[1:], [new_feature.flatten()], axis=0)
    
    # Inverse transform the predictions to original scale
    predictions = scaler_y.inverse_transform(np.array(predictions).reshape(-1, 1))
    return predictions.flatten()

# Example Usage:
# Using the last sequence from the test set to predict the next 20 steps
last_sequence = X_test[-1]
future_steps = 20
future_predictions = forecast_multivariate(model, last_sequence, scaler_y, n_steps=future_steps)

# 16. Plot Future Predictions with Dates
# Generate future dates (business days)
last_date = test_dates[-1]
future_dates = pd.bdate_range(start=last_date + pd.Timedelta(days=1), periods=future_steps)

plt.figure(figsize=(14, 7))
plt.plot(future_dates, future_predictions, label='Future Predictions', color='green')
plt.title('Future Closing Price Predictions')
plt.xlabel('Date')
plt.ylabel('Closing Price')
plt.legend()
plt.show()


###幫我優化重寫