Varriety commited on
Commit
e4242e8
Β·
verified Β·
1 Parent(s): 7925486

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +143 -121
src/streamlit_app.py CHANGED
@@ -5,8 +5,8 @@ import re
5
  import io
6
  import time
7
  import requests
8
- import matplotlib.pyplot as plt
9
- import seaborn as sns
10
  from datetime import datetime, timezone
11
  from textblob import TextBlob
12
  from scipy.stats import pearsonr
@@ -35,11 +35,11 @@ if 'page' not in st.session_state:
35
  st.session_state.page = "uji_kalimat"
36
 
37
  # ==============================
38
- # "CSS HACKING" - VANCOUVER BITCOIN VIBE (ORANGE BLUR)
39
  # ==============================
40
  st.markdown("""
41
  <style>
42
- /* Mengimpor Font Inter (Standar Industri Web Web3/Crypto) */
43
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
44
 
45
  /* Menyembunyikan elemen bawaan Streamlit */
@@ -47,22 +47,8 @@ st.markdown("""
47
  footer {visibility: hidden;}
48
  header {visibility: hidden;}
49
 
50
- /* Pengaturan Font & Background Global */
51
  html, body, [class*="css"] {
52
  font-family: 'Inter', sans-serif !important;
53
- color: #0F172A !important; /* Warna teks gelap utama */
54
- }
55
-
56
- /* BACKGROUND HACK: Efek "Ngeblur Sedikit Opacity" Warna Oranye.
57
- Menggunakan radial-gradient untuk menciptakan pendaran (glow) di latar belakang putih.
58
- */
59
- .stApp {
60
- background-color: #FAFAFC;
61
- background-image:
62
- radial-gradient(circle at 10% 10%, rgba(247, 147, 26, 0.12) 0%, transparent 40%),
63
- radial-gradient(circle at 90% 90%, rgba(247, 147, 26, 0.08) 0%, transparent 40%),
64
- radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.8) 0%, transparent 100%);
65
- background-attachment: fixed;
66
  }
67
 
68
  /* Padding Kontainer Utama */
@@ -72,83 +58,113 @@ st.markdown("""
72
  max-width: 1200px !important;
73
  }
74
 
75
- /* STYLING TOMBOL STREAMLIT (Dibuat Mirip Tombol Website Asli)
76
- */
 
77
  div[data-testid="stButton"] > button {
78
- background-color: #F7931A !important; /* Orange Bitcoin */
79
  color: #FFFFFF !important;
80
- border: none !important;
81
- border-radius: 8px !important;
82
- font-weight: 600 !important;
83
- padding: 0.6rem 1.8rem !important;
84
- box-shadow: 0 4px 10px rgba(247, 147, 26, 0.25) !important;
 
85
  transition: all 0.3s ease !important;
86
  }
87
  div[data-testid="stButton"] > button:hover {
88
- background-color: #E68310 !important;
89
- transform: translateY(-2px) !important;
90
- box-shadow: 0 6px 15px rgba(247, 147, 26, 0.35) !important;
 
 
91
  }
92
 
93
- /* STYLING TEXT AREA (Glassmorphism & Clean Input)
94
- */
95
  .stTextArea textarea {
96
- background-color: rgba(255, 255, 255, 0.8) !important;
97
- backdrop-filter: blur(5px);
98
  color: #0F172A !important;
99
- border: 1px solid #CBD5E1 !important;
100
- border-radius: 10px !important;
101
  font-size: 1.05rem;
102
  padding: 1.2rem !important;
103
- box-shadow: inset 0 2px 4px rgba(0,0,0,0.02) !important;
104
  }
105
  .stTextArea textarea:focus {
106
  border-color: #F7931A !important;
107
- box-shadow: 0 0 0 2px rgba(247, 147, 26, 0.3) !important;
108
  }
109
 
110
- /* STYLING METRIK KARTU (Tab Analisis Batch)
111
- */
112
  div[data-testid="metric-container"] {
113
  background-color: rgba(255, 255, 255, 0.95) !important;
114
- backdrop-filter: blur(10px);
115
  border: 1px solid #E2E8F0 !important;
116
- border-left: 5px solid #F7931A !important; /* Garis Oranye Kiri */
117
  padding: 1.2rem !important;
118
  border-radius: 12px !important;
119
- box-shadow: 0 4px 15px rgba(0,0,0,0.03) !important;
120
- }
121
- div[data-testid="metric-container"] label {
122
- color: #64748B !important;
123
- font-weight: 500 !important;
124
  }
125
- div[data-testid="metric-container"] div {
126
- color: #0F172A !important;
127
- font-weight: 800 !important;
128
- }
129
-
130
- /* Dataframe & Table Modern */
131
  div[data-testid="stDataFrame"] {
132
- background-color: white;
133
- border: 1px solid #E2E8F0;
134
  border-radius: 12px;
135
- box-shadow: 0 4px 10px rgba(0,0,0,0.02);
136
- }
137
-
138
- /* Header Typography */
139
- h1, h2, h3, h4, h5 {
140
- color: #0F172A !important;
141
- font-weight: 800 !important;
142
- letter-spacing: -0.5px !important;
143
- }
144
- p, span, label {
145
- color: #334155 !important;
146
  }
147
  </style>
148
  """, unsafe_allow_html=True)
