omniverse1 commited on
Commit
091b5c7
·
verified ·
1 Parent(s): 961b441

update config

Browse files
Files changed (1) hide show
  1. config.py +506 -64
config.py CHANGED
@@ -1,69 +1,511 @@
1
- # Indonesian Stock Exchange (IDX) major stocks
2
- IDX_STOCKS = {
3
- "BBCA.JK": "Bank Central Asia",
4
- "BBRI.JK": "Bank BRI",
5
- "BBNI.JK": "Bank BNI",
6
- "BMRI.JK": "Bank Mandiri",
7
- "TLKM.JK": "Telkom Indonesia",
8
- "UNVR.JK": "Unilever Indonesia",
9
- "ASII.JK": "Astra International",
10
- "INDF.JK": "Indofood Sukses Makmur",
11
- "KLBF.JK": "Kalbe Farma",
12
- "HMSP.JK": "HM Sampoerna",
13
- "GGRM.JK": "Gudang Garam",
14
- "ADRO.JK": "Adaro Energy",
15
- "PGAS.JK": "Perusahaan Gas Negara",
16
- "JSMR.JK": "Jasa Marga",
17
- "WIKA.JK": "Wijaya Karya",
18
- "PTBA.JK": "Tambang Batubara Bukit Asam",
19
- "ANTM.JK": "Aneka Tambang",
20
- "SMGR.JK": "Semen Indonesia",
21
- "INTP.JK": "Indocement Tunggal Prakasa",
22
- "ITMG.JK": "Indo Tambangraya Megah"
23
- }
24
 
25
- # Technical indicators configuration
26
- TECHNICAL_INDICATORS = {
27
- 'rsi': {
28
- 'period': 14,
29
- 'oversold': 30,
30
- 'overbought': 70
31
- },
32
- 'macd': {
33
- 'fast': 12,
34
- 'slow': 26,
35
- 'signal': 9
36
- },
37
- 'bollinger': {
38
- 'period': 20,
39
- 'std_dev': 2
40
- },
41
- 'moving_averages': {
42
- 'sma_short': 20,
43
- 'sma_medium': 50,
44
- 'sma_long': 200,
45
- 'ema_short': 12,
46
- 'ema_long': 26
 
47
  }
48
- }
49
 
