Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| from datetime import datetime, timedelta | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import warnings | |
| import yfinance as yf | |
| from typing import Dict, List, Tuple | |
| import atexit | |
| warnings.filterwarnings('ignore') | |
| from models import load_timesfm_model, predict_stock_prices | |
| from utils import ( | |
| get_idx_stocks, fetch_stock_data, prepare_timesfm_data, | |
| create_forecast_plot, get_stock_info, market_status_manager, | |
| calculate_technical_indicators | |
| ) | |
| from config import IDX_STOCKS, DEFAULT_PERIOD, DEFAULT_FORECAST_HORIZON, IDX_MARKET_CONFIG | |
| # Load model at startup | |
| model = None | |
| def load_model(): | |
| """Load the Chronos Pipeline""" | |
| global model | |
| if model is None: | |
| model = load_timesfm_model() | |
| return model | |
| def get_stock_choices(): | |
| """Get available stock choices for dropdown""" | |
| return list(IDX_STOCKS.keys()) | |
| def get_idx_market_status() -> str: | |
| """Get formatted IDX market status for display""" | |
| status = market_status_manager.get_status('IDX_STOCKS') | |
| status_emoji = "🟢" if status.is_open else "🔴" | |
| status_text = "BUKA" if status.is_open else "TUTUP" | |
| info_lines = [ | |
| f"**Status Saat Ini:** {status.status_text} (WIB)", | |
| f"**Waktu Buka/Tutup:** {IDX_MARKET_CONFIG['IDX_STOCKS']['open_time']} - {IDX_MARKET_CONFIG['IDX_STOCKS']['close_time']} WIB", | |
| f"**Waktu Sampai Tutup:** {status.time_until_close}", | |
| f"**Hari Trading Berikutnya:** {status.next_trading_day}", | |
| f"**Waktu Sampai Buka:** {status.time_until_open}", | |
| f"**Terakhir Diperbarui:** {status.last_updated}" | |
| ] | |
| return f"## {status_emoji} {status.market_name}: {status_text}\n\n" + "\n".join(info_lines) | |
| def analyze_stock(symbol, period, forecast_horizon, use_volume): | |
| """Main analysis function""" | |
| try: | |
| # Load model if not already loaded | |
| model = load_model() | |
| # Fetch stock data. This now includes technical indicators. | |
| stock_data = fetch_stock_data(symbol, period) | |
| if stock_data is None or len(stock_data) < 30: | |
| return None, None, None, get_idx_market_status() + f"\n\nError: Data tidak mencukupi untuk {symbol}. Coba periode yang lebih panjang." | |
| # Prepare data for Chronos (unscaled 'Close' prices) | |
| timesfm_data, scaler = prepare_timesfm_data(stock_data, use_volume) | |
| # Make predictions | |
| forecast_prices = predict_stock_prices(model, timesfm_data, forecast_horizon) | |
| # Create dates for forecast | |
| last_date = stock_data.index[-1] | |
| forecast_dates = pd.date_range( | |
| start=last_date + timedelta(days=1), | |
| periods=forecast_horizon, | |
| freq='D' | |
| ) | |
| # Calculate last price and technical summary | |
| last_price = stock_data['Close'].iloc[-1] | |
| # Create forecast plot (using the simplified utility function) | |
| fig = create_forecast_plot(stock_data, forecast_dates, forecast_prices, symbol) | |
| # --- Create Summary Table --- | |
| # Get last technical indicator values | |
| last_rsi = stock_data['RSI'].iloc[-1] | |
| last_macd = stock_data['MACD'].iloc[-1] | |
| last_macd_signal = stock_data['MACD_Signal'].iloc[-1] | |
| summary_data = { | |
| 'Metrik': ['Harga Saat Ini', 'Puncak Prediksi', 'Dasar Prediksi', | |
| 'Perubahan Prediksi (%)', 'Volatilitas Historis', 'RSI (14 Hari)', 'Sinyal MACD'], | |
| 'Nilai': [ | |
| f"Rp {last_price:,.2f}", | |
| f"Rp {np.max(forecast_prices):,.2f}", | |
| f"Rp {np.min(forecast_prices):,.2f}", | |
| f"{((np.mean(forecast_prices) - last_price) / last_price * 100):.2f}%", | |
| f"{stock_data['Volatility'].iloc[-1] * np.sqrt(252) * 100:.2f}% (Tahunan)", | |
| f"{last_rsi:.2f}", | |
| f"{'Beli' if last_macd > last_macd_signal else 'Jual' if last_macd < last_macd_signal else 'Netral'}" | |
| ] | |
| } | |
| summary_df = pd.DataFrame(summary_data) | |
| # --- Get Additional Stock Info --- | |
| stock_info = get_stock_info(symbol) | |
| info_text = f""" | |
| **{stock_info.get('shortName', symbol)}** | |
| **Statistik Saat Ini:** | |
| - Kapitalisasi Pasar: {stock_info.get('marketCap', 'N/A')} | |
| - Volume Trading: {stock_data['Volume'].iloc[-1]:,.0f} | |
| - 52W Tertinggi: Rp {stock_info.get('fiftyTwoWeekHigh', 'N/A'):,.0f} | |
| - 52W Terendah: Rp {stock_info.get('fiftyTwoWeekLow', 'N/A'):,.0f} | |
| **Ringkasan Prediksi:** | |
| - Periode Prediksi: {forecast_horizon} hari | |
| - Tren Harapan: {'Naik' if np.mean(forecast_prices) > last_price else 'Turun'} | |
| - Rentang Harga: Rp {np.min(forecast_prices):,.2f} - Rp {np.max(forecast_prices):,.2f} | |
| """ | |
| return fig, summary_df, info_text, get_idx_market_status() | |
| except Exception as e: | |
| return None, None, None, get_idx_market_status() + f"\n\nError occurred during analysis: {str(e)}" | |
| def create_interface(): | |
| """Create the Gradio interface""" | |
| # Register cleanup function | |
| def cleanup_on_exit(): | |
| market_status_manager.stop() | |
| print("Market status manager stopped successfully") | |
| atexit.register(cleanup_on_exit) | |
| with gr.Blocks( | |
| title="IDX Stock Price Predictor (Chronos-Bolt)", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .gradio-container { | |
| max-width: 1200px !important; | |
| } | |
| .plot-container { | |
| height: 500px !important; | |
| } | |
| """ | |
| ) as demo: | |
| gr.Markdown( | |
| """ | |
| # 🇮🇩 IDX Stock Price Predictor | |
| **Didukung oleh Model Chronos-Bolt (Base) Amazon** | |
| Memprediksi harga saham Bursa Efek Indonesia (IDX) menggunakan model *Time Series Foundation Model* Chronos-Bolt. | |
| **Instruksi:** | |
| 1. Pilih saham IDX dari *dropdown* | |
| 2. Tentukan periode data historis (Hari) | |
| 3. Tentukan horison prediksi (Hari) | |
| 4. Klik "Analisis Saham" untuk melihat prediksi | |
| """ | |
| ) | |
| # --- Market Status Display --- | |
| market_status_output = gr.Markdown( | |
| value=get_idx_market_status(), | |
| label="Status Pasar IDX Saat Ini" | |
| ) | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📊 Parameter Analisis") | |
| stock_dropdown = gr.Dropdown( | |
| choices=get_stock_choices(), | |
| value="BBCA.JK", | |
| label="Pilih Saham", | |
| info="Pilih dari saham-saham utama IDX" | |
| ) | |
| period_slider = gr.Slider( | |
| minimum=30, | |
| maximum=365, | |
| value=DEFAULT_PERIOD, | |
| step=1, | |
| label="Periode Historis (Hari)", | |
| info="Jumlah hari data historis yang akan digunakan" | |
| ) | |
| forecast_slider = gr.Slider( | |
| minimum=1, | |
| maximum=30, | |
| value=DEFAULT_FORECAST_HORIZON, | |
| step=1, | |
| label="Horison Prediksi (Hari)", | |
| info="Jumlah hari ke depan yang akan diprediksi" | |
| ) | |
| volume_checkbox = gr.Checkbox( | |
| label="Sertakan Volume dalam Analisis (Eksperimental)", | |
| value=False, | |
| info="Gunakan volume trading sebagai fitur tambahan (mungkin tidak kompatibel dengan Chronos-Bolt)" | |
| ) | |
| analyze_btn = gr.Button( | |
| "🔍 Analisis Saham", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=2): | |
| gr.Markdown("### 📈 Hasil Prediksi") | |
| info_output = gr.Markdown( | |
| label="Detail Saham", | |
| value="Pilih saham dan klik analisis untuk melihat informasi." | |
| ) | |
| with gr.Tab("Grafik Prediksi"): | |
| plot_output = gr.Plot( | |
| label="Prediksi Harga", | |
| show_label=False | |
| ) | |
| with gr.Tab("Ringkasan Statistik"): | |
| summary_output = gr.DataFrame( | |
| label="Ringkasan Prediksi", | |
| show_label=False | |
| ) | |
| # Examples section | |
| gr.Markdown("### 💡 Contoh Cepat") | |
| examples = gr.Examples( | |
| examples=[ | |
| ["BBCA.JK", 90, 7, False], | |
| ["TLKM.JK", 60, 14, True], | |
| ["UNVR.JK", 120, 10, False], | |
| ["BMRI.JK", 180, 5, True], | |
| ], | |
| inputs=[stock_dropdown, period_slider, forecast_slider, volume_checkbox], | |
| outputs=[plot_output, summary_output, info_output, market_status_output], | |
| fn=analyze_stock, | |
| cache_examples=False | |
| ) | |
| # Footer | |
| gr.Markdown( | |
| """ | |
| --- | |
| **⚠️ Disclaimer:** Alat ini hanya untuk tujuan edukasi. Prediksi pasar saham tidak pasti dan tidak boleh digunakan sebagai nasihat keuangan. Selalu berkonsultasi dengan penasihat keuangan yang berkualifikasi sebelum membuat keputusan investasi. | |
| **Sumber Data:** Yahoo Finance | **Model:** Amazon Chronos-Bolt-Base | |
| """ | |
| ) | |
| # Event handlers | |
| analyze_btn.click( | |
| fn=analyze_stock, | |
| inputs=[stock_dropdown, period_slider, forecast_slider, volume_checkbox], | |
| outputs=[plot_output, summary_output, info_output, market_status_output], | |
| show_progress=True | |
| ) | |
| # Initial load to update market status and load model | |
| demo.load( | |
| fn=lambda: (load_model(), get_idx_market_status()), | |
| outputs=[gr.State(), market_status_output], | |
| show_progress=False | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| import atexit | |
| demo = create_interface() | |
| # Cleanup atexit is registered inside create_interface | |
| demo.launch(share=True, server_name="0.0.0.0") |