From 38b54f89b9825c2b9cae92628ef75532af8212c7 Mon Sep 17 00:00:00 2001 From: Azmikun1 Date: Sun, 28 Jun 2026 21:20:28 +0700 Subject: [PATCH] new --- app1.py | 452 ++++++++++++++++++++++++---------------------- apptest.py | 479 +++++++++++++++++++++++++++++++++++++++++++++++++ eth_backup.csv | 25 ++- 3 files changed, 737 insertions(+), 219 deletions(-) create mode 100644 apptest.py diff --git a/app1.py b/app1.py index f6a3377..119f52b 100644 --- a/app1.py +++ b/app1.py @@ -3,10 +3,10 @@ import streamlit as st import joblib import pandas as pd import numpy as np -import os # Tambahan untuk manajemen file 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 @@ -18,19 +18,9 @@ rcParams['font.family'] = 'DejaVu Sans' # --- Konfigurasi Halaman --- st.set_page_config( page_title="Prediksi Harga Ethereum (GRU)", - page_icon="๐Ÿช™", - layout="wide" # Opsional: agar tampilan lebih luas + page_icon="๐Ÿช™" ) -# --- KONFIGURASI FILE ADMIN --- -DATA_FOLDER = 'dataset' -DATA_FILE = 'databackup_eth.csv' -DATA_PATH = os.path.join(DATA_FOLDER, DATA_FILE) - -# Pastikan folder dataset ada -if not os.path.exists(DATA_FOLDER): - os.makedirs(DATA_FOLDER) - # --- 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 4bd28de..6d37902 100644 --- a/eth_backup.csv +++ b/eth_backup.csv @@ -882,4 +882,27 @@ Date,Close,High,Low,Open,Volume 2026-05-30,2019.458251953125,2028.4417724609375,2000.165771484375,2011.909423828125,7478416074 2026-05-31,2004.341552734375,2034.1192626953125,1991.90673828125,2019.242431640625,9253505209 2026-06-01,2003.22119140625,2017.6077880859375,1956.1522216796875,2004.2425537109375,19458810001 -2026-06-03,1848.1400146484375,1870.7220458984375,1848.860107421875,1857.32568359375,25659957248 +2026-06-02,1857.71630859375,2003.970703125,1838.05859375,2003.26025390625,25205401403 +2026-06-03,1811.7322998046875,1889.058837890625,1771.3145751953125,1857.6417236328125,24083350214 +2026-06-04,1769.58056640625,1817.3465576171875,1717.802490234375,1811.7425537109375,27112623897 +2026-06-05,1580.86083984375,1772.1077880859375,1540.1619873046875,1769.986083984375,39924177669 +2026-06-06,1568.7677001953125,1599.9534912109375,1506.5057373046875,1580.88037109375,18426248218 +2026-06-07,1686.396240234375,1714.970703125,1563.77587890625,1568.74951171875,15172606860 +2026-06-08,1690.1485595703125,1713.4710693359375,1644.79150390625,1686.251220703125,17427269148 +2026-06-09,1637.7066650390625,1695.52294921875,1613.834228515625,1690.21337890625,15196855528 +2026-06-10,1620.1376953125,1665.7607421875,1603.8258056640625,1637.6348876953125,12655731412 +2026-06-11,1672.2806396484375,1690.4488525390625,1620.08447265625,1620.1400146484375,12379882531 +2026-06-12,1665.1278076171875,1689.4443359375,1650.628662109375,1671.8189697265625,9989137815 +2026-06-13,1680.214599609375,1693.603271484375,1661.609130859375,1665.0487060546875,6101094488 +2026-06-14,1724.61328125,1729.350830078125,1654.2105712890625,1680.146484375,7804982667 +2026-06-15,1794.961181640625,1847.769775390625,1709.253662109375,1724.5716552734375,17921331594 +2026-06-16,1790.398193359375,1837.2041015625,1758.130126953125,1794.93798828125,14698492893 +2026-06-17,1747.885498046875,1807.2828369140625,1724.7178955078125,1790.396240234375,14185457929 +2026-06-18,1709.533447265625,1760.880859375,1670.1038818359375,1747.8778076171875,12942260783 +2026-06-19,1710.982421875,1717.1932373046875,1678.1962890625,1709.4986572265625,7855725190 +2026-06-20,1739.3013916015625,1747.259521484375,1702.8017578125,1710.982177734375,7773536600 +2026-06-21,1704.58056640625,1739.48583984375,1701.9364013671875,1739.341064453125,8498711726 +2026-06-22,1726.5108642578125,1776.8321533203125,1704.5166015625,1704.6304931640625,13584368622 +2026-06-23,1665.436767578125,1733.6123046875,1638.253173828125,1726.519775390625,10431438278 +2026-06-24,1619.9249267578125,1687.4923095703125,1551.4842529296875,1665.309814453125,14059894232 +2026-06-26,1556.2900390625,1568.429443359375,1556.8143310546875,1564.8594970703125,15403906048