IDX-Chronos / app.py
omniverse1's picture
Update app.py
39f2b35 verified
import gradio as gr
import pandas as pd
import yfinance as yf
from utils import (
calculate_technical_indicators,
generate_trading_signals,
get_fundamental_data,
predict_prices,
create_price_chart,
create_technical_chart,
create_prediction_chart,
calculate_advanced_risk_metrics
)
import warnings
import numpy as np
warnings.filterwarnings("ignore")
def analyze_stock(symbol, prediction_days=30):
try:
if not symbol.strip():
raise ValueError("Please enter a valid stock symbol.")
if not symbol.endswith(".JK"):
symbol = symbol.upper() + ".JK"
stock = yf.Ticker(symbol)
data = stock.history(period="1y", interval="1d")
if data.empty:
raise ValueError("No price data available for this stock.")
indicators = calculate_technical_indicators(data)
signals = generate_trading_signals(data, indicators)
fundamental_info = get_fundamental_data(stock)
risk_metrics = calculate_advanced_risk_metrics(data.copy())
predictions = predict_prices(data, prediction_days=prediction_days)
fig_price = create_price_chart(data, indicators)
fig_technical = create_technical_chart(data, indicators)
fig_prediction = create_prediction_chart(data, predictions)
# kalkulasi TP1, TP2, SL yang diperbarui berdasarkan quantiles/range prediksi
last_price = data['Close'].iloc[-1]
# Dapatkan array prediksi dengan fallback ke array yang berisi harga terakhir
q05 = predictions.get('values', np.array([last_price]))
q01 = predictions.get('q01', np.array([last_price * 0.95]))
q09 = predictions.get('q09', np.array([last_price * 1.05]))
# Robust max/min calculation
q05_max = np.max(q05) if q05.size > 0 else last_price
q09_max = np.max(q09) if q09.size > 0 else last_price * 1.05
q01_min = np.min(q01) if q01.size > 0 else last_price * 0.95
# Target/SL calculation
tp1 = (last_price + q05_max) / 2
tp2 = q09_max
sl = q01_min
if tp1 > tp2:
tp1, tp2 = tp2, tp1
if sl > last_price:
sl = last_price * 0.95 # Fallback
predictions["tp1"] = tp1
predictions["tp2"] = tp2
predictions["sl"] = sl
return fundamental_info, indicators, signals, risk_metrics, fig_price, fig_technical, fig_prediction, predictions
except Exception as e:
print(f"Error analyzing {symbol}: {e}")
try:
stock = yf.Ticker(symbol)
data = stock.history(period="1d", interval="1d")
last_price = data['Close'].iloc[-1] if not data.empty else 0
except:
last_price = 0
default_tp1 = last_price * 1.01
default_tp2 = last_price * 1.02
default_sl = last_price * 0.95
empty_predictions = {
"high_30d": 0, "low_30d": 0, "change_pct": 0,
"summary": f"Prediction unavailable. Model error: {e}",
# Menggunakan list kosong sebagai fallback jika error terjadi
"q01": [], "q09": [],
"tp1": default_tp1, "tp2": default_tp2, "sl": default_sl,
}
empty_risk = {"error": "Prediction Model Failed to Load/Run. See console for details."}
# Mengembalikan None untuk output plot Gradio untuk membersihkan plot
return {}, {}, {}, empty_risk, None, None, None, empty_predictions
def update_analysis(symbol, prediction_days):
(
fundamental_info,
indicators,
signals,
risk_metrics,
fig_price,
fig_technical,
fig_prediction,
predictions,
) = analyze_stock(symbol, prediction_days)
# Cek apakah ada plot yang None (berarti ada error)
if fig_price is None:
error_msg = f"Unable to run AI prediction or fetch data for {symbol.upper()}. Check the model logs for details."
tp_sl_info = f"<b>TP1:</b> Rp{predictions.get('tp1', 0):,.2f}<br><b>TP2:</b> Rp{predictions.get('tp2', 0):,.2f}<br><b>Stop Loss:</b> Rp{predictions.get('sl', 0):,.2f}<br><br><b>Model Insight:</b><br>{predictions.get('summary', 'Data fetching or model execution failed. Cannot proceed with analysis.')}"
return (
f"""<div style="color: red; padding: 10px; border: 1px solid red; border-radius: 5px;">{error_msg}</div><br>{tp_sl_info}""",
None, # fig_price
None, # fig_technical
None, # fig_prediction
)
# --- AI FORECAST ---
q01_values = predictions.get('q01', [])
q09_values = predictions.get('q09', [])
# FIX: Robust check untuk array NumPy/list kosong
# Konversi ke NumPy array dan periksa ukuran (.size > 0) untuk menghindari ValueError
q01_arr = np.array(q01_values)
q09_arr = np.array(q09_values)
band_min = float(np.min(q01_arr)) if q01_arr.size > 0 else 0
band_max = float(np.max(q09_arr)) if q09_arr.size > 0 else 0
# --- FUNDAMENTALS ---
fundamentals = f"""
<h4>COMPANY FUNDAMENTALS</h4>
<b>Name:</b> {fundamental_info.get('name', 'N/A')} ({symbol.upper()})<br>
<b>Current Price:</b> Rp{fundamental_info.get('current_price', 0):,.2f}<br>
<b>Market Cap:</b> {fundamental_info.get('market_cap', 0):,}<br>
<b>P/E Ratio:</b> {fundamental_info.get('pe_ratio', 0):.2f}<br>
<b>Dividend Yield:</b> {fundamental_info.get('dividend_yield', 0):.2f}%<br>
<b>Volume:</b> {fundamental_info.get('volume', 0):,}<br>
"""
# --- TECHNICAL SIGNAL ---
details_list = "".join(
[f"<li>{line.strip()}</li>" for line in signals.get("details", "").split("\n") if line.strip()]
)
trading_signal = f"""
<h4>TECHNICAL SIGNAL SUMMARY</h4>
<b>Overall Trend:</b> {signals.get('overall', 'N/A')}<br>
<b>Signal Strength:</b> {signals.get('strength', 0):.2f}%<br>
<b>Support:</b> Rp{signals.get('support', 0):,.2f}<br>
<b>Resistance:</b> Rp{signals.get('resistance', 0):,.2f}<br>
<b>Stop Loss:</b> Rp{signals.get('stop_loss', 0):,.2f}<br><br>
<b>Detailed Signals:</b>
<ul style="margin-top: 8px; padding-left: 20px; line-height: 1.6;">
{details_list}
</ul>
"""
# --- RISK METRICS ---
risk_details = ""
if "error" in risk_metrics:
risk_details = f"<b style='color: red;'>{risk_metrics['error']}</b>"
else:
for key, value in risk_metrics.items():
risk_details += f"<b>{key.replace('_', ' ')}:</b> {value}<br>"
risk_report = f"""
<h4>ADVANCED RISK METRICS (1Y HISTORICAL)</h4>
{risk_details}
"""
# --- AI FORECAST (Lanjutan) ---
prediction = f"""
<h4>{prediction_days}-DAY AI FORECAST (CHRONOS-2 + COVARIATES)</h4>
<b>Predicted Median High:</b> Rp{predictions.get('high_30d', 0):,.2f}<br>
<b>Predicted Median Low:</b> Rp{predictions.get('low_30d', 0):,.2f}<br>
<b>Expected Change:</b> {predictions.get('change_pct', 0):.2f}%<br>
---
<h4>RISK ANALYSIS (90% CONFIDENCE)</h4>
<b>Min. Possible Price (Q0.1):</b> Rp{band_min:,.2f}<br>
<b>Max. Possible Price (Q0.9):</b> Rp{band_max:,.2f}<br>
---
<b>TP1 (Conservative Target):</b> Rp{predictions.get('tp1', 0):,.2f}<br>
<b>TP2 (Aggressive Target):</b> Rp{predictions.get('tp2', 0):,.2f}<br>
<b>Stop Loss (Q0.1 based):</b> Rp{predictions.get('sl', 0):,.2f}<br><br>
<b>Model Insight:</b><br>{predictions.get('summary', 'No analysis available')}
"""
# Menggunakan tata letak 4 kolom/panel
return (
f"""
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px;">
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px; height: 100%;">{fundamentals}</div>
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px; height: 100%;">{trading_signal}</div>
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px; height: 100%;">{risk_report}</div>
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px; height: 100%;">{prediction}</div>
</div>
""",
fig_price,
fig_technical,
fig_prediction,
)
# --- Gradio Interface ---
with gr.Blocks(
title="REXPRO FINANCIAL AI DASHBOARD"
) as app:
gr.Markdown("# REXPRO FINANCIAL AI DASHBOARD")
gr.Markdown(
"Comprehensive stock analytics powered by **AI forecasting, advanced risk metrics, and future technical analysis.**"
)
with gr.Row():
symbol = gr.Textbox(
label="STOCK SYMBOL (IDX)",
value="BBCA",
placeholder="Example: BBCA, TLKM, ADRO, BMRI",
interactive=True,
)
prediction_days = gr.Slider(
label="FORECAST PERIOD (DAYS)",
minimum=5,
maximum=60,
step=5,
value=30,
interactive=True,
)
analyze_button = gr.Button("RUN ADVANCED ANALYSIS")
gr.Markdown("---")
report_section = gr.HTML()
gr.Markdown("---")
with gr.Tab("MARKET CHARTS"):
with gr.Row():
price_chart = gr.Plot(label="PRICE & MOVING AVERAGES")
technical_chart = gr.Plot(label="TECHNICAL INDICATORS OVERVIEW")
gr.Markdown("---")
prediction_chart = gr.Plot(label="AI FORECAST & FUTURE TECHNICAL PROJECTION")
analyze_button.click(
fn=update_analysis,
inputs=[symbol, prediction_days],
outputs=[report_section, price_chart, technical_chart, prediction_chart],
)
if __name__ == "__main__":
app.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=True)