Spaces:
Running
Running
| 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) |