bram4627 commited on
Commit
6404666
·
verified ·
1 Parent(s): 2bdecc5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +436 -268
app.py CHANGED
@@ -1,268 +1,436 @@
1
- from flask import Flask, render_template, request, jsonify
2
- import pandas as pd
3
- import numpy as np
4
- import pickle
5
- import os
6
- from datetime import datetime, timedelta
7
- import matplotlib
8
- matplotlib.use('Agg') # Use non-interactive backend
9
- import matplotlib.pyplot as plt
10
- import seaborn as sns
11
- import base64
12
- import io
13
-
14
- app = Flask(__name__)
15
-
16
- # Load model and scaler
17
- def load_model_and_scaler():
18
- try:
19
- with open('model.pkl', 'rb') as model_file:
20
- model = pickle.load(model_file)
21
-
22
- with open('scaler.pkl', 'rb') as scaler_file:
23
- scaler = pickle.load(scaler_file)
24
-
25
- return model, scaler
26
- except FileNotFoundError:
27
- return None, None
28
-
29
- def load_data():
30
- """Load and preprocess the gold data"""
31
- try:
32
- data = pd.read_csv('gold.csv')
33
- data['date'] = pd.to_datetime(data['date'], format='%d/%m/%Y').dt.strftime('%Y-%m-%d')
34
- temp_df = data[['date', 'close', 'open']]
35
-
36
- # Preprocessing
37
- df = temp_df.copy()
38
- df['date'] = pd.to_datetime(df['date'], dayfirst=True, format='%Y-%m-%d').dt.date
39
- df.set_index('date', inplace=True)
40
- df.index = pd.to_datetime(df.index)
41
- df = df.sort_index()
42
-
43
- return df
44
- except FileNotFoundError:
45
- return None
46
-
47
- def normalize_data(df, scaler):
48
- """Normalize the data using the provided scaler"""
49
- np_data_unscaled = np.array(df)
50
- np_data_scaled = scaler.transform(np_data_unscaled)
51
- normalized_df = pd.DataFrame(np_data_scaled, columns=df.columns, index=df.index)
52
- return normalized_df
53
-
54
- def sliding_window(data, lag):
55
- """Create sliding window features"""
56
- series_close = data['close']
57
- series_open = data['open']
58
- result = pd.DataFrame()
59
-
60
- # Add lag columns for 'close'
61
- for l in lag:
62
- result[f'close-{l}'] = series_close.shift(l)
63
-
64
- # Add lag columns for 'open'
65
- for l in lag:
66
- result[f'open-{l}'] = series_open.shift(l)
67
-
68
- # Add original 'close' and 'open' columns
69
- result['close'] = series_close[max(lag):]
70
- result['open'] = series_open[max(lag):]
71
-
72
- # Remove missing values (NaN)
73
- result = result.dropna()
74
-
75
- # Set index according to lag values
76
- result.index = series_close.index[max(lag):]
77
-
78
- return result
79
-
80
- def predict_next_7_days(model, scaler, data):
81
- """Predict gold prices for the next 7 days"""
82
- # Normalize data
83
- normalized_df = normalize_data(data, scaler)
84
-
85
- # Create sliding window
86
- windowed_data = sliding_window(normalized_df, [1, 2, 3, 4, 5, 6, 7])
87
- windowed_data = windowed_data[['close', 'close-1', 'close-2', 'close-3', 'close-4', 'close-5', 'close-6', 'close-7',
88
- 'open', 'open-1', 'open-2', 'open-3', 'open-4', 'open-5', 'open-6', 'open-7']]
89
-
90
- # Initialize predictions list
91
- predictions = []
92
-
93
- # Get last row as initial input
94
- last_row = windowed_data.drop(columns=['close', 'open']).iloc[-1].values.reshape(1, -1)
95
-
96
- # Iterate for 7 days
97
- for _ in range(7):
98
- # Predict value for next day
99
- predicted_value_normalized = model.predict(last_row)
100
- predicted_value = scaler.inverse_transform(predicted_value_normalized.reshape(-1, 2))
101
-
102
- # Save prediction
103
- predictions.append(predicted_value[0])
104
-
105
- # Update input for next iteration
106
- new_row_normalized = np.hstack([last_row[0, 2:], predicted_value_normalized[0]])
107
- last_row = new_row_normalized.reshape(1, -1)
108
-
109
- # Transform predictions to DataFrame
110
- predictions_df = pd.DataFrame(
111
- predictions,
112
- columns=['close', 'open'],
113
- index=pd.date_range(start=normalized_df.index[-1] + pd.Timedelta(days=1), periods=7)
114
- )
115
-
116
- # Get last price
117
- last_price = scaler.inverse_transform(normalized_df[['close', 'open']].iloc[-1].values.reshape(-1, 2))
118
-
119
- # Calculate daily percentage changes
120
- predictions_df['close_change'] = predictions_df['close'].pct_change().fillna(0) * 100
121
- predictions_df['open_change'] = predictions_df['open'].pct_change().fillna(0) * 100
122
-
123
- # Calculate total change from today to day 7
124
- total_close_change = ((predictions_df['close'].iloc[-1] - last_price[0][0]) / last_price[0][0]) * 100
125
- total_open_change = ((predictions_df['open'].iloc[-1] - last_price[0][1]) / last_price[0][1]) * 100
126
-
127
- return predictions_df, last_price[0], total_close_change, total_open_change
128
-
129
- def create_prediction_chart(data, predictions_df, last_price):
130
- """Create a chart showing historical and predicted prices"""
131
- plt.style.use('default')
132
- fig, ax = plt.subplots(figsize=(12, 8))
133
-
134
- # Plot last 30 days of historical data
135
- recent_data = data.tail(30)
136
- ax.plot(recent_data.index, recent_data['close'], 'b-', label='Historical Close Price', linewidth=2)
137
- ax.plot(recent_data.index, recent_data['open'], 'g-', label='Historical Open Price', linewidth=2)
138
-
139
- # Add current day point
140
- ax.plot(recent_data.index[-1], last_price[0], 'ro', markersize=8, label='Current Close Price')
141
- ax.plot(recent_data.index[-1], last_price[1], 'go', markersize=8, label='Current Open Price')
142
-
143
- # Plot predictions
144
- ax.plot(predictions_df.index, predictions_df['close'], 'r--', label='Predicted Close Price', linewidth=2, marker='o')
145
- ax.plot(predictions_df.index, predictions_df['open'], 'orange', linestyle='--', label='Predicted Open Price', linewidth=2, marker='s')
146
-
147
- ax.set_title('Gold Price Prediction - Next 7 Days', fontsize=16, fontweight='bold')
148
- ax.set_xlabel('Date', fontsize=12)
149
- ax.set_ylabel('Price (IDR)', fontsize=12)
150
- ax.legend()
151
- ax.grid(True, alpha=0.3)
152
-
153
- # Format y-axis to show prices in millions
154
- ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1000000:.1f}M'))
155
-
156
- plt.xticks(rotation=45)
157
- plt.tight_layout()
158
-
159
- # Convert plot to base64 string
160
- img_buffer = io.BytesIO()
161
- plt.savefig(img_buffer, format='png', dpi=150, bbox_inches='tight')
162
- img_buffer.seek(0)
163
- img_string = base64.b64encode(img_buffer.read()).decode()
164
- plt.close()
165
-
166
- return img_string
167
-
168
- @app.route('/')
169
- def index():
170
- return render_template('index.html')
171
-
172
- @app.route('/predict', methods=['POST'])
173
- def predict():
174
- try:
175
- # Load model and scaler
176
- model, scaler = load_model_and_scaler()
177
- if model is None or scaler is None:
178
- return jsonify({'error': 'Model or scaler not found. Please train the model first.'}), 500
179
-
180
- # Load data
181
- data = load_data()
182
- if data is None:
183
- return jsonify({'error': 'Data file not found.'}), 500
184
-
185
- # Make predictions
186
- predictions_df, last_price, total_close_change, total_open_change = predict_next_7_days(model, scaler, data)
187
-
188
- # Create chart
189
- chart_img = create_prediction_chart(data, predictions_df, last_price)
190
-
191
- # Prepare response data
192
- predictions_list = []
193
- for date, row in predictions_df.iterrows():
194
- predictions_list.append({
195
- 'date': date.strftime('%Y-%m-%d'),
196
- 'close_price': round(row['close'], 2),
197
- 'open_price': round(row['open'], 2),
198
- 'close_change': round(row['close_change'], 2),
199
- 'open_change': round(row['open_change'], 2)
200
- })
201
-
202
- response = {
203
- 'success': True,
204
- 'current_prices': {
205
- 'close': round(last_price[0], 2),
206
- 'open': round(last_price[1], 2)
207
- },
208
- 'predictions': predictions_list,
209
- 'total_changes': {
210
- 'close': round(total_close_change, 2),
211
- 'open': round(total_open_change, 2)
212
- },
213
- 'chart': chart_img
214
- }
215
-
216
- return jsonify(response)
217
-
218
- except Exception as e:
219
- return jsonify({'error': f'An error occurred: {str(e)}'}), 500
220
-
221
- @app.route('/data-analysis')
222
- def data_analysis():
223
- """Show data analysis page"""
224
- try:
225
- data = load_data()
226
- if data is None:
227
- return render_template('error.html', error='Data file not found.')
228
-
229
- # Create historical price chart
230
- fig, ax = plt.subplots(figsize=(12, 6))
231
- ax.plot(data.index, data['close'], 'b-', label='Close Price', linewidth=1.5)
232
- ax.plot(data.index, data['open'], 'g-', label='Open Price', linewidth=1.5)
233
- ax.set_title('Historical Gold Prices', fontsize=16, fontweight='bold')
234
- ax.set_xlabel('Date', fontsize=12)
235
- ax.set_ylabel('Price (IDR)', fontsize=12)
236
- ax.legend()
237
- ax.grid(True, alpha=0.3)
238
- ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1000000:.1f}M'))
239
- plt.xticks(rotation=45)
240
- plt.tight_layout()
241
-
242
- img_buffer = io.BytesIO()
243
- plt.savefig(img_buffer, format='png', dpi=150, bbox_inches='tight')
244
- img_buffer.seek(0)
245
- historical_chart = base64.b64encode(img_buffer.read()).decode()
246
- plt.close()
247
-
248
- # Calculate statistics
249
- stats = {
250
- 'total_records': len(data),
251
- 'date_range': f"{data.index.min().strftime('%Y-%m-%d')} to {data.index.max().strftime('%Y-%m-%d')}",
252
- 'avg_close': round(data['close'].mean(), 2),
253
- 'avg_open': round(data['open'].mean(), 2),
254
- 'min_close': round(data['close'].min(), 2),
255
- 'max_close': round(data['close'].max(), 2),
256
- 'current_close': round(data['close'].iloc[-1], 2),
257
- 'current_open': round(data['open'].iloc[-1], 2)
258
- }
259
-
260
- return render_template('data_analysis.html', chart=historical_chart, stats=stats)
261
-
262
- except Exception as e:
263
- return render_template('error.html', error=f'An error occurred: {str(e)}')
264
-
265
- if __name__ == '__main__':
266
- # For Hugging Face Spaces, use port 7860
267
- port = int(os.environ.get('PORT', 7860))
268
- app.run(host='0.0.0.0', port=port, debug=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import pandas as pd
3
+ import numpy as np
4
+ import pickle
5
+ import os
6
+ from datetime import datetime, timedelta
7
+ import plotly.graph_objects as go
8
+ import plotly.express as px
9
+ from plotly.subplots import make_subplots
10
+ import plotly.io as pio
11
+
12
+ app = Flask(__name__)
13
+
14
+ # Load model and scaler
15
+ def load_model_and_scaler():
16
+ try:
17
+ with open('model.pkl', 'rb') as model_file:
18
+ model = pickle.load(model_file)
19
+
20
+ with open('scaler.pkl', 'rb') as scaler_file:
21
+ scaler = pickle.load(scaler_file)
22
+
23
+ return model, scaler
24
+ except FileNotFoundError:
25
+ return None, None
26
+
27
+ def load_data():
28
+ """Load and preprocess the gold data"""
29
+ try:
30
+ data = pd.read_csv('gold.csv')
31
+ data['date'] = pd.to_datetime(data['date'], format='%d/%m/%Y').dt.strftime('%Y-%m-%d')
32
+ temp_df = data[['date', 'close', 'open']]
33
+
34
+ # Preprocessing
35
+ df = temp_df.copy()
36
+ df['date'] = pd.to_datetime(df['date'], dayfirst=True, format='%Y-%m-%d').dt.date
37
+ df.set_index('date', inplace=True)
38
+ df.index = pd.to_datetime(df.index)
39
+ df = df.sort_index()
40
+
41
+ return df
42
+ except FileNotFoundError:
43
+ return None
44
+
45
+ def normalize_data(df, scaler):
46
+ """Normalize the data using the provided scaler"""
47
+ np_data_unscaled = np.array(df)
48
+ np_data_scaled = scaler.transform(np_data_unscaled)
49
+ normalized_df = pd.DataFrame(np_data_scaled, columns=df.columns, index=df.index)
50
+ return normalized_df
51
+
52
+ def sliding_window(data, lag):
53
+ """Create sliding window features"""
54
+ series_close = data['close']
55
+ series_open = data['open']
56
+ result = pd.DataFrame()
57
+
58
+ # Add lag columns for 'close'
59
+ for l in lag:
60
+ result[f'close-{l}'] = series_close.shift(l)
61
+
62
+ # Add lag columns for 'open'
63
+ for l in lag:
64
+ result[f'open-{l}'] = series_open.shift(l)
65
+
66
+ # Add original 'close' and 'open' columns
67
+ result['close'] = series_close[max(lag):]
68
+ result['open'] = series_open[max(lag):]
69
+
70
+ # Remove missing values (NaN)
71
+ result = result.dropna()
72
+
73
+ # Set index according to lag values
74
+ result.index = series_close.index[max(lag):]
75
+
76
+ return result
77
+
78
+ def predict_next_7_days(model, scaler, data):
79
+ """Predict gold prices for the next 7 days"""
80
+ # Normalize data
81
+ normalized_df = normalize_data(data, scaler)
82
+
83
+ # Create sliding window
84
+ windowed_data = sliding_window(normalized_df, [1, 2, 3, 4, 5, 6, 7])
85
+ windowed_data = windowed_data[['close', 'close-1', 'close-2', 'close-3', 'close-4', 'close-5', 'close-6', 'close-7',
86
+ 'open', 'open-1', 'open-2', 'open-3', 'open-4', 'open-5', 'open-6', 'open-7']]
87
+
88
+ # Initialize predictions list
89
+ predictions = []
90
+
91
+ # Get last row as initial input
92
+ last_row = windowed_data.drop(columns=['close', 'open']).iloc[-1].values.reshape(1, -1)
93
+
94
+ # Iterate for 7 days
95
+ for _ in range(7):
96
+ # Predict value for next day
97
+ predicted_value_normalized = model.predict(last_row)
98
+ predicted_value = scaler.inverse_transform(predicted_value_normalized.reshape(-1, 2))
99
+
100
+ # Save prediction
101
+ predictions.append(predicted_value[0])
102
+
103
+ # Update input for next iteration
104
+ new_row_normalized = np.hstack([last_row[0, 2:], predicted_value_normalized[0]])
105
+ last_row = new_row_normalized.reshape(1, -1)
106
+
107
+ # Transform predictions to DataFrame
108
+ predictions_df = pd.DataFrame(
109
+ predictions,
110
+ columns=['close', 'open'],
111
+ index=pd.date_range(start=normalized_df.index[-1] + pd.Timedelta(days=1), periods=7)
112
+ )
113
+
114
+ # Get last price
115
+ last_price = scaler.inverse_transform(normalized_df[['close', 'open']].iloc[-1].values.reshape(-1, 2))
116
+
117
+ # Calculate daily percentage changes
118
+ predictions_df['close_change'] = predictions_df['close'].pct_change().fillna(0) * 100
119
+ predictions_df['open_change'] = predictions_df['open'].pct_change().fillna(0) * 100
120
+
121
+ # Calculate total change from today to day 7
122
+ total_close_change = ((predictions_df['close'].iloc[-1] - last_price[0][0]) / last_price[0][0]) * 100
123
+ total_open_change = ((predictions_df['open'].iloc[-1] - last_price[0][1]) / last_price[0][1]) * 100
124
+
125
+ return predictions_df, last_price[0], total_close_change, total_open_change
126
+
127
+ def create_prediction_chart(data, predictions_df, last_price):
128
+ """Create an interactive chart showing historical and predicted prices using Plotly"""
129
+
130
+ # Get last 30 days of historical data
131
+ recent_data = data.tail(30)
132
+
133
+ # Create subplots
134
+ fig = make_subplots(
135
+ rows=1, cols=1,
136
+ subplot_titles=['Prediksi Harga Emas - 7 Hari Kedepan'],
137
+ x_title='Tanggal',
138
+ y_title='Harga (IDR)'
139
+ )
140
+
141
+ # Add historical close price
142
+ fig.add_trace(
143
+ go.Scatter(
144
+ x=recent_data.index,
145
+ y=recent_data['close'],
146
+ mode='lines',
147
+ name='Harga Tutup Historis',
148
+ line=dict(color='#1f77b4', width=2),
149
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
150
+ '<b>Harga Tutup:</b> IDR %{y:,.0f}<br>' +
151
+ '<extra></extra>'
152
+ )
153
+ )
154
+
155
+ # Add historical open price
156
+ fig.add_trace(
157
+ go.Scatter(
158
+ x=recent_data.index,
159
+ y=recent_data['open'],
160
+ mode='lines',
161
+ name='Harga Buka Historis',
162
+ line=dict(color='#2ca02c', width=2),
163
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
164
+ '<b>Harga Buka:</b> IDR %{y:,.0f}<br>' +
165
+ '<extra></extra>'
166
+ )
167
+ )
168
+
169
+ # Add current day points
170
+ fig.add_trace(
171
+ go.Scatter(
172
+ x=[recent_data.index[-1]],
173
+ y=[last_price[0]],
174
+ mode='markers',
175
+ name='Harga Tutup Saat Ini',
176
+ marker=dict(color='red', size=10),
177
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
178
+ '<b>Harga Tutup Saat Ini:</b> IDR %{y:,.0f}<br>' +
179
+ '<extra></extra>'
180
+ )
181
+ )
182
+
183
+ fig.add_trace(
184
+ go.Scatter(
185
+ x=[recent_data.index[-1]],
186
+ y=[last_price[1]],
187
+ mode='markers',
188
+ name='Harga Buka Saat Ini',
189
+ marker=dict(color='green', size=10),
190
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
191
+ '<b>Harga Buka Saat Ini:</b> IDR %{y:,.0f}<br>' +
192
+ '<extra></extra>'
193
+ )
194
+ )
195
+
196
+ # Add predicted close price
197
+ fig.add_trace(
198
+ go.Scatter(
199
+ x=predictions_df.index,
200
+ y=predictions_df['close'],
201
+ mode='lines+markers',
202
+ name='Prediksi Harga Tutup',
203
+ line=dict(color='red', width=2, dash='dash'),
204
+ marker=dict(size=6),
205
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
206
+ '<b>Prediksi Harga Tutup:</b> IDR %{y:,.0f}<br>' +
207
+ '<extra></extra>'
208
+ )
209
+ )
210
+
211
+ # Add predicted open price
212
+ fig.add_trace(
213
+ go.Scatter(
214
+ x=predictions_df.index,
215
+ y=predictions_df['open'],
216
+ mode='lines+markers',
217
+ name='Prediksi Harga Buka',
218
+ line=dict(color='orange', width=2, dash='dash'),
219
+ marker=dict(size=6, symbol='square'),
220
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
221
+ '<b>Prediksi Harga Buka:</b> IDR %{y:,.0f}<br>' +
222
+ '<extra></extra>'
223
+ )
224
+ )
225
+
226
+ # Update layout
227
+ fig.update_layout(
228
+ title={
229
+ 'text': 'Prediksi Harga Emas - 7 Hari Kedepan',
230
+ 'x': 0.5,
231
+ 'xanchor': 'center',
232
+ 'font': {'size': 20, 'family': 'Arial Black'}
233
+ },
234
+ xaxis_title='Tanggal',
235
+ yaxis_title='Harga (IDR)',
236
+ hovermode='x unified',
237
+ showlegend=True,
238
+ legend=dict(
239
+ orientation="h",
240
+ yanchor="bottom",
241
+ y=1.02,
242
+ xanchor="right",
243
+ x=1
244
+ ),
245
+ plot_bgcolor='white',
246
+ paper_bgcolor='white',
247
+ font=dict(family="Arial", size=12),
248
+ height=500,
249
+ margin=dict(l=50, r=50, t=80, b=50)
250
+ )
251
+
252
+ # Update axes
253
+ fig.update_xaxes(
254
+ showgrid=True,
255
+ gridwidth=1,
256
+ gridcolor='lightgray',
257
+ showline=True,
258
+ linewidth=1,
259
+ linecolor='black'
260
+ )
261
+
262
+ fig.update_yaxes(
263
+ showgrid=True,
264
+ gridwidth=1,
265
+ gridcolor='lightgray',
266
+ showline=True,
267
+ linewidth=1,
268
+ linecolor='black',
269
+ tickformat=',.0f'
270
+ )
271
+
272
+ # Convert to HTML
273
+ html_string = pio.to_html(fig, include_plotlyjs='cdn', div_id="prediction-chart")
274
+
275
+ return html_string
276
+
277
+ @app.route('/')
278
+ def index():
279
+ return render_template('index.html')
280
+
281
+ @app.route('/predict', methods=['POST'])
282
+ def predict():
283
+ try:
284
+ # Load model and scaler
285
+ model, scaler = load_model_and_scaler()
286
+ if model is None or scaler is None:
287
+ return jsonify({'error': 'Model or scaler not found. Please train the model first.'}), 500
288
+
289
+ # Load data
290
+ data = load_data()
291
+ if data is None:
292
+ return jsonify({'error': 'Data file not found.'}), 500
293
+
294
+ # Make predictions
295
+ predictions_df, last_price, total_close_change, total_open_change = predict_next_7_days(model, scaler, data)
296
+
297
+ # Create chart
298
+ chart_img = create_prediction_chart(data, predictions_df, last_price)
299
+
300
+ # Prepare response data
301
+ predictions_list = []
302
+ for date, row in predictions_df.iterrows():
303
+ predictions_list.append({
304
+ 'date': date.strftime('%Y-%m-%d'),
305
+ 'close_price': round(row['close'], 2),
306
+ 'open_price': round(row['open'], 2),
307
+ 'close_change': round(row['close_change'], 2),
308
+ 'open_change': round(row['open_change'], 2)
309
+ })
310
+ response = {
311
+ 'success': True,
312
+ 'current_prices': {
313
+ 'close': round(last_price[0], 2),
314
+ 'open': round(last_price[1], 2)
315
+ },
316
+ 'predictions': predictions_list,
317
+ 'total_changes': {
318
+ 'close': round(total_close_change, 2),
319
+ 'open': round(total_open_change, 2)
320
+ },
321
+ 'chart_html': chart_img
322
+ }
323
+
324
+ return jsonify(response)
325
+
326
+ except Exception as e:
327
+ return jsonify({'error': f'An error occurred: {str(e)}'}), 500
328
+
329
+ @app.route('/data-analysis')
330
+ def data_analysis():
331
+ """Show data analysis page"""
332
+ try:
333
+ data = load_data()
334
+ if data is None:
335
+ return render_template('error.html', error='Data file not found.')
336
+ # Create historical price chart with Plotly
337
+ fig = go.Figure()
338
+
339
+ # Add close price line
340
+ fig.add_trace(
341
+ go.Scatter(
342
+ x=data.index,
343
+ y=data['close'],
344
+ mode='lines',
345
+ name='Harga Tutup',
346
+ line=dict(color='#1f77b4', width=1.5),
347
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
348
+ '<b>Harga Tutup:</b> IDR %{y:,.0f}<br>' +
349
+ '<extra></extra>'
350
+ )
351
+ )
352
+
353
+ # Add open price line
354
+ fig.add_trace(
355
+ go.Scatter(
356
+ x=data.index,
357
+ y=data['open'],
358
+ mode='lines',
359
+ name='Harga Buka',
360
+ line=dict(color='#2ca02c', width=1.5),
361
+ hovertemplate='<b>Tanggal:</b> %{x}<br>' +
362
+ '<b>Harga Buka:</b> IDR %{y:,.0f}<br>' +
363
+ '<extra></extra>'
364
+ )
365
+ )
366
+
367
+ # Update layout
368
+ fig.update_layout(
369
+ title={
370
+ 'text': 'Data Historis Harga Emas',
371
+ 'x': 0.5,
372
+ 'xanchor': 'center',
373
+ 'font': {'size': 20, 'family': 'Arial Black'}
374
+ },
375
+ xaxis_title='Tanggal',
376
+ yaxis_title='Harga (IDR)',
377
+ hovermode='x unified',
378
+ showlegend=True,
379
+ legend=dict(
380
+ orientation="h",
381
+ yanchor="bottom",
382
+ y=1.02,
383
+ xanchor="right",
384
+ x=1
385
+ ),
386
+ plot_bgcolor='white',
387
+ paper_bgcolor='white',
388
+ font=dict(family="Arial", size=12),
389
+ height=400,
390
+ margin=dict(l=50, r=50, t=80, b=50)
391
+ )
392
+
393
+ # Update axes
394
+ fig.update_xaxes(
395
+ showgrid=True,
396
+ gridwidth=1,
397
+ gridcolor='lightgray',
398
+ showline=True,
399
+ linewidth=1,
400
+ linecolor='black'
401
+ )
402
+
403
+ fig.update_yaxes(
404
+ showgrid=True,
405
+ gridwidth=1,
406
+ gridcolor='lightgray',
407
+ showline=True,
408
+ linewidth=1,
409
+ linecolor='black',
410
+ tickformat=',.0f'
411
+ )
412
+
413
+ # Convert to HTML
414
+ historical_chart = pio.to_html(fig, include_plotlyjs='cdn', div_id="historical-chart")
415
+
416
+ # Calculate statistics
417
+ stats = {
418
+ 'total_records': len(data),
419
+ 'date_range': f"{data.index.min().strftime('%Y-%m-%d')} to {data.index.max().strftime('%Y-%m-%d')}",
420
+ 'avg_close': round(data['close'].mean(), 2),
421
+ 'avg_open': round(data['open'].mean(), 2),
422
+ 'min_close': round(data['close'].min(), 2),
423
+ 'max_close': round(data['close'].max(), 2),
424
+ 'current_close': round(data['close'].iloc[-1], 2),
425
+ 'current_open': round(data['open'].iloc[-1], 2)
426
+ }
427
+
428
+ return render_template('data_analysis.html', chart=historical_chart, stats=stats)
429
+
430
+ except Exception as e:
431
+ return render_template('error.html', error=f'An error occurred: {str(e)}')
432
+
433
+ if __name__ == '__main__':
434
+ # For Hugging Face Spaces, use port 7860
435
+ port = int(os.environ.get('PORT', 7860))
436
+ app.run(host='0.0.0.0', port=port, debug=False)