149
 
150
  # ==============================
151
- # HEADER & NAVIGASI (Dibuat ala Navbar Web)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  # ==============================
153
  def set_page(page_name):
154
  st.session_state.page = page_name
@@ -157,22 +173,23 @@ st.markdown("<div style='padding-bottom: 0.5rem;'></div>", unsafe_allow_html=Tru
157
  col_logo, col_space, col_nav1, col_nav2 = st.columns([3, 4, 1.5, 1.5], gap="small")
158
 
159
  with col_logo:
160
- # Logo text Vancouver Bitcoin style
161
- st.markdown("<h3 style='margin:0; padding-top:5px; font-weight:800; color:#0F172A !important;'><span style='color:#F7931A;'>β‚Ώitcoin</span> Sentimen</h3>", unsafe_allow_html=True)
 
162
 
163
  with col_nav1:
164
- if st.button("Uji Kalimat", use_container_width=True, key="nav_uji"):
165
  set_page("uji_kalimat")
166
  st.rerun()
167
 
168
  with col_nav2:
169
- if st.button("Analisis Batch", use_container_width=True, key="nav_batch"):
170
  set_page("analisis_batch")
171
  st.rerun()
172
 
173
  # Divider bawah navbar
174
- st.markdown("<hr style='border: none; height: 1px; background-color: #E2E8F0; margin-top: 10px; margin-bottom: 40px;'>", unsafe_allow_html=True)
175
-
176
 
177
  # ==============================
178
  # DOWNLOAD RESOURCES & LOAD MODELS
@@ -229,22 +246,22 @@ def get_daily_label(score):
229
 
230
 
231
  # ==============================================================================
232
- # HALAMAN 1: UJI KALIMAT (Mirip Gambar 1 - Tema Terang Oranye)
233
  # ==============================================================================
234
  if st.session_state.page == "uji_kalimat":
235
  col_text, col_img = st.columns([1.1, 1], gap="large")
236
 
237
  with col_text:
238
  st.markdown("""
239
- <div style="padding-top: 1rem;">
240
- <h1 style="font-size: 3.5rem; line-height: 1.1; margin-bottom: 1rem; font-weight: 800; letter-spacing: -1.5px; color: #0F172A;">
241
- Bitcoin Volatility <br><span style="color: #F7931A;">vs Public Sentiment</span>
242
  </h1>
243
- <p style='font-size: 1.15rem; font-weight: 400; color: #475569 !important; margin-bottom: 2rem;'>
244
  Analisis Volatilitas Harga Bitcoin Terhadap Sentimen Publik Pada Platform X Berbasis Python.
245
  </p>
246
- <div style="background-color: #FFFFFF; border: 1px solid #E2E8F0; padding: 15px; border-radius: 8px; margin-bottom: 2.5rem; box-shadow: 0 2px 10px rgba(0,0,0,0.02);">
247
- <p style="margin: 0; font-size: 0.95rem; color: #334155 !important;">
248
  <span style="color: #F7931A;">πŸ“Œ</span> <b>Peneliti:</b> Arya Galuh Saputra (H1D022022)
249
  </p>
250
  </div>
@@ -263,8 +280,8 @@ if st.session_state.page == "uji_kalimat":
263
  st.info(f"Visualisasi Hero akan muncul di sini. (Pastikan file {os.path.basename(img_hero)} tersedia di folder yang sama)")
264
 
265
  if analyze_btn:
266
- st.markdown("<br><hr style='border-color: #E2E8F0;'><br>", unsafe_allow_html=True)
267
- st.markdown("<h3 style='text-align: center; margin-bottom: 2rem; color: #0F172A;'>πŸ“‹ Hasil Deteksi Sentimen</h3>", unsafe_allow_html=True)
268
 
269
  try:
270
  if detect(user_input) != 'en':
@@ -305,28 +322,17 @@ if st.session_state.page == "uji_kalimat":
305
 
306
 
307
  # ==============================================================================
308
- # HALAMAN 2: ANALISIS BATCH DATA (Mirip Gambar 2 - Dashboard Modern)
309
  # ==============================================================================
310
  elif st.session_state.page == "analisis_batch":
311
- # Tema Matplotlib/Seaborn disesuaikan dengan latar terang transparan
312
- plt.style.use('default')
313
- sns.set_theme(style="whitegrid", rc={
314
- "axes.facecolor": "rgba(255,255,255,0)",
315
- "figure.facecolor": "rgba(255,255,255,0)",
316
- "axes.edgecolor": "#E2E8F0",
317
- "text.color": "#0F172A",
318
- "xtick.color": "#64748B",
319
- "ytick.color": "#64748B",
320
- "grid.color": "#F1F5F9"
321
- })
322
 
323
  col_upload, col_img_batch = st.columns([1.5, 1], gap="large")
324
 
325
  with col_upload:
326
  st.markdown("""
327
  <div style="padding-top: 1rem;">
328
- <h2 style="font-size: 2.5rem; margin-bottom: 0.5rem; font-weight: 800; letter-spacing: -1px; color: #0F172A;">Analisis Batch Data</h2>
329
- <p style='font-size: 1.1rem; color: #475569 !important; margin-bottom: 1.5rem;'>Unggah file rekam jejak tweet (.txt) untuk diekstraksi dan dianalisis secara masal terhadap volatilitas pasar.</p>
330
  </div>
331
  """, unsafe_allow_html=True)
332
 
@@ -413,7 +419,6 @@ elif st.session_state.page == "analisis_batch":
413
  if df.empty:
414
  st.error("❌ Data kosong. Pastikan format penulisan TXT benar dan tweet berbahasa Inggris.")
415
  else:
416
- # Metrics Dashboard
417
  st.markdown("<h3 style='margin-bottom: 1.5rem;'>πŸ“Š Ringkasan Pemrosesan</h3>", unsafe_allow_html=True)
418
  col_metric1, col_metric2, col_metric3 = st.columns(3)
419
  col_metric1.metric("Tweet Berhasil Diproses", f"{total_tweets_uploaded}", border=False)
@@ -486,7 +491,6 @@ elif st.session_state.page == "analisis_batch":
486
  final_display_cols = ["date", "price", "pct_change", "log_return"] + [c for c in daily_display_cols if c != "date"]
487
  st.dataframe(df_merged[final_display_cols], use_container_width=True, hide_index=True)
488
 
489
- # Tombol Download
490
  col_dl1, col_dl2, _ = st.columns([1, 1, 3])
491
  csv_data = df_merged.to_csv(index=False).encode('utf-8')
492
  col_dl1.download_button("πŸ“₯ Unduh CSV", data=csv_data, file_name="sentiment_volatility.csv", mime="text/csv", use_container_width=True)
@@ -498,7 +502,6 @@ elif st.session_state.page == "analisis_batch":
498
 
499
  st.markdown("<hr style='border-color: #E2E8F0; margin: 3rem 0;'>", unsafe_allow_html=True)
500
 
501
- # UJI KORELASI PEARSON
502
  st.subheader("πŸ”¬ Uji Korelasi Pearson")
503
  st.caption("Menganalisis hubungan statistik antara skor sentimen harian dan volatilitas log-return BTC.")
504
 
@@ -521,24 +524,37 @@ elif st.session_state.page == "analisis_batch":
521
 
522
  st.table(pd.DataFrame(corr_data))
523
 
524
- # LINE CHART
 
 
525
  st.markdown("<br>", unsafe_allow_html=True)
526
  st.subheader("πŸ“ˆ Trend Analisis: Sentiment vs BTC Volatility")
527
 
528
- fig_line, ax_line = plt.subplots(figsize=(14, 6))
529
-
530
- ax_line.plot(df_merged["date"], df_merged["log_return"], label="BTC Log Return", color="#F7931A", linewidth=3, linestyle="-")
531
-
 
 
 
 
532
  colors = ["#3B82F6", "#10B981", "#EC4899", "#14B8A6", "#6366F1"]
533
  for idx, method in enumerate(["vader", "textblob", "roberta", "roberta_large", "bertweet"]):
534
- ax_line.plot(df_merged["date"], df_merged[method], label=f"Sentiment: {method.upper()}", color=colors[idx], linewidth=1.5, linestyle="--", alpha=0.8)
535
-
536
- ax_line.set_title("Pergerakan Sentimen vs Log Return Bitcoin", fontsize=14, pad=15, fontweight='bold')
537
- ax_line.set_xlabel("Tanggal", fontsize=11)
538
- ax_line.set_ylabel("Nilai Metrik", fontsize=11)
539
- ax_line.legend(loc='upper left', bbox_to_anchor=(1, 1), frameon=True)
540
- plt.tight_layout()
541
- st.pyplot(fig_line)
 
 
 
 
 
 
 
542
 
543
  # SCATTER PLOT
544
  st.markdown("<br>### πŸ”΅ Pola Distribusi Scatter", unsafe_allow_html=True)
@@ -548,17 +564,23 @@ elif st.session_state.page == "analisis_batch":
548
 
549
  for idx, method in enumerate(models_list):
550
  with cols[idx % 3]:
551
- fig_scatter, ax_scatter = plt.subplots(figsize=(5, 4))
552
- sns.regplot(data=df_merged, x=method, y="log_return", ax=ax_scatter,
553
- scatter_kws={"s": 40, "color": "#0F172A", "alpha": 0.5},
554
- line_kws={"color": "#F7931A", "linewidth": 2})
555
- ax_scatter.set_title(f"{method.upper()}", fontweight='bold')
556
- ax_scatter.set_xlabel("Sentimen Score")
557
- ax_scatter.set_ylabel("Log Return")
558
- plt.tight_layout()
559
- st.pyplot(fig_scatter)
 
 
 
 
 
 
 
560
 
561
- # KESIMPULAN
562
  st.markdown("<hr style='border-color: #E2E8F0; margin: 3rem 0;'>", unsafe_allow_html=True)
563
  st.subheader("πŸ“ Kesimpulan Otomatis")
564
 
 
5
  import io
6
  import time
7
  import requests
8
+ import plotly.graph_objects as go
9
+ import plotly.express as px
10
  from datetime import datetime, timezone
11
  from textblob import TextBlob
12
  from scipy.stats import pearsonr
 
35
  st.session_state.page = "uji_kalimat"
36
 
37
  # ==============================
38
+ # "CSS HACKING" - STYLING GLOBAL & TOMBOL
39
  # ==============================
40
  st.markdown("""
41
  <style>
42
+ /* Mengimpor Font Inter */
43
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
44
 
45
  /* Menyembunyikan elemen bawaan Streamlit */
 
47
  footer {visibility: hidden;}
48
  header {visibility: hidden;}
49
 
 
50
  html, body, [class*="css"] {
51
  font-family: 'Inter', sans-serif !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
 
54
  /* Padding Kontainer Utama */
 
58
  max-width: 1200px !important;
59
  }
60
 
61
+ /* =========================================
62
+ STYLING TOMBOL (Ala Vancouver Bitcoin)
63
+ ========================================= */
64
  div[data-testid="stButton"] > button {
65
+ background-color: #0F172A !important; /* Warna Gelap Pekat */
66
  color: #FFFFFF !important;
67
+ border: 2px solid #0F172A !important;
68
+ border-radius: 50px !important; /* Bentuk Pill (Bulat di ujung) */
69
+ font-weight: 700 !important;
70
+ letter-spacing: 0.5px;
71
+ padding: 0.6rem 2rem !important;
72
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15) !important;
73
  transition: all 0.3s ease !important;
74
  }
75
  div[data-testid="stButton"] > button:hover {
76
+ background-color: #FFFFFF !important;
77
+ color: #0F172A !important;
78
+ border: 2px solid #0F172A !important;
79
+ transform: translateY(-3px) !important;
80
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2) !important;
81
  }
