File size: 9,736 Bytes
7458986
db6d471
65d5f73
7458986
 
 
 
db6d471
7458986
 
65d5f73
cd2ec3f
7458986
db6d471
33fb7c0
db6d471
 
972a547
05c066f
cd2ec3f
7458986
db6d471
 
 
 
 
 
7458986
cd2ec3f
05c066f
7458986
cd2ec3f
525527f
65d5f73
 
db6d471
cd2ec3f
 
 
db6d471
972a547
65d5f73
 
db6d471
 
cd2ec3f
db6d471
cd2ec3f
39f2b35
33fb7c0
 
 
cd2ec3f
39f2b35
33fb7c0
 
 
 
 
 
 
 
cd2ec3f
 
 
 
 
972a547
db6d471
 
 
 
cd2ec3f
05c066f
65d5f73
db6d471
117e6a7
 
 
 
 
 
 
 
cd2ec3f
 
 
117e6a7
db6d471
cd2ec3f
 
39f2b35
cd2ec3f
 
db6d471
33fb7c0
 
39f2b35
33fb7c0
db6d471
 
cd2ec3f
db6d471
 
 
 
33fb7c0
db6d471
 
 
 
cd2ec3f
db6d471
39f2b35
33fb7c0
 
 
117e6a7
05c066f
cd2ec3f
33fb7c0
 
 
05c066f
380e150
39f2b35
 
 
 
 
 
 
 
 
 
 
 
cd2ec3f
daab9d7
 
 
 
 
 
 
 
dbe373a
65d5f73
cd2ec3f
db6d471
 
 
c67acf2
daab9d7
 
 
 
 
 
 
 
3686f17
 
 
dbe373a
9c4f0cf
cd2ec3f
 
 
 
 
 
 
 
 
 
 
 
 
39f2b35
daab9d7
cd2ec3f
9c4f0cf
 
 
 
 
cd2ec3f
 
9c4f0cf
cd2ec3f
 
 
daab9d7
db6d471
65d5f73
cd2ec3f
db6d471
daab9d7
cd2ec3f
 
 
 
 
daab9d7
 
db6d471
 
 
 
65d5f73
972a547
cd2ec3f
db6d471
8dd55d0
db6d471
8dd55d0
 
cd2ec3f
8dd55d0
 
daab9d7
8dd55d0
 
 
 
 
 
 
 
 
 
 
 
 
daab9d7
cd2ec3f
daab9d7
8dd55d0
 
 
 
 
daab9d7
8dd55d0
 
 
cd2ec3f
c54bdd8
65d5f73
db6d471
cd2ec3f
daab9d7
65d5f73
7458986
db6d471
3686f17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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)