{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6616cdbf",
   "metadata": {},
   "source": [
    "# Practical-4 (Recurrent Neural Network - Google Stock Price Dataset)\n",
    "\n",
    "---\n",
    "\n",
    "Problem Statement: Recurrent neural network (RNN): Use the Google stock prices dataset and design a time series\n",
    "analysis and prediction system using RNN.\n",
    "\n",
    "- Dataset link: https://www.kaggle.com/datasets/henryshan/google-stock-price\n",
    "- Dataset available in [Datasets](https://git.kska.io/sppu-be-comp-content/DeepLearning/src/branch/main/Datasets/) directory.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "1d71b3c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. Import Libraries\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import yfinance as yf\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "from sklearn.metrics import mean_squared_error, mean_absolute_error\n",
    "from tensorflow.keras.models import Sequential\n",
    "from tensorflow.keras.layers import Input, Dense, SimpleRNN, Dropout\n",
    "from tensorflow.keras.callbacks import EarlyStopping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e9de5b2c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  1 of 1 completed"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset Shape: (1509, 5)\n",
      "Date Range: 2018-01-02 to 2023-12-29\n",
      "Price           Close       High        Low       Open    Volume\n",
      "Date                                                            \n",
      "2018-01-02  53.220634  53.357999  52.219411  52.219411  31766000\n",
      "2018-01-03  54.128628  54.355751  53.231543  53.256336  31318000\n",
      "2018-01-04  54.338886  54.751477  54.264504  54.404843  26052000\n",
      "2018-01-05  55.059437  55.222590  54.638417  54.720241  30250000\n",
      "2018-01-08  55.253830  55.499303  55.045057  55.094646  24644000\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 2. Load Dataset\n",
    "# Downloads GOOGL stock data from Yahoo Finance for the given date range\n",
    "ticker = \"GOOGL\"\n",
    "df = yf.download(ticker, start=\"2018-01-01\", end=\"2024-01-01\")\n",
    "\n",
    "# --- Offline alternative (comment out the yf.download above and use this instead if using local dataset) ---\n",
    "# df = pd.read_csv('GOOGL.csv', index_col='Date', parse_dates=True)\n",
    "# df = df.sort_index()  # ensure chronological order\n",
    "\n",
    "# yfinance returns MultiIndex columns — flatten to single level\n",
    "df.columns = df.columns.get_level_values(0)\n",
    "\n",
    "print(f\"Dataset Shape: {df.shape}\")\n",
    "print(f\"Date Range: {df.index.min().date()} to {df.index.max().date()}\")\n",
    "print(df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "694dc4eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Dataset Info ===\n",
      "<class 'pandas.DataFrame'>\n",
      "DatetimeIndex: 1509 entries, 2018-01-02 to 2023-12-29\n",
      "Data columns (total 5 columns):\n",
      " #   Column  Non-Null Count  Dtype  \n",
      "---  ------  --------------  -----  \n",
      " 0   Close   1509 non-null   float64\n",
      " 1   High    1509 non-null   float64\n",
      " 2   Low     1509 non-null   float64\n",
      " 3   Open    1509 non-null   float64\n",
      " 4   Volume  1509 non-null   int64  \n",
      "dtypes: float64(4), int64(1)\n",
      "memory usage: 70.7 KB\n",
      "None\n",
      "\n",
      "=== Statistical Summary ===\n",
      "Price        Close         High          Low         Open        Volume\n",
      "count  1509.000000  1509.000000  1509.000000  1509.000000  1.509000e+03\n",
      "mean     90.444294    91.426617    89.414133    90.395013  3.485950e+07\n",
      "std      31.091515    31.412016    30.777228    31.099673  1.562928e+07\n",
      "min      48.829926    50.191172    48.482297    48.812567  9.312000e+06\n",
      "25%      59.727352    60.004070    59.073264    59.574123  2.505600e+07\n",
      "50%      87.298477    88.128108    86.215913    87.377828  3.091200e+07\n",
      "75%     118.718796   120.046815   117.588134   118.644398  3.990000e+07\n",
      "max     148.610245   150.304257   147.678433   150.010194  1.331780e+08\n",
      "\n",
      "=== Missing Values ===\n",
      "Price\n",
      "Close     0\n",
      "High      0\n",
      "Low       0\n",
      "Open      0\n",
      "Volume    0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "# 3. Exploratory Data Analysis (EDA)\n",
    "print(\"=== Dataset Info ===\")\n",
    "print(df.info())\n",
    "print(\"\\n=== Statistical Summary ===\")\n",
    "print(df.describe())\n",
    "print(\"\\n=== Missing Values ===\")\n",
    "print(df.isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "7498dc20",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 113172 characters]",
      "text/plain": [
       "<Figure size 1600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 4. Visualize Closing Price Over Time\n",
    "plt.figure(figsize=(16, 6))\n",
    "plt.plot(df.index, df['Close'], color='steelblue', linewidth=1.5, label='Close Price')\n",
    "plt.title('Google (GOOGL) Stock Closing Price (2018–2024)')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Price (USD)')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "4483fd37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original data range: [48.83, 148.61]\n",
      "Scaled data range:   [0.0000, 1.0000]\n",
      "Total data points:   1509\n"
     ]
    }
   ],
   "source": [
    "# 5. Preprocess Data - Normalize Closing Price\n",
    "data = df[['Close']].values   # use only Close price for prediction\n",
    "\n",
    "scaler = MinMaxScaler(feature_range=(0, 1))\n",
    "data_scaled = scaler.fit_transform(data)  # scale values to [0, 1]\n",
    "\n",
    "print(f\"Original data range: [{data.min():.2f}, {data.max():.2f}]\")\n",
    "print(f\"Scaled data range:   [{data_scaled.min():.4f}, {data_scaled.max():.4f}]\")\n",
    "print(f\"Total data points:   {len(data_scaled)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "ace55b7b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training samples: (1147, 60, 1)\n",
      "Testing samples:  (302, 60, 1)\n"
     ]
    }
   ],
   "source": [
    "# 6. Create Sequences for RNN Input\n",
    "def create_sequences(data, time_steps=60):\n",
    "    X, y = [], []\n",
    "    for i in range(time_steps, len(data)):\n",
    "        X.append(data[i - time_steps:i, 0])  # window of past `time_steps` days\n",
    "        y.append(data[i, 0])                  # next day's price\n",
    "    return np.array(X), np.array(y)\n",
    "\n",
    "TIME_STEPS = 60  # use past 60 days to predict the next day\n",
    "\n",
    "# 80/20 train-test split (manual, to preserve time order)\n",
    "train_size = int(len(data_scaled) * 0.80)\n",
    "train_data = data_scaled[:train_size]\n",
    "test_data  = data_scaled[train_size - TIME_STEPS:]  # overlap ensures test sequences start correctly\n",
    "\n",
    "X_train, y_train = create_sequences(train_data, TIME_STEPS)\n",
    "X_test,  y_test  = create_sequences(test_data,  TIME_STEPS)\n",
    "\n",
    "# Reshape to [samples, time_steps, features] — required format for RNN layers\n",
    "X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1))\n",
    "X_test  = X_test.reshape((X_test.shape[0],   X_test.shape[1],  1))\n",
    "\n",
    "print(f\"Training samples: {X_train.shape}\")\n",
    "print(f\"Testing samples:  {X_test.shape}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "83129417",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">Model: \"sequential_1\"</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1mModel: \"sequential_1\"\u001b[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
       "┃<span style=\"font-weight: bold\"> Layer (type)                    </span>┃<span style=\"font-weight: bold\"> Output Shape           </span>┃<span style=\"font-weight: bold\">       Param # </span>┃\n",
       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
       "│ simple_rnn_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">SimpleRNN</span>)        │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">60</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)         │         <span style=\"color: #00af00; text-decoration-color: #00af00\">4,224</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)             │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">60</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)         │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ simple_rnn_3 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">SimpleRNN</span>)        │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)             │         <span style=\"color: #00af00; text-decoration-color: #00af00\">8,256</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout_3 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)             │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">64</span>)             │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_2 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">32</span>)             │         <span style=\"color: #00af00; text-decoration-color: #00af00\">2,080</span> │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_3 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">1</span>)              │            <span style=\"color: #00af00; text-decoration-color: #00af00\">33</span> │\n",
       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
       "</pre>\n"
      ],
      "text/plain": [
       "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
       "┃\u001b[1m \u001b[0m\u001b[1mLayer (type)                   \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape          \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m      Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
       "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
       "│ simple_rnn_2 (\u001b[38;5;33mSimpleRNN\u001b[0m)        │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m60\u001b[0m, \u001b[38;5;34m64\u001b[0m)         │         \u001b[38;5;34m4,224\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout_2 (\u001b[38;5;33mDropout\u001b[0m)             │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m60\u001b[0m, \u001b[38;5;34m64\u001b[0m)         │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ simple_rnn_3 (\u001b[38;5;33mSimpleRNN\u001b[0m)        │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m)             │         \u001b[38;5;34m8,256\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dropout_3 (\u001b[38;5;33mDropout\u001b[0m)             │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m)             │             \u001b[38;5;34m0\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_2 (\u001b[38;5;33mDense\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m32\u001b[0m)             │         \u001b[38;5;34m2,080\u001b[0m │\n",
       "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
       "│ dense_3 (\u001b[38;5;33mDense\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1\u001b[0m)              │            \u001b[38;5;34m33\u001b[0m │\n",
       "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Total params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">14,593</span> (57.00 KB)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m14,593\u001b[0m (57.00 KB)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">14,593</span> (57.00 KB)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m14,593\u001b[0m (57.00 KB)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Non-trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> (0.00 B)\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 7. Build the RNN Model\n",
    "model = Sequential()\n",
    "\n",
    "model.add(Input(shape=(TIME_STEPS, 1)))                               # input: sequence of 60 days\n",
    "model.add(SimpleRNN(units=64, return_sequences=True))                 # first RNN layer, passes output to next\n",
    "model.add(Dropout(0.2))                                               # drop 20% neurons to reduce overfitting\n",
    "model.add(SimpleRNN(units=64, return_sequences=False))                # second RNN layer, outputs single vector\n",
    "model.add(Dropout(0.2))\n",
    "model.add(Dense(units=32, activation='relu'))                         # fully connected layer\n",
    "model.add(Dense(units=1))                                             # output: single predicted price\n",
    "\n",
    "model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mae'])\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "5024ae96",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m3s\u001b[0m 31ms/step - loss: 0.0449 - mae: 0.1446 - val_loss: 0.0027 - val_mae: 0.0411\n",
      "Epoch 2/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0114 - mae: 0.0780 - val_loss: 0.0043 - val_mae: 0.0546\n",
      "Epoch 3/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0084 - mae: 0.0675 - val_loss: 0.0018 - val_mae: 0.0342\n",
      "Epoch 4/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0062 - mae: 0.0580 - val_loss: 0.0022 - val_mae: 0.0376\n",
      "Epoch 5/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0053 - mae: 0.0516 - val_loss: 0.0017 - val_mae: 0.0335\n",
      "Epoch 6/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0056 - mae: 0.0516 - val_loss: 0.0016 - val_mae: 0.0329\n",
      "Epoch 7/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0043 - mae: 0.0462 - val_loss: 0.0022 - val_mae: 0.0363\n",
      "Epoch 8/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0041 - mae: 0.0450 - val_loss: 0.0016 - val_mae: 0.0331\n",
      "Epoch 9/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0035 - mae: 0.0411 - val_loss: 0.0015 - val_mae: 0.0311\n",
      "Epoch 10/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0029 - mae: 0.0377 - val_loss: 0.0027 - val_mae: 0.0408\n",
      "Epoch 11/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0032 - mae: 0.0388 - val_loss: 0.0048 - val_mae: 0.0589\n",
      "Epoch 12/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0034 - mae: 0.0408 - val_loss: 0.0014 - val_mae: 0.0298\n",
      "Epoch 13/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0029 - mae: 0.0374 - val_loss: 0.0014 - val_mae: 0.0312\n",
      "Epoch 14/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0025 - mae: 0.0352 - val_loss: 0.0063 - val_mae: 0.0709\n",
      "Epoch 15/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0023 - mae: 0.0341 - val_loss: 0.0023 - val_mae: 0.0378\n",
      "Epoch 16/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0028 - mae: 0.0367 - val_loss: 0.0018 - val_mae: 0.0330\n",
      "Epoch 17/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0021 - mae: 0.0327 - val_loss: 0.0013 - val_mae: 0.0293\n",
      "Epoch 18/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0027 - mae: 0.0358 - val_loss: 0.0084 - val_mae: 0.0851\n",
      "Epoch 19/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0026 - mae: 0.0344 - val_loss: 0.0012 - val_mae: 0.0289\n",
      "Epoch 20/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0021 - mae: 0.0316 - val_loss: 0.0017 - val_mae: 0.0322\n",
      "Epoch 21/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0022 - mae: 0.0314 - val_loss: 0.0012 - val_mae: 0.0285\n",
      "Epoch 22/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0020 - mae: 0.0309 - val_loss: 0.0034 - val_mae: 0.0482\n",
      "Epoch 23/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0019 - mae: 0.0299 - val_loss: 0.0025 - val_mae: 0.0398\n",
      "Epoch 24/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0020 - mae: 0.0301 - val_loss: 0.0048 - val_mae: 0.0606\n",
      "Epoch 25/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0021 - mae: 0.0321 - val_loss: 0.0012 - val_mae: 0.0286\n",
      "Epoch 26/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0026 - mae: 0.0338 - val_loss: 0.0017 - val_mae: 0.0319\n",
      "Epoch 27/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0021 - mae: 0.0317 - val_loss: 0.0025 - val_mae: 0.0400\n",
      "Epoch 28/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0022 - mae: 0.0323 - val_loss: 0.0013 - val_mae: 0.0285\n",
      "Epoch 29/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 16ms/step - loss: 0.0018 - mae: 0.0287 - val_loss: 0.0014 - val_mae: 0.0292\n",
      "Epoch 30/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 17ms/step - loss: 0.0017 - mae: 0.0285 - val_loss: 0.0012 - val_mae: 0.0281\n",
      "Epoch 31/60\n",
      "\u001b[1m33/33\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m1s\u001b[0m 15ms/step - loss: 0.0016 - mae: 0.0272 - val_loss: 0.0023 - val_mae: 0.0376\n",
      "\n",
      "Training stopped at epoch: 31\n"
     ]
    }
   ],
   "source": [
    "# 8. Train the Model\n",
    "# EarlyStopping stops training if val_loss doesn't improve for 10 consecutive epochs\n",
    "early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)\n",
    "\n",
    "history = model.fit(\n",
    "    X_train, y_train,\n",
    "    epochs=60,\n",
    "    batch_size=32,\n",
    "    validation_split=0.1,   # use 10% of training data for validation\n",
    "    callbacks=[early_stop],\n",
    "    verbose=1\n",
    ")\n",
    "print(f\"\\nTraining stopped at epoch: {len(history.history['loss'])}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "40c233a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 43228 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 9. Plot Training vs Validation Loss\n",
    "plt.plot(history.history['loss'], label='Train Loss', color='royalblue')\n",
    "plt.plot(history.history['val_loss'], label='Val Loss', color='tomato')\n",
    "plt.title('Model Training Loss Over Epochs')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('MSE Loss')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "0322a525",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1m10/10\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 24ms/step\n",
      "Sample predictions (first 5): [97.55 97.3  97.72 98.28 98.23]\n",
      "Actual values      (first 5): [ 99.94  98.81  99.15 100.3  101.68]\n"
     ]
    }
   ],
   "source": [
    "# 10. Make Predictions and Inverse Scale\n",
    "y_pred_scaled = model.predict(X_test)\n",
    "\n",
    "# Convert scaled predictions back to original USD price range\n",
    "y_pred   = scaler.inverse_transform(y_pred_scaled)\n",
    "y_actual = scaler.inverse_transform(y_test.reshape(-1, 1))\n",
    "\n",
    "print(f\"Sample predictions (first 5): {y_pred[:5].flatten().round(2)}\")\n",
    "print(f\"Actual values      (first 5): {y_actual[:5].flatten().round(2)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "9bd83cb1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "========================================\n",
      "     MODEL EVALUATION METRICS\n",
      "========================================\n",
      "  MSE  : 13.7776\n",
      "  RMSE : 3.7118\n",
      "  MAE  : 3.0172\n",
      "  MAPE : 2.68%\n",
      "========================================\n"
     ]
    }
   ],
   "source": [
    "# 11. Evaluate the Model\n",
    "mse  = mean_squared_error(y_actual, y_pred)\n",
    "rmse = np.sqrt(mse)\n",
    "mae  = mean_absolute_error(y_actual, y_pred)\n",
    "mape = np.mean(np.abs((y_actual - y_pred) / y_actual)) * 100  # mean absolute percentage error\n",
    "\n",
    "print(\"=\" * 40)\n",
    "print(\"     MODEL EVALUATION METRICS\")\n",
    "print(\"=\" * 40)\n",
    "print(f\"  MSE  : {mse:.4f}\")\n",
    "print(f\"  RMSE : {rmse:.4f}\")\n",
    "print(f\"  MAE  : {mae:.4f}\")\n",
    "print(f\"  MAPE : {mape:.2f}%\")\n",
    "print(\"=\" * 40)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "58b41348",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 139684 characters]",
      "text/plain": [
       "<Figure size 1600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 12. Plot Actual vs Predicted Stock Price\n",
    "test_dates = df.index[train_size:]   # align dates with test predictions\n",
    "\n",
    "plt.figure(figsize=(16, 6))\n",
    "plt.plot(test_dates, y_actual, label='Actual Price',    color='steelblue', linewidth=1.5)\n",
    "plt.plot(test_dates, y_pred,   label='Predicted Price', color='tomato',    linewidth=1.5, linestyle='--')\n",
    "plt.title('Google Stock Price: Actual vs Predicted (RNN)')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Price (USD)')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7411dc74",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 106136 characters]",
      "text/plain": [
       "<Figure size 1600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Forecasted price range: 115.14 USD - 134.90 USD\n"
     ]
    }
   ],
   "source": [
    "# 13. Forecast Next 30 Days\n",
    "n_future = 30  # number of future days to predict\n",
    "\n",
    "# Seed the forecast with the last TIME_STEPS days of known data\n",
    "future_input       = data_scaled[-TIME_STEPS:].reshape(1, TIME_STEPS, 1)\n",
    "future_predictions = []\n",
    "\n",
    "for _ in range(n_future):\n",
    "    pred = model.predict(future_input, verbose=0)\n",
    "    future_predictions.append(pred[0, 0])\n",
    "    # Slide the window: drop oldest day, append new prediction\n",
    "    future_input = np.append(future_input[:, 1:, :], pred.reshape(1, 1, 1), axis=1)\n",
    "\n",
    "# Inverse scale forecasted prices back to USD\n",
    "future_prices = scaler.inverse_transform(np.array(future_predictions).reshape(-1, 1))\n",
    "\n",
    "# Generate business day dates starting from the day after last known date\n",
    "last_date    = df.index[-1]\n",
    "future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=n_future, freq='B')\n",
    "\n",
    "plt.figure(figsize=(16, 6))\n",
    "plt.plot(df.index[-120:], scaler.inverse_transform(data_scaled[-120:]),\n",
    "         label='Historical', color='steelblue', linewidth=1.5)\n",
    "plt.plot(future_dates, future_prices,\n",
    "         label='30-Day Forecast', color='orange', linewidth=1.5)\n",
    "plt.axvline(x=last_date, color='gray', linestyle='--', label='Forecast Start')\n",
    "plt.title('Google Stock — 30-Day Future Price Forecast (RNN)')\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Price (USD)')\n",
    "plt.legend()\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f\"\\nForecasted price range: {future_prices.min():.2f} USD - {future_prices.max():.2f} USD\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83d2583f-c355-4fc3-9429-39c108ae3ca7",
   "metadata": {},
   "source": [
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