82
 
83
+ /* STYLING TEXT AREA */
 
84
  .stTextArea textarea {
85
+ background-color: rgba(255, 255, 255, 0.95) !important;
 
86
  color: #0F172A !important;
87
+ border: 2px solid #CBD5E1 !important;
88
+ border-radius: 12px !important;
89
  font-size: 1.05rem;
90
  padding: 1.2rem !important;
91
+ transition: border-color 0.3s ease;
92
  }
93
  .stTextArea textarea:focus {
94
  border-color: #F7931A !important;
95
+ box-shadow: 0 0 0 3px rgba(247, 147, 26, 0.2) !important;
96
  }
97
 
98
+ /* STYLING METRIK KARTU */
 
99
  div[data-testid="metric-container"] {
100
  background-color: rgba(255, 255, 255, 0.95) !important;
 
101
  border: 1px solid #E2E8F0 !important;
102
+ border-left: 6px solid #F7931A !important;
103
  padding: 1.2rem !important;
104
  border-radius: 12px !important;
105
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05) !important;
 
 
 
 
106
  }
 
 
 
 
 
 
107
  div[data-testid="stDataFrame"] {
 
 
108
  border-radius: 12px;
109
+ box-shadow: 0 4px 10px rgba(0,0,0,0.03);
 
 
 
 
 
 
 
 
 
 
110
  }
