sigma-terminal 3.2.0__py3-none-any.whl → 3.3.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/__init__.py +2 -2
- sigma/app.py +347 -103
- sigma/cli.py +37 -6
- sigma/config.py +174 -3
- sigma/llm.py +256 -18
- sigma/setup.py +84 -16
- sigma_terminal-3.3.0.dist-info/METADATA +583 -0
- {sigma_terminal-3.2.0.dist-info → sigma_terminal-3.3.0.dist-info}/RECORD +11 -11
- sigma_terminal-3.2.0.dist-info/METADATA +0 -298
- {sigma_terminal-3.2.0.dist-info → sigma_terminal-3.3.0.dist-info}/WHEEL +0 -0
- {sigma_terminal-3.2.0.dist-info → sigma_terminal-3.3.0.dist-info}/entry_points.txt +0 -0
- {sigma_terminal-3.2.0.dist-info → sigma_terminal-3.3.0.dist-info}/licenses/LICENSE +0 -0
sigma/app.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""Sigma v3.
|
|
1
|
+
"""Sigma v3.3.0 - Finance Research Agent."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from typing import Optional, List
|
|
7
8
|
|
|
@@ -12,7 +13,7 @@ from rich.text import Text
|
|
|
12
13
|
from textual import on, work
|
|
13
14
|
from textual.app import App, ComposeResult
|
|
14
15
|
from textual.binding import Binding
|
|
15
|
-
from textual.containers import Container, Horizontal, ScrollableContainer
|
|
16
|
+
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
|
16
17
|
from textual.widgets import Footer, Input, RichLog, Static
|
|
17
18
|
from textual.suggester import Suggester
|
|
18
19
|
|
|
@@ -22,43 +23,32 @@ from .tools import TOOLS, execute_tool
|
|
|
22
23
|
from .backtest import run_backtest, get_available_strategies, BACKTEST_TOOL
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
__version__ = "3.
|
|
26
|
+
__version__ = "3.3.0"
|
|
26
27
|
SIGMA = "σ"
|
|
27
28
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
|
|
52
|
-
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold white]
|
|
53
|
-
[dim]analyzing..[/dim]""",
|
|
54
|
-
"""[bold cyan]
|
|
55
|
-
███████╗██╗ ██████╗ ███╗ ███╗ █████╗
|
|
56
|
-
██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗
|
|
57
|
-
███████╗██║██║ ███╗██╔████╔██║███████║
|
|
58
|
-
╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║
|
|
59
|
-
███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
|
|
60
|
-
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold cyan]
|
|
61
|
-
[dim]analyzing...[/dim]""",
|
|
29
|
+
# Common stock tickers for recognition
|
|
30
|
+
COMMON_TICKERS = {
|
|
31
|
+
"AAPL", "MSFT", "GOOGL", "GOOG", "AMZN", "NVDA", "META", "TSLA", "BRK.A", "BRK.B",
|
|
32
|
+
"JPM", "JNJ", "V", "PG", "UNH", "HD", "MA", "DIS", "PYPL", "BAC", "ADBE", "NFLX",
|
|
33
|
+
"CRM", "INTC", "AMD", "CSCO", "PEP", "KO", "ABT", "NKE", "MRK", "PFE", "TMO",
|
|
34
|
+
"COST", "AVGO", "WMT", "ACN", "LLY", "MCD", "DHR", "TXN", "NEE", "PM", "HON",
|
|
35
|
+
"UPS", "BMY", "QCOM", "LOW", "MS", "RTX", "UNP", "ORCL", "IBM", "GE", "CAT",
|
|
36
|
+
"SBUX", "AMAT", "GS", "BLK", "DE", "AMT", "NOW", "ISRG", "LMT", "MDLZ", "AXP",
|
|
37
|
+
"SYK", "BKNG", "PLD", "GILD", "ADI", "TMUS", "CVS", "MMC", "ZTS", "CB", "C",
|
|
38
|
+
"SPY", "QQQ", "IWM", "DIA", "VTI", "VOO", "VXX", "ARKK", "XLF", "XLK", "XLE",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Small sigma animation frames (minimal footprint)
|
|
42
|
+
SMALL_SIGMA_FRAMES = [
|
|
43
|
+
"[bold blue]σ[/bold blue]",
|
|
44
|
+
"[bold cyan]σ[/bold cyan]",
|
|
45
|
+
"[bold white]σ[/bold white]",
|
|
46
|
+
"[bold #60a5fa]σ[/bold #60a5fa]",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Tool call animation frames
|
|
50
|
+
TOOL_CALL_FRAMES = [
|
|
51
|
+
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"
|
|
62
52
|
]
|
|
63
53
|
|
|
64
54
|
# Welcome banner - clean design
|
|
@@ -70,7 +60,7 @@ WELCOME_BANNER = """
|
|
|
70
60
|
[bold blue]███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║[/bold blue]
|
|
71
61
|
[bold blue]╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold blue]
|
|
72
62
|
|
|
73
|
-
[bold cyan]Finance Research Agent[/bold cyan] [dim]v3.
|
|
63
|
+
[bold cyan]Finance Research Agent[/bold cyan] [dim]v3.3.0 | Native macOS[/dim]
|
|
74
64
|
"""
|
|
75
65
|
|
|
76
66
|
SYSTEM_PROMPT = """You are Sigma, a Finance Research Agent. You provide comprehensive market analysis, trading strategies, and investment insights.
|
|
@@ -95,8 +85,9 @@ RESPONSE STYLE:
|
|
|
95
85
|
|
|
96
86
|
When users ask about stocks, always gather current data using your tools before responding."""
|
|
97
87
|
|
|
98
|
-
#
|
|
88
|
+
# Enhanced autocomplete suggestions with more variety
|
|
99
89
|
SUGGESTIONS = [
|
|
90
|
+
# Analysis commands
|
|
100
91
|
"analyze AAPL",
|
|
101
92
|
"analyze MSFT",
|
|
102
93
|
"analyze GOOGL",
|
|
@@ -104,30 +95,79 @@ SUGGESTIONS = [
|
|
|
104
95
|
"analyze TSLA",
|
|
105
96
|
"analyze META",
|
|
106
97
|
"analyze AMZN",
|
|
98
|
+
"analyze AMD",
|
|
99
|
+
"analyze SPY",
|
|
100
|
+
# Comparisons
|
|
107
101
|
"compare AAPL MSFT GOOGL",
|
|
108
102
|
"compare NVDA AMD INTC",
|
|
103
|
+
"compare META GOOGL AMZN",
|
|
104
|
+
"compare TSLA RIVN LCID",
|
|
105
|
+
# Technical
|
|
109
106
|
"technical analysis of AAPL",
|
|
110
107
|
"technical analysis of SPY",
|
|
108
|
+
"technical analysis of NVDA",
|
|
109
|
+
"technical analysis of QQQ",
|
|
110
|
+
# Backtesting
|
|
111
111
|
"backtest SMA crossover on AAPL",
|
|
112
112
|
"backtest RSI strategy on SPY",
|
|
113
|
+
"backtest MACD on NVDA",
|
|
114
|
+
"backtest momentum on QQQ",
|
|
115
|
+
# Market
|
|
113
116
|
"market overview",
|
|
114
117
|
"sector performance",
|
|
118
|
+
"what sectors are hot today",
|
|
119
|
+
# Quotes
|
|
115
120
|
"get quote for AAPL",
|
|
116
121
|
"price of NVDA",
|
|
122
|
+
"how is TSLA doing",
|
|
123
|
+
# Fundamentals
|
|
117
124
|
"fundamentals of MSFT",
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
|
|
125
|
+
"financials for AAPL",
|
|
126
|
+
"earnings of NVDA",
|
|
127
|
+
# Activity
|
|
128
|
+
"insider trading for AAPL",
|
|
129
|
+
"institutional holders of NVDA",
|
|
130
|
+
"analyst recommendations for TSLA",
|
|
131
|
+
# Natural language queries
|
|
132
|
+
"what should I know about AAPL",
|
|
133
|
+
"is NVDA overvalued",
|
|
134
|
+
"best tech stocks right now",
|
|
135
|
+
"should I buy TSLA",
|
|
136
|
+
# Commands
|
|
121
137
|
"/help",
|
|
122
138
|
"/clear",
|
|
123
139
|
"/keys",
|
|
124
140
|
"/models",
|
|
125
141
|
"/status",
|
|
142
|
+
"/backtest",
|
|
126
143
|
]
|
|
127
144
|
|
|
128
145
|
|
|
146
|
+
def extract_tickers(text: str) -> List[str]:
|
|
147
|
+
"""Extract stock tickers from text."""
|
|
148
|
+
# Look for common patterns: $AAPL, or standalone uppercase words
|
|
149
|
+
# Only match if it's a known ticker or starts with $
|
|
150
|
+
words = text.upper().split()
|
|
151
|
+
tickers = []
|
|
152
|
+
|
|
153
|
+
for word in words:
|
|
154
|
+
# Clean the word
|
|
155
|
+
clean = word.strip('.,!?()[]{}":;')
|
|
156
|
+
|
|
157
|
+
# Check for $TICKER format
|
|
158
|
+
if clean.startswith('$'):
|
|
159
|
+
ticker = clean[1:]
|
|
160
|
+
if ticker and ticker.isalpha() and len(ticker) <= 5:
|
|
161
|
+
tickers.append(ticker)
|
|
162
|
+
# Check if it's a known ticker
|
|
163
|
+
elif clean in COMMON_TICKERS:
|
|
164
|
+
tickers.append(clean)
|
|
165
|
+
|
|
166
|
+
return list(dict.fromkeys(tickers)) # Dedupe while preserving order
|
|
167
|
+
|
|
168
|
+
|
|
129
169
|
class SigmaSuggester(Suggester):
|
|
130
|
-
"""
|
|
170
|
+
"""Enhanced autocomplete suggester with ticker recognition."""
|
|
131
171
|
|
|
132
172
|
def __init__(self):
|
|
133
173
|
super().__init__(use_cache=True, case_sensitive=False)
|
|
@@ -138,9 +178,24 @@ class SigmaSuggester(Suggester):
|
|
|
138
178
|
return None
|
|
139
179
|
|
|
140
180
|
value_lower = value.lower()
|
|
181
|
+
|
|
182
|
+
# Check for ticker pattern (all caps or starts with $)
|
|
183
|
+
if value.startswith("$") or value.isupper():
|
|
184
|
+
ticker = value.lstrip("$").upper()
|
|
185
|
+
for common in COMMON_TICKERS:
|
|
186
|
+
if common.startswith(ticker) and common != ticker:
|
|
187
|
+
return f"analyze {common}"
|
|
188
|
+
|
|
189
|
+
# Standard suggestions
|
|
141
190
|
for suggestion in SUGGESTIONS:
|
|
142
191
|
if suggestion.lower().startswith(value_lower):
|
|
143
192
|
return suggestion
|
|
193
|
+
|
|
194
|
+
# Try partial match in middle of suggestion
|
|
195
|
+
for suggestion in SUGGESTIONS:
|
|
196
|
+
if value_lower in suggestion.lower():
|
|
197
|
+
return suggestion
|
|
198
|
+
|
|
144
199
|
return None
|
|
145
200
|
|
|
146
201
|
|
|
@@ -173,16 +228,43 @@ Screen {
|
|
|
173
228
|
padding: 1 0;
|
|
174
229
|
}
|
|
175
230
|
|
|
176
|
-
#
|
|
177
|
-
height:
|
|
231
|
+
#status-bar {
|
|
232
|
+
height: 3;
|
|
233
|
+
background: #0d1117;
|
|
234
|
+
border-top: solid #1a1a2e;
|
|
235
|
+
padding: 0 2;
|
|
236
|
+
dock: bottom;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#status-content {
|
|
178
240
|
width: 100%;
|
|
241
|
+
height: 100%;
|
|
242
|
+
content-align: left middle;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#thinking-indicator {
|
|
246
|
+
width: auto;
|
|
247
|
+
height: 1;
|
|
179
248
|
content-align: center middle;
|
|
180
|
-
background: #0a0a0f;
|
|
181
249
|
display: none;
|
|
182
|
-
padding: 1;
|
|
183
250
|
}
|
|
184
251
|
|
|
185
|
-
#thinking-
|
|
252
|
+
#thinking-indicator.visible {
|
|
253
|
+
display: block;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#tool-calls-display {
|
|
257
|
+
width: 100%;
|
|
258
|
+
height: auto;
|
|
259
|
+
max-height: 6;
|
|
260
|
+
background: #0d1117;
|
|
261
|
+
border: solid #1a1a2e;
|
|
262
|
+
margin: 0 2;
|
|
263
|
+
padding: 0 1;
|
|
264
|
+
display: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#tool-calls-display.visible {
|
|
186
268
|
display: block;
|
|
187
269
|
}
|
|
188
270
|
|
|
@@ -219,6 +301,13 @@ Screen {
|
|
|
219
301
|
border: solid #22c55e;
|
|
220
302
|
}
|
|
221
303
|
|
|
304
|
+
#ticker-highlight {
|
|
305
|
+
width: auto;
|
|
306
|
+
height: 1;
|
|
307
|
+
padding: 0 1;
|
|
308
|
+
background: transparent;
|
|
309
|
+
}
|
|
310
|
+
|
|
222
311
|
Footer {
|
|
223
312
|
background: #0d1117;
|
|
224
313
|
height: 1;
|
|
@@ -238,26 +327,51 @@ Footer > .footer--key {
|
|
|
238
327
|
Footer > .footer--description {
|
|
239
328
|
color: #6b7280;
|
|
240
329
|
}
|
|
330
|
+
|
|
331
|
+
#help-panel {
|
|
332
|
+
width: 100%;
|
|
333
|
+
height: auto;
|
|
334
|
+
padding: 1;
|
|
335
|
+
background: #0d1117;
|
|
336
|
+
border: solid #3b82f6;
|
|
337
|
+
margin: 1 2;
|
|
338
|
+
display: none;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#help-panel.visible {
|
|
342
|
+
display: block;
|
|
343
|
+
}
|
|
241
344
|
"""
|
|
242
345
|
|
|
243
346
|
|
|
244
|
-
class
|
|
245
|
-
"""Animated
|
|
347
|
+
class ToolCallDisplay(Static):
|
|
348
|
+
"""Animated display for tool calls."""
|
|
246
349
|
|
|
247
350
|
def __init__(self, *args, **kwargs):
|
|
248
351
|
super().__init__(*args, **kwargs)
|
|
352
|
+
self.tool_calls: List[dict] = []
|
|
249
353
|
self.frame = 0
|
|
250
354
|
self.timer = None
|
|
251
355
|
|
|
252
|
-
def
|
|
253
|
-
"""
|
|
356
|
+
def add_tool_call(self, name: str, status: str = "running"):
|
|
357
|
+
"""Add a tool call to the display."""
|
|
358
|
+
self.tool_calls.append({"name": name, "status": status, "frame": 0})
|
|
254
359
|
self.add_class("visible")
|
|
255
|
-
self.
|
|
256
|
-
self.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def
|
|
260
|
-
"""
|
|
360
|
+
self._render()
|
|
361
|
+
if not self.timer:
|
|
362
|
+
self.timer = self.set_interval(0.1, self._animate)
|
|
363
|
+
|
|
364
|
+
def complete_tool_call(self, name: str):
|
|
365
|
+
"""Mark a tool call as complete."""
|
|
366
|
+
for tc in self.tool_calls:
|
|
367
|
+
if tc["name"] == name and tc["status"] == "running":
|
|
368
|
+
tc["status"] = "complete"
|
|
369
|
+
break
|
|
370
|
+
self._render()
|
|
371
|
+
|
|
372
|
+
def clear(self):
|
|
373
|
+
"""Clear all tool calls."""
|
|
374
|
+
self.tool_calls = []
|
|
261
375
|
if self.timer:
|
|
262
376
|
self.timer.stop()
|
|
263
377
|
self.timer = None
|
|
@@ -265,13 +379,31 @@ class ThinkingDisplay(Static):
|
|
|
265
379
|
self.update("")
|
|
266
380
|
|
|
267
381
|
def _animate(self):
|
|
268
|
-
"""
|
|
269
|
-
self.frame = (self.frame + 1) % len(
|
|
270
|
-
self.
|
|
382
|
+
"""Animate the spinner."""
|
|
383
|
+
self.frame = (self.frame + 1) % len(TOOL_CALL_FRAMES)
|
|
384
|
+
for tc in self.tool_calls:
|
|
385
|
+
if tc["status"] == "running":
|
|
386
|
+
tc["frame"] = self.frame
|
|
387
|
+
self._render()
|
|
388
|
+
|
|
389
|
+
def _render(self):
|
|
390
|
+
"""Render the tool calls display."""
|
|
391
|
+
if not self.tool_calls:
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
lines = []
|
|
395
|
+
for tc in self.tool_calls:
|
|
396
|
+
if tc["status"] == "running":
|
|
397
|
+
spinner = TOOL_CALL_FRAMES[tc["frame"]]
|
|
398
|
+
lines.append(f" [cyan]{spinner}[/cyan] [bold]{tc['name']}[/bold] [dim]executing...[/dim]")
|
|
399
|
+
else:
|
|
400
|
+
lines.append(f" [green]✓[/green] [bold]{tc['name']}[/bold] [green]complete[/green]")
|
|
401
|
+
|
|
402
|
+
self.update(Text.from_markup("\n".join(lines)))
|
|
271
403
|
|
|
272
404
|
|
|
273
405
|
class SigmaIndicator(Static):
|
|
274
|
-
"""Pulsing sigma indicator."""
|
|
406
|
+
"""Pulsing sigma indicator with minimal footprint."""
|
|
275
407
|
|
|
276
408
|
def __init__(self, *args, **kwargs):
|
|
277
409
|
super().__init__(*args, **kwargs)
|
|
@@ -285,24 +417,46 @@ class SigmaIndicator(Static):
|
|
|
285
417
|
def set_active(self, active: bool):
|
|
286
418
|
self.active = active
|
|
287
419
|
if active and not self.timer:
|
|
288
|
-
self.timer = self.set_interval(0.
|
|
420
|
+
self.timer = self.set_interval(0.15, self._pulse)
|
|
289
421
|
elif not active and self.timer:
|
|
290
422
|
self.timer.stop()
|
|
291
423
|
self.timer = None
|
|
292
424
|
self.update(Text.from_markup(f"[bold blue]{SIGMA}[/bold blue]"))
|
|
293
425
|
|
|
294
426
|
def _pulse(self):
|
|
295
|
-
|
|
296
|
-
self.
|
|
297
|
-
|
|
427
|
+
self.frame = (self.frame + 1) % len(SMALL_SIGMA_FRAMES)
|
|
428
|
+
self.update(Text.from_markup(SMALL_SIGMA_FRAMES[self.frame]))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class TickerHighlight(Static):
|
|
432
|
+
"""Display detected tickers in real-time."""
|
|
433
|
+
|
|
434
|
+
def update_tickers(self, text: str):
|
|
435
|
+
"""Update displayed tickers based on input."""
|
|
436
|
+
tickers = extract_tickers(text)
|
|
437
|
+
if tickers:
|
|
438
|
+
ticker_text = " ".join([f"[cyan]${t}[/cyan]" for t in tickers[:3]])
|
|
439
|
+
self.update(Text.from_markup(ticker_text))
|
|
440
|
+
else:
|
|
441
|
+
self.update("")
|
|
298
442
|
|
|
299
443
|
|
|
300
444
|
class ChatLog(RichLog):
|
|
301
445
|
"""Chat log with rich formatting."""
|
|
302
446
|
|
|
303
447
|
def write_user(self, message: str):
|
|
448
|
+
# Highlight any tickers in user message
|
|
449
|
+
highlighted = message
|
|
450
|
+
for ticker in extract_tickers(message):
|
|
451
|
+
highlighted = re.sub(
|
|
452
|
+
rf'\b{ticker}\b',
|
|
453
|
+
f'[cyan]{ticker}[/cyan]',
|
|
454
|
+
highlighted,
|
|
455
|
+
flags=re.IGNORECASE
|
|
456
|
+
)
|
|
457
|
+
|
|
304
458
|
self.write(Panel(
|
|
305
|
-
Text(message, style="white"),
|
|
459
|
+
Text.from_markup(highlighted) if '[cyan]' in highlighted else Text(message, style="white"),
|
|
306
460
|
title="[bold blue]You[/bold blue]",
|
|
307
461
|
border_style="blue",
|
|
308
462
|
padding=(0, 1),
|
|
@@ -317,7 +471,8 @@ class ChatLog(RichLog):
|
|
|
317
471
|
))
|
|
318
472
|
|
|
319
473
|
def write_tool(self, tool_name: str):
|
|
320
|
-
|
|
474
|
+
# This is now handled by ToolCallDisplay
|
|
475
|
+
pass
|
|
321
476
|
|
|
322
477
|
def write_error(self, message: str):
|
|
323
478
|
self.write(Panel(Text(message, style="red"), title="[red]Error[/red]", border_style="red"))
|
|
@@ -338,6 +493,7 @@ class SigmaApp(App):
|
|
|
338
493
|
BINDINGS = [
|
|
339
494
|
Binding("ctrl+l", "clear", "Clear"),
|
|
340
495
|
Binding("ctrl+m", "models", "Models"),
|
|
496
|
+
Binding("ctrl+h", "help_toggle", "Help"),
|
|
341
497
|
Binding("ctrl+p", "palette", "palette", show=True),
|
|
342
498
|
Binding("escape", "cancel", show=False),
|
|
343
499
|
]
|
|
@@ -350,6 +506,7 @@ class SigmaApp(App):
|
|
|
350
506
|
self.is_processing = False
|
|
351
507
|
self.history: List[str] = []
|
|
352
508
|
self.history_idx = -1
|
|
509
|
+
self.show_help = False
|
|
353
510
|
|
|
354
511
|
def compose(self) -> ComposeResult:
|
|
355
512
|
yield Container(
|
|
@@ -357,15 +514,17 @@ class SigmaApp(App):
|
|
|
357
514
|
ChatLog(id="chat-log", highlight=True, markup=True),
|
|
358
515
|
id="chat-area",
|
|
359
516
|
),
|
|
360
|
-
|
|
517
|
+
ToolCallDisplay(id="tool-calls-display"),
|
|
518
|
+
Static(id="help-panel"),
|
|
361
519
|
Container(
|
|
362
520
|
Horizontal(
|
|
363
521
|
SigmaIndicator(id="sigma-indicator"),
|
|
364
522
|
Input(
|
|
365
|
-
placeholder="Ask about any stock, market, or strategy...",
|
|
523
|
+
placeholder="Ask about any stock, market, or strategy... (Tab to autocomplete)",
|
|
366
524
|
id="prompt-input",
|
|
367
525
|
suggester=SigmaSuggester(),
|
|
368
526
|
),
|
|
527
|
+
TickerHighlight(id="ticker-highlight"),
|
|
369
528
|
id="input-row",
|
|
370
529
|
),
|
|
371
530
|
id="input-area",
|
|
@@ -380,7 +539,7 @@ class SigmaApp(App):
|
|
|
380
539
|
|
|
381
540
|
provider = getattr(self.settings.default_provider, 'value', str(self.settings.default_provider))
|
|
382
541
|
chat.write_system(f"{SIGMA} Provider: {provider} | Model: {self.settings.default_model}")
|
|
383
|
-
chat.write_system(f"{SIGMA} Type /help for commands
|
|
542
|
+
chat.write_system(f"{SIGMA} Type /help for commands • Ctrl+H for quick help • Tab to autocomplete")
|
|
384
543
|
chat.write_system("")
|
|
385
544
|
|
|
386
545
|
self._init_llm()
|
|
@@ -394,6 +553,12 @@ class SigmaApp(App):
|
|
|
394
553
|
chat.write_error(f"LLM init failed: {e}")
|
|
395
554
|
chat.write_system("Use /keys to configure API keys")
|
|
396
555
|
|
|
556
|
+
@on(Input.Changed)
|
|
557
|
+
def on_input_change(self, event: Input.Changed):
|
|
558
|
+
"""Update ticker highlight as user types."""
|
|
559
|
+
ticker_display = self.query_one("#ticker-highlight", TickerHighlight)
|
|
560
|
+
ticker_display.update_tickers(event.value)
|
|
561
|
+
|
|
397
562
|
@on(Input.Submitted)
|
|
398
563
|
def handle_input(self, event: Input.Submitted):
|
|
399
564
|
if self.is_processing:
|
|
@@ -421,20 +586,7 @@ class SigmaApp(App):
|
|
|
421
586
|
args = parts[1:] if len(parts) > 1 else []
|
|
422
587
|
|
|
423
588
|
if command == "/help":
|
|
424
|
-
|
|
425
|
-
[bold]{SIGMA} Commands[/bold]
|
|
426
|
-
/help Show commands
|
|
427
|
-
/clear Clear chat
|
|
428
|
-
/keys Configure API keys
|
|
429
|
-
/models Show models
|
|
430
|
-
/provider <name> Switch provider
|
|
431
|
-
/model <name> Switch model
|
|
432
|
-
/status Show configuration
|
|
433
|
-
/backtest Show strategies
|
|
434
|
-
|
|
435
|
-
[bold]{SIGMA} Shortcuts[/bold]
|
|
436
|
-
Ctrl+L Clear Ctrl+M Models Ctrl+P Palette
|
|
437
|
-
""")
|
|
589
|
+
self._show_comprehensive_help(chat)
|
|
438
590
|
elif command == "/clear":
|
|
439
591
|
chat.clear()
|
|
440
592
|
self.conversation = []
|
|
@@ -453,8 +605,86 @@ class SigmaApp(App):
|
|
|
453
605
|
self._switch_model(args[0], chat)
|
|
454
606
|
elif command.startswith("/setkey") and len(parts) >= 3:
|
|
455
607
|
self._set_key(parts[1], parts[2], chat)
|
|
608
|
+
elif command == "/tickers":
|
|
609
|
+
self._show_popular_tickers(chat)
|
|
456
610
|
else:
|
|
457
|
-
chat.write_error(f"Unknown command: {command}")
|
|
611
|
+
chat.write_error(f"Unknown command: {command}. Type /help for available commands.")
|
|
612
|
+
|
|
613
|
+
def _show_comprehensive_help(self, chat: ChatLog):
|
|
614
|
+
"""Show comprehensive help with examples."""
|
|
615
|
+
help_text = f"""
|
|
616
|
+
[bold cyan]═══════════════════════════════════════════════════════════════[/bold cyan]
|
|
617
|
+
[bold] {SIGMA} SIGMA HELP CENTER [/bold]
|
|
618
|
+
[bold cyan]═══════════════════════════════════════════════════════════════[/bold cyan]
|
|
619
|
+
|
|
620
|
+
[bold yellow]QUICK START[/bold yellow]
|
|
621
|
+
Just type naturally! Examples:
|
|
622
|
+
• "analyze AAPL" - Full analysis of Apple
|
|
623
|
+
• "compare NVDA AMD INTC" - Compare multiple stocks
|
|
624
|
+
• "is TSLA overvalued?" - Get AI insights
|
|
625
|
+
• "market overview" - See major indices
|
|
626
|
+
|
|
627
|
+
[bold yellow]COMMANDS[/bold yellow]
|
|
628
|
+
[cyan]/help[/cyan] This help screen
|
|
629
|
+
[cyan]/clear[/cyan] Clear chat history
|
|
630
|
+
[cyan]/keys[/cyan] Configure API keys
|
|
631
|
+
[cyan]/models[/cyan] Show available models
|
|
632
|
+
[cyan]/status[/cyan] Current configuration
|
|
633
|
+
[cyan]/backtest[/cyan] Show backtest strategies
|
|
634
|
+
[cyan]/provider <name>[/cyan] Switch AI provider
|
|
635
|
+
[cyan]/model <name>[/cyan] Switch model
|
|
636
|
+
[cyan]/setkey <p> <k>[/cyan] Set API key
|
|
637
|
+
[cyan]/tickers[/cyan] Popular tickers list
|
|
638
|
+
|
|
639
|
+
[bold yellow]ANALYSIS EXAMPLES[/bold yellow]
|
|
640
|
+
• "technical analysis of SPY"
|
|
641
|
+
• "fundamentals of MSFT"
|
|
642
|
+
• "insider trading for AAPL"
|
|
643
|
+
• "analyst recommendations for NVDA"
|
|
644
|
+
• "sector performance"
|
|
645
|
+
|
|
646
|
+
[bold yellow]BACKTESTING[/bold yellow]
|
|
647
|
+
• "backtest SMA crossover on AAPL"
|
|
648
|
+
• "backtest RSI strategy on SPY"
|
|
649
|
+
• "backtest MACD on NVDA"
|
|
650
|
+
Strategies: sma_crossover, rsi, macd, bollinger, momentum, breakout
|
|
651
|
+
|
|
652
|
+
[bold yellow]KEYBOARD SHORTCUTS[/bold yellow]
|
|
653
|
+
[bold]Tab[/bold] Autocomplete suggestion
|
|
654
|
+
[bold]Ctrl+L[/bold] Clear chat
|
|
655
|
+
[bold]Ctrl+M[/bold] Show models
|
|
656
|
+
[bold]Ctrl+H[/bold] Toggle quick help
|
|
657
|
+
[bold]Ctrl+P[/bold] Command palette
|
|
658
|
+
[bold]Esc[/bold] Cancel operation
|
|
659
|
+
|
|
660
|
+
[bold yellow]TIPS[/bold yellow]
|
|
661
|
+
• Type [cyan]$AAPL[/cyan] or [cyan]AAPL[/cyan] - tickers auto-detected
|
|
662
|
+
• Use Tab for smart autocomplete
|
|
663
|
+
• Detected tickers shown next to input
|
|
664
|
+
"""
|
|
665
|
+
chat.write(Panel(
|
|
666
|
+
Text.from_markup(help_text),
|
|
667
|
+
title=f"[bold cyan]{SIGMA} Help[/bold cyan]",
|
|
668
|
+
border_style="cyan",
|
|
669
|
+
padding=(0, 1),
|
|
670
|
+
))
|
|
671
|
+
|
|
672
|
+
def _show_popular_tickers(self, chat: ChatLog):
|
|
673
|
+
"""Show popular tickers organized by category."""
|
|
674
|
+
tickers_text = """
|
|
675
|
+
[bold]Tech Giants[/bold]: AAPL, MSFT, GOOGL, AMZN, META, NVDA
|
|
676
|
+
[bold]Semiconductors[/bold]: NVDA, AMD, INTC, AVGO, QCOM, TSM
|
|
677
|
+
[bold]EVs & Auto[/bold]: TSLA, RIVN, LCID, F, GM
|
|
678
|
+
[bold]Finance[/bold]: JPM, BAC, GS, MS, V, MA
|
|
679
|
+
[bold]Healthcare[/bold]: JNJ, PFE, UNH, MRK, ABBV
|
|
680
|
+
[bold]ETFs[/bold]: SPY, QQQ, IWM, DIA, VTI, VOO
|
|
681
|
+
[bold]Sector ETFs[/bold]: XLK, XLF, XLE, XLV, XLI
|
|
682
|
+
"""
|
|
683
|
+
chat.write(Panel(
|
|
684
|
+
Text.from_markup(tickers_text),
|
|
685
|
+
title=f"[cyan]{SIGMA} Popular Tickers[/cyan]",
|
|
686
|
+
border_style="dim",
|
|
687
|
+
))
|
|
458
688
|
|
|
459
689
|
def _show_keys(self, chat: ChatLog):
|
|
460
690
|
chat.write_system(f"""
|
|
@@ -541,19 +771,12 @@ Example: /setkey google AIzaSy...
|
|
|
541
771
|
return
|
|
542
772
|
|
|
543
773
|
self.is_processing = True
|
|
544
|
-
thinking = self.query_one("#thinking-area", Static)
|
|
545
774
|
indicator = self.query_one("#sigma-indicator", SigmaIndicator)
|
|
775
|
+
tool_display = self.query_one("#tool-calls-display", ToolCallDisplay)
|
|
776
|
+
ticker_highlight = self.query_one("#ticker-highlight", TickerHighlight)
|
|
546
777
|
|
|
547
|
-
#
|
|
548
|
-
|
|
549
|
-
frame = [0]
|
|
550
|
-
|
|
551
|
-
def animate():
|
|
552
|
-
thinking.update(Text.from_markup(THINKING_LOGO_FRAMES[frame[0]]))
|
|
553
|
-
frame[0] = (frame[0] + 1) % len(THINKING_LOGO_FRAMES)
|
|
554
|
-
|
|
555
|
-
animate()
|
|
556
|
-
timer = self.set_interval(0.3, animate)
|
|
778
|
+
# Clear ticker highlight and start sigma animation
|
|
779
|
+
ticker_highlight.update("")
|
|
557
780
|
indicator.set_active(True)
|
|
558
781
|
|
|
559
782
|
try:
|
|
@@ -564,13 +787,20 @@ Example: /setkey google AIzaSy...
|
|
|
564
787
|
all_tools = TOOLS + [BACKTEST_TOOL]
|
|
565
788
|
|
|
566
789
|
async def on_tool(name: str, args: dict):
|
|
567
|
-
|
|
790
|
+
tool_display.add_tool_call(name)
|
|
568
791
|
if name == "run_backtest":
|
|
569
|
-
|
|
570
|
-
|
|
792
|
+
result = run_backtest(**args)
|
|
793
|
+
else:
|
|
794
|
+
result = execute_tool(name, args)
|
|
795
|
+
tool_display.complete_tool_call(name)
|
|
796
|
+
return result
|
|
571
797
|
|
|
572
798
|
response = await self.llm.generate(messages, tools=all_tools, on_tool_call=on_tool)
|
|
573
799
|
|
|
800
|
+
# Clear tool display after getting response
|
|
801
|
+
await asyncio.sleep(0.5) # Brief pause to show completion
|
|
802
|
+
tool_display.clear()
|
|
803
|
+
|
|
574
804
|
if response:
|
|
575
805
|
chat.write_assistant(response)
|
|
576
806
|
self.conversation.append({"role": "user", "content": query})
|
|
@@ -580,11 +810,9 @@ Example: /setkey google AIzaSy...
|
|
|
580
810
|
else:
|
|
581
811
|
chat.write_error("No response")
|
|
582
812
|
except Exception as e:
|
|
813
|
+
tool_display.clear()
|
|
583
814
|
chat.write_error(str(e))
|
|
584
815
|
finally:
|
|
585
|
-
timer.stop()
|
|
586
|
-
thinking.remove_class("visible")
|
|
587
|
-
thinking.update("")
|
|
588
816
|
indicator.set_active(False)
|
|
589
817
|
self.is_processing = False
|
|
590
818
|
self.query_one("#prompt-input", Input).focus()
|
|
@@ -598,9 +826,25 @@ Example: /setkey google AIzaSy...
|
|
|
598
826
|
def action_models(self):
|
|
599
827
|
self._show_models(self.query_one("#chat-log", ChatLog))
|
|
600
828
|
|
|
829
|
+
def action_help_toggle(self):
|
|
830
|
+
"""Toggle quick help panel."""
|
|
831
|
+
help_panel = self.query_one("#help-panel", Static)
|
|
832
|
+
if self.show_help:
|
|
833
|
+
help_panel.remove_class("visible")
|
|
834
|
+
help_panel.update("")
|
|
835
|
+
else:
|
|
836
|
+
help_panel.add_class("visible")
|
|
837
|
+
help_panel.update(Text.from_markup(
|
|
838
|
+
"[bold]Quick Commands:[/bold] /help /clear /keys /models /status /backtest "
|
|
839
|
+
"[bold]Shortcuts:[/bold] Tab=autocomplete Ctrl+L=clear Ctrl+M=models"
|
|
840
|
+
))
|
|
841
|
+
self.show_help = not self.show_help
|
|
842
|
+
|
|
601
843
|
def action_cancel(self):
|
|
602
844
|
if self.is_processing:
|
|
603
845
|
self.is_processing = False
|
|
846
|
+
tool_display = self.query_one("#tool-calls-display", ToolCallDisplay)
|
|
847
|
+
tool_display.clear()
|
|
604
848
|
|
|
605
849
|
|
|
606
850
|
def launch():
|