# ============================= Complete LSTM Stock Price Prediction Script =============================

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_percentage_error
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.callbacks import EarlyStopping
import tensorflow as tf
import warnings
from datetime import timedelta

# Suppress warnings for cleaner output
warnings.filterwarnings("ignore")
tf.get_logger().setLevel('ERROR')

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

# Display first few rows and check for missing values
print("Sample Data:")
print(data.head())
print("\nMissing Values:")
print(data.isnull().sum())

# 2. Select Features and Target Variable
# Set 'Date' as the DataFrame index if it exists
if 'Date' in data.columns:
    data.set_index('Date', inplace=True)

features = ['Opening Price', 'Highest Price', 'Lowest Price', 'Closing Price', 'MA_5', 'MA_20']
target = 'Closing Price'
data = data[features]

# Ensure data is sorted by date
data.sort_index(inplace=True)

# Handle missing values (e.g., forward fill)
data.fillna(method='ffill', inplace=True)

# 3. Feature Scaling
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data)

# 4. Create Sequences for LSTM
def create_sequences(data, sequence_length):
    X = []
    y = []
    for i in range(sequence_length, len(data)):
        X.append(data[i-sequence_length:i])
        y.append(data[i][3])  # 'Closing Price' is the 4th feature (index 3)
    return np.array(X), np.array(y)

sequence_length = 70  # You can adjust this value
X, y = create_sequences(scaled_data, sequence_length)

# 5. Manual Train-Test Split to Ensure Alignment
train_size = int(len(X) * 0.8)
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

# 6. Build the LSTM Model
model = Sequential()
model.add(LSTM(units=100, return_sequences=True, input_shape=(X_train.shape[1], X_train.shape[2])))
model.add(LSTM(units=100))
model.add(Dense(1))

# Compile the model
model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mean_absolute_percentage_error'])

# 7. Implement Early Stopping
early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)

# 8. Train the Model
history = model.fit(
    X_train, y_train,
    epochs=100,
    batch_size=16,
    validation_split=0.1,
    callbacks=[early_stop],
    verbose=1
)

# 9. Make Predictions on Test Set
predictions = model.predict(X_test)

# Inverse Transform to Get Actual Prices
# Since only 'Closing Price' is used as the target, we need to invert the scaling for that feature
# Create a scaler for the target
scaler_target = MinMaxScaler()
# Manually set the min and scale for 'Closing Price'
scaler_target.min_ = scaler.min_[3]
scaler_target.scale_ = scaler.scale_[3]

# Inverse transform y_test and predictions
y_test_inverse = scaler_target.inverse_transform(y_test.reshape(-1, 1))
predictions_inverse = scaler_target.inverse_transform(predictions)

# 10. Calculate MAPE
mape = mean_absolute_percentage_error(y_test_inverse, predictions_inverse)
print(f"\nMean Absolute Percentage Error (MAPE): {mape * 100:.2f}%")

# 11. Plot Actual vs Predicted Closing Prices
plt.figure(figsize=(14, 7))
plt.plot(y_test_inverse, color='blue', label='Actual Closing Price')
plt.plot(predictions_inverse, color='red', label='Predicted Closing Price')
plt.title('Actual vs Predicted Closing Prices')
plt.xlabel('Time')
plt.ylabel('Closing Price')
plt.legend()
plt.show()
# =================== 12. Predict Future 2 Weeks of Stock Prices ===================

# 12.1 Prepare the Latest Data Sequence
last_sequence = scaled_data[-sequence_length:]  # Shape: (sequence_length, num_features)

# Initialize a list to store future predictions
future_predictions_scaled = []

# Define the number of future days to predict (2 weeks ≈ 10 trading days)
future_days = 10  # Adjusted to represent two weeks of trading days

# Copy the last_sequence to avoid modifying the original scaled_data
current_sequence = last_sequence.copy()

for day in range(future_days):
    # Reshape to match the input shape of the model: (1, sequence_length, num_features)
    input_seq = current_sequence.reshape((1, sequence_length, data.shape[1]))
    
    # Predict the next day's closing price
    predicted_scaled = model.predict(input_seq)
    
    # Extract the predicted 'Closing Price' (assuming it's the 4th feature, index 3)
    predicted_closing_scaled = predicted_scaled[0][0]
    
    # Append the predicted closing price to future_predictions_scaled
    future_predictions_scaled.append(predicted_closing_scaled)
    
    # Create a new row for the next input sequence
    # Since we don't have actual future values for other features, we'll reuse the last known values
    new_row = current_sequence[-1].copy()
    new_row[3] = predicted_closing_scaled  # Update 'Closing Price'
    
    # Optionally, update Moving Averages (MA_5, MA_20) based on the new closing price
    # This requires maintaining a separate list of past closing prices
    # For simplicity, we'll skip updating MAs in this example
    
    # Append the updated row to current_sequence and remove the first row
    current_sequence = np.vstack([current_sequence[1:], new_row])

# 12.2 Inverse Transform the Predicted Values
future_predictions_scaled = np.array(future_predictions_scaled).reshape(-1, 1)
future_predictions_inverse = scaler_target.inverse_transform(future_predictions_scaled)

# 12.3 Prepare Dates for Future Predictions
# Get the last date from the original dataset
if isinstance(data.index, pd.DatetimeIndex):
    last_date = data.index[-1]
else:
    last_date = pd.to_datetime(data['Date'].iloc[-1])

# Generate future trading dates (excluding weekends)
future_dates = []
current_date = last_date
while len(future_dates) < future_days:
    current_date += timedelta(days=1)
    if current_date.weekday() < 5:  # Monday=0, Sunday=6
        future_dates.append(current_date)

future_dates = pd.to_datetime(future_dates)

# 12.4 Create DataFrames for Historical and Future Data
# Historical Closing Prices
history_df = pd.DataFrame({
    'Date': data.index[-len(y_test_inverse):],
    'Actual Closing Price': y_test_inverse.flatten()
})

# Future Predicted Closing Prices
future_df = pd.DataFrame({
    'Date': future_dates,
    'Predicted Closing Price': future_predictions_inverse.flatten()
})

# 12.5 Plot Predicted Closing Prices Only
import matplotlib.dates as mdates

plt.figure(figsize=(14, 7))
plt.plot(future_df['Date'], future_df['Predicted Closing Price'], color='red', marker='o', label='Predicted Closing Price')
plt.title('Predicted Closing Prices for the Next 2 Weeks')
plt.xlabel('Date')
plt.ylabel('Closing Price')
plt.legend()

# Format the x-axis to show dates in YYYY-MM-DD format
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1))  # Set major ticks to daily intervals

# Rotate date labels for better readability
plt.xticks(rotation=45)

# Ensure layout fits well
plt.tight_layout()
plt.show()

# 12.6 Print Future Predictions
print("\nPredicted Closing Prices for the Next 2 Weeks:")
print(future_df)

# ============================= Script End =============================

###有個麻煩點，當我增加csv的資料後l，lstm就不准了，請幫我設置 隨機搜尋 (Random Search)，請用英文