sigma-terminal 2.0.2__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/cli.py ADDED
@@ -0,0 +1,434 @@
1
+ """CLI entry point for Sigma v3.2.0."""
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from typing import Optional
7
+
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+ from rich.panel import Panel
11
+
12
+ from .app import launch
13
+ from .config import get_settings, save_api_key, save_setting, AVAILABLE_MODELS, LLMProvider
14
+
15
+
16
+ __version__ = "3.2.0"
17
+
18
+ console = Console()
19
+
20
+
21
+ def show_banner():
22
+ """Show the Sigma banner."""
23
+ banner = """
24
+ [bold white]███████╗██╗ ██████╗ ███╗ ███╗ █████╗ [/bold white]
25
+ [bold white]██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗[/bold white]
26
+ [bold white]███████╗██║██║ ███╗██╔████╔██║███████║[/bold white]
27
+ [bold white]╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║[/bold white]
28
+ [bold white]███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║[/bold white]
29
+ [bold white]╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold white]
30
+
31
+ [dim]v3.2.0[/dim] [bold cyan]σ[/bold cyan] [bold]Finance Research Agent[/bold]
32
+ """
33
+ console.print(banner)
34
+
35
+
36
+ def main():
37
+ """Main CLI entry point."""
38
+ parser = argparse.ArgumentParser(
39
+ prog="sigma",
40
+ description="Sigma v3.2.0 - Finance Research Agent",
41
+ formatter_class=argparse.RawDescriptionHelpFormatter,
42
+ epilog="""
43
+ Examples:
44
+ sigma # Launch interactive app
45
+ sigma ask "analyze AAPL" # Quick query
46
+ sigma quote AAPL GOOGL MSFT # Get quotes
47
+ sigma --setup # Run setup wizard
48
+ """
49
+ )
50
+
51
+ parser.add_argument(
52
+ "--version", "-v",
53
+ action="store_true",
54
+ help="Show version and exit"
55
+ )
56
+
57
+ parser.add_argument(
58
+ "--setup",
59
+ action="store_true",
60
+ help="Run the setup wizard"
61
+ )
62
+
63
+ parser.add_argument(
64
+ "--setkey",
65
+ nargs=2,
66
+ metavar=("PROVIDER", "KEY"),
67
+ help="Set API key for provider (google, openai, anthropic, groq, xai)"
68
+ )
69
+
70
+ parser.add_argument(
71
+ "--provider",
72
+ choices=["google", "openai", "anthropic", "groq", "xai", "ollama"],
73
+ help="Set default AI provider"
74
+ )
75
+
76
+ parser.add_argument(
77
+ "--model",
78
+ help="Set default model"
79
+ )
80
+
81
+ parser.add_argument(
82
+ "--list-models",
83
+ action="store_true",
84
+ help="List available models"
85
+ )
86
+
87
+ parser.add_argument(
88
+ "--status",
89
+ action="store_true",
90
+ help="Show current configuration"
91
+ )
92
+
93
+ # Subcommands
94
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
95
+
96
+ # Ask command
97
+ ask_parser = subparsers.add_parser("ask", help="Ask a question")
98
+ ask_parser.add_argument("query", nargs="+", help="Your question")
99
+
100
+ # Quote command
101
+ quote_parser = subparsers.add_parser("quote", help="Get stock quotes")
102
+ quote_parser.add_argument("symbols", nargs="+", help="Stock symbols")
103
+
104
+ # Chart command
105
+ chart_parser = subparsers.add_parser("chart", help="Generate a chart")
106
+ chart_parser.add_argument("symbol", help="Stock symbol")
107
+ chart_parser.add_argument("--period", default="6mo", help="Time period (default: 6mo)")
108
+ chart_parser.add_argument("--output", "-o", help="Output file path")
109
+
110
+ # Backtest command
111
+ backtest_parser = subparsers.add_parser("backtest", help="Run a backtest")
112
+ backtest_parser.add_argument("symbol", help="Stock symbol")
113
+ backtest_parser.add_argument("--strategy", "-s", default="sma_crossover",
114
+ help="Strategy name (default: sma_crossover)")
115
+ backtest_parser.add_argument("--period", default="1y", help="Time period (default: 1y)")
116
+
117
+ # Compare command
118
+ compare_parser = subparsers.add_parser("compare", help="Compare stocks")
119
+ compare_parser.add_argument("symbols", nargs="+", help="Stock symbols to compare")
120
+
121
+ args = parser.parse_args()
122
+
123
+ if args.version:
124
+ show_banner()
125
+ return 0
126
+
127
+ if args.setup:
128
+ from .setup import run_setup
129
+ return 0 if run_setup() else 1
130
+
131
+ if args.list_models:
132
+ console.print("\n[bold]Available Models by Provider:[/bold]\n")
133
+ for provider, models in AVAILABLE_MODELS.items():
134
+ console.print(f" [cyan]{provider}:[/cyan]")
135
+ for model in models:
136
+ console.print(f" • {model}")
137
+ return 0
138
+
139
+ if args.status:
140
+ settings = get_settings()
141
+
142
+ table = Table(title="Sigma Configuration", show_header=False)
143
+ table.add_column("Setting", style="bold")
144
+ table.add_column("Value")
145
+
146
+ table.add_row("Provider", settings.default_provider.value if hasattr(settings.default_provider, 'value') else str(settings.default_provider))
147
+ table.add_row("Model", settings.default_model)
148
+ table.add_row("", "")
149
+ table.add_row("API Keys", "")
150
+ table.add_row(" Google", "[green]OK[/green]" if settings.google_api_key else "[red]--[/red]")
151
+ table.add_row(" OpenAI", "[green]OK[/green]" if settings.openai_api_key else "[red]--[/red]")
152
+ table.add_row(" Anthropic", "[green]OK[/green]" if settings.anthropic_api_key else "[red]--[/red]")
153
+ table.add_row(" Groq", "[green]OK[/green]" if settings.groq_api_key else "[red]--[/red]")
154
+ table.add_row(" xAI", "[green]OK[/green]" if settings.xai_api_key else "[red]--[/red]")
155
+
156
+ console.print(table)
157
+ return 0
158
+
159
+ if args.setkey:
160
+ provider, key = args.setkey
161
+ provider = provider.lower()
162
+
163
+ try:
164
+ provider_enum = LLMProvider(provider)
165
+ save_api_key(provider_enum, key)
166
+ console.print(f"[bold cyan]σ[/bold cyan] API key for {provider} saved.")
167
+ return 0
168
+ except ValueError:
169
+ console.print(f"[red]Error:[/red] Unknown provider '{provider}'")
170
+ console.print(f"Valid providers: google, openai, anthropic, groq, xai")
171
+ return 1
172
+
173
+ if args.provider or args.model:
174
+ if args.provider:
175
+ save_setting("default_provider", args.provider)
176
+ console.print(f"[bold cyan]σ[/bold cyan] Default provider: {args.provider}")
177
+
178
+ if args.model:
179
+ save_setting("default_model", args.model)
180
+ console.print(f"[bold cyan]σ[/bold cyan] Default model: {args.model}")
181
+
182
+ return 0
183
+
184
+ # Handle subcommands
185
+ if args.command == "ask":
186
+ query = " ".join(args.query)
187
+ return handle_ask(query)
188
+
189
+ elif args.command == "quote":
190
+ return handle_quotes(args.symbols)
191
+
192
+ elif args.command == "chart":
193
+ return handle_chart(args.symbol, args.period, args.output)
194
+
195
+ elif args.command == "backtest":
196
+ return handle_backtest(args.symbol, args.strategy, args.period)
197
+
198
+ elif args.command == "compare":
199
+ return handle_compare(args.symbols)
200
+
201
+ # Default: Launch the app
202
+ launch()
203
+ return 0
204
+
205
+
206
+ def handle_ask(query: str) -> int:
207
+ """Handle ask command."""
208
+ import asyncio
209
+ from .llm import get_llm
210
+ from .tools import TOOLS, execute_tool
211
+
212
+ settings = get_settings()
213
+
214
+ console.print(f"\n[dim]Using {settings.default_provider.value} / {settings.default_model}[/dim]\n")
215
+
216
+ try:
217
+ llm = get_llm(settings.default_provider, settings.default_model)
218
+
219
+ async def run_query():
220
+ """Run the query asynchronously."""
221
+ messages = [
222
+ {"role": "system", "content": "You are Sigma, a helpful financial intelligence assistant. Use the tools available to provide accurate, data-driven insights."},
223
+ {"role": "user", "content": query}
224
+ ]
225
+
226
+ async def handle_tool(name: str, args: dict):
227
+ """Handle tool calls."""
228
+ console.print(f"[dim]Executing: {name}[/dim]")
229
+ return execute_tool(name, args)
230
+
231
+ response = await llm.generate(messages, TOOLS, handle_tool)
232
+ return response
233
+
234
+ with console.status("[bold blue]σ analyzing...[/bold blue]"):
235
+ response = asyncio.run(run_query())
236
+
237
+ console.print(Panel(response, title="[bold cyan]σ Sigma[/bold cyan]"))
238
+ return 0
239
+
240
+ except Exception as e:
241
+ console.print(f"[red]Error:[/red] {e}")
242
+ return 1
243
+
244
+
245
+ def handle_quotes(symbols: list) -> int:
246
+ """Handle quote command."""
247
+ from .tools import get_stock_quote
248
+
249
+ table = Table(title="Stock Quotes")
250
+ table.add_column("Symbol", style="cyan")
251
+ table.add_column("Price", justify="right")
252
+ table.add_column("Change", justify="right")
253
+ table.add_column("Change %", justify="right")
254
+ table.add_column("Volume", justify="right")
255
+
256
+ for symbol in symbols:
257
+ quote = get_stock_quote(symbol)
258
+
259
+ if "error" in quote:
260
+ table.add_row(symbol, "[red]Error[/red]", "-", "-", "-")
261
+ continue
262
+
263
+ change = quote.get("change", 0)
264
+ change_pct = quote.get("change_percent", 0)
265
+ change_style = "green" if change >= 0 else "red"
266
+
267
+ table.add_row(
268
+ quote.get("symbol", symbol),
269
+ f"${quote.get('price', 0):,.2f}",
270
+ f"[{change_style}]{change:+.2f}[/{change_style}]",
271
+ f"[{change_style}]{change_pct:+.2f}%[/{change_style}]",
272
+ f"{quote.get('volume', 0):,}",
273
+ )
274
+
275
+ console.print(table)
276
+ return 0
277
+
278
+
279
+ def handle_chart(symbol: str, period: str, output: Optional[str] = None) -> int:
280
+ """Handle chart command."""
281
+ from .charts import create_candlestick_chart
282
+ import yfinance as yf
283
+
284
+ with console.status(f"[bold blue]Generating chart for {symbol}...[/bold blue]"):
285
+ try:
286
+ ticker = yf.Ticker(symbol.upper())
287
+ hist = ticker.history(period=period)
288
+
289
+ if hist.empty:
290
+ console.print(f"[red]Error:[/red] No data found for {symbol}")
291
+ return 1
292
+
293
+ filepath = create_candlestick_chart(symbol, hist)
294
+ except Exception as e:
295
+ console.print(f"[red]Error:[/red] {e}")
296
+ return 1
297
+
298
+ console.print(f"[bold cyan]σ[/bold cyan] Chart saved to: {filepath}")
299
+
300
+ # Try to open the chart
301
+ import subprocess
302
+ try:
303
+ subprocess.run(["open", filepath], check=True)
304
+ except Exception:
305
+ pass
306
+
307
+ return 0
308
+
309
+
310
+ def handle_backtest(symbol: str, strategy: str, period: str) -> int:
311
+ """Handle backtest command."""
312
+ from .backtest import run_backtest, get_available_strategies
313
+
314
+ strategies = get_available_strategies()
315
+
316
+ if strategy not in strategies:
317
+ console.print(f"[red]Error:[/red] Unknown strategy '{strategy}'")
318
+ console.print(f"Available: {', '.join(strategies.keys())}")
319
+ return 1
320
+
321
+ with console.status(f"[bold blue]Running backtest: {strategy} on {symbol}...[/bold blue]"):
322
+ result = run_backtest(symbol, strategy, period)
323
+
324
+ if "error" in result:
325
+ console.print(f"[red]Error:[/red] {result['error']}")
326
+ return 1
327
+
328
+ # Display results
329
+ console.print()
330
+ console.print(Panel(
331
+ f"[bold]{result.get('strategy', strategy.upper())}[/bold]\n"
332
+ f"[dim]{result.get('strategy_description', '')}[/dim]",
333
+ title=f"[bold cyan]Backtest: {symbol.upper()}[/bold cyan]",
334
+ ))
335
+
336
+ # Performance table
337
+ perf = result.get("performance", {})
338
+ table = Table(title="Performance", show_header=False)
339
+ table.add_column("Metric", style="bold")
340
+ table.add_column("Value", justify="right")
341
+
342
+ table.add_row("Initial Capital", perf.get("initial_capital", "$100,000"))
343
+ table.add_row("Final Equity", perf.get("final_equity", "N/A"))
344
+ table.add_row("Total Return", perf.get("total_return", "N/A"))
345
+ table.add_row("Annual Return", perf.get("annual_return", "N/A"))
346
+ table.add_row("Buy & Hold Return", perf.get("buy_hold_return", "N/A"))
347
+ table.add_row("Alpha", perf.get("alpha", "N/A"))
348
+
349
+ console.print(table)
350
+
351
+ # Risk table
352
+ risk = result.get("risk", {})
353
+ risk_table = Table(title="Risk Metrics", show_header=False)
354
+ risk_table.add_column("Metric", style="bold")
355
+ risk_table.add_column("Value", justify="right")
356
+
357
+ risk_table.add_row("Volatility", risk.get("volatility", "N/A"))
358
+ risk_table.add_row("Max Drawdown", risk.get("max_drawdown", "N/A"))
359
+ risk_table.add_row("Sharpe Ratio", risk.get("sharpe_ratio", "N/A"))
360
+ risk_table.add_row("Sortino Ratio", risk.get("sortino_ratio", "N/A"))
361
+ risk_table.add_row("Calmar Ratio", risk.get("calmar_ratio", "N/A"))
362
+
363
+ console.print(risk_table)
364
+
365
+ # Trade stats
366
+ trades = result.get("trades", {})
367
+ trade_table = Table(title="Trade Statistics", show_header=False)
368
+ trade_table.add_column("Metric", style="bold")
369
+ trade_table.add_column("Value", justify="right")
370
+
371
+ trade_table.add_row("Total Trades", str(trades.get("total_trades", 0)))
372
+ trade_table.add_row("Win Rate", trades.get("win_rate", "N/A"))
373
+ trade_table.add_row("Profit Factor", trades.get("profit_factor", "N/A"))
374
+ trade_table.add_row("Avg Win", trades.get("avg_win", "N/A"))
375
+ trade_table.add_row("Avg Loss", trades.get("avg_loss", "N/A"))
376
+
377
+ console.print(trade_table)
378
+
379
+ return 0
380
+
381
+
382
+ def handle_compare(symbols: list) -> int:
383
+ """Handle compare command."""
384
+ from .tools import compare_stocks
385
+
386
+ with console.status(f"[bold blue]Comparing {', '.join(symbols)}...[/bold blue]"):
387
+ result = compare_stocks(symbols)
388
+
389
+ if "error" in result:
390
+ console.print(f"[red]Error:[/red] {result['error']}")
391
+ return 1
392
+
393
+ comparison = result.get("comparison", [])
394
+
395
+ if not comparison:
396
+ console.print("[yellow]No data found for the specified symbols[/yellow]")
397
+ return 1
398
+
399
+ # Display comparison table
400
+ table = Table(title=f"Stock Comparison ({result.get('period', '1y')})")
401
+ table.add_column("Symbol", style="cyan")
402
+ table.add_column("Name", style="dim")
403
+ table.add_column("Price", justify="right")
404
+ table.add_column("Return", justify="right")
405
+ table.add_column("Volatility", justify="right")
406
+ table.add_column("Sharpe", justify="right")
407
+ table.add_column("P/E", justify="right")
408
+
409
+ for stock in comparison:
410
+ return_val = stock.get("total_return", 0)
411
+ return_style = "green" if return_val >= 0 else "red"
412
+
413
+ table.add_row(
414
+ stock.get("symbol", ""),
415
+ stock.get("name", "N/A")[:20],
416
+ f"${stock.get('price', 0):,.2f}",
417
+ f"[{return_style}]{return_val:+.2f}%[/{return_style}]",
418
+ f"{stock.get('volatility', 0):.1f}%",
419
+ f"{stock.get('sharpe', 0):.2f}",
420
+ str(stock.get("pe_ratio", "N/A")),
421
+ )
422
+
423
+ console.print(table)
424
+
425
+ # Summary
426
+ console.print()
427
+ console.print(f"[green]Best:[/green] {result.get('best_performer', 'N/A')}")
428
+ console.print(f"[red]Worst:[/red] {result.get('worst_performer', 'N/A')}")
429
+
430
+ return 0
431
+
432
+
433
+ if __name__ == "__main__":
434
+ sys.exit(main())