File size: 9,602 Bytes
7458986
db6d471
65d5f73
7458986
 
 
 
db6d471
7458986
 
65d5f73
cd2ec3f
 
7458986
db6d471
cd2ec3f
db6d471
 
972a547
05c066f
cd2ec3f
7458986
db6d471
 
 
 
 
 
7458986
cd2ec3f
 
05c066f
7458986
cd2ec3f
525527f
cd2ec3f
65d5f73
 
db6d471
cd2ec3f
 
 
 
 
db6d471
972a547
65d5f73
 
db6d471
 
cd2ec3f
db6d471
cd2ec3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
972a547
db6d471
 
 
 
cd2ec3f
 
05c066f
65d5f73
db6d471
 
117e6a7
 
 
 
 
 
 
 
cd2ec3f
 
 
117e6a7
db6d471
cd2ec3f
 
 
 
db6d471
cd2ec3f
 
db6d471
 
cd2ec3f
db6d471
 
 
 
cd2ec3f
db6d471
 
 
 
cd2ec3f
db6d471
daab9d7
117e6a7
 
 
05c066f
cd2ec3f
db6d471
 
 
05c066f
380e150
cd2ec3f
daab9d7
 
 
 
 
 
 
 
dbe373a
65d5f73
cd2ec3f
db6d471
 
 
c67acf2
daab9d7
 
 
 
 
 
 
 
3686f17
 
 
dbe373a
9c4f0cf
cd2ec3f
 
 
 
 
 
 
 
 
 
 
 
 
 
117e6a7
 
65d5f73
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
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,
    # Import fungsi baru
    calculate_advanced_risk_metrics 
)
import warnings
import numpy as np # Tambahkan import numpy

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)
        # Menggunakan periode 1 tahun untuk metrik risiko yang lebih relevan
        data = stock.history(period="1y", interval="1d") 

        if data.empty:
            raise ValueError("No price data available for this stock.")

        # Menghitung Indikator Teknikal (Ini juga akan mengisi kolom RSI/MACD ke 'data')
        indicators = calculate_technical_indicators(data)
        signals = generate_trading_signals(data, indicators)
        fundamental_info = get_fundamental_data(stock)
        
        # Menghitung Metrik Risiko Tingkat Lanjut
        risk_metrics = calculate_advanced_risk_metrics(data.copy())

        # Prediksi Chronos-2 dengan Covariates
        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]
        
        # Target/SL yang lebih konservatif berdasarkan range prediksi Q0.1-Q0.9
        q05 = predictions.get('values', [last_price])
        q01 = predictions.get('q01', [last_price * 0.95])
        q09 = predictions.get('q09', [last_price * 1.05])
        
        # TP1 (Target Konservatif): Midpoint antara harga terakhir dan median tertinggi
        tp1 = (last_price + np.max(q05)) / 2
        # TP2 (Target Agresif): Quantile 90% tertinggi
        tp2 = np.max(q09)
        # SL (Stop Loss): Quantile 10% terendah
        sl = np.min(q01)
        
        # Pastikan TP1 < TP2 dan SL lebih rendah dari TP
        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

        # Menambahkan risk_metrics ke return
        return fundamental_info, indicators, signals, risk_metrics, fig_price, fig_technical, fig_prediction, predictions

    except Exception as e:
        print(f"Error analyzing {symbol}: {e}")
        empty_fig = gr.Plot.update(value=None)
        
        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}",
            "q01": [], "q09": [], 
            "tp1": default_tp1, "tp2": default_tp2, "sl": default_sl,
        }
        # Mengembalikan dictionary kosong untuk risk_metrics saat error
        return {}, {}, {}, {}, empty_fig, empty_fig, empty_fig, empty_predictions


def update_analysis(symbol, prediction_days):
    (
        fundamental_info,
        indicators,
        signals,
        risk_metrics, # Tambahan metrik risiko
        fig_price,
        fig_technical,
        fig_prediction,
        predictions,
    ) = analyze_stock(symbol, prediction_days)

    if not fundamental_info:
        error_msg = f"Unable to fetch stock data for {symbol.upper()}. Please check the symbol."
        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>Data fetching 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}""",
            gr.Plot.update(value=None),
            gr.Plot.update(value=None),
            gr.Plot.update(value=None),
        )

    # --- 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 ---
    band_min = float(min(predictions.get('q01', [0]))) if predictions.get('q01') and predictions.get('q01').size > 0 else 0
    band_max = float(max(predictions.get('q09', [0]))) if predictions.get('q09') and predictions.get('q09').size > 0 else 0

    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)