QAway-to commited on
Commit
a312a60
·
1 Parent(s): 280aa9c

New tabs and functions v1.5

Browse files
Files changed (2) hide show
  1. app.py +61 -217
  2. core/crypto_dashboard.py +78 -60
app.py CHANGED
@@ -5,6 +5,7 @@ from core.comparer import PortfolioComparer
5
  from core.chat import ChatAssistant
6
  from core.metrics import show_metrics_table
7
  from core.visualization import build_alpha_chart
 
8
 
9
  # === Model setup ===
10
  MODEL_NAME = "meta-llama/Meta-Llama-3.1-8B-Instruct"
@@ -13,7 +14,7 @@ analyzer = PortfolioAnalyzer(llm_service, MODEL_NAME)
13
  comparer = PortfolioComparer(llm_service, MODEL_NAME)
14
  chatbot = ChatAssistant(llm_service, MODEL_NAME)
15
 
16
- # === Bloomberg-style dark theme ===
17
  dark_theme = gr.themes.Base(
18
  primary_hue="violet",
19
  secondary_hue="gray",
@@ -33,251 +34,94 @@ dark_theme = gr.themes.Base(
33
  )
34
 
35
  with gr.Blocks(theme=dark_theme, css="""
36
- /* === Layout width === */
37
  #root, [data-testid="block-container"] {
38
- max-width: 1050px !important; /* unified width -50px */
39
  margin: auto !important;
40
  }
41
- [data-testid="tabitem"] {
42
- width: 100% !important;
43
- }
44
- .gradio-container {
45
- font-family: 'Inter', sans-serif;
46
- background-color: #0d1117 !important;
47
- }
48
-
49
- /* === LLM Commentary & Analysis Output (same dark style) === */
50
- #llm_comment_box textarea,
51
- #analysis_output textarea {
52
- height: auto !important;
53
- min-height: 520px !important;
54
- overflow-y: auto !important;
55
- background-color: #161b22 !important;
56
- color: #f0f6fc !important;
57
- border: 1px solid #30363d !important;
58
- border-radius: 6px !important;
59
- font-family: 'JetBrains Mono', monospace !important;
60
- font-size: 14px !important;
61
- line-height: 1.5 !important;
62
- padding: 12px !important;
63
- }
64
-
65
- /* === Comparison Table === */
66
- #comparison_table {
67
- height: 620px !important;
68
- overflow-y: auto !important;
69
- margin-top: 10px !important;
70
- }
71
- #comparison_table table {
72
- width: 100% !important;
73
- }
74
-
75
- /* === Universal Loader Style === */
76
- .loading-spinner {
77
- border: 3px solid rgba(255, 255, 255, 0.1);
78
- border-top: 3px solid #6366f1;
79
- border-radius: 50%;
80
- width: 22px;
81
- height: 22px;
82
- animation: spin 0.8s linear infinite;
83
- margin: 12px auto;
84
- }
85
- @keyframes spin {
86
- 0% { transform: rotate(0deg); }
87
- 100% { transform: rotate(360deg); }
88
- }
89
-
90
- /* Typography & Buttons */
91
- h2, h3, .gr-markdown {
92
- font-weight: 600;
93
- color: #f0f6fc !important;
94
- }
95
- .gr-button {
96
- border-radius: 6px !important;
97
- font-weight: 600 !important;
98
- letter-spacing: 0.3px;
99
- box-shadow: 0 2px 4px rgba(0,0,0,0.25);
100
- }
101
- .gr-textbox, .gr-dataframe {
102
- border-radius: 6px !important;
103
- }
104
- .gr-button.primary {
105
- background: linear-gradient(90deg, #6366f1, #4f46e5);
106
- border: none !important;
107
- }
108
- .gr-tab {
109
- background-color: #161b22 !important;
110
- color: #c9d1d9 !important;
111
- }
112
- .gr-tabs {
113
- border-bottom: 1px solid #30363d !important;
114
- }
115
- .gr-plot {
116
- background: #0d1117 !important;
117
- }
118
-
119
- /* Fix Gradio plot overlay */
120
- #alpha_chart svg {
121
- display: none !important;
122
- }
123
- #alpha_chart .wrap {
124
- background: transparent !important;
125
- box-shadow: none !important;
126
- }
127
-
128
- /* === Styled comparison & metrics tables === */
129
- .gr-dataframe table {
130
- border-collapse: collapse !important;
131
- width: 100% !important;
132
- color: #c9d1d9 !important;
133
- background-color: #161b22 !important;
134
- }
135
- .gr-dataframe th {
136
- background-color: #21262d !important;
137
- color: #f0f6fc !important;
138
- font-weight: 600 !important;
139
- text-transform: uppercase;
140
- border-bottom: 1px solid #30363d !important;
141
- }
142
- .gr-dataframe td {
143
- border-top: 1px solid #30363d !important;
144
- padding: 8px !important;
145
  }
 
 
 
146
  """) as demo:
147
 
148
- # === Header ===
149
  gr.Markdown("## Investment Portfolio Analyzer")
150
  gr.Markdown(
151
- "A professional dashboard for analyzing and comparing investment portfolios using AI insights.",
152
  elem_classes="subtitle",
153
  )
154
 
155
  with gr.Tabs():
156
- # --- Analysis Tab ---
157
  with gr.TabItem("Analysis"):
158
- portfolio_input = gr.Textbox(
159
- label="Portfolio ID or Link",
160
- placeholder="Enter a portfolio ID (e.g. ea856c1b-...)",
161
- lines=1,
162
- value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb",
163
- )
164
- analyze_button = gr.Button("Run Analysis", variant="primary")
165
- analyze_output = gr.Textbox(
166
- label="Analysis Result",
167
- lines=15,
168
- elem_id="analysis_output",
169
- )
170
- analyze_button.click(
171
- fn=analyzer.run,
172
- inputs=portfolio_input,
173
- outputs=analyze_output,
174
- show_progress="minimal", # ensures loader display
175
- )
176
-
177
- # --- Comparison Table Tab ---
178
  with gr.TabItem("Comparison Table"):
179
- comp_input_1 = gr.Textbox(label="Portfolio A", value="3852a354-e66e-4bc5-97e9-55124e31e687")
180
- comp_input_2 = gr.Textbox(label="Portfolio B", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb")
181
- comp_button = gr.Button("Load Comparison", variant="primary")
182
-
183
- comp_output_comment = gr.Textbox(
184
- label="AI Commentary",
185
- lines=14,
186
- interactive=False,
187
- show_copy_button=True,
188
- elem_id="llm_comment_box",
189
- )
190
-
191
- comp_output_table = gr.Dataframe(
192
- label="Comparative Metrics",
193
- wrap=True,
194
- elem_id="comparison_table",
195
- )
196
-
197
  from core.comparison_table import show_comparison_table
198
- comp_button.click(
199
- fn=show_comparison_table,
200
- inputs=[comp_input_1, comp_input_2],
201
- outputs=[comp_output_table, comp_output_comment],
202
- show_progress="minimal",
203
- )
204
-
205
- # --- Chat Assistant Tab ---
 
 
206
  with gr.TabItem("Assistant"):
207
- chat_input = gr.Textbox(label="Ask about investments or analysis!")
208
- chat_button = gr.Button("Send Question", variant="primary")
209
- chat_output = gr.Textbox(label="AI Response", lines=8)
210
- chat_button.click(
211
- fn=chatbot.run,
212
- inputs=chat_input,
213
- outputs=chat_output,
214
- show_progress="minimal",
215
- )
216
 
217
- # --- Metrics Table Tab ---
218
  with gr.TabItem("Metrics Table"):
219
- metrics_input = gr.Textbox(label="Portfolio ID", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb")
220
- metrics_button = gr.Button("Load Metrics", variant="primary")
221
- metrics_output = gr.Dataframe(label="Portfolio Metrics", wrap=True)
222
- metrics_button.click(
223
- fn=show_metrics_table,
224
- inputs=metrics_input,
225
- outputs=metrics_output,
226
- show_progress="minimal",
227
- )
228
 
229
- # --- AlphaBTC Chart Tab ---
230
  with gr.TabItem("AlphaBTC Chart"):
231
- chart_input = gr.Textbox(label="Portfolio ID", value="3852a354-e66e-4bc5-97e9-55124e31e687")
232
- chart_button = gr.Button("Generate Chart", variant="primary")
233
- chart_output = gr.Plot(
234
- label="Alpha vs BTC",
235
- show_label=False,
236
- elem_id="alpha_chart",
237
- )
238
- chart_button.click(
239
- fn=build_alpha_chart,
240
- inputs=chart_input,
241
- outputs=chart_output,
242
- show_progress="minimal",
243
- )
244
- # --- Crypto DAX Dashboard ---
245
- with gr.TabItem("Crypto DAX Dashboard"):
246
- gr.Markdown("### 📊 Live Market Dashboard (CoinGecko + Portfolio Metrics)")
247
 
248
  with gr.Row():
249
  coin_input = gr.Dropdown(
250
  label="Select Cryptocurrency",
251
- choices=["bitcoin", "ethereum", "solana", "dogecoin", "cardano"],
252
- value="bitcoin",
253
- )
254
- portfolio_input_dax = gr.Textbox(
255
- label="Portfolio ID",
256
- value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb",
257
- placeholder="Your portfolio UUID"
258
  )
259
  days_slider = gr.Slider(
260
- label="Period (days)",
261
- minimum=7,
262
- maximum=180,
263
- step=1,
264
- value=30,
265
  )
 
266
 
267
- load_button = gr.Button("Generate Dashboard", variant="primary")
268
  kpi_plot = gr.Plot(label="KPI Metrics")
269
- price_plot = gr.Plot(label="Price Trend")
270
- vol_plot = gr.Plot(label="Volatility")
271
- ai_comment = gr.Textbox(label="AI Market Commentary", lines=8, elem_id="llm_comment_box")
272
-
273
- from core.crypto_dashboard import build_dashboard
274
-
275
- load_button.click(
276
- fn=build_dashboard,
277
- inputs=[coin_input, portfolio_input_dax, days_slider],
278
- outputs=[kpi_plot, price_plot, vol_plot, ai_comment],
279
- show_progress="minimal",
280
- )
281
 
282
  gr.Markdown("---")
283
  gr.Markdown(
 
5
  from core.chat import ChatAssistant
6
  from core.metrics import show_metrics_table
7
  from core.visualization import build_alpha_chart
8
+ from core.crypto_dashboard import build_crypto_dashboard # 🆕 новая вкладка
9
 
10
  # === Model setup ===
11
  MODEL_NAME = "meta-llama/Meta-Llama-3.1-8B-Instruct"
 
14
  comparer = PortfolioComparer(llm_service, MODEL_NAME)
15
  chatbot = ChatAssistant(llm_service, MODEL_NAME)
16
 
17
+ # === Theme ===
18
  dark_theme = gr.themes.Base(
19
  primary_hue="violet",
20
  secondary_hue="gray",
 
34
  )
35
 
36
  with gr.Blocks(theme=dark_theme, css="""
 
37
  #root, [data-testid="block-container"] {
38
+ max-width: 1050px !important;
39
  margin: auto !important;
40
  }
41
+ .gradio-container { font-family: 'Inter', sans-serif; background-color:#0d1117!important;}
42
+ #llm_comment_box textarea,#analysis_output textarea{
43
+ min-height:520px!important;background-color:#161b22!important;color:#f0f6fc!important;
44
+ border:1px solid #30363d!important;border-radius:6px!important;font-family:'JetBrains Mono',monospace!important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
+ .gr-dataframe table{width:100%!important;color:#c9d1d9!important;background:#161b22!important;}
47
+ .gr-dataframe th{background:#21262d!important;color:#f0f6fc!important;border-bottom:1px solid #30363d!important;}
48
+ .gr-dataframe td{border-top:1px solid #30363d!important;padding:8px!important;}
49
  """) as demo:
50
 
 
51
  gr.Markdown("## Investment Portfolio Analyzer")
52
  gr.Markdown(
53
+ "Professional AI-driven analytics for investment and crypto markets.",
54
  elem_classes="subtitle",
55
  )
56
 
57
  with gr.Tabs():
58
+ # --- Analysis ---
59
  with gr.TabItem("Analysis"):
60
+ portfolio_input = gr.Textbox(label="Portfolio ID or Link",
61
+ placeholder="Enter portfolio ID (uuid)",
62
+ value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb")
63
+ analyze_btn = gr.Button("Run Analysis", variant="primary")
64
+ analyze_out = gr.Textbox(label="Analysis Result", lines=15, elem_id="analysis_output")
65
+ analyze_btn.click(fn=analyzer.run, inputs=portfolio_input, outputs=analyze_out)
66
+
67
+ # --- Comparison ---
 
 
 
 
 
 
 
 
 
 
 
 
68
  with gr.TabItem("Comparison Table"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  from core.comparison_table import show_comparison_table
70
+ pid_a = gr.Textbox(label="Portfolio A", value="3852a354-e66e-4bc5-97e9-55124e31e687")
71
+ pid_b = gr.Textbox(label="Portfolio B", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb")
72
+ compare_btn = gr.Button("Load Comparison", variant="primary")
73
+ comp_table = gr.Dataframe(label="Comparative Metrics", wrap=True)
74
+ comp_comment = gr.Textbox(label="AI Commentary", lines=14, elem_id="llm_comment_box")
75
+ compare_btn.click(fn=show_comparison_table,
76
+ inputs=[pid_a, pid_b],
77
+ outputs=[comp_table, comp_comment])
78
+
79
+ # --- Assistant ---
80
  with gr.TabItem("Assistant"):
81
+ chat_in = gr.Textbox(label="Ask about investments or analysis")
82
+ chat_btn = gr.Button("Send Question", variant="primary")
83
+ chat_out = gr.Textbox(label="AI Response", lines=8)
84
+ chat_btn.click(fn=chatbot.run, inputs=chat_in, outputs=chat_out)
 
 
 
 
 
85
 
86
+ # --- Metrics ---
87
  with gr.TabItem("Metrics Table"):
88
+ metrics_in = gr.Textbox(label="Portfolio ID", value="b1ef37aa-5b9a-41b4-9394-8823f2de36bb")
89
+ metrics_btn = gr.Button("Load Metrics", variant="primary")
90
+ metrics_out = gr.Dataframe(label="Portfolio Metrics", wrap=True)
91
+ metrics_btn.click(fn=show_metrics_table, inputs=metrics_in, outputs=metrics_out)
 
 
 
 
 
92
 
93
+ # --- AlphaBTC Chart ---
94
  with gr.TabItem("AlphaBTC Chart"):
95
+ chart_in = gr.Textbox(label="Portfolio ID", value="3852a354-e66e-4bc5-97e9-55124e31e687")
96
+ chart_btn = gr.Button("Generate Chart", variant="primary")
97
+ chart_out = gr.Plot(label="Alpha vs BTC")
98
+ chart_btn.click(fn=build_alpha_chart, inputs=chart_in, outputs=chart_out)
99
+
100
+ # --- 🧠 Crypto Intelligence Dashboard (CoinGecko) ---
101
+ with gr.TabItem("Crypto Intelligence Dashboard"):
102
+ gr.Markdown("### 📈 Real-Time Crypto Analytics (CoinGecko API + AI Insights)")
 
 
 
 
 
 
 
 
103
 
104
  with gr.Row():
105
  coin_input = gr.Dropdown(
106
  label="Select Cryptocurrency",
107
+ choices=["bitcoin", "ethereum", "solana", "bnb", "dogecoin"],
108
+ value="bitcoin"
 
 
 
 
 
109
  )
110
  days_slider = gr.Slider(
111
+ label="Days Range", minimum=7, maximum=180, step=1, value=90
 
 
 
 
112
  )
113
+ load_btn = gr.Button("Load Market Data", variant="primary")
114
 
 
115
  kpi_plot = gr.Plot(label="KPI Metrics")
116
+ price_plot = gr.Plot(label="Price Chart")
117
+ rsi_plot = gr.Plot(label="RSI Indicator")
118
+ corr_plot = gr.Plot(label="Correlation Heatmap")
119
+ ai_summary = gr.Textbox(label="AI Market Summary", lines=8, elem_id="llm_comment_box")
120
+
121
+ load_btn.click(fn=build_crypto_dashboard,
122
+ inputs=[coin_input, days_slider],
123
+ outputs=[kpi_plot, price_plot, rsi_plot, corr_plot, ai_summary],
124
+ show_progress="minimal")
 
 
 
125
 
126
  gr.Markdown("---")
127
  gr.Markdown(
core/crypto_dashboard.py CHANGED
@@ -1,29 +1,22 @@
1
  """
2
  🇬🇧 Module: crypto_dashboard.py
3
- Purpose: Build Power BI–style dashboard for crypto market using CoinGecko API + portfolio metrics.
4
  🇷🇺 Модуль: crypto_dashboard.py
5
- Назначение: Создание Power BI-подобного дашборда на основе CoinGecko API и метрик портфеля.
6
  """
7
 
8
  import requests
9
  import pandas as pd
10
- import plotly.graph_objects as go
11
  import plotly.express as px
12
- from datetime import datetime
13
- from services.output_api import fetch_metrics_async
14
  from services.llm_client import llm_service
15
- import asyncio
16
 
17
  COINGECKO_API = "https://api.coingecko.com/api/v3"
18
 
19
 
20
- async def _get_portfolio_metrics(portfolio_id):
21
- metrics = await fetch_metrics_async(portfolio_id)
22
- return metrics or {}
23
-
24
-
25
- def get_market_data(coin_id: str = "bitcoin", days: int = 30):
26
- """Fetch market data (price, volume, market cap) from CoinGecko."""
27
  url = f"{COINGECKO_API}/coins/{coin_id}/market_chart?vs_currency=usd&days={days}"
28
  r = requests.get(url)
29
  data = r.json()
@@ -31,76 +24,101 @@ def get_market_data(coin_id: str = "bitcoin", days: int = 30):
31
  df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
32
  df["returns"] = df["price"].pct_change()
33
  df["volatility"] = df["returns"].rolling(7).std() * 100
 
34
  return df
35
 
36
 
37
- def build_dashboard(coin_id: str, portfolio_id: str, days: int = 30):
38
- """Main sync wrapper: returns 3 Plotly figures + LLM commentary."""
39
- df = get_market_data(coin_id, days)
40
- metrics = asyncio.run(_get_portfolio_metrics(portfolio_id))
 
 
 
 
 
 
 
41
 
 
 
 
42
  if df.empty:
43
- return None, None, None, "❌ No market data."
44
 
45
- # === KPI Cards ===
46
  growth = (df["price"].iloc[-1] / df["price"].iloc[0] - 1) * 100
47
  vol = df["volatility"].mean()
48
- sharpe = metrics.get("sharpe", 0)
49
 
50
- kpi = go.Figure()
51
- kpi.add_trace(go.Indicator(
52
- mode="number+delta",
53
  value=round(growth, 2),
54
- delta={"reference": 0, "position": "right"},
55
  title={"text": f"{coin_id.capitalize()} Growth %"},
56
- domain={'row': 0, 'column': 0}
57
- ))
58
- kpi.add_trace(go.Indicator(
59
- mode="number",
60
  value=round(vol, 2),
61
  title={"text": "Volatility %"},
62
- domain={'row': 0, 'column': 1}
63
- ))
64
- kpi.add_trace(go.Indicator(
65
- mode="number",
66
- value=round(sharpe, 2),
67
- title={"text": "Sharpe (Portfolio)"},
68
- domain={'row': 0, 'column': 2}
69
- ))
70
- kpi.update_layout(grid={'rows': 1, 'columns': 3}, template="plotly_dark", height=200)
71
-
72
- # === Price timeline ===
73
  price_fig = px.line(df, x="timestamp", y="price", title=f"{coin_id.capitalize()} Price (USD)")
74
  price_fig.update_layout(template="plotly_dark", height=400)
75
 
76
- # === Volatility chart ===
77
- vol_fig = px.area(df, x="timestamp", y="volatility", title="7-Day Rolling Volatility")
78
- vol_fig.update_traces(line_color="#4f46e5")
79
- vol_fig.update_layout(template="plotly_dark", height=300)
80
 
81
- # === Generate AI summary ===
82
- commentary = _generate_ai_comment(df, coin_id, metrics)
83
- return kpi, price_fig, vol_fig, commentary
84
 
 
 
 
 
85
 
86
- def _generate_ai_comment(df, coin_id, metrics):
87
- """Generate an AI summary (LLM commentary)."""
88
- change = (df["price"].iloc[-1] / df["price"].iloc[0] - 1) * 100
89
- avg_vol = df["volatility"].mean()
90
- prompt = f"""
91
- Act as a financial analyst.
92
- Summarize {coin_id.capitalize()} market performance over the last period.
93
 
94
- - Growth: {change:.2f}%
95
- - Average Volatility: {avg_vol:.2f}%
96
- - Portfolio Sharpe: {metrics.get('sharpe', 0):.2f}
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- Provide 3–5 concise sentences: performance, stability, and potential outlook.
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  """
100
- commentary = ""
101
  for delta in llm_service.stream_chat(
102
  messages=[{"role": "user", "content": prompt}],
103
  model="meta-llama/Meta-Llama-3.1-8B-Instruct"
104
  ):
105
- commentary += delta
106
- return commentary
 
1
  """
2
  🇬🇧 Module: crypto_dashboard.py
3
+ Purpose: Power BI–style dashboard for crypto analytics using CoinGecko API.
4
  🇷🇺 Модуль: crypto_dashboard.py
5
+ Назначение: интерактивный дашборд уровня Power BI для анализа крипторынка.
6
  """
7
 
8
  import requests
9
  import pandas as pd
10
+ import numpy as np
11
  import plotly.express as px
12
+ import plotly.graph_objects as go
 
13
  from services.llm_client import llm_service
 
14
 
15
  COINGECKO_API = "https://api.coingecko.com/api/v3"
16
 
17
 
18
+ # === Data Fetching ===
19
+ def get_market_data(coin_id: str, days: int = 90):
 
 
 
 
 
20
  url = f"{COINGECKO_API}/coins/{coin_id}/market_chart?vs_currency=usd&days={days}"
21
  r = requests.get(url)
22
  data = r.json()
 
24
  df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")
25
  df["returns"] = df["price"].pct_change()
26
  df["volatility"] = df["returns"].rolling(7).std() * 100
27
+ df["rsi"] = _calc_rsi(df["price"])
28
  return df
29
 
30
 
31
+ def _calc_rsi(prices, period: int = 14):
32
+ """RSI indicator like in trading terminals."""
33
+ delta = prices.diff()
34
+ gain = np.where(delta > 0, delta, 0)
35
+ loss = np.where(delta < 0, -delta, 0)
36
+ avg_gain = pd.Series(gain).rolling(period).mean()
37
+ avg_loss = pd.Series(loss).rolling(period).mean()
38
+ rs = avg_gain / avg_loss
39
+ rsi = 100 - (100 / (1 + rs))
40
+ return rsi
41
+
42
 
43
+ # === KPI and Figures ===
44
+ def build_crypto_dashboard(coin_id: str, days: int = 90):
45
+ df = get_market_data(coin_id, days)
46
  if df.empty:
47
+ return None, None, None, None, "❌ No market data."
48
 
49
+ # === KPI ===
50
  growth = (df["price"].iloc[-1] / df["price"].iloc[0] - 1) * 100
51
  vol = df["volatility"].mean()
52
+ latest_rsi = df["rsi"].iloc[-1]
53
 
54
+ kpi_fig = go.Figure()
55
+ kpi_fig.add_trace(go.Indicator(mode="number+delta",
 
56
  value=round(growth, 2),
57
+ delta={"reference": 0},
58
  title={"text": f"{coin_id.capitalize()} Growth %"},
59
+ domain={'row': 0, 'column': 0}))
60
+ kpi_fig.add_trace(go.Indicator(mode="number",
 
 
61
  value=round(vol, 2),
62
  title={"text": "Volatility %"},
63
+ domain={'row': 0, 'column': 1}))
64
+ kpi_fig.add_trace(go.Indicator(mode="number",
65
+ value=round(latest_rsi, 2),
66
+ title={"text": "RSI"},
67
+ domain={'row': 0, 'column': 2}))
68
+ kpi_fig.update_layout(grid={'rows': 1, 'columns': 3}, template="plotly_dark", height=180)
69
+
70
+ # === Price Chart ===
 
 
 
71
  price_fig = px.line(df, x="timestamp", y="price", title=f"{coin_id.capitalize()} Price (USD)")
72
  price_fig.update_layout(template="plotly_dark", height=400)
73
 
74
+ # === RSI Chart ===
75
+ rsi_fig = px.line(df, x="timestamp", y="rsi", title="RSI (Momentum Oscillator)")
76
+ rsi_fig.update_layout(template="plotly_dark", height=300)
 
77
 
78
+ # === Heatmap of Correlations ===
79
+ corr_fig = _build_correlation_heatmap()
 
80
 
81
+ # === LLM commentary ===
82
+ commentary = _generate_market_summary(df, coin_id, growth, vol, latest_rsi)
83
+
84
+ return kpi_fig, price_fig, rsi_fig, corr_fig, commentary
85
 
 
 
 
 
 
 
 
86
 
87
+ def _build_correlation_heatmap():
88
+ """Build correlation heatmap for top coins."""
89
+ coins = ["bitcoin", "ethereum", "solana", "bnb", "dogecoin"]
90
+ frames = {}
91
+ for c in coins:
92
+ try:
93
+ df = get_market_data(c, 60)
94
+ frames[c] = df["price"].pct_change().dropna().reset_index(drop=True)
95
+ except Exception:
96
+ continue
97
+ merged = pd.DataFrame(frames)
98
+ corr = merged.corr()
99
+ fig = px.imshow(corr, text_auto=True, title="Correlation Heatmap (Top 5 Coins)")
100
+ fig.update_layout(template="plotly_dark", height=400)
101
+ return fig
102
 
103
+
104
+ def _generate_market_summary(df, coin, growth, vol, rsi):
105
+ """LLM generates human-like market insight."""
106
+ prompt = f"""
107
+ Summarize the {coin.capitalize()} market:
108
+ - Growth: {growth:.2f}%
109
+ - Avg Volatility: {vol:.2f}%
110
+ - RSI: {rsi:.2f}
111
+
112
+ Provide 4-5 concise sentences describing:
113
+ • Market trend (bullish / bearish)
114
+ • Risk and volatility
115
+ • Momentum signal (based on RSI)
116
+ • Outlook for the short term
117
  """
118
+ summary = ""
119
  for delta in llm_service.stream_chat(
120
  messages=[{"role": "user", "content": prompt}],
121
  model="meta-llama/Meta-Llama-3.1-8B-Instruct"
122
  ):
123
+ summary += delta
124
+ return summary