sigma-terminal 2.0.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/core/models.py ADDED
@@ -0,0 +1,153 @@
1
+ """Data models for Sigma."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Any, Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class MessageRole(str, Enum):
11
+ """Message roles."""
12
+ SYSTEM = "system"
13
+ USER = "user"
14
+ ASSISTANT = "assistant"
15
+ TOOL = "tool"
16
+
17
+
18
+ class ToolCall(BaseModel):
19
+ """Tool call."""
20
+ id: str
21
+ name: str
22
+ arguments: dict[str, Any]
23
+
24
+
25
+ class Message(BaseModel):
26
+ """Chat message."""
27
+ role: MessageRole
28
+ content: str
29
+ tool_calls: list[ToolCall] = Field(default_factory=list)
30
+ tool_call_id: Optional[str] = None
31
+ name: Optional[str] = None
32
+ timestamp: datetime = Field(default_factory=datetime.now)
33
+
34
+
35
+ class ToolResult(BaseModel):
36
+ """Tool execution result."""
37
+ tool_name: str
38
+ tool_call_id: str
39
+ success: bool
40
+ result: Any
41
+ error: Optional[str] = None
42
+ duration_ms: float = 0.0
43
+
44
+ @property
45
+ def display_result(self) -> str:
46
+ """Get result for display."""
47
+ if self.error:
48
+ return f"Error: {self.error}"
49
+ if isinstance(self.result, dict):
50
+ return str(self.result)
51
+ return str(self.result) if self.result else "No result"
52
+
53
+
54
+ class AgentStep(BaseModel):
55
+ """Agent execution step."""
56
+ step_number: int
57
+ action: str
58
+ tool_calls: list[ToolCall] = Field(default_factory=list)
59
+ tool_results: list[ToolResult] = Field(default_factory=list)
60
+ reasoning: Optional[str] = None
61
+ timestamp: datetime = Field(default_factory=datetime.now)
62
+
63
+
64
+ class FinancialMetric(BaseModel):
65
+ """Financial metric."""
66
+ name: str
67
+ value: float | str | None
68
+ unit: Optional[str] = None
69
+ period: Optional[str] = None
70
+ change: Optional[float] = None
71
+ change_pct: Optional[float] = None
72
+
73
+
74
+ class StockSnapshot(BaseModel):
75
+ """Stock market snapshot."""
76
+ symbol: str
77
+ company_name: str
78
+ price: float
79
+ change: float
80
+ change_pct: float
81
+ volume: int
82
+ market_cap: Optional[float] = None
83
+ pe_ratio: Optional[float] = None
84
+ dividend_yield: Optional[float] = None
85
+ week_52_high: Optional[float] = None
86
+ week_52_low: Optional[float] = None
87
+ avg_volume: Optional[int] = None
88
+ beta: Optional[float] = None
89
+ eps: Optional[float] = None
90
+ timestamp: datetime = Field(default_factory=datetime.now)
91
+
92
+
93
+ class FinancialStatement(BaseModel):
94
+ """Financial statement data."""
95
+ symbol: str
96
+ statement_type: str # income, balance, cash_flow
97
+ period: str # annual, quarterly
98
+ date: str
99
+ metrics: dict[str, float | None]
100
+
101
+
102
+ class EarningsData(BaseModel):
103
+ """Earnings data."""
104
+ symbol: str
105
+ date: str
106
+ eps_estimate: Optional[float] = None
107
+ eps_actual: Optional[float] = None
108
+ revenue_estimate: Optional[float] = None
109
+ revenue_actual: Optional[float] = None
110
+ surprise_pct: Optional[float] = None
111
+
112
+
113
+ class InsiderTrade(BaseModel):
114
+ """Insider trading data."""
115
+ symbol: str
116
+ insider_name: str
117
+ title: str
118
+ transaction_type: str
119
+ shares: int
120
+ price: Optional[float] = None
121
+ value: Optional[float] = None
122
+ date: str
123
+
124
+
125
+ class AnalystRating(BaseModel):
126
+ """Analyst rating."""
127
+ symbol: str
128
+ firm: str
129
+ analyst: Optional[str] = None
130
+ rating: str
131
+ price_target: Optional[float] = None
132
+ date: str
133
+
134
+
135
+ class NewsItem(BaseModel):
136
+ """News item."""
137
+ title: str
138
+ source: str
139
+ url: str
140
+ published: datetime
141
+ summary: Optional[str] = None
142
+ sentiment: Optional[str] = None
143
+
144
+
145
+ class ResearchReport(BaseModel):
146
+ """Research report."""
147
+ title: str
148
+ summary: str
149
+ sections: list[dict[str, Any]]
150
+ data_sources: list[str]
151
+ symbols_analyzed: list[str]
152
+ generated_at: datetime = Field(default_factory=datetime.now)
153
+ confidence: float = 0.0
sigma/setup.py ADDED
@@ -0,0 +1,455 @@
1
+ """Sigma Setup Wizard - Beautiful first-run configuration experience."""
2
+
3
+ import os
4
+ import sys
5
+ import time
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.prompt import Prompt, Confirm
12
+ from rich.progress import Progress, SpinnerColumn, TextColumn
13
+ from rich.table import Table
14
+ from rich.text import Text
15
+ from rich import box
16
+
17
+ console = Console()
18
+
19
+ # Config directory
20
+ CONFIG_DIR = Path.home() / ".sigma"
21
+ CONFIG_FILE = CONFIG_DIR / "config.env"
22
+ SETUP_COMPLETE_FILE = CONFIG_DIR / ".setup_complete"
23
+
24
+
25
+ def is_setup_complete() -> bool:
26
+ """Check if setup has been completed."""
27
+ return SETUP_COMPLETE_FILE.exists()
28
+
29
+
30
+ def clear_screen():
31
+ """Clear the terminal screen."""
32
+ os.system('cls' if os.name == 'nt' else 'clear')
33
+
34
+
35
+ def print_logo():
36
+ """Print the Sigma logo."""
37
+ logo = """
38
+ [bold bright_cyan]
39
+ ███████╗██╗ ██████╗ ███╗ ███╗ █████╗
40
+ ██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗
41
+ ███████╗██║██║ ███╗██╔████╔██║███████║
42
+ ╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║
43
+ ███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
44
+ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
45
+ [/bold bright_cyan]
46
+ """
47
+ console.print(logo)
48
+
49
+
50
+ def animate_text(text: str, delay: float = 0.02):
51
+ """Animate text character by character."""
52
+ for char in text:
53
+ console.print(char, end="", highlight=False)
54
+ time.sleep(delay)
55
+ console.print()
56
+
57
+
58
+ def show_welcome():
59
+ """Show the welcome screen."""
60
+ clear_screen()
61
+ print_logo()
62
+
63
+ console.print()
64
+ console.print(Panel(
65
+ "[bold]Welcome to Sigma[/bold]\n\n"
66
+ "[dim]The Institutional-Grade Financial Research Agent[/dim]\n\n"
67
+ "This setup wizard will help you configure Sigma for\n"
68
+ "optimal performance. It only takes about 2 minutes.",
69
+ title="[bold bright_green]Setup Wizard[/bold bright_green]",
70
+ border_style="bright_green",
71
+ padding=(1, 2)
72
+ ))
73
+ console.print()
74
+
75
+ Prompt.ask("[dim]Press Enter to begin[/dim]", default="")
76
+
77
+
78
+ def show_progress_step(step: int, total: int, title: str):
79
+ """Show progress header."""
80
+ console.print()
81
+ console.print(f"[bold bright_cyan]Step {step}/{total}:[/bold bright_cyan] {title}")
82
+ console.print("[dim]" + "─" * 50 + "[/dim]")
83
+ console.print()
84
+
85
+
86
+ def setup_llm_provider() -> dict:
87
+ """Configure LLM provider."""
88
+ clear_screen()
89
+ print_logo()
90
+ show_progress_step(1, 4, "Choose Your AI Model")
91
+
92
+ providers = [
93
+ ("google", "Google Gemini", "Free tier available, fast responses", "GOOGLE_API_KEY"),
94
+ ("openai", "OpenAI GPT-4", "Most capable, best for complex analysis", "OPENAI_API_KEY"),
95
+ ("anthropic", "Anthropic Claude", "Excellent reasoning, very safe", "ANTHROPIC_API_KEY"),
96
+ ("groq", "Groq (Llama)", "Extremely fast, free tier available", "GROQ_API_KEY"),
97
+ ("xai", "xAI Grok", "Real-time knowledge, unique insights", "XAI_API_KEY"),
98
+ ("ollama", "Ollama (Local)", "Free, private, runs on your machine", None),
99
+ ]
100
+
101
+ table = Table(box=box.ROUNDED, border_style="bright_blue")
102
+ table.add_column("#", style="bold cyan", justify="center", width=3)
103
+ table.add_column("Provider", style="bold")
104
+ table.add_column("Description", style="dim")
105
+
106
+ for i, (key, name, desc, _) in enumerate(providers, 1):
107
+ table.add_row(str(i), name, desc)
108
+
109
+ console.print(table)
110
+ console.print()
111
+
112
+ choice = Prompt.ask(
113
+ "[bold]Select your preferred AI provider[/bold]",
114
+ choices=[str(i) for i in range(1, len(providers) + 1)],
115
+ default="1"
116
+ )
117
+
118
+ selected = providers[int(choice) - 1]
119
+ provider_key, provider_name, _, env_key = selected
120
+
121
+ config = {"DEFAULT_PROVIDER": provider_key}
122
+
123
+ console.print()
124
+ console.print(f"[green]Selected:[/green] {provider_name}")
125
+
126
+ if env_key:
127
+ console.print()
128
+ console.print(Panel(
129
+ f"[bold]Get your API key:[/bold]\n\n"
130
+ f"{'https://aistudio.google.com/apikey' if provider_key == 'google' else ''}"
131
+ f"{'https://platform.openai.com/api-keys' if provider_key == 'openai' else ''}"
132
+ f"{'https://console.anthropic.com/settings/keys' if provider_key == 'anthropic' else ''}"
133
+ f"{'https://console.groq.com/keys' if provider_key == 'groq' else ''}"
134
+ f"{'https://console.x.ai/' if provider_key == 'xai' else ''}",
135
+ title=f"[bold yellow]{provider_name} API Key[/bold yellow]",
136
+ border_style="yellow"
137
+ ))
138
+ console.print()
139
+
140
+ api_key = Prompt.ask(
141
+ f"[bold]Enter your {provider_name} API key[/bold]",
142
+ password=True
143
+ )
144
+ if api_key:
145
+ config[env_key] = api_key
146
+ else:
147
+ # Ollama
148
+ console.print()
149
+ console.print(Panel(
150
+ "[bold]Ollama Setup:[/bold]\n\n"
151
+ "1. Install Ollama: https://ollama.ai\n"
152
+ "2. Run: ollama pull llama3.2\n"
153
+ "3. Ollama runs automatically in the background",
154
+ title="[bold yellow]Local AI Setup[/bold yellow]",
155
+ border_style="yellow"
156
+ ))
157
+ console.print()
158
+ Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
159
+
160
+ return config
161
+
162
+
163
+ def setup_additional_providers(config: dict) -> dict:
164
+ """Configure additional LLM providers."""
165
+ clear_screen()
166
+ print_logo()
167
+ show_progress_step(2, 4, "Additional AI Providers (Optional)")
168
+
169
+ console.print(Panel(
170
+ "You can add more AI providers to switch between them.\n"
171
+ "This is [bold]optional[/bold] - skip if you only need one provider.",
172
+ border_style="blue"
173
+ ))
174
+ console.print()
175
+
176
+ if not Confirm.ask("[bold]Add additional AI providers?[/bold]", default=False):
177
+ return config
178
+
179
+ providers = [
180
+ ("GOOGLE_API_KEY", "Google Gemini", "https://aistudio.google.com/apikey"),
181
+ ("OPENAI_API_KEY", "OpenAI GPT-4", "https://platform.openai.com/api-keys"),
182
+ ("ANTHROPIC_API_KEY", "Anthropic Claude", "https://console.anthropic.com/settings/keys"),
183
+ ("GROQ_API_KEY", "Groq (Llama)", "https://console.groq.com/keys"),
184
+ ("XAI_API_KEY", "xAI Grok", "https://console.x.ai/"),
185
+ ]
186
+
187
+ for env_key, name, url in providers:
188
+ if env_key in config:
189
+ continue
190
+
191
+ console.print()
192
+ if Confirm.ask(f"[bold]Add {name}?[/bold]", default=False):
193
+ console.print(f" [dim]Get key: {url}[/dim]")
194
+ api_key = Prompt.ask(f" [bold]API key[/bold]", password=True)
195
+ if api_key:
196
+ config[env_key] = api_key
197
+ console.print(f" [green]Added![/green]")
198
+
199
+ return config
200
+
201
+
202
+ def setup_data_providers(config: dict) -> dict:
203
+ """Configure financial data providers."""
204
+ clear_screen()
205
+ print_logo()
206
+ show_progress_step(3, 4, "Financial Data Providers (Optional)")
207
+
208
+ console.print(Panel(
209
+ "[bold]Sigma works great with free data from Yahoo Finance.[/bold]\n\n"
210
+ "For premium features, you can add professional data sources.\n"
211
+ "All of these are [bold]optional[/bold].",
212
+ border_style="blue"
213
+ ))
214
+ console.print()
215
+
216
+ # Table of data providers
217
+ table = Table(box=box.ROUNDED, border_style="dim")
218
+ table.add_column("Provider", style="bold")
219
+ table.add_column("Features", style="dim")
220
+ table.add_column("Free Tier")
221
+
222
+ table.add_row("Financial Modeling Prep", "Fundamentals, SEC filings", "[green]Yes[/green]")
223
+ table.add_row("Polygon.io", "Real-time data, options", "[green]Yes[/green]")
224
+ table.add_row("Alpha Vantage", "Technical indicators", "[green]Yes[/green]")
225
+ table.add_row("Exa Search", "AI-powered news search", "[yellow]Limited[/yellow]")
226
+
227
+ console.print(table)
228
+ console.print()
229
+
230
+ if not Confirm.ask("[bold]Configure data providers?[/bold]", default=False):
231
+ return config
232
+
233
+ providers = [
234
+ ("FMP_API_KEY", "Financial Modeling Prep", "https://financialmodelingprep.com/developer/docs/"),
235
+ ("POLYGON_API_KEY", "Polygon.io", "https://polygon.io/dashboard/api-keys"),
236
+ ("ALPHA_VANTAGE_API_KEY", "Alpha Vantage", "https://www.alphavantage.co/support/#api-key"),
237
+ ("EXASEARCH_API_KEY", "Exa Search", "https://exa.ai/"),
238
+ ]
239
+
240
+ for env_key, name, url in providers:
241
+ console.print()
242
+ if Confirm.ask(f"[bold]Add {name}?[/bold]", default=False):
243
+ console.print(f" [dim]Get key: {url}[/dim]")
244
+ api_key = Prompt.ask(f" [bold]API key[/bold]", password=True)
245
+ if api_key:
246
+ config[env_key] = api_key
247
+ console.print(f" [green]Added![/green]")
248
+
249
+ return config
250
+
251
+
252
+ def setup_preferences(config: dict) -> dict:
253
+ """Configure user preferences."""
254
+ clear_screen()
255
+ print_logo()
256
+ show_progress_step(4, 4, "Preferences")
257
+
258
+ # Default model selection
259
+ provider = config.get("DEFAULT_PROVIDER", "google")
260
+
261
+ console.print(Panel(
262
+ "Configure your default settings.\n"
263
+ "You can change these anytime with /model and /mode commands.",
264
+ border_style="blue"
265
+ ))
266
+ console.print()
267
+
268
+ # Analysis mode
269
+ modes = [
270
+ ("default", "Comprehensive - Uses all available tools"),
271
+ ("technical", "Technical - Charts, indicators, price action"),
272
+ ("fundamental", "Fundamental - Financials, ratios, valuations"),
273
+ ("quant", "Quantitative - Predictions, backtesting"),
274
+ ]
275
+
276
+ console.print("[bold]Default analysis mode:[/bold]")
277
+ for i, (key, desc) in enumerate(modes, 1):
278
+ console.print(f" {i}. {desc}")
279
+ console.print()
280
+
281
+ mode_choice = Prompt.ask(
282
+ "[bold]Select mode[/bold]",
283
+ choices=["1", "2", "3", "4"],
284
+ default="1"
285
+ )
286
+ config["DEFAULT_MODE"] = modes[int(mode_choice) - 1][0]
287
+
288
+ return config
289
+
290
+
291
+ def save_config(config: dict):
292
+ """Save configuration to file."""
293
+ # Ensure config directory exists
294
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
295
+
296
+ # Write config file
297
+ with open(CONFIG_FILE, 'w') as f:
298
+ f.write("# Sigma Configuration\n")
299
+ f.write("# Generated by setup wizard\n")
300
+ f.write("# You can edit this file or run 'sigma --setup' again\n\n")
301
+
302
+ for key, value in config.items():
303
+ f.write(f"{key}={value}\n")
304
+
305
+ # Mark setup as complete
306
+ SETUP_COMPLETE_FILE.touch()
307
+
308
+ # Also create/update .env in current directory if it exists
309
+ cwd_env = Path.cwd() / ".env"
310
+ if cwd_env.exists() or Path.cwd().name == "sigma":
311
+ with open(cwd_env, 'a') as f:
312
+ f.write("\n# Added by Sigma setup\n")
313
+ for key, value in config.items():
314
+ if "API_KEY" in key:
315
+ f.write(f"{key}={value}\n")
316
+
317
+
318
+ def show_completion(config: dict):
319
+ """Show completion screen."""
320
+ clear_screen()
321
+ print_logo()
322
+
323
+ console.print()
324
+ console.print(Panel(
325
+ "[bold bright_green]Setup Complete![/bold bright_green]\n\n"
326
+ "Sigma is ready to use. Here's what's configured:",
327
+ border_style="bright_green",
328
+ padding=(1, 2)
329
+ ))
330
+ console.print()
331
+
332
+ # Summary table
333
+ table = Table(box=box.ROUNDED, border_style="green")
334
+ table.add_column("Setting", style="bold")
335
+ table.add_column("Value", style="cyan")
336
+
337
+ provider_names = {
338
+ "google": "Google Gemini",
339
+ "openai": "OpenAI GPT-4",
340
+ "anthropic": "Anthropic Claude",
341
+ "groq": "Groq (Llama)",
342
+ "xai": "xAI Grok",
343
+ "ollama": "Ollama (Local)"
344
+ }
345
+
346
+ table.add_row("AI Provider", provider_names.get(config.get("DEFAULT_PROVIDER", "google"), "Google Gemini"))
347
+ table.add_row("Config Location", str(CONFIG_FILE))
348
+
349
+ # Count configured APIs
350
+ api_count = sum(1 for k in config if "API_KEY" in k)
351
+ table.add_row("API Keys Configured", str(api_count))
352
+
353
+ console.print(table)
354
+
355
+ console.print()
356
+ console.print(Panel(
357
+ "[bold]Quick Start:[/bold]\n\n"
358
+ " [cyan]sigma[/cyan] Start Sigma\n"
359
+ " [cyan]sigma --help[/cyan] Show help\n"
360
+ " [cyan]sigma --setup[/cyan] Run setup again\n\n"
361
+ "[bold]Inside Sigma:[/bold]\n\n"
362
+ " [dim]Analyze NVDA stock[/dim]\n"
363
+ " [dim]Compare AAPL, MSFT, GOOGL[/dim]\n"
364
+ " [dim]/lean run TSLA macd_momentum[/dim]\n\n"
365
+ "[dim]Type /help for all commands[/dim]",
366
+ title="[bold bright_cyan]Getting Started[/bold bright_cyan]",
367
+ border_style="cyan",
368
+ padding=(1, 2)
369
+ ))
370
+ console.print()
371
+
372
+
373
+ def run_setup(force: bool = False) -> dict:
374
+ """Run the setup wizard.
375
+
376
+ Args:
377
+ force: Force setup even if already complete
378
+
379
+ Returns:
380
+ Configuration dictionary
381
+ """
382
+ if is_setup_complete() and not force:
383
+ return load_config()
384
+
385
+ try:
386
+ show_welcome()
387
+
388
+ config = {}
389
+ config = setup_llm_provider()
390
+ config = setup_additional_providers(config)
391
+ config = setup_data_providers(config)
392
+ config = setup_preferences(config)
393
+
394
+ # Save with progress
395
+ console.print()
396
+ with Progress(
397
+ SpinnerColumn(),
398
+ TextColumn("[bold]Saving configuration...[/bold]"),
399
+ console=console
400
+ ) as progress:
401
+ task = progress.add_task("save", total=None)
402
+ save_config(config)
403
+ time.sleep(0.5)
404
+
405
+ show_completion(config)
406
+
407
+ Prompt.ask("\n[dim]Press Enter to start Sigma[/dim]", default="")
408
+
409
+ return config
410
+
411
+ except KeyboardInterrupt:
412
+ console.print("\n\n[yellow]Setup cancelled. Run 'sigma --setup' to try again.[/yellow]")
413
+ sys.exit(0)
414
+
415
+
416
+ def load_config() -> dict:
417
+ """Load existing configuration."""
418
+ config = {}
419
+
420
+ if CONFIG_FILE.exists():
421
+ with open(CONFIG_FILE) as f:
422
+ for line in f:
423
+ line = line.strip()
424
+ if line and not line.startswith('#') and '=' in line:
425
+ key, value = line.split('=', 1)
426
+ config[key.strip()] = value.strip()
427
+
428
+ return config
429
+
430
+
431
+ def apply_config_to_env(config: dict):
432
+ """Apply loaded config to environment variables."""
433
+ for key, value in config.items():
434
+ if key not in os.environ:
435
+ os.environ[key] = value
436
+
437
+
438
+ def ensure_setup() -> dict:
439
+ """Ensure setup is complete, running wizard if needed.
440
+
441
+ Returns:
442
+ Configuration dictionary
443
+ """
444
+ if is_setup_complete():
445
+ config = load_config()
446
+ apply_config_to_env(config)
447
+ return config
448
+ else:
449
+ config = run_setup()
450
+ apply_config_to_env(config)
451
+ return config
452
+
453
+
454
+ if __name__ == "__main__":
455
+ run_setup(force=True)
@@ -0,0 +1,5 @@
1
+ """Tools module initialization."""
2
+
3
+ from sigma.tools.financial import get_all_tools, execute_tool
4
+
5
+ __all__ = ["get_all_tools", "execute_tool"]