{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":96164,"databundleVersionId":11418275,"sourceType":"competition"},{"sourceId":12052216,"sourceType":"datasetVersion","datasetId":1346}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport seaborn as sns\nimport numpy as np\nimport pandas as pd\nfrom datetime import datetime\nfrom itertools import product\nimport warnings\nfrom scipy import stats\nimport statsmodels.api as sm\nwarnings.filterwarnings('ignore')\nplt.style.use('seaborn-poster')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:21.944986Z","iopub.execute_input":"2025-06-04T22:52:21.945248Z","iopub.status.idle":"2025-06-04T22:52:24.421243Z","shell.execute_reply.started":"2025-06-04T22:52:21.945226Z","shell.execute_reply":"2025-06-04T22:52:24.420705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/bitcoin-historical-data/btcusd_1-min_data.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:24.422232Z","iopub.execute_input":"2025-06-04T22:52:24.422594Z","iopub.status.idle":"2025-06-04T22:52:31.564074Z","shell.execute_reply.started":"2025-06-04T22:52:24.422566Z","shell.execute_reply":"2025-06-04T22:52:31.563502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:31.564641Z","iopub.execute_input":"2025-06-04T22:52:31.564879Z","iopub.status.idle":"2025-06-04T22:52:31.588466Z","shell.execute_reply.started":"2025-06-04T22:52:31.564862Z","shell.execute_reply":"2025-06-04T22:52:31.587947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.tail()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:31.589844Z","iopub.execute_input":"2025-06-04T22:52:31.590113Z","iopub.status.idle":"2025-06-04T22:52:31.603585Z","shell.execute_reply.started":"2025-06-04T22:52:31.590098Z","shell.execute_reply":"2025-06-04T22:52:31.603078Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Setting Data**","metadata":{}},{"cell_type":"code","source":"df.Timestamp = pd.to_datetime(df.Timestamp, unit='s')\ndf.index = df.Timestamp\n\n\ndf = df.resample('D').mean()\ndf = df[df['Timestamp'] >= '2020-03-01']\ndf_month = df.resample('M').mean()\ndf_year = df.resample('A-DEC').mean()\ndf_Q = df.resample('Q-DEC').mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:31.604467Z","iopub.execute_input":"2025-06-04T22:52:31.604897Z","iopub.status.idle":"2025-06-04T22:52:35.076822Z","shell.execute_reply.started":"2025-06-04T22:52:31.604873Z","shell.execute_reply":"2025-06-04T22:52:35.076228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:35.077630Z","iopub.execute_input":"2025-06-04T22:52:35.077907Z","iopub.status.idle":"2025-06-04T22:52:35.087857Z","shell.execute_reply.started":"2025-06-04T22:52:35.077883Z","shell.execute_reply":"2025-06-04T22:52:35.087123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.tail()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:35.088468Z","iopub.execute_input":"2025-06-04T22:52:35.088631Z","iopub.status.idle":"2025-06-04T22:52:35.109553Z","shell.execute_reply.started":"2025-06-04T22:52:35.088617Z","shell.execute_reply":"2025-06-04T22:52:35.109034Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Real Data Plot**","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n\nfig = plt.figure(figsize=[18, 10])\nplt.suptitle('Bitcoin Exchange Rates - Mean USD', fontsize=22, fontweight='bold')\n\n\nplt.subplot(221)\nplt.plot(df['Close'], '-', label='By Days', color='dodgerblue', linewidth=2)\nplt.title('Bitcoin by Day', fontsize=18)\nplt.xlabel('Date', fontsize=14)\nplt.ylabel('Price (USD)', fontsize=14)\nplt.legend(loc='upper left')\nplt.grid(True, linestyle='--', alpha=0.6)\n\nplt.subplot(222)\nplt.plot(df_month['Close'], '-', label='By Months', color='tomato', linewidth=2)\nplt.title('Bitcoin by Month', fontsize=18)\nplt.xlabel('Date', fontsize=14)\nplt.ylabel('Price (USD)', fontsize=14)\nplt.legend(loc='upper left')\nplt.grid(True, linestyle='--', alpha=0.6)\n\nplt.subplot(223)\nplt.plot(df_Q['Close'], '-', label='By Quarters', color='seagreen', linewidth=2)\nplt.title('Bitcoin by Quarter', fontsize=18)\nplt.xlabel('Date', fontsize=14)\nplt.ylabel('Price (USD)', fontsize=14)\nplt.legend(loc='upper left')\nplt.grid(True, linestyle='--', alpha=0.6)\n\nplt.subplot(224)\nplt.plot(df_year['Close'], '-', label='By Year', color='purple', linewidth=2)\nplt.title('Bitcoin by Year', fontsize=18)\nplt.xlabel('Date', fontsize=14)\nplt.ylabel('Price (USD)', fontsize=14)\nplt.legend(loc='upper left')\nplt.grid(True, linestyle='--', alpha=0.6)\n\n\nplt.tight_layout(rect=[0, 0, 1, 0.96])\n\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:35.110152Z","iopub.execute_input":"2025-06-04T22:52:35.110326Z","iopub.status.idle":"2025-06-04T22:52:36.082876Z","shell.execute_reply.started":"2025-06-04T22:52:35.110311Z","shell.execute_reply":"2025-06-04T22:52:36.082158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Seasonal Decomposition**","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport statsmodels.api as sm\n\nplt.figure(figsize=[17, 10])\n\nresult = sm.tsa.seasonal_decompose(df_month['Close'], model='multiplicative', period=12)\n\nresult.plot()\nplt.suptitle('Seasonal Decomposition of Bitcoin Monthly Close Prices', fontsize=18, fontweight='bold')\nplt.subplots_adjust(top=0.92)\n\nfor ax in plt.gcf().get_axes():\n    ax.set_xlabel('Date', fontsize=12)\n    ax.set_ylabel('Price (USD)', fontsize=12)\n    ax.tick_params(axis='both', labelsize=10)\n    ax.grid(True, linestyle='--', alpha=0.5)\n\nadf_result = sm.tsa.stattools.adfuller(df_month['Close'])\n\nprint(f\"ADF Statistic: {adf_result[0]:.4f}\")\nprint(f\"p-value: {adf_result[1]:.4f}\")\n\nif adf_result[1] < 0.05:\n    print(\"Result: The time series is stationary (reject H0).\")\nelse:\n    print(\"Result: The time series is NOT stationary (fail to reject H0).\")\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:36.083816Z","iopub.execute_input":"2025-06-04T22:52:36.084012Z","iopub.status.idle":"2025-06-04T22:52:37.072407Z","shell.execute_reply.started":"2025-06-04T22:52:36.083996Z","shell.execute_reply":"2025-06-04T22:52:37.071720Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Box-Cox Transformation**","metadata":{}},{"cell_type":"code","source":"from scipy import stats\nimport statsmodels.api as sm\n\ndf_month['Close_box'], lmbda = stats.boxcox(df_month['Close'])\n\nadf_result = sm.tsa.stattools.adfuller(df_month['Close_box'])\n\nprint(f\"Box-Cox Lambda: {lmbda:.4f}\")\nprint(f\"ADF Statistic (Box-Cox transformed): {adf_result[0]:.4f}\")\nprint(f\"p-value: {adf_result[1]:.4f}\")\n\nif adf_result[1] < 0.05:\n    print(\"Result: The transformed series is stationary (reject H0).\")\nelse:\n    print(\"Result: The transformed series is NOT stationary (fail to reject H0).\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:37.075162Z","iopub.execute_input":"2025-06-04T22:52:37.075560Z","iopub.status.idle":"2025-06-04T22:52:37.108459Z","shell.execute_reply.started":"2025-06-04T22:52:37.075543Z","shell.execute_reply":"2025-06-04T22:52:37.107729Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Seasonal Transformation**","metadata":{}},{"cell_type":"code","source":"import statsmodels.api as sm\n\n\ndf_month['Close_box_diff'] = df_month['Close_box'] - df_month['Close_box'].shift(12)\n\nadf_result = sm.tsa.stattools.adfuller(df_month['Close_box_diff'].dropna())\n\nprint(f\"ADF Statistic (Box-Cox transformed & seasonally differenced): {adf_result[0]:.4f}\")\nprint(f\"p-value: {adf_result[1]:.4f}\")\n\nif adf_result[1] < 0.05:\n    print(\"Result: The transformed & differenced series is stationary (reject H0).\")\nelse:\n    print(\"Result: The transformed & differenced series is NOT stationary (fail to reject H0).\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:37.109225Z","iopub.execute_input":"2025-06-04T22:52:37.109744Z","iopub.status.idle":"2025-06-04T22:52:37.125776Z","shell.execute_reply.started":"2025-06-04T22:52:37.109718Z","shell.execute_reply":"2025-06-04T22:52:37.125164Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Regular Transformation**","metadata":{}},{"cell_type":"code","source":"# Second difference: difference of the seasonally differenced series by lag 1\ndf_month['Close_box_diff2'] = df_month['Close_box_diff'] - df_month['Close_box_diff'].shift(1)\n\n# Drop initial NaNs due to differencing (first 13 rows)\ndiff2_series = df_month['Close_box_diff2'].dropna()\n\n# Augmented Dickey-Fuller test on the twice differenced series\nadf_result = sm.tsa.stattools.adfuller(diff2_series)\np_value = adf_result[1]\n\nif p_value < 0.05:\n    stationarity_message = \"The series is stationary (p < 0.05).\"\nelse:\n    stationarity_message = \"The series is NOT stationary (p >= 0.05).\"\n\n# Seasonal decomposition on the twice differenced series (additive model)\nresult = sm.tsa.seasonal_decompose(diff2_series, model='additive', period=12)\n\n# Plotting decomposition results in 4 stacked plots\nfig, axes = plt.subplots(4, 1, figsize=(15, 12), sharex=True)\n\nresult.observed.plot(ax=axes[0], color='blue', label='Observed')\naxes[0].set_title('Observed')\naxes[0].legend(loc='upper left')\n\nresult.trend.plot(ax=axes[1], color='orange', label='Trend')\naxes[1].set_title('Trend')\naxes[1].legend(loc='upper left')\n\nresult.seasonal.plot(ax=axes[2], color='green', label='Seasonal')\naxes[2].set_title('Seasonal')\naxes[2].legend(loc='upper left')\n\nresult.resid.plot(ax=axes[3], color='red', label='Residual')\naxes[3].set_title('Residual')\naxes[3].legend(loc='upper left')\n\nplt.suptitle('Seasonal Decomposition of Close Price (Box-Cox Transformed & 2nd Differenced)', fontsize=16)\nplt.subplots_adjust(top=0.9)  # Title spacing\n\n# Show Dickey-Fuller p-value and stationarity message below plots\nplt.figtext(0.1, 0.01, f'Dickey-Fuller p-value: {p_value:.5f}\\n{stationarity_message}', fontsize=12)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:37.126569Z","iopub.execute_input":"2025-06-04T22:52:37.126832Z","iopub.status.idle":"2025-06-04T22:52:37.881872Z","shell.execute_reply.started":"2025-06-04T22:52:37.126810Z","shell.execute_reply":"2025-06-04T22:52:37.880941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **ACF Plot**","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport statsmodels.api as sm\n\n\nseries = df_month['Close_box_diff2'].dropna()\n\nplt.figure(figsize=(15, 8))\n\n# Plot ACF\nax1 = plt.subplot(211)\nsm.graphics.tsa.plot_acf(series.values.squeeze(), lags=15, ax=ax1)\nax1.set_title('Autocorrelation Function (ACF)', fontsize=14)\nax1.set_xlabel('Lags', fontsize=12)\nax1.set_ylabel('ACF', fontsize=12)\nax1.grid(True)\n\n# Plot PACF\nax2 = plt.subplot(212)\nsm.graphics.tsa.plot_pacf(series.values.squeeze(), lags=15, ax=ax2, method='ywm')  # method='ywm' is a stable default\nax2.set_title('Partial Autocorrelation Function (PACF)', fontsize=14)\nax2.set_xlabel('Lags', fontsize=12)\nax2.set_ylabel('PACF', fontsize=12)\nax2.grid(True)\n\nplt.tight_layout()\nplt.suptitle('ACF and PACF for Close Price (Box-Cox Transformed & 2nd Differenced)', fontsize=16, y=1.02)\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:37.882677Z","iopub.execute_input":"2025-06-04T22:52:37.883178Z","iopub.status.idle":"2025-06-04T22:52:38.262986Z","shell.execute_reply.started":"2025-06-04T22:52:37.883154Z","shell.execute_reply":"2025-06-04T22:52:38.262244Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Model Fitting**","metadata":{}},{"cell_type":"code","source":"import warnings\nimport statsmodels.api as sm\nfrom itertools import product\n\n\n# (p, q) non-seasonal AR and MA orders\np = range(0, 3)\nq = range(0, 3)\n\n# (P, Q) seasonal AR and MA orders\nP = range(0, 2)\nQ = range(0, 2)\n\n# Fixed differencing parameters\nd = 1    # regular differencing order \nD = 1    # seasonal differencing order (1 to handle yearly seasonality)\n\nseasonal_period = 12  # Monthly data seasonality\n\n# Generate all parameter combinations for order and seasonal_order\nparameters = product(p, q, P, Q)\nparameters_list = list(parameters)\n\nbest_aic = float(\"inf\")\nbest_model = None\nbest_param = None\nresults = []\n\nwarnings.filterwarnings('ignore')  # Hide convergence warnings during fitting\n\nprint(\"Starting SARIMAX grid search...\")\n\nfor param in parameters_list:\n    try:\n        order = (param[0], d, param[1])\n        seasonal_order = (param[2], D, param[3], seasonal_period)\n\n        model = sm.tsa.statespace.SARIMAX(df_month['Close_box'], \n                                          order=order,\n                                          seasonal_order=seasonal_order,\n                                          enforce_stationarity=False,\n                                          enforce_invertibility=False).fit(disp=False)\n        \n        aic = model.aic\n\n        results.append((param, aic))\n\n        if aic < best_aic:\n            best_aic = aic\n            best_model = model\n            best_param = param\n\n    except Exception as e:\n        # Skip invalid parameter combos silently or print minimal info\n        # print(f\"Skipping {param} due to error: {e}\")\n        continue\n\nprint(f\"Grid search complete!\")\nprint(f\"Best SARIMAX model AIC: {best_aic:.3f}\")\nprint(f\"Best parameters: order={best_param[:2]}, seasonal_order=({best_param[2]}, {D}, {best_param[3]}, {seasonal_period})\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:38.263699Z","iopub.execute_input":"2025-06-04T22:52:38.263904Z","iopub.status.idle":"2025-06-04T22:52:42.888034Z","shell.execute_reply.started":"2025-06-04T22:52:38.263889Z","shell.execute_reply":"2025-06-04T22:52:42.885566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport statsmodels.api as sm\n\nplt.figure(figsize=(15, 7))\n\n# Plot residuals time series\nax1 = plt.subplot(211)\nbest_model.resid[13:].plot(ax=ax1, title='Residuals')\nax1.set_ylabel('Residuals')\n\n# Plot ACF of residuals\nax2 = plt.subplot(212)\nsm.graphics.tsa.plot_acf(best_model.resid[13:].dropna().values.squeeze(), lags=12, ax=ax2)\nax2.set_title('ACF of Residuals')\n\nplt.tight_layout()\n\n# Dickey-Fuller test for residuals (checking stationarity/white noise)\nadf_pvalue = sm.tsa.stattools.adfuller(best_model.resid[13:].dropna())[1]\nprint(f\"Dickey–Fuller test p-value for residuals: {adf_pvalue:.6f}\")\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:42.888598Z","iopub.execute_input":"2025-06-04T22:52:42.888886Z","iopub.status.idle":"2025-06-04T22:52:43.263079Z","shell.execute_reply.started":"2025-06-04T22:52:42.888866Z","shell.execute_reply":"2025-06-04T22:52:43.262204Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Inverse Box-Cox Transformation Function**","metadata":{}},{"cell_type":"code","source":"def invboxcox(y,lmbda):\n   if lmbda == 0:\n      return(np.exp(y))\n   else:\n      return(np.exp(np.log(lmbda*y+1)/lmbda))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:43.263974Z","iopub.execute_input":"2025-06-04T22:52:43.264281Z","iopub.status.idle":"2025-06-04T22:52:43.269129Z","shell.execute_reply.started":"2025-06-04T22:52:43.264255Z","shell.execute_reply":"2025-06-04T22:52:43.268248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Future Prediction**","metadata":{}},{"cell_type":"code","source":"from datetime import datetime\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n\ndf_month2 = df_month[['Close']].copy()\n\n\nstart_date = datetime(2023, 12, 30)\nend_date = datetime(2028, 3, 31)\ndate_list = pd.date_range(start=start_date, end=end_date, freq='M')\n\n\nfuture = pd.DataFrame(index=date_list, columns=df_month.columns)\n\n\ndf_month2 = pd.concat([df_month2, future])\n\n\nforecast_values = best_model.predict(start=len(df_month2)-len(future), end=len(df_month2)-1)\n\ndf_month2.loc[date_list, 'forecast'] = invboxcox(forecast_values, lmbda)\n\n\nplt.figure(figsize=(15, 7))\ndf_month2['Close'].plot(label='Actual Close Price')\ndf_month2['forecast'].plot(color='r', ls='--', label='Forecasted Close Price')\nplt.legend()\nplt.title('Bitcoin Close Price Forecast')\nplt.ylabel('Price (USD)')\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-04T22:52:43.269981Z","iopub.execute_input":"2025-06-04T22:52:43.270242Z","iopub.status.idle":"2025-06-04T22:52:43.553514Z","shell.execute_reply.started":"2025-06-04T22:52:43.270219Z","shell.execute_reply":"2025-06-04T22:52:43.552928Z"}},"outputs":[],"execution_count":null}]}