idx-2 / app.py
omniverse1's picture
update app
087ca47 verified
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
# Mengimpor modul utils secara keseluruhan
import utils
from config import 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_idx_market_status() -> str:
"""Get formatted IDX market status for display"""
# Mengakses manager melalui fungsi getter
manager = utils.get_market_manager()
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 qualify_symbol(symbol: str) -> str:
"""Tambahkan ekstensi pasar IDX (.JK) jika belum ada dan pastikan uppercase."""
# Hapus spasi dan jadikan huruf besar
symbol = symbol.strip().upper()
# Periksa apakah sudah diakhiri dengan .JK
if not symbol.endswith(".JK"):
symbol += ".JK"
return symbol
def analyze_stock(unqualified_symbol, period, forecast_horizon, use_volume):
"""Main analysis function"""
# 1. QUALIFY SYMBOL
qualified_symbol = qualify_symbol(unqualified_symbol)
try:
# Load model if not already loaded
model = load_model()
# Fetch stock data. This now includes technical indicators.
stock_data = utils.fetch_stock_data(qualified_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 {qualified_symbol}. Coba periode yang lebih panjang atau pastikan simbol benar."
# Prepare data for Chronos (unscaled 'Close' prices)
timesfm_data, scaler = utils.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
fig = utils.create_forecast_plot(stock_data, forecast_dates, forecast_prices, qualified_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 = utils.get_stock_info(qualified_symbol)
info_text = f"""
**{stock_info.get('shortName', qualified_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 for {qualified_symbol}: {str(e)}"
def create_interface():
"""Create the Gradio interface"""
def cleanup_on_exit():
# MENGGUNAKAN get_market_manager()
manager = utils.get_market_manager()
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. **Input kode saham IDX (misal: BBCA)**
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")
# Mengganti Dropdown menjadi Textbox untuk input manual
stock_input = gr.Textbox(
value="BBCA",
label="Input Kode Saham IDX (misal: BBCA)",
info="Otomatis ditambahkan ekstensi pasar '.JK'"
)
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", 90, 7, False],
["TLKM", 60, 14, True],
["UNVR", 120, 10, False],
["BMRI", 180, 5, True],
],
inputs=[stock_input, 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_input, 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")