omniverse1 commited on
Commit
7458986
·
verified ·
1 Parent(s): 05903d9

Deploy Gradio app with multiple files

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