sigma-terminal 2.0.1__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sigma/charts.py ADDED
@@ -0,0 +1,407 @@
1
+ """Chart generation for Sigma using Plotly."""
2
+
3
+ import os
4
+ import tempfile
5
+ from datetime import datetime
6
+ from typing import Optional
7
+
8
+ import pandas as pd
9
+ import plotly.graph_objects as go
10
+ from plotly.subplots import make_subplots
11
+
12
+
13
+ # Chart theme
14
+ SIGMA_THEME = {
15
+ "bg_color": "#0a0a0f",
16
+ "paper_color": "#0a0a0f",
17
+ "grid_color": "#1a1a2e",
18
+ "text_color": "#e4e4e7",
19
+ "accent": "#3b82f6",
20
+ "positive": "#22c55e",
21
+ "negative": "#ef4444",
22
+ "neutral": "#6b7280",
23
+ }
24
+
25
+
26
+ def create_candlestick_chart(
27
+ symbol: str,
28
+ data: pd.DataFrame,
29
+ title: Optional[str] = None,
30
+ show_volume: bool = True,
31
+ show_sma: bool = True,
32
+ sma_periods: list = [20, 50],
33
+ ) -> str:
34
+ """Create a candlestick chart with optional indicators."""
35
+
36
+ if show_volume:
37
+ fig = make_subplots(
38
+ rows=2, cols=1,
39
+ shared_xaxes=True,
40
+ vertical_spacing=0.03,
41
+ row_heights=[0.7, 0.3],
42
+ subplot_titles=[None, None],
43
+ )
44
+ else:
45
+ fig = make_subplots(rows=1, cols=1)
46
+
47
+ # Candlestick chart
48
+ fig.add_trace(
49
+ go.Candlestick(
50
+ x=data.index,
51
+ open=data["Open"],
52
+ high=data["High"],
53
+ low=data["Low"],
54
+ close=data["Close"],
55
+ name=symbol.upper(),
56
+ increasing_line_color=SIGMA_THEME["positive"],
57
+ decreasing_line_color=SIGMA_THEME["negative"],
58
+ ),
59
+ row=1, col=1
60
+ )
61
+
62
+ # Add SMAs
63
+ if show_sma:
64
+ colors = ["#f59e0b", "#8b5cf6", "#06b6d4"]
65
+ for i, period in enumerate(sma_periods):
66
+ if len(data) >= period:
67
+ sma = data["Close"].rolling(period).mean()
68
+ fig.add_trace(
69
+ go.Scatter(
70
+ x=data.index,
71
+ y=sma,
72
+ name=f"SMA {period}",
73
+ line=dict(color=colors[i % len(colors)], width=1),
74
+ ),
75
+ row=1, col=1
76
+ )
77
+
78
+ # Volume
79
+ if show_volume and "Volume" in data.columns:
80
+ colors = [SIGMA_THEME["positive"] if data["Close"].iloc[i] >= data["Open"].iloc[i]
81
+ else SIGMA_THEME["negative"] for i in range(len(data))]
82
+
83
+ fig.add_trace(
84
+ go.Bar(
85
+ x=data.index,
86
+ y=data["Volume"],
87
+ name="Volume",
88
+ marker_color=colors,
89
+ opacity=0.7,
90
+ ),
91
+ row=2, col=1
92
+ )
93
+
94
+ # Layout
95
+ chart_title = title or f"{symbol.upper()} Price Chart"
96
+ _apply_layout(fig, chart_title, show_volume)
97
+
98
+ return _save_chart(fig, f"{symbol}_candlestick")
99
+
100
+
101
+ def create_line_chart(
102
+ symbol: str,
103
+ data: pd.DataFrame,
104
+ title: Optional[str] = None,
105
+ show_volume: bool = False,
106
+ ) -> str:
107
+ """Create a line chart."""
108
+
109
+ if show_volume:
110
+ fig = make_subplots(
111
+ rows=2, cols=1,
112
+ shared_xaxes=True,
113
+ vertical_spacing=0.03,
114
+ row_heights=[0.7, 0.3],
115
+ )
116
+ else:
117
+ fig = make_subplots(rows=1, cols=1)
118
+
119
+ # Price line
120
+ fig.add_trace(
121
+ go.Scatter(
122
+ x=data.index,
123
+ y=data["Close"],
124
+ name=symbol.upper(),
125
+ line=dict(color=SIGMA_THEME["accent"], width=2),
126
+ fill="tozeroy",
127
+ fillcolor="rgba(59, 130, 246, 0.1)",
128
+ ),
129
+ row=1, col=1
130
+ )
131
+
132
+ # Volume
133
+ if show_volume and "Volume" in data.columns:
134
+ fig.add_trace(
135
+ go.Bar(
136
+ x=data.index,
137
+ y=data["Volume"],
138
+ name="Volume",
139
+ marker_color=SIGMA_THEME["neutral"],
140
+ opacity=0.5,
141
+ ),
142
+ row=2, col=1
143
+ )
144
+
145
+ chart_title = title or f"{symbol.upper()} Price"
146
+ _apply_layout(fig, chart_title, show_volume)
147
+
148
+ return _save_chart(fig, f"{symbol}_line")
149
+
150
+
151
+ def create_comparison_chart(
152
+ symbols: list,
153
+ data_dict: dict,
154
+ title: Optional[str] = None,
155
+ normalize: bool = True,
156
+ ) -> str:
157
+ """Create a comparison chart for multiple symbols."""
158
+
159
+ fig = go.Figure()
160
+
161
+ colors = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4"]
162
+
163
+ for i, symbol in enumerate(symbols):
164
+ if symbol not in data_dict:
165
+ continue
166
+
167
+ data = data_dict[symbol]
168
+
169
+ if normalize:
170
+ values = (data["Close"] / data["Close"].iloc[0] - 1) * 100
171
+ y_label = "Return (%)"
172
+ else:
173
+ values = data["Close"]
174
+ y_label = "Price ($)"
175
+
176
+ fig.add_trace(
177
+ go.Scatter(
178
+ x=data.index,
179
+ y=values,
180
+ name=symbol.upper(),
181
+ line=dict(color=colors[i % len(colors)], width=2),
182
+ )
183
+ )
184
+
185
+ chart_title = title or "Stock Comparison"
186
+ _apply_layout(fig, chart_title, False)
187
+ fig.update_yaxes(title_text=y_label if normalize else "Price ($)")
188
+
189
+ return _save_chart(fig, "comparison")
190
+
191
+
192
+ def create_technical_chart(
193
+ symbol: str,
194
+ data: pd.DataFrame,
195
+ indicators: list = ["rsi", "macd"],
196
+ title: Optional[str] = None,
197
+ ) -> str:
198
+ """Create a chart with technical indicators."""
199
+
200
+ num_indicators = len(indicators)
201
+ heights = [0.5] + [0.25 / max(1, num_indicators)] * num_indicators + [0.25]
202
+
203
+ fig = make_subplots(
204
+ rows=num_indicators + 2,
205
+ cols=1,
206
+ shared_xaxes=True,
207
+ vertical_spacing=0.03,
208
+ row_heights=heights,
209
+ )
210
+
211
+ row = 1
212
+
213
+ # Candlestick
214
+ fig.add_trace(
215
+ go.Candlestick(
216
+ x=data.index,
217
+ open=data["Open"],
218
+ high=data["High"],
219
+ low=data["Low"],
220
+ close=data["Close"],
221
+ name=symbol.upper(),
222
+ increasing_line_color=SIGMA_THEME["positive"],
223
+ decreasing_line_color=SIGMA_THEME["negative"],
224
+ ),
225
+ row=row, col=1
226
+ )
227
+ row += 1
228
+
229
+ # Add indicators
230
+ for indicator in indicators:
231
+ if indicator.lower() == "rsi":
232
+ rsi = _calculate_rsi(data["Close"])
233
+ fig.add_trace(
234
+ go.Scatter(x=data.index, y=rsi, name="RSI", line=dict(color=SIGMA_THEME["accent"])),
235
+ row=row, col=1
236
+ )
237
+ fig.add_hline(y=70, line_dash="dash", line_color=SIGMA_THEME["negative"], row=row, col=1)
238
+ fig.add_hline(y=30, line_dash="dash", line_color=SIGMA_THEME["positive"], row=row, col=1)
239
+ fig.update_yaxes(range=[0, 100], row=row, col=1)
240
+ row += 1
241
+
242
+ elif indicator.lower() == "macd":
243
+ macd, signal, hist = _calculate_macd(data["Close"])
244
+ colors = [SIGMA_THEME["positive"] if h >= 0 else SIGMA_THEME["negative"] for h in hist]
245
+
246
+ fig.add_trace(
247
+ go.Bar(x=data.index, y=hist, name="MACD Hist", marker_color=colors, opacity=0.5),
248
+ row=row, col=1
249
+ )
250
+ fig.add_trace(
251
+ go.Scatter(x=data.index, y=macd, name="MACD", line=dict(color=SIGMA_THEME["accent"])),
252
+ row=row, col=1
253
+ )
254
+ fig.add_trace(
255
+ go.Scatter(x=data.index, y=signal, name="Signal", line=dict(color="#f59e0b")),
256
+ row=row, col=1
257
+ )
258
+ row += 1
259
+
260
+ # Volume
261
+ if "Volume" in data.columns:
262
+ colors = [SIGMA_THEME["positive"] if data["Close"].iloc[i] >= data["Open"].iloc[i]
263
+ else SIGMA_THEME["negative"] for i in range(len(data))]
264
+ fig.add_trace(
265
+ go.Bar(x=data.index, y=data["Volume"], name="Volume", marker_color=colors, opacity=0.7),
266
+ row=row, col=1
267
+ )
268
+
269
+ chart_title = title or f"{symbol.upper()} Technical Analysis"
270
+ _apply_layout(fig, chart_title, True)
271
+
272
+ return _save_chart(fig, f"{symbol}_technical")
273
+
274
+
275
+ def create_performance_chart(
276
+ equity_curve: list,
277
+ title: str = "Portfolio Performance",
278
+ ) -> str:
279
+ """Create a performance/equity curve chart."""
280
+
281
+ fig = go.Figure()
282
+
283
+ x = list(range(len(equity_curve)))
284
+
285
+ # Equity curve
286
+ fig.add_trace(
287
+ go.Scatter(
288
+ x=x,
289
+ y=equity_curve,
290
+ name="Portfolio Value",
291
+ line=dict(color=SIGMA_THEME["accent"], width=2),
292
+ fill="tozeroy",
293
+ fillcolor="rgba(59, 130, 246, 0.1)",
294
+ )
295
+ )
296
+
297
+ # Starting value reference line
298
+ fig.add_hline(
299
+ y=equity_curve[0],
300
+ line_dash="dash",
301
+ line_color=SIGMA_THEME["neutral"],
302
+ annotation_text=f"Start: ${equity_curve[0]:,.0f}",
303
+ )
304
+
305
+ _apply_layout(fig, title, False)
306
+ fig.update_xaxes(title_text="Trading Days")
307
+ fig.update_yaxes(title_text="Portfolio Value ($)")
308
+
309
+ return _save_chart(fig, "performance")
310
+
311
+
312
+ def create_sector_chart(sector_data: dict) -> str:
313
+ """Create a sector performance chart."""
314
+
315
+ sectors = list(sector_data.keys())
316
+ values = list(sector_data.values())
317
+
318
+ colors = [SIGMA_THEME["positive"] if v >= 0 else SIGMA_THEME["negative"] for v in values]
319
+
320
+ fig = go.Figure()
321
+
322
+ fig.add_trace(
323
+ go.Bar(
324
+ x=sectors,
325
+ y=values,
326
+ marker_color=colors,
327
+ text=[f"{v:+.2f}%" for v in values],
328
+ textposition="outside",
329
+ )
330
+ )
331
+
332
+ _apply_layout(fig, "Sector Performance", False)
333
+ fig.update_yaxes(title_text="Return (%)")
334
+
335
+ return _save_chart(fig, "sectors")
336
+
337
+
338
+ def _calculate_rsi(prices: pd.Series, period: int = 14) -> pd.Series:
339
+ """Calculate RSI indicator."""
340
+ delta = prices.diff()
341
+ gain = (delta.where(delta > 0, 0)).rolling(period).mean()
342
+ loss = (-delta.where(delta < 0, 0)).rolling(period).mean()
343
+ rs = gain / loss
344
+ return 100 - (100 / (1 + rs))
345
+
346
+
347
+ def _calculate_macd(prices: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9):
348
+ """Calculate MACD indicator."""
349
+ ema_fast = prices.ewm(span=fast).mean()
350
+ ema_slow = prices.ewm(span=slow).mean()
351
+ macd = ema_fast - ema_slow
352
+ macd_signal = macd.ewm(span=signal).mean()
353
+ macd_hist = macd - macd_signal
354
+ return macd, macd_signal, macd_hist
355
+
356
+
357
+ def _apply_layout(fig: go.Figure, title: str, has_volume: bool):
358
+ """Apply Sigma theme to chart."""
359
+ fig.update_layout(
360
+ title=dict(
361
+ text=title,
362
+ font=dict(size=18, color=SIGMA_THEME["text_color"]),
363
+ x=0.5,
364
+ ),
365
+ paper_bgcolor=SIGMA_THEME["paper_color"],
366
+ plot_bgcolor=SIGMA_THEME["bg_color"],
367
+ font=dict(color=SIGMA_THEME["text_color"], family="SF Mono, Menlo, monospace"),
368
+ xaxis=dict(
369
+ gridcolor=SIGMA_THEME["grid_color"],
370
+ showgrid=True,
371
+ zeroline=False,
372
+ ),
373
+ yaxis=dict(
374
+ gridcolor=SIGMA_THEME["grid_color"],
375
+ showgrid=True,
376
+ zeroline=False,
377
+ title_text="Price ($)",
378
+ ),
379
+ legend=dict(
380
+ bgcolor="rgba(0,0,0,0)",
381
+ font=dict(color=SIGMA_THEME["text_color"]),
382
+ orientation="h",
383
+ yanchor="bottom",
384
+ y=1.02,
385
+ xanchor="right",
386
+ x=1,
387
+ ),
388
+ margin=dict(l=60, r=40, t=80, b=40),
389
+ xaxis_rangeslider_visible=False,
390
+ hovermode="x unified",
391
+ )
392
+
393
+
394
+ def _save_chart(fig: go.Figure, name: str) -> str:
395
+ """Save chart to file and return path."""
396
+
397
+ # Create charts directory
398
+ charts_dir = os.path.expanduser("~/.sigma/charts")
399
+ os.makedirs(charts_dir, exist_ok=True)
400
+
401
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
402
+ filename = f"{name}_{timestamp}.png"
403
+ filepath = os.path.join(charts_dir, filename)
404
+
405
+ fig.write_image(filepath, width=1200, height=800, scale=2)
406
+
407
+ return filepath