From f0d5a49f88891b67e337b163fb41ab4e7d67efcb Mon Sep 17 00:00:00 2001 From: Azmikun1 Date: Thu, 16 Jul 2026 07:19:21 +0700 Subject: [PATCH] new --- app1.py | 479 ------------------------------------------------- apptest.py | 479 ------------------------------------------------- eth_backup.csv | 19 +- 3 files changed, 18 insertions(+), 959 deletions(-) delete mode 100644 app1.py delete mode 100644 apptest.py diff --git a/app1.py b/app1.py deleted file mode 100644 index 119f52b..0000000 --- a/app1.py +++ /dev/null @@ -1,479 +0,0 @@ -import requests -import streamlit as st -import joblib -import pandas as pd -import numpy as np -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.models import load_model -from datetime import date, timedelta -import yfinance as yf -from pathlib import Path -import matplotlib.pyplot as plt -import matplotlib.dates as mdates -from matplotlib import rcParams - -# Set font untuk mendukung karakter Indonesia -rcParams['font.family'] = 'DejaVu Sans' - -# --- Konfigurasi Halaman --- -st.set_page_config( - page_title="Prediksi Harga Ethereum (GRU)", - page_icon="๐Ÿช™" -) - -# --- GLOBAL STYLES (UI only) --- -st.markdown(""" - -""", unsafe_allow_html=True) - - -# --- Fungsi-fungsi Bantuan --- - -@st.cache_data(ttl="1h") -def load_eth_data(): - ticker = "ETH-USD" - df = None - - # 1. COBA ONLINE - try: - - df = yf.download( - ticker, - start="2024-01-01", - end=date.today() + timedelta(days=1), - progress=False, - auto_adjust=True, - multi_level_index=False - ) - - if df is not None and not df.empty: - # Bersihkan Index & Kolom - df = df.reset_index() - - new_cols = [] - for col in df.columns: - col_name = col[0] if isinstance(col, tuple) else str(col) - new_cols.append(col_name) - df.columns = new_cols - - # Kolom pertama adalah Date - if 'Date' not in df.columns: - df = df.rename(columns={df.columns[0]: 'Date'}) - - # Hapus Timezone - df['Date'] = pd.to_datetime(df['Date']).dt.tz_localize(None) - - # Simpan Backup (Timpa file lama agar fresh) - try: - df.to_csv("eth_backup.csv", index=False) - except: - pass - - return df, "online" - - except Exception as e: - print(f"Gagal Online: {e}") - - # 2. COBA BACKUP (JIKA ONLINE GAGAL) - try: - df_backup = pd.read_csv("eth_backup.csv") - - # Bersihkan kolom sampah jika ada - if "Unnamed: 0" in df_backup.columns: - df_backup = df_backup.drop(columns=["Unnamed: 0"]) - - # Pastikan kolom Date dikenali - if "Date" in df_backup.columns: - df_backup["Date"] = pd.to_datetime(df_backup["Date"]) - elif df_backup.columns[0].lower() == "date": - df_backup = df_backup.rename(columns={df_backup.columns[0]: "Date"}) - df_backup["Date"] = pd.to_datetime(df_backup["Date"]) - - return df_backup, "backup" - - except FileNotFoundError: - # 3. GAGAL TOTAL - return None, "error" - -def validate_scaler(scaler): - """Validasi scaler agar konsisten dengan training.""" - issues = [] - warnings = [] - - if not isinstance(scaler, MinMaxScaler): - warnings.append(f"Scaler bukan MinMaxScaler (terdeteksi: {type(scaler).__name__}). Pastikan ini scaler training.") - - required_attrs = ["n_features_in_", "data_min_", "data_max_", "scale_", "min_"] - for a in required_attrs: - if not hasattr(scaler, a): - issues.append(f"Scaler belum ter-fit atau tidak valid (atribut '{a}' tidak ada).") - - if hasattr(scaler, "n_features_in_"): - if int(scaler.n_features_in_) != 1: - issues.append(f"Scaler mengharapkan {scaler.n_features_in_} fitur, tapi aplikasi hanya pakai 1 fitur ('Close').") - - ok = (len(issues) == 0) - return ok, issues, warnings - - -def validate_model_input(model): - """Pastikan input model bentuknya (None, time_step, 1).""" - issues = [] - try: - shape = model.input_shape # biasanya (None, time_step, 1) - if not (isinstance(shape, (list, tuple)) and len(shape) == 3): - issues.append(f"input_shape tidak sesuai harapan: {shape} (harus 3 dimensi).") - else: - if shape[2] != 1: - issues.append(f"Jumlah fitur input model = {shape[2]}, tapi aplikasi membentuk fitur=1.") - if shape[1] is None or int(shape[1]) <= 1: - issues.append(f"time_step tidak valid: {shape[1]}.") - except Exception as e: - issues.append(f"Gagal membaca input_shape model: {e}") - - ok = (len(issues) == 0) - return ok, issues - - -@st.cache_resource -def load_gru_assets(): - """Load model & scaler hasil training (wajib pakai scaler yang sama).""" - model_path = Path("gru_model.h5") - scaler_path = Path("scaler_gru.pkl") - - if not model_path.exists() or not scaler_path.exists(): - st.warning( - f"File tidak ditemukan.\n" - f"Model: {model_path.resolve().name} ada? {model_path.exists()}\n" - f"Scaler: {scaler_path.resolve().name} ada? {scaler_path.exists()}" - ) - return None, None - - try: - model = load_model(model_path, compile=False) - scaler = joblib.load(scaler_path) - - ok_scaler, scaler_issues, scaler_warnings = validate_scaler(scaler) - for w in scaler_warnings: - st.warning("โš ๏ธ " + w) - if not ok_scaler: - st.error("โŒ Scaler tidak kompatibel:\n- " + "\n- ".join(scaler_issues)) - return None, None - - ok_model, model_issues = validate_model_input(model) - if not ok_model: - st.error("โŒ Model input tidak kompatibel:\n- " + "\n- ".join(model_issues)) - return None, None - - return model, scaler - - except Exception as e: - st.error(f"Gagal load aset: {e}") - return None, None - - -def predict_from_sequence_pure(model, initial_sequence_scaled, n_days): - - - seq = np.array(initial_sequence_scaled, dtype="float32").reshape(-1) # (time_step,) - preds = [] - - for _ in range(n_days): - x = seq.reshape(1, -1, 1) # (1, time_step, 1) - y = float(model.predict(x, verbose=0)[0, 0]) # output model (scaled) - preds.append(y) - seq = np.append(seq[1:], y) # autoregressive (murni) - - return np.array(preds, dtype="float32").reshape(-1, 1) - - -def create_combined_chart(df, start_date, future_dates, future_predictions): - """Membuat grafik gabungan dengan gaya yang sama seperti referensi.""" - context_start_date = start_date - timedelta(days=609) - recent_df = df[(df["Date"] >= context_start_date) & (df["Date"] <= start_date)].copy() - - window_size = 7 - recent_df["Trend_Aktual"] = recent_df["Close"].rolling(window=window_size).mean() - - predictions_flat = future_predictions.flatten() - - fig, ax = plt.subplots(figsize=(14, 8)) - - - ax.plot( - recent_df["Date"], recent_df["Trend_Aktual"], - color="#1f77b4", linewidth=2.5, label="Tren Harga Aktual", alpha=0.9 - ) - # Membuat tren prediksi menggunakan moving average 7 hari - pred_trend = ( - pd.Series(predictions_flat) - .rolling(window=7, min_periods=1) - .mean() - ) - - ax.plot( - future_dates, - pred_trend, - color="#d62728", - linewidth=2.5, - label="Tren Harga Prediksi (GRU)", - alpha=0.9 - ) - ax.scatter( - recent_df["Date"], recent_df["Close"], - color="black", s=15, alpha=0.6, zorder=5, label="Data Harian Aktual" - ) - - # garis penghubung trend terakhir ke prediksi pertama - try: - last_trend_date = recent_df["Date"].iloc[-1] - last_trend_price = recent_df["Trend_Aktual"].dropna().iloc[-1] - first_prediction_date = future_dates[0] - first_prediction_price = predictions_flat[0] - - ax.plot( - [last_trend_date, first_prediction_date], - [last_trend_price, first_prediction_price], - color="#d62728", linestyle="--", linewidth=2.0, alpha=0.7 - ) - except Exception: - pass - - ax.set_xlabel("Waktu", fontsize=12, fontweight="bold") - ax.set_ylabel("Harga (USD)", fontsize=12, fontweight="bold") - ax.set_title("Analisis Tren Historis dan Prediksi Harga Ethereum", fontsize=16, fontweight="bold", pad=20) - - ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d, %Y")) - plt.xticks(rotation=45) - ax.grid(True, alpha=0.3, linestyle="--") - - handles, labels = ax.get_legend_handles_labels() - order = [2, 0, 1, 3] - if len(handles) == 4: - ax.legend([handles[idx] for idx in order], [labels[idx] for idx in order], loc="upper left", fontsize=11) - else: - ax.legend(loc="upper left", fontsize=11) - - plt.tight_layout() - ax.set_facecolor("#f8f9fa") - fig.patch.set_facecolor("white") - - return fig - - -# --- Tampilan Antarmuka Aplikasi --- - -st.markdown("
Prediksi Harga Ethereum (GRU)
", unsafe_allow_html=True) -st.markdown("
Data historis & prediksi ETH-USD berbasis GRU
", unsafe_allow_html=True) - -df, data_source = load_eth_data() -model, scaler = load_gru_assets() - -# LOGIC UI (TOAST/WARNING) DITARUH DI SINI (DILUAR CACHE) -if data_source == "backup": - st.toast("Koneksi Yahoo lambat. Menggunakan data backup lokal.", icon="โš ๏ธ") -elif data_source == "error": - st.error("โŒ Gagal memuat data (Online gagal & Backup tidak ada).") - -if df is not None and model is not None and scaler is not None: - # Menampilkan tabel data historis - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐Ÿ“š Data Historis Harga Ethereum
", unsafe_allow_html=True) - st.dataframe(df, height=300, use_container_width=True) - st.markdown("
", unsafe_allow_html=True) - - # Menampilkan visualisasi line chart - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐Ÿ“ˆ Visualisasi Harga Historis
", unsafe_allow_html=True) - st.line_chart(df.rename(columns={"Date": "index"}).set_index("index")["Close"]) - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐ŸŽฏ Mulai Prediksi Berdasarkan Tanggal
", unsafe_allow_html=True) - - time_step = int(model.input_shape[1]) - min_selectable_date = (df["Date"].min() + timedelta(days=time_step)).date() - max_selectable_date = df["Date"].max().date() - - col1, col2 = st.columns(2) - with col1: - selected_date = st.date_input( - "Pilih tanggal mulai prediksi:", - value=max_selectable_date, - min_value=min_selectable_date, - max_value=max_selectable_date, - help=f"Pilih tanggal antara {min_selectable_date} dan {max_selectable_date}" - ) - with col2: - prediction_days = st.slider( - "Pilih jumlah hari untuk prediksi (1-30 hari):", - min_value=1, max_value=30, value=15, step=1 - ) - - if st.button("Buat Prediksi", key="predict_button"): - if prediction_days < 1: - st.warning("Jumlah hari prediksi minimal **1**. Silakan geser slidernya dulu. ๐Ÿ™‚") - st.stop() - - with st.spinner("Memproses dan menjalankan prediksi..."): - # scaling (mengikuti scaler training) - close_values = df[["Close"]].values.astype(float) - scaled_data = scaler.transform(close_values) - - # Cocokkan berdasarkan tanggal (robust) - selected_date_dt = pd.to_datetime(selected_date) - mask = df["Date"].dt.date == selected_date_dt.date() - idxs = np.where(mask.to_numpy())[0] - - if len(idxs) == 0: - st.error(f"Tanggal {selected_date_dt.strftime('%Y-%m-%d')} tidak ditemukan dalam dataset.") - st.stop() - - start_index = int(idxs[0]) - - if start_index - time_step < 0: - st.error( - f"Tanggal terlalu awal untuk diprediksi. " - f"Butuh {time_step} hari data sebelumnya. Pilih tanggal setelah " - f"{(df['Date'].min() + timedelta(days=time_step)).strftime('%Y-%m-%d')}." - ) - st.stop() - - initial_sequence = scaled_data[start_index - time_step: start_index] # (time_step,1) - - # โœ… Prediksi MURNI dari model - future_predictions_scaled = predict_from_sequence_pure(model, initial_sequence, prediction_days) - - # โœ… Inverse transform MURNI dari scaler - future_predictions = scaler.inverse_transform(future_predictions_scaled) - - # sanity-check - if not np.isfinite(future_predictions).all(): - st.error("โŒ Hasil prediksi mengandung NaN/Inf. Periksa kompatibilitas model/scaler.") - st.stop() - - future_dates = [selected_date_dt + timedelta(days=i) for i in range(1, prediction_days + 1)] - - st.markdown("
", unsafe_allow_html=True) - st.subheader("๐Ÿ“Š Grafik Prediksi (Historis + Prediksi)") - fig = create_combined_chart(df, selected_date_dt, future_dates, future_predictions) - st.pyplot(fig) - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - #Test - st.subheader("๐Ÿ“ˆ Grafik Detail Prediksi") - - fig2, ax2 = plt.subplots(figsize=(12, 5)) - - ax2.plot( - future_dates, - future_predictions.flatten(), - marker='o', - linewidth=2.5 - ) - - ax2.set_title( - "Grafik Detail Hasil Prediksi Harga Ethereum", - fontsize=14, - fontweight="bold" - ) - - ax2.set_xlabel("Tanggal") - ax2.set_ylabel("Harga Prediksi (USD)") - - ax2.grid(True, alpha=0.3) - - plt.xticks(rotation=45) - plt.tight_layout() - - st.pyplot(fig2) - - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - st.subheader("๐Ÿงพ Tabel Detail Prediksi") - prediction_table_df = pd.DataFrame({ - "Tanggal": [d.strftime("%Y-%m-%d") for d in future_dates], - "Harga Prediksi (USD)": [f"USD {price[0]:,.0f}" for price in future_predictions] - }) - prediction_table_df.index = prediction_table_df.index + 1 - st.dataframe(prediction_table_df, use_container_width=True) - st.markdown("
", unsafe_allow_html=True) - - last_price = float(df.loc[start_index, "Close"]) - max_prediction = float(np.max(future_predictions)) - min_prediction = float(np.min(future_predictions)) - - st.info(f""" -๐Ÿ“Š **Informasi Prediksi:** -- Harga terakhir pada tanggal {selected_date.strftime('%Y-%m-%d')}: USD {last_price:,.0f} -- Harga prediksi tertinggi: USD {max_prediction:,.0f} -- Harga prediksi terendah: USD {min_prediction:,.0f} -""") - - st.markdown("
", unsafe_allow_html=True) - st.markdown("", unsafe_allow_html=True) - -elif model is None: - st.error("Gagal memuat model. Pastikan file 'gru_model.h5' ada di folder yang sama dengan aplikasi.") -elif scaler is None: - st.error("Gagal memuat scaler. Pastikan file 'scaler_gru.pkl' ada di folder yang sama dengan aplikasi.") -else: - st.error("Gagal memuat data. Coba cek koneksi internet atau sumber data yfinance.") diff --git a/apptest.py b/apptest.py deleted file mode 100644 index 119f52b..0000000 --- a/apptest.py +++ /dev/null @@ -1,479 +0,0 @@ -import requests -import streamlit as st -import joblib -import pandas as pd -import numpy as np -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.models import load_model -from datetime import date, timedelta -import yfinance as yf -from pathlib import Path -import matplotlib.pyplot as plt -import matplotlib.dates as mdates -from matplotlib import rcParams - -# Set font untuk mendukung karakter Indonesia -rcParams['font.family'] = 'DejaVu Sans' - -# --- Konfigurasi Halaman --- -st.set_page_config( - page_title="Prediksi Harga Ethereum (GRU)", - page_icon="๐Ÿช™" -) - -# --- GLOBAL STYLES (UI only) --- -st.markdown(""" - -""", unsafe_allow_html=True) - - -# --- Fungsi-fungsi Bantuan --- - -@st.cache_data(ttl="1h") -def load_eth_data(): - ticker = "ETH-USD" - df = None - - # 1. COBA ONLINE - try: - - df = yf.download( - ticker, - start="2024-01-01", - end=date.today() + timedelta(days=1), - progress=False, - auto_adjust=True, - multi_level_index=False - ) - - if df is not None and not df.empty: - # Bersihkan Index & Kolom - df = df.reset_index() - - new_cols = [] - for col in df.columns: - col_name = col[0] if isinstance(col, tuple) else str(col) - new_cols.append(col_name) - df.columns = new_cols - - # Kolom pertama adalah Date - if 'Date' not in df.columns: - df = df.rename(columns={df.columns[0]: 'Date'}) - - # Hapus Timezone - df['Date'] = pd.to_datetime(df['Date']).dt.tz_localize(None) - - # Simpan Backup (Timpa file lama agar fresh) - try: - df.to_csv("eth_backup.csv", index=False) - except: - pass - - return df, "online" - - except Exception as e: - print(f"Gagal Online: {e}") - - # 2. COBA BACKUP (JIKA ONLINE GAGAL) - try: - df_backup = pd.read_csv("eth_backup.csv") - - # Bersihkan kolom sampah jika ada - if "Unnamed: 0" in df_backup.columns: - df_backup = df_backup.drop(columns=["Unnamed: 0"]) - - # Pastikan kolom Date dikenali - if "Date" in df_backup.columns: - df_backup["Date"] = pd.to_datetime(df_backup["Date"]) - elif df_backup.columns[0].lower() == "date": - df_backup = df_backup.rename(columns={df_backup.columns[0]: "Date"}) - df_backup["Date"] = pd.to_datetime(df_backup["Date"]) - - return df_backup, "backup" - - except FileNotFoundError: - # 3. GAGAL TOTAL - return None, "error" - -def validate_scaler(scaler): - """Validasi scaler agar konsisten dengan training.""" - issues = [] - warnings = [] - - if not isinstance(scaler, MinMaxScaler): - warnings.append(f"Scaler bukan MinMaxScaler (terdeteksi: {type(scaler).__name__}). Pastikan ini scaler training.") - - required_attrs = ["n_features_in_", "data_min_", "data_max_", "scale_", "min_"] - for a in required_attrs: - if not hasattr(scaler, a): - issues.append(f"Scaler belum ter-fit atau tidak valid (atribut '{a}' tidak ada).") - - if hasattr(scaler, "n_features_in_"): - if int(scaler.n_features_in_) != 1: - issues.append(f"Scaler mengharapkan {scaler.n_features_in_} fitur, tapi aplikasi hanya pakai 1 fitur ('Close').") - - ok = (len(issues) == 0) - return ok, issues, warnings - - -def validate_model_input(model): - """Pastikan input model bentuknya (None, time_step, 1).""" - issues = [] - try: - shape = model.input_shape # biasanya (None, time_step, 1) - if not (isinstance(shape, (list, tuple)) and len(shape) == 3): - issues.append(f"input_shape tidak sesuai harapan: {shape} (harus 3 dimensi).") - else: - if shape[2] != 1: - issues.append(f"Jumlah fitur input model = {shape[2]}, tapi aplikasi membentuk fitur=1.") - if shape[1] is None or int(shape[1]) <= 1: - issues.append(f"time_step tidak valid: {shape[1]}.") - except Exception as e: - issues.append(f"Gagal membaca input_shape model: {e}") - - ok = (len(issues) == 0) - return ok, issues - - -@st.cache_resource -def load_gru_assets(): - """Load model & scaler hasil training (wajib pakai scaler yang sama).""" - model_path = Path("gru_model.h5") - scaler_path = Path("scaler_gru.pkl") - - if not model_path.exists() or not scaler_path.exists(): - st.warning( - f"File tidak ditemukan.\n" - f"Model: {model_path.resolve().name} ada? {model_path.exists()}\n" - f"Scaler: {scaler_path.resolve().name} ada? {scaler_path.exists()}" - ) - return None, None - - try: - model = load_model(model_path, compile=False) - scaler = joblib.load(scaler_path) - - ok_scaler, scaler_issues, scaler_warnings = validate_scaler(scaler) - for w in scaler_warnings: - st.warning("โš ๏ธ " + w) - if not ok_scaler: - st.error("โŒ Scaler tidak kompatibel:\n- " + "\n- ".join(scaler_issues)) - return None, None - - ok_model, model_issues = validate_model_input(model) - if not ok_model: - st.error("โŒ Model input tidak kompatibel:\n- " + "\n- ".join(model_issues)) - return None, None - - return model, scaler - - except Exception as e: - st.error(f"Gagal load aset: {e}") - return None, None - - -def predict_from_sequence_pure(model, initial_sequence_scaled, n_days): - - - seq = np.array(initial_sequence_scaled, dtype="float32").reshape(-1) # (time_step,) - preds = [] - - for _ in range(n_days): - x = seq.reshape(1, -1, 1) # (1, time_step, 1) - y = float(model.predict(x, verbose=0)[0, 0]) # output model (scaled) - preds.append(y) - seq = np.append(seq[1:], y) # autoregressive (murni) - - return np.array(preds, dtype="float32").reshape(-1, 1) - - -def create_combined_chart(df, start_date, future_dates, future_predictions): - """Membuat grafik gabungan dengan gaya yang sama seperti referensi.""" - context_start_date = start_date - timedelta(days=609) - recent_df = df[(df["Date"] >= context_start_date) & (df["Date"] <= start_date)].copy() - - window_size = 7 - recent_df["Trend_Aktual"] = recent_df["Close"].rolling(window=window_size).mean() - - predictions_flat = future_predictions.flatten() - - fig, ax = plt.subplots(figsize=(14, 8)) - - - ax.plot( - recent_df["Date"], recent_df["Trend_Aktual"], - color="#1f77b4", linewidth=2.5, label="Tren Harga Aktual", alpha=0.9 - ) - # Membuat tren prediksi menggunakan moving average 7 hari - pred_trend = ( - pd.Series(predictions_flat) - .rolling(window=7, min_periods=1) - .mean() - ) - - ax.plot( - future_dates, - pred_trend, - color="#d62728", - linewidth=2.5, - label="Tren Harga Prediksi (GRU)", - alpha=0.9 - ) - ax.scatter( - recent_df["Date"], recent_df["Close"], - color="black", s=15, alpha=0.6, zorder=5, label="Data Harian Aktual" - ) - - # garis penghubung trend terakhir ke prediksi pertama - try: - last_trend_date = recent_df["Date"].iloc[-1] - last_trend_price = recent_df["Trend_Aktual"].dropna().iloc[-1] - first_prediction_date = future_dates[0] - first_prediction_price = predictions_flat[0] - - ax.plot( - [last_trend_date, first_prediction_date], - [last_trend_price, first_prediction_price], - color="#d62728", linestyle="--", linewidth=2.0, alpha=0.7 - ) - except Exception: - pass - - ax.set_xlabel("Waktu", fontsize=12, fontweight="bold") - ax.set_ylabel("Harga (USD)", fontsize=12, fontweight="bold") - ax.set_title("Analisis Tren Historis dan Prediksi Harga Ethereum", fontsize=16, fontweight="bold", pad=20) - - ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d, %Y")) - plt.xticks(rotation=45) - ax.grid(True, alpha=0.3, linestyle="--") - - handles, labels = ax.get_legend_handles_labels() - order = [2, 0, 1, 3] - if len(handles) == 4: - ax.legend([handles[idx] for idx in order], [labels[idx] for idx in order], loc="upper left", fontsize=11) - else: - ax.legend(loc="upper left", fontsize=11) - - plt.tight_layout() - ax.set_facecolor("#f8f9fa") - fig.patch.set_facecolor("white") - - return fig - - -# --- Tampilan Antarmuka Aplikasi --- - -st.markdown("
Prediksi Harga Ethereum (GRU)
", unsafe_allow_html=True) -st.markdown("
Data historis & prediksi ETH-USD berbasis GRU
", unsafe_allow_html=True) - -df, data_source = load_eth_data() -model, scaler = load_gru_assets() - -# LOGIC UI (TOAST/WARNING) DITARUH DI SINI (DILUAR CACHE) -if data_source == "backup": - st.toast("Koneksi Yahoo lambat. Menggunakan data backup lokal.", icon="โš ๏ธ") -elif data_source == "error": - st.error("โŒ Gagal memuat data (Online gagal & Backup tidak ada).") - -if df is not None and model is not None and scaler is not None: - # Menampilkan tabel data historis - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐Ÿ“š Data Historis Harga Ethereum
", unsafe_allow_html=True) - st.dataframe(df, height=300, use_container_width=True) - st.markdown("
", unsafe_allow_html=True) - - # Menampilkan visualisasi line chart - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐Ÿ“ˆ Visualisasi Harga Historis
", unsafe_allow_html=True) - st.line_chart(df.rename(columns={"Date": "index"}).set_index("index")["Close"]) - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - st.markdown("
๐ŸŽฏ Mulai Prediksi Berdasarkan Tanggal
", unsafe_allow_html=True) - - time_step = int(model.input_shape[1]) - min_selectable_date = (df["Date"].min() + timedelta(days=time_step)).date() - max_selectable_date = df["Date"].max().date() - - col1, col2 = st.columns(2) - with col1: - selected_date = st.date_input( - "Pilih tanggal mulai prediksi:", - value=max_selectable_date, - min_value=min_selectable_date, - max_value=max_selectable_date, - help=f"Pilih tanggal antara {min_selectable_date} dan {max_selectable_date}" - ) - with col2: - prediction_days = st.slider( - "Pilih jumlah hari untuk prediksi (1-30 hari):", - min_value=1, max_value=30, value=15, step=1 - ) - - if st.button("Buat Prediksi", key="predict_button"): - if prediction_days < 1: - st.warning("Jumlah hari prediksi minimal **1**. Silakan geser slidernya dulu. ๐Ÿ™‚") - st.stop() - - with st.spinner("Memproses dan menjalankan prediksi..."): - # scaling (mengikuti scaler training) - close_values = df[["Close"]].values.astype(float) - scaled_data = scaler.transform(close_values) - - # Cocokkan berdasarkan tanggal (robust) - selected_date_dt = pd.to_datetime(selected_date) - mask = df["Date"].dt.date == selected_date_dt.date() - idxs = np.where(mask.to_numpy())[0] - - if len(idxs) == 0: - st.error(f"Tanggal {selected_date_dt.strftime('%Y-%m-%d')} tidak ditemukan dalam dataset.") - st.stop() - - start_index = int(idxs[0]) - - if start_index - time_step < 0: - st.error( - f"Tanggal terlalu awal untuk diprediksi. " - f"Butuh {time_step} hari data sebelumnya. Pilih tanggal setelah " - f"{(df['Date'].min() + timedelta(days=time_step)).strftime('%Y-%m-%d')}." - ) - st.stop() - - initial_sequence = scaled_data[start_index - time_step: start_index] # (time_step,1) - - # โœ… Prediksi MURNI dari model - future_predictions_scaled = predict_from_sequence_pure(model, initial_sequence, prediction_days) - - # โœ… Inverse transform MURNI dari scaler - future_predictions = scaler.inverse_transform(future_predictions_scaled) - - # sanity-check - if not np.isfinite(future_predictions).all(): - st.error("โŒ Hasil prediksi mengandung NaN/Inf. Periksa kompatibilitas model/scaler.") - st.stop() - - future_dates = [selected_date_dt + timedelta(days=i) for i in range(1, prediction_days + 1)] - - st.markdown("
", unsafe_allow_html=True) - st.subheader("๐Ÿ“Š Grafik Prediksi (Historis + Prediksi)") - fig = create_combined_chart(df, selected_date_dt, future_dates, future_predictions) - st.pyplot(fig) - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - #Test - st.subheader("๐Ÿ“ˆ Grafik Detail Prediksi") - - fig2, ax2 = plt.subplots(figsize=(12, 5)) - - ax2.plot( - future_dates, - future_predictions.flatten(), - marker='o', - linewidth=2.5 - ) - - ax2.set_title( - "Grafik Detail Hasil Prediksi Harga Ethereum", - fontsize=14, - fontweight="bold" - ) - - ax2.set_xlabel("Tanggal") - ax2.set_ylabel("Harga Prediksi (USD)") - - ax2.grid(True, alpha=0.3) - - plt.xticks(rotation=45) - plt.tight_layout() - - st.pyplot(fig2) - - st.markdown("
", unsafe_allow_html=True) - - st.markdown("
", unsafe_allow_html=True) - - st.subheader("๐Ÿงพ Tabel Detail Prediksi") - prediction_table_df = pd.DataFrame({ - "Tanggal": [d.strftime("%Y-%m-%d") for d in future_dates], - "Harga Prediksi (USD)": [f"USD {price[0]:,.0f}" for price in future_predictions] - }) - prediction_table_df.index = prediction_table_df.index + 1 - st.dataframe(prediction_table_df, use_container_width=True) - st.markdown("
", unsafe_allow_html=True) - - last_price = float(df.loc[start_index, "Close"]) - max_prediction = float(np.max(future_predictions)) - min_prediction = float(np.min(future_predictions)) - - st.info(f""" -๐Ÿ“Š **Informasi Prediksi:** -- Harga terakhir pada tanggal {selected_date.strftime('%Y-%m-%d')}: USD {last_price:,.0f} -- Harga prediksi tertinggi: USD {max_prediction:,.0f} -- Harga prediksi terendah: USD {min_prediction:,.0f} -""") - - st.markdown("
", unsafe_allow_html=True) - st.markdown("", unsafe_allow_html=True) - -elif model is None: - st.error("Gagal memuat model. Pastikan file 'gru_model.h5' ada di folder yang sama dengan aplikasi.") -elif scaler is None: - st.error("Gagal memuat scaler. Pastikan file 'scaler_gru.pkl' ada di folder yang sama dengan aplikasi.") -else: - st.error("Gagal memuat data. Coba cek koneksi internet atau sumber data yfinance.") diff --git a/eth_backup.csv b/eth_backup.csv index 33c3b33..695e344 100644 --- a/eth_backup.csv +++ b/eth_backup.csv @@ -908,4 +908,21 @@ Date,Close,High,Low,Open,Volume 2026-06-25,1564.816650390625,1656.646484375,1531.7803955078125,1619.8853759765625,15976182558 2026-06-26,1576.6160888671875,1590.2000732421875,1510.505859375,1564.6136474609375,15272225856 2026-06-27,1571.5882568359375,1607.865966796875,1561.972900390625,1576.5911865234375,6111411513 -2026-06-28,1579.5999755859375,1582.509033203125,1561.6748046875,1571.6817626953125,6003073536 +2026-06-28,1570.3612060546875,1585.2197265625,1548.761962890625,1571.564453125,6231562560 +2026-06-29,1610.205810546875,1633.64501953125,1550.036376953125,1569.993408203125,11882501822 +2026-06-30,1569.5838623046875,1611.3472900390625,1549.251953125,1610.163818359375,9639804637 +2026-07-01,1608.960693359375,1642.454833984375,1551.1392822265625,1569.90966796875,11666600557 +2026-07-02,1698.16943359375,1720.34326171875,1595.9393310546875,1609.0125732421875,13538465176 +2026-07-03,1756.5186767578125,1772.543212890625,1693.038330078125,1698.41796875,10177742550 +2026-07-04,1779.032958984375,1804.5887451171875,1743.564208984375,1756.57568359375,8083146790 +2026-07-05,1783.0059814453125,1805.9140625,1749.1376953125,1779.042724609375,11323496681 +2026-07-06,1797.57373046875,1829.513671875,1728.9652099609375,1783.002685546875,16997487730 +2026-07-07,1768.511474609375,1810.329833984375,1756.712646484375,1797.562255859375,9901110376 +2026-07-08,1742.6776123046875,1782.8583984375,1711.9000244140625,1768.3896484375,10110272581 +2026-07-09,1744.484619140625,1760.206298828125,1721.298583984375,1742.664794921875,7763668656 +2026-07-10,1795.6932373046875,1809.75830078125,1736.72314453125,1744.47314453125,9431692646 +2026-07-11,1788.099609375,1827.83154296875,1786.295654296875,1795.8193359375,6901680020 +2026-07-12,1805.7880859375,1824.98046875,1779.6370849609375,1787.568359375,6064480351 +2026-07-13,1773.500732421875,1842.455078125,1749.3475341796875,1805.7579345703125,11436340419 +2026-07-14,1889.4798583984375,1894.062255859375,1772.0177001953125,1773.471435546875,13835437084 +2026-07-15,1932.93994140625,1938.8372802734375,1863.8087158203125,1889.969482421875,13269965824