50
- # Prediction model configuration
51
- PREDICTION_CONFIG = {
52
- 'model_name': 'amazon/chronos-bolt-base',
53
- 'context_length': 512,
54
- 'prediction_length': 30,
55
- 'temperature': 1.0,
56
- 'top_k': 50,
57
- 'top_p': 0.9
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # Chart styling
61
- CHART_CONFIG = {
62
- 'template': 'plotly_white',
63
- 'color_scheme': {
64
- 'bullish': '#10b981',
65
- 'bearish': '#ef4444',
66
- 'neutral': '#6b7280',
67
- 'accent': '#3b82f6'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ import pandas as pd
3
+ import numpy as np
4
+ import torch
5
+ from datetime import datetime, timedelta
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ from plotly.subplots import make_subplots
9
+ import spaces
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ def get_indonesian_stocks():
12
+ """Get list of major Indonesian stocks"""
13
+ return {
14
+ "BBCA.JK": "Bank Central Asia",
15
+ "BBRI.JK": "Bank BRI",
16
+ "BBNI.JK": "Bank BNI",
17
+ "BMRI.JK": "Bank Mandiri",
18
+ "TLKM.JK": "Telkom Indonesia",
19
+ "UNVR.JK": "Unilever Indonesia",
20
+ "ASII.JK": "Astra International",
21
+ "INDF.JK": "Indofood Sukses Makmur",
22
+ "KLBF.JK": "Kalbe Farma",
23
+ "HMSP.JK": "HM Sampoerna",
24
+ "GGRM.JK": "Gudang Garam",
25
+ "ADRO.JK": "Adaro Energy",
26
+ "PGAS.JK": "Perusahaan Gas Negara",
27
+ "JSMR.JK": "Jasa Marga",
28
+ "WIKA.JK": "Wijaya Karya",
29
+ "PTBA.JK": "Tambang Batubara Bukit Asam",
30
+ "ANTM.JK": "Aneka Tambang",
31
+ "SMGR.JK": "Semen Indonesia",
32
+ "INTP.JK": "Indocement Tunggal Prakasa",
33
+ "ITMG.JK": "Indo Tambangraya Megah"
34
  }
 
35
 
36
+ def calculate_technical_indicators(data):
37
+ """Calculate various technical indicators"""
38
+ indicators = {}
39
+
40
+ # RSI (Relative Strength Index)
41
+ def calculate_rsi(prices, period=14):
42
+ delta = prices.diff()
43
+ gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
44
+ loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
45
+ rs = gain / loss
46
+ rsi = 100 - (100 / (1 + rs))
47
+ return rsi
48
+
49
+ indicators['rsi'] = {
50
+ 'current': calculate_rsi(data['Close']).iloc[-1],
51
+ 'values': calculate_rsi(data['Close'])
52
+ }
53
+
54
+ # MACD
55
+ def calculate_macd(prices, fast=12, slow=26, signal=9):
56
+ exp1 = prices.ewm(span=fast).mean()
57
+ exp2 = prices.ewm(span=slow).mean()
58
+ macd = exp1 - exp2
59
+ signal_line = macd.ewm(span=signal).mean()
60
+ histogram = macd - signal_line
61
+ return macd, signal_line, histogram
62
+
63
+ macd, signal_line, histogram = calculate_macd(data['Close'])
64
+ indicators['macd'] = {
65
+ 'macd': macd.iloc[-1],
66
+ 'signal': signal_line.iloc[-1],
67
+ 'histogram': histogram.iloc[-1],
68
+ 'signal_text': 'BUY' if histogram.iloc[-1] > 0 else 'SELL'
69
+ }
70
+
71
+ # Bollinger Bands
72
+ def calculate_bollinger_bands(prices, period=20, std_dev=2):
73
+ sma = prices.rolling(window=period).mean()
74
+ std = prices.rolling(window=period).std()
75
+ upper_band = sma + (std * std_dev)
76
+ lower_band = sma - (std * std_dev)
77
+ return upper_band, sma, lower_band
78
+
79
+ upper, middle, lower = calculate_bollinger_bands(data['Close'])
80
+ current_price = data['Close'].iloc[-1]
81
+ bb_position = (current_price - lower.iloc[-1]) / (upper.iloc[-1] - lower.iloc[-1])
82
+
83
+ indicators['bollinger'] = {
84
+ 'upper': upper.iloc[-1],
85
+ 'middle': middle.iloc[-1],
86
+ 'lower': lower.iloc[-1],
87
+ 'position': 'UPPER' if bb_position > 0.8 else 'LOWER' if bb_position < 0.2 else 'MIDDLE'
88
+ }
89
+
90
+ # Moving Averages
91
+ indicators['moving_averages'] = {
92
+ 'sma_20': data['Close'].rolling(20).mean().iloc[-1],
93
+ 'sma_50': data['Close'].rolling(50).mean().iloc[-1],
94
+ 'sma_200': data['Close'].rolling(200).mean().iloc[-1],
95
+ 'ema_12': data['Close'].ewm(span=12).mean().iloc[-1],
96
+ 'ema_26': data['Close'].ewm(span=26).mean().iloc[-1]
97
+ }
98
+
99
+ # Volume indicators
100
+ indicators['volume'] = {
101
+ 'current': data['Volume'].iloc[-1],
102
+ 'avg_20': data['Volume'].rolling(20).mean().iloc[-1],
103
+ 'ratio': data['Volume'].iloc[-1] / data['Volume'].rolling(20).mean().iloc[-1]
104
+ }
105
+
106
+ return indicators
107
 
108
+ def generate_trading_signals(data, indicators):
109
+ """Generate trading signals based on technical indicators"""
110
+ signals = {}
111
+
112
+ current_price = data['Close'].iloc[-1]
113
+
114
+ # Initialize scores
115
+ buy_signals = 0
116
+ sell_signals = 0
117
+
118
+ signal_details = []
119
+
120
+ # RSI Signal
121
+ rsi = indicators['rsi']['current']
122
+ if rsi < 30:
123
+ buy_signals += 1
124
+ signal_details.append(f"✅ RSI ({rsi:.1f}) - Oversold - BUY signal")
125
+ elif rsi > 70:
126
+ sell_signals += 1
127
+ signal_details.append(f"❌ RSI ({rsi:.1f}) - Overbought - SELL signal")
128
+ else:
129
+ signal_details.append(f"⚪ RSI ({rsi:.1f}) - Neutral")
130
+
131
+ # MACD Signal
132
+ macd_hist = indicators['macd']['histogram']
133
+ if macd_hist > 0:
134
+ buy_signals += 1
135
+ signal_details.append(f"✅ MACD Histogram ({macd_hist:.4f}) - Positive - BUY signal")
136
+ else:
137
+ sell_signals += 1
138
+ signal_details.append(f"❌ MACD Histogram ({macd_hist:.4f}) - Negative - SELL signal")
139
+
140
+ # Bollinger Bands Signal
141
+ bb_position = indicators['bollinger']['position']
142
+ if bb_position == 'LOWER':
143
+ buy_signals += 1
144
+ signal_details.append(f"✅ Bollinger Bands - Near lower band - BUY signal")
145
+ elif bb_position == 'UPPER':
146
+ sell_signals += 1
147
+ signal_details.append(f"❌ Bollinger Bands - Near upper band - SELL signal")
148
+ else:
149
+ signal_details.append("⚪ Bollinger Bands - Middle position")
150
+
151
+ # Moving Averages Signal
152
+ sma_20 = indicators['moving_averages']['sma_20']
153
+ sma_50 = indicators['moving_averages']['sma_50']
154
+
155
+ if current_price > sma_20 > sma_50:
156
+ buy_signals += 1
157
+ signal_details.append(f"✅ Price above MA(20,50) - Bullish - BUY signal")
158
+ elif current_price < sma_20 < sma_50:
159
+ sell_signals += 1
160
+ signal_details.append(f"❌ Price below MA(20,50) - Bearish - SELL signal")
161
+ else:
162
+ signal_details.append("⚪ Moving Averages - Mixed signals")
163
+
164
+ # Volume Signal
165
+ volume_ratio = indicators['volume']['ratio']
166
+ if volume_ratio > 1.5:
167
+ buy_signals += 0.5
168
+ signal_details.append(f"✅ High volume ({volume_ratio:.1f}x avg) - Strengthens BUY signal")
169
+ elif volume_ratio < 0.5:
170
+ sell_signals += 0.5
171
+ signal_details.append(f"❌ Low volume ({volume_ratio:.1f}x avg) - Weakens SELL signal")
172
+ else:
173
+ signal_details.append(f"⚪ Normal volume ({volume_ratio:.1f}x avg)")
174
+
175
+ # Determine overall signal
176
+ total_signals = buy_signals + sell_signals
177
+ signal_strength = (buy_signals / max(total_signals, 1)) * 100
178
+
179
+ if buy_signals > sell_signals:
180
+ overall_signal = "BUY"
181
+ elif sell_signals > buy_signals:
182
+ overall_signal = "SELL"
183
+ else:
184
+ overall_signal = "HOLD"
185
+
186
+ # Calculate support and resistance
187
+ recent_high = data['High'].tail(20).max()
188
+ recent_low = data['Low'].tail(20).min()
189
+
190
+ signals = {
191
+ 'overall': overall_signal,
192
+ 'strength': signal_strength,
193
+ 'details': '\n'.join(signal_details),
194
+ 'support': recent_low,
195
+ 'resistance': recent_high,
196
+ 'stop_loss': recent_low * 0.95 if overall_signal == "BUY" else recent_high * 1.05
197
  }
198
+
199
+ return signals
200
+
201
+ def get_fundamental_data(stock):
202
+ """Get fundamental data for the stock"""
203
+ try:
204
+ info = stock.info
205
+ history = stock.history(period="1d")
206
+
207
+ fundamental_info = {
208
+ 'name': info.get('longName', 'N/A'),
209
+ 'current_price': history['Close'].iloc[-1] if not history.empty else 0,
210
+ 'market_cap': info.get('marketCap', 0),
211
+ 'pe_ratio': info.get('forwardPE', 0),
212
+ 'dividend_yield': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 0,
213
+ 'volume': history['Volume'].iloc[-1] if not history.empty else 0,
214
+ 'info': f"""
215
+ Sector: {info.get('sector', 'N/A')}
216
+ Industry: {info.get('industry', 'N/A')}
217
+ Market Cap: {format_large_number(info.get('marketCap', 0))}
218
+ 52 Week High: {info.get('fiftyTwoWeekHigh', 'N/A')}
219
+ 52 Week Low: {info.get('fiftyTwoWeekLow', 'N/A')}
220
+ Beta: {info.get('beta', 'N/A')}
221
+ EPS: {info.get('forwardEps', 'N/A')}
222
+ Book Value: {info.get('bookValue', 'N/A')}
223
+ Price to Book: {info.get('priceToBook', 'N/A')}
224
+ """.strip()
225
+ }
226
+
227
+ return fundamental_info
228
+ except Exception as e:
229
+ print(f"Error getting fundamental data: {e}")
230
+ return {
231
+ 'name': 'N/A',
232
+ 'current_price': 0,
233
+ 'market_cap': 0,
234
+ 'pe_ratio': 0,
235
+ 'dividend_yield': 0,
236
+ 'volume': 0,
237
+ 'info': 'Unable to fetch fundamental data'
238
+ }
239
+
240
+ def format_large_number(num):
241
+ """Format large numbers to readable format"""
242
+ if num >= 1e12:
243
+ return f"{num/1e12:.2f}T"
244
+ elif num >= 1e9:
245
+ return f"{num/1e9:.2f}B"
246
+ elif num >= 1e6:
247
+ return f"{num/1e6:.2f}M"
248
+ elif num >= 1e3:
249
+ return f"{num/1e3:.2f}K"
250
+ else:
251
+ return f"{num:.2f}"
252
+
253
+ @spaces.GPU(duration=120)
254
+ def predict_prices(data, model, tokenizer, prediction_days=30):
255
+ """Predict future prices using Chronos-Bolt model"""
256
+ try:
257
+ # Prepare data for prediction
258
+ prices = data['Close'].values
259
+ context_length = min(len(prices), 512)
260
+
261
+ # Tokenize the input
262
+ input_sequence = prices[-context_length:]
263
+
264
+ # Create prediction input
265
+ prediction_input = torch.tensor(input_sequence).unsqueeze(0).float()
266
+
267
+ # Generate predictions
268
+ with torch.no_grad():
269
+ forecast = model.generate(
270
+ prediction_input,
271
+ prediction_length=prediction_days,
272
+ temperature=1.0,
273
+ top_k=50,
274
+ top_p=0.9
275
+ )
276
+
277
+ predictions = forecast[0].numpy()
278
+
279
+ # Calculate prediction statistics
280
+ last_price = prices[-1]
281
+ predicted_high = np.max(predictions)
282
+ predicted_low = np.min(predictions)
283
+ predicted_mean = np.mean(predictions)
284
+ change_pct = ((predicted_mean - last_price) / last_price) * 100
285
+
286
+ return {
287
+ 'values': predictions,
288
+ 'dates': pd.date_range(
289
+ start=data.index[-1] + timedelta(days=1),
290
+ periods=prediction_days,
291
+ freq='D'
292
+ ),
293
+ 'high_30d': predicted_high,
294
+ 'low_30d': predicted_low,
295
+ 'mean_30d': predicted_mean,
296
+ 'change_pct': change_pct,
297
+ 'summary': f"""
298
+ AI Model: Amazon Chronos-Bolt
299
+ Prediction Period: {prediction_days} days
300
+ Expected Change: {change_pct:.2f}%
301
+ Confidence: Medium (based on historical patterns)
302
+ Note: AI predictions are for reference only and not financial advice
303
+ """.strip()
304
+ }
305
+ except Exception as e:
306
+ print(f"Error in prediction: {e}")
307
+ return {
308
+ 'values': [],
309
+ 'dates': [],
310
+ 'high_30d': 0,
311
+ 'low_30d': 0,
312
+ 'mean_30d': 0,
313
+ 'change_pct': 0,
314
+ 'summary': 'Prediction unavailable due to model error'
315
+ }
316
+
317
+ def create_price_chart(data, indicators):
318
+ """Create price chart with technical indicators"""
319
+ fig = make_subplots(
320
+ rows=3, cols=1,
321
+ shared_xaxes=True,
322
+ vertical_spacing=0.05,
323
+ subplot_titles=('Price & Moving Averages', 'RSI', 'MACD'),
324
+ row_width=[0.2, 0.2, 0.7]
325
+ )
326
+
327
+ # Price and Moving Averages
328
+ fig.add_trace(
329
+ go.Candlestick(
330
+ x=data.index,
331
+ open=data['Open'],
332
+ high=data['High'],
333
+ low=data['Low'],
334
+ close=data['Close'],
335
+ name='Price'
336
+ ),
337
+ row=1, col=1
338
+ )
339
+
340
+ # Add moving averages
341
+ fig.add_trace(
342
+ go.Scatter(
343
+ x=data.index,
344
+ y=indicators['moving_averages']['sma_20'],
345
+ name='SMA 20',
346
+ line=dict(color='orange', width=1)
347
+ ),
348
+ row=1, col=1
349
+ )
350
+
351
+ fig.add_trace(
352
+ go.Scatter(
353
+ x=data.index,
354
+ y=indicators['moving_averages']['sma_50'],
355
+ name='SMA 50',
356
+ line=dict(color='blue', width=1)
357
+ ),
358
+ row=1, col=1
359
+ )
360
+
361
+ # RSI
362
+ fig.add_trace(
363
+ go.Scatter(
364
+ x=data.index,
365
+ y=indicators['rsi']['values'],
366
+ name='RSI',
367
+ line=dict(color='purple')
368
+ ),
369
+ row=2, col=1
370
+ )
371
+
372
+ fig.add_hline(y=70, line_dash="dash", line_color="red", row=2, col=1)
373
+ fig.add_hline(y=30, line_dash="dash", line_color="green", row=2, col=1)
374
+
375
+ # MACD
376
+ fig.add_trace(
377
+ go.Scatter(
378
+ x=data.index,
379
+ y=indicators['macd']['macd'],
380
+ name='MACD',
381
+ line=dict(color='blue')
382
+ ),
383
+ row=3, col=1
384
+ )
385
+
386
+ fig.add_trace(
387
+ go.Scatter(
388
+ x=data.index,
389
+ y=indicators['macd']['signal'],
390
+ name='Signal',
391
+ line=dict(color='red')
392
+ ),
393
+ row=3, col=1
394
+ )
395
+
396
+ fig.update_layout(
397
+ title='Technical Analysis Dashboard',
398
+ height=900,
399
+ showlegend=True,
400
+ xaxis_rangeslider_visible=False
401
+ )
402
+
403
+ return fig
404
+
405
+ def create_technical_chart(data, indicators):
406
+ """Create technical indicators dashboard"""
407
+ fig = make_subplots(
408
+ rows=2, cols=2,
409
+ subplot_titles=('Bollinger Bands', 'Volume', 'Price vs MA', 'RSI Analysis'),
410
+ specs=[[{"secondary_y": False}, {"secondary_y": False}],
411
+ [{"secondary_y": False}, {"secondary_y": False}]]
412
+ )
413
+
414
+ # Bollinger Bands
415
+ fig.add_trace(
416
+ go.Scatter(x=data.index, y=data['Close'], name='Price', line=dict(color='black')),
417
+ row=1, col=1
418
+ )
419
+
420
+ # Volume
421
+ fig.add_trace(
422
+ go.Bar(x=data.index, y=data['Volume'], name='Volume', marker_color='lightblue'),
423
+ row=1, col=2
424
+ )
425
+
426
+ # Price vs Moving Averages
427
+ fig.add_trace(
428
+ go.Scatter(x=data.index, y=data['Close'], name='Price', line=dict(color='black')),
429
+ row=2, col=1
430
+ )
431
+
432
+ fig.add_trace(
433
+ go.Scatter(
434
+ x=data.index,
435
+ y=[indicators['moving_averages']['sma_20']] * len(data),
436
+ name='SMA 20',
437
+ line=dict(color='orange', dash='dash')
438
+ ),
439
+ row=2, col=1
440
+ )
441
+
442
+ fig.update_layout(
443
+ title='Technical Indicators Overview',
444
+ height=600,
445
+ showlegend=False
446
+ )
447
+
448
+ return fig
449
+
450
+ def create_prediction_chart(data, predictions):
451
+ """Create prediction visualization"""
452
+ if not predictions['values'].size:
453
+ return go.Figure()
454
+
455
+ fig = go.Figure()
456
+
457
+ # Historical prices
458
+ fig.add_trace(
459
+ go.Scatter(
460
+ x=data.index[-60:],
461
+ y=data['Close'].values[-60:],
462
+ name='Historical Price',
463
+ line=dict(color='blue', width=2)
464
+ )
465
+ )
466
+
467
+ # Predictions
468
+ fig.add_trace(
469
+ go.Scatter(
470
+ x=predictions['dates'],
471
+ y=predictions['values'],
472
+ name='AI Prediction',
473
+ line=dict(color='red', width=2, dash='dash')
474
+ )
475
+ )
476
+
477
+ # Confidence interval (simple)
478
+ pred_std = np.std(predictions['values'])
479
+ upper_band = predictions['values'] + (pred_std * 1.96)
480
+ lower_band = predictions['values'] - (pred_std * 1.96)
481
+
482
+ fig.add_trace(
483
+ go.Scatter(
484
+ x=predictions['dates'],
485
+ y=upper_band,
486
+ name='Upper Band',
487
+ line=dict(color='lightcoral', width=1),
488
+ fill=None
489
+ )
490
+ )
491
+
492
+ fig.add_trace(
493
+ go.Scatter(
494
+ x=predictions['dates'],
495
+ y=lower_band,
496
+ name='Lower Band',
497
+ line=dict(color='lightcoral', width=1),
498
+ fill='tonexty',
499
+ fillcolor='rgba(255,182,193,0.2)'
500
+ )
501
+ )
502
+
503
+ fig.update_layout(
504
+ title=f'Price Prediction - Next {len(predictions["dates"])} Days',
505
+ xaxis_title='Date',
506
+ yaxis_title='Price (IDR)',
507
+ hovermode='x unified',
508
+ height=500
509
+ )
510
+
511
+ return fig