idx-2 / app.py
omniverse1's picture
update app
087ca47 verified
Raw
History Blame Contribute Delete
11.1 kB
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")