111
  </style>
112
  """, unsafe_allow_html=True)
113
 
114
  # ==============================
115
+ # CSS BACKGROUND DINAMIS BERDASARKAN TAB
116
+ # ==============================
117
+ if st.session_state.page == "uji_kalimat":
118
+ st.markdown("""
119
+ <style>
120
+ /* Latar Belakang Oranye dengan Efek Blur (Uji Kalimat) */
121
+ .stApp {
122
+ background: linear-gradient(135deg, rgba(247, 147, 26, 0.9), rgba(220, 120, 15, 0.8));
123
+ background-attachment: fixed;
124
+ position: relative;
125
+ }
126
+ .stApp::before {
127
+ content: "";
128
+ position: absolute;
129
+ top: 0; left: 0; right: 0; bottom: 0;
130
+ background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.2) 0%, transparent 70%);
131
+ backdrop-filter: blur(15px);
132
+ z-index: 0;
133
+ }
134
+ /* Menyesuaikan warna teks agar kontras dengan background oranye */
135
+ .stMarkdown, .stMarkdown h1, .stMarkdown h3, .stMarkdown p {
136
+ color: #FFFFFF !important;
137
+ position: relative;
138
+ z-index: 1;
139
+ }
140
+ /* Kecuali untuk box peneliti */
141
+ .researcher-box p, .researcher-box b {
142
+ color: #0F172A !important;
143
+ }
144
+ /* Navbar logo text */
145
+ .nav-logo { color: #FFFFFF !important; }
146
+ </style>
147
+ """, unsafe_allow_html=True)
148
+ else:
149
+ st.markdown("""
150
+ <style>
151
+ /* Latar Belakang Putih/Terang (Analisis Batch) */
152
+ .stApp {
153
+ background-color: #FAFAFC;
154
+ }
155
+ .stMarkdown h1, .stMarkdown h2, .stMarkdown h3, .stMarkdown h4 {
156
+ color: #0F172A !important;
157
+ font-weight: 800 !important;
158
+ }
159
+ .stMarkdown p, .stMarkdown span {
160
+ color: #334155 !important;
161
+ }
162
+ .nav-logo { color: #0F172A !important; }
163
+ </style>
164
+ """, unsafe_allow_html=True)
165
+
166
+ # ==============================
167
+ # HEADER & NAVIGASI
168
  # ==============================
169
  def set_page(page_name):
170
  st.session_state.page = page_name
 
173
  col_logo, col_space, col_nav1, col_nav2 = st.columns([3, 4, 1.5, 1.5], gap="small")
174
 
175
  with col_logo:
176
+ # Warna teks logo menyesuaikan background tab
177
+ logo_color = "#FFFFFF" if st.session_state.page == "uji_kalimat" else "#0F172A"
178
+ st.markdown(f"<h3 style='margin:0; padding-top:5px; font-weight:900; color:{logo_color} !important;'><span style='color:#F7931A;'>β‚Ώitcoin</span> Sentimen</h3>", unsafe_allow_html=True)
179
 
180
  with col_nav1:
181
+ if st.button("πŸ“ Uji Kalimat", use_container_width=True, key="nav_uji"):
182
  set_page("uji_kalimat")
183
  st.rerun()
184
 
185
  with col_nav2:
186
+ if st.button("πŸ“Š Analisis Batch", use_container_width=True, key="nav_batch"):
187
  set_page("analisis_batch")
188
  st.rerun()
189
 
190
  # Divider bawah navbar
191
+ divider_color = "rgba(255,255,255,0.3)" if st.session_state.page == "uji_kalimat" else "#E2E8F0"
192
+ st.markdown(f"<hr style='border: none; height: 1px; background-color: {divider_color}; margin-top: 10px; margin-bottom: 40px;'>", unsafe_allow_html=True)
193
 
194
  # ==============================
195
  # DOWNLOAD RESOURCES & LOAD MODELS
 
246
 
247
 
248
  # ==============================================================================
249
+ # HALAMAN 1: UJI KALIMAT (Background Orange Blur)
250
  # ==============================================================================
251
  if st.session_state.page == "uji_kalimat":
252
  col_text, col_img = st.columns([1.1, 1], gap="large")
253
 
254
  with col_text:
255
  st.markdown("""
256
+ <div style="padding-top: 1rem; position: relative; z-index: 1;">
257
+ <h1 style="font-size: 3.8rem; line-height: 1.1; margin-bottom: 1rem; font-weight: 800; letter-spacing: -1.5px;">
258
+ Bitcoin Volatility <br>vs Public Sentiment
259
  </h1>
260
+ <p style='font-size: 1.15rem; font-weight: 400; margin-bottom: 2rem; opacity: 0.9;'>
261
  Analisis Volatilitas Harga Bitcoin Terhadap Sentimen Publik Pada Platform X Berbasis Python.
262
  </p>
263
+ <div class="researcher-box" style="background-color: #FFFFFF; border-left: 5px solid #0F172A; padding: 15px 20px; border-radius: 8px; margin-bottom: 2.5rem; box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
264
+ <p style="margin: 0; font-size: 0.95rem;">
265
  <span style="color: #F7931A;">πŸ“Œ</span> <b>Peneliti:</b> Arya Galuh Saputra (H1D022022)
266
  </p>
267
  </div>
 
280
  st.info(f"Visualisasi Hero akan muncul di sini. (Pastikan file {os.path.basename(img_hero)} tersedia di folder yang sama)")
281
 
282
  if analyze_btn:
283
+ st.markdown("<br><hr style='border-color: rgba(255,255,255,0.3);'><br>", unsafe_allow_html=True)
284
+ st.markdown("<h3 style='text-align: center; margin-bottom: 2rem;'>πŸ“‹ Hasil Deteksi Sentimen</h3>", unsafe_allow_html=True)
285
 
286
  try:
287
  if detect(user_input) != 'en':
 
322
 
323
 
324
  # ==============================================================================
325
+ # HALAMAN 2: ANALISIS BATCH DATA (Background Putih)
326
  # ==============================================================================
327
  elif st.session_state.page == "analisis_batch":
 
 
 
 
 
 
 
 
 
 
 
328
 
329
  col_upload, col_img_batch = st.columns([1.5, 1], gap="large")
330
 
331
  with col_upload:
332
  st.markdown("""
333
  <div style="padding-top: 1rem;">
334
+ <h2 style="font-size: 2.8rem; margin-bottom: 0.5rem; font-weight: 800; letter-spacing: -1px;">Analisis Batch Data</h2>
335
+ <p style='font-size: 1.1rem; margin-bottom: 1.5rem;'>Unggah file rekam jejak tweet (.txt) untuk diekstraksi dan dianalisis secara masal terhadap volatilitas pasar.</p>
336
  </div>
337
  """, unsafe_allow_html=True)
338
 
 
419
  if df.empty:
420
  st.error("❌ Data kosong. Pastikan format penulisan TXT benar dan tweet berbahasa Inggris.")
421
  else:
 
422
  st.markdown("<h3 style='margin-bottom: 1.5rem;'>πŸ“Š Ringkasan Pemrosesan</h3>", unsafe_allow_html=True)
423
  col_metric1, col_metric2, col_metric3 = st.columns(3)
424
  col_metric1.metric("Tweet Berhasil Diproses", f"{total_tweets_uploaded}", border=False)
 
491
  final_display_cols = ["date", "price", "pct_change", "log_return"] + [c for c in daily_display_cols if c != "date"]
492
  st.dataframe(df_merged[final_display_cols], use_container_width=True, hide_index=True)
493
 
 
494
  col_dl1, col_dl2, _ = st.columns([1, 1, 3])
495
  csv_data = df_merged.to_csv(index=False).encode('utf-8')
496
  col_dl1.download_button("πŸ“₯ Unduh CSV", data=csv_data, file_name="sentiment_volatility.csv", mime="text/csv", use_container_width=True)
 
502
 
503
  st.markdown("<hr style='border-color: #E2E8F0; margin: 3rem 0;'>", unsafe_allow_html=True)
504
 
 
505
  st.subheader("πŸ”¬ Uji Korelasi Pearson")
506
  st.caption("Menganalisis hubungan statistik antara skor sentimen harian dan volatilitas log-return BTC.")
507
 
 
524
 
525
  st.table(pd.DataFrame(corr_data))
526
 
527
+ # ==============================
528
+ # PLOTLY INTERAKTIF (Bebas Error)
529
+ # ==============================
530
  st.markdown("<br>", unsafe_allow_html=True)
531
  st.subheader("πŸ“ˆ Trend Analisis: Sentiment vs BTC Volatility")
532
 
533
+ fig_line = go.Figure()
534
+
535
+ fig_line.add_trace(go.Scatter(
536
+ x=df_merged["date"], y=df_merged["log_return"],
537
+ mode='lines', name='BTC Log Return',
538
+ line=dict(color='#F7931A', width=3)
539
+ ))
540
+
541
  colors = ["#3B82F6", "#10B981", "#EC4899", "#14B8A6", "#6366F1"]
542
  for idx, method in enumerate(["vader", "textblob", "roberta", "roberta_large", "bertweet"]):
543
+ fig_line.add_trace(go.Scatter(
544
+ x=df_merged["date"], y=df_merged[method],
545
+ mode='lines', name=f"Sentiment: {method.upper()}",
546
+ line=dict(color=colors[idx], width=1.5, dash='dash'), opacity=0.8
547
+ ))
548
+
549
+ fig_line.update_layout(
550
+ title="Pergerakan Sentimen vs Log Return Bitcoin",
551
+ hovermode="x unified",
552
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
553
+ plot_bgcolor='rgba(255,255,255,0)',
554
+ xaxis=dict(showgrid=True, gridcolor='#F1F5F9'),
555
+ yaxis=dict(showgrid=True, gridcolor='#F1F5F9')
556
+ )
557
+ st.plotly_chart(fig_line, use_container_width=True)
558
 
559
  # SCATTER PLOT
560
  st.markdown("<br>### πŸ”΅ Pola Distribusi Scatter", unsafe_allow_html=True)
 
564
 
565
  for idx, method in enumerate(models_list):
566
  with cols[idx % 3]:
567
+ fig_scatter = px.scatter(
568
+ df_merged, x=method, y="log_return",
569
+ trendline="ols", title=f"{method.upper()}",
570
+ color_discrete_sequence=["#0F172A"]
571
+ )
572
+ if len(fig_scatter.data) > 1:
573
+ fig_scatter.data[1].line.color = "#F7931A"
574
+ fig_scatter.data[1].line.width = 2
575
+
576
+ fig_scatter.update_layout(
577
+ plot_bgcolor='rgba(255,255,255,0)',
578
+ xaxis=dict(showgrid=True, gridcolor='#F1F5F9'),
579
+ yaxis=dict(showgrid=True, gridcolor='#F1F5F9'),
580
+ margin=dict(l=0, r=0, t=40, b=0)
581
+ )
582
+ st.plotly_chart(fig_scatter, use_container_width=True)
583
 
 
584
  st.markdown("<hr style='border-color: #E2E8F0; margin: 3rem 0;'>", unsafe_allow_html=True)
585
  st.subheader("πŸ“ Kesimpulan Otomatis")
586