idx-2 / app.py
omniverse1's picture
update app 1.5
81179e3 verified
raw
history blame
10.7 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
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")