sigma-terminal 3.2.0__py3-none-any.whl → 3.3.1__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/setup.py CHANGED
@@ -1,4 +1,4 @@
1
- """Sigma v3.2.0 - Setup Wizard."""
1
+ """Sigma v3.3.1 - Setup Wizard."""
2
2
 
3
3
  import os
4
4
  import sys
@@ -19,10 +19,13 @@ from .config import (
19
19
  LLMProvider,
20
20
  AVAILABLE_MODELS,
21
21
  CONFIG_DIR,
22
+ detect_lean_installation,
23
+ detect_ollama,
24
+ install_lean_cli_sync,
22
25
  )
23
26
 
24
27
 
25
- __version__ = "3.2.0"
28
+ __version__ = "3.3.1"
26
29
  SIGMA = "σ"
27
30
  console = Console()
28
31
 
@@ -35,7 +38,7 @@ BANNER = """
35
38
  [bold blue]███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║[/bold blue]
36
39
  [bold blue]╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold blue]
37
40
 
38
- [bold cyan]σ Finance Research Agent[/bold cyan] [dim]- Setup Wizard v3.2.0[/dim]
41
+ [bold cyan]σ Finance Research Agent[/bold cyan] [dim]- Setup Wizard v3.3.1[/dim]
39
42
  """
40
43
 
41
44
 
@@ -258,22 +261,101 @@ class SetupWizard:
258
261
  console.print(Panel("[bold]Step 5: Integrations[/bold]", border_style="blue"))
259
262
  console.print()
260
263
 
261
- # Ollama (if not primary)
264
+ # Ollama detection
262
265
  if self.settings.default_provider != LLMProvider.OLLAMA:
263
- if Confirm.ask("Setup Ollama for local fallback?", default=False):
264
- console.print("[dim]Install: https://ollama.ai/download[/dim]")
265
- console.print("[dim]Run: ollama pull llama3.2[/dim]")
266
-
267
- # LEAN
268
- if Confirm.ask("Setup LEAN/QuantConnect?", default=False):
269
- console.print("[dim]LEAN provides advanced backtesting.[/dim]")
270
- console.print("[dim]Install: pip install lean[/dim]")
266
+ ollama_running, ollama_host = detect_ollama()
267
+ if ollama_running and ollama_host:
268
+ console.print(f"[green]✓[/green] Ollama detected at {ollama_host}")
269
+ if Confirm.ask("Enable Ollama as local fallback?", default=True):
270
+ save_setting("ollama_host", ollama_host)
271
+ console.print(f"[cyan]{SIGMA}[/cyan] Ollama enabled")
272
+ else:
273
+ if Confirm.ask("Setup Ollama for local fallback?", default=False):
274
+ console.print("[dim]Install: https://ollama.ai/download[/dim]")
275
+ console.print("[dim]Run: ollama pull llama3.2[/dim]")
276
+
277
+ console.print()
278
+
279
+ # LEAN auto-detection
280
+ lean_installed, lean_cli, lean_dir = detect_lean_installation()
281
+
282
+ if lean_installed:
283
+ console.print(f"[green]✓[/green] LEAN/QuantConnect detected!")
284
+ if lean_cli:
285
+ console.print(f" [dim]CLI: {lean_cli}[/dim]")
286
+ if lean_dir:
287
+ console.print(f" [dim]Directory: {lean_dir}[/dim]")
288
+ console.print()
289
+
290
+ if Confirm.ask("Enable LEAN integration?", default=True):
291
+ save_setting("lean_enabled", "true")
292
+ if lean_cli:
293
+ save_setting("lean_cli_path", lean_cli)
294
+ if lean_dir:
295
+ save_setting("lean_directory", lean_dir)
296
+ console.print(f"[cyan]{SIGMA}[/cyan] LEAN integration enabled")
297
+ else:
298
+ save_setting("lean_enabled", "false")
299
+ else:
300
+ console.print("[yellow]![/yellow] LEAN/QuantConnect not detected")
301
+ console.print("[dim]LEAN provides institutional-grade backtesting with QuantConnect's engine.[/dim]")
271
302
  console.print()
272
303
 
273
- lean_path = Prompt.ask("LEAN CLI path (or Enter to skip)", default="")
274
- if lean_path:
275
- save_setting("lean_cli_path", lean_path)
276
- console.print(f"[cyan]{SIGMA}[/cyan] LEAN configured")
304
+ lean_choice = Prompt.ask(
305
+ "Would you like to",
306
+ choices=["install", "manual", "skip"],
307
+ default="skip"
308
+ )
309
+
310
+ if lean_choice == "install":
311
+ console.print()
312
+ console.print(f"[cyan]{SIGMA}[/cyan] Installing LEAN CLI via pip...")
313
+ console.print("[dim]This may take a minute...[/dim]")
314
+
315
+ with console.status("[bold blue]Installing LEAN...[/bold blue]"):
316
+ success, message = install_lean_cli_sync()
317
+
318
+ if success:
319
+ console.print(f"[green]✓[/green] {message}")
320
+ save_setting("lean_enabled", "true")
321
+ save_setting("lean_cli_path", "lean")
322
+
323
+ # Verify installation
324
+ lean_installed, lean_cli, lean_dir = detect_lean_installation()
325
+ if lean_cli:
326
+ console.print(f"[cyan]{SIGMA}[/cyan] LEAN CLI ready: {lean_cli}")
327
+ else:
328
+ console.print(f"[red]✗[/red] {message}")
329
+ console.print("[dim]You can install manually later: pip install lean[/dim]")
330
+
331
+ elif lean_choice == "manual":
332
+ console.print()
333
+ console.print("[bold]Manual Installation Options:[/bold]")
334
+ console.print(" 1. [cyan]pip install lean[/cyan] - LEAN CLI (recommended)")
335
+ console.print(" 2. [cyan]https://github.com/QuantConnect/Lean[/cyan] - Full source")
336
+ console.print()
337
+
338
+ lean_path = Prompt.ask("Enter LEAN CLI path (or Enter to skip)", default="")
339
+ if lean_path:
340
+ save_setting("lean_enabled", "true")
341
+ save_setting("lean_cli_path", lean_path)
342
+ console.print(f"[cyan]{SIGMA}[/cyan] LEAN configured: {lean_path}")
343
+ else:
344
+ console.print("[dim]Skipping LEAN setup. You can configure it later.[/dim]")
345
+
346
+ console.print()
347
+
348
+ # Exa API Key (Optional)
349
+ console.print("[bold]Exa Search (Optional)[/bold]")
350
+ console.print("[dim]Enables financial news search, SEC filings, and earnings transcripts.[/dim]")
351
+ console.print()
352
+
353
+ if Confirm.ask("Configure Exa? (financial news search, SEC filings)", default=False):
354
+ console.print("[dim]Get key: https://exa.ai[/dim]")
355
+ exa_key = Prompt.ask("Exa API key", password=True, default="")
356
+ if exa_key:
357
+ save_setting("exa_api_key", exa_key)
358
+ console.print(f"[cyan]{SIGMA}[/cyan] Exa configured")
277
359
 
278
360
  def _show_summary(self):
279
361
  """Show summary."""
sigma/tools.py CHANGED
@@ -122,10 +122,17 @@ def get_financial_statements(symbol: str, statement: str = "income") -> dict:
122
122
  else:
123
123
  data[str(idx)] = f"${val:,.0f}"
124
124
 
125
+ # Get period from column
126
+ col = df.columns[0]
127
+ if hasattr(col, 'date'):
128
+ period_str = str(col.date()) # type: ignore[union-attr]
129
+ else:
130
+ period_str = str(col)
131
+
125
132
  return {
126
133
  "symbol": symbol.upper(),
127
134
  "statement_type": statement,
128
- "period": str(df.columns[0].date()) if hasattr(df.columns[0], 'date') else str(df.columns[0]),
135
+ "period": period_str,
129
136
  "data": data
130
137
  }
131
138
  except Exception as e:
@@ -139,11 +146,11 @@ def get_analyst_recommendations(symbol: str) -> dict:
139
146
 
140
147
  # Get recommendations
141
148
  recs = ticker.recommendations
142
- if recs is not None and not recs.empty:
149
+ rec_summary = {}
150
+ if isinstance(recs, pd.DataFrame) and not recs.empty:
143
151
  recent = recs.tail(10)
144
- rec_summary = recent["To Grade"].value_counts().to_dict() if "To Grade" in recent.columns else {}
145
- else:
146
- rec_summary = {}
152
+ if "To Grade" in recent.columns:
153
+ rec_summary = recent["To Grade"].value_counts().to_dict()
147
154
 
148
155
  # Get info for targets
149
156
  info = ticker.info
@@ -431,6 +438,325 @@ def get_sector_performance() -> dict:
431
438
  return {"sectors": results, "timestamp": datetime.now().isoformat()}
432
439
 
433
440
 
441
+ # ============================================================================
442
+ # ALPHA VANTAGE TOOLS (Economic Data, Intraday, News)
443
+ # ============================================================================
444
+
445
+ def _get_alpha_vantage_key() -> Optional[str]:
446
+ """Get Alpha Vantage API key from config."""
447
+ try:
448
+ from .config import get_settings
449
+ return get_settings().alpha_vantage_api_key
450
+ except:
451
+ return None
452
+
453
+
454
+ def get_economic_indicators(indicator: str = "GDP") -> dict:
455
+ """Get economic indicators from Alpha Vantage (GDP, inflation, unemployment, etc.)."""
456
+ api_key = _get_alpha_vantage_key()
457
+ if not api_key:
458
+ return {"error": "Alpha Vantage API key not configured. Set ALPHA_VANTAGE_API_KEY in ~/.sigma/config.env"}
459
+
460
+ import requests
461
+
462
+ indicator_map = {
463
+ "GDP": "REAL_GDP",
464
+ "INFLATION": "INFLATION",
465
+ "UNEMPLOYMENT": "UNEMPLOYMENT",
466
+ "INTEREST_RATE": "FEDERAL_FUNDS_RATE",
467
+ "CPI": "CPI",
468
+ "RETAIL_SALES": "RETAIL_SALES",
469
+ "NONFARM_PAYROLL": "NONFARM_PAYROLL",
470
+ }
471
+
472
+ av_indicator = indicator_map.get(indicator.upper(), indicator.upper())
473
+
474
+ try:
475
+ url = f"https://www.alphavantage.co/query?function={av_indicator}&apikey={api_key}"
476
+ response = requests.get(url, timeout=10)
477
+ data = response.json()
478
+
479
+ if "Error Message" in data:
480
+ return {"error": data["Error Message"]}
481
+
482
+ if "data" in data:
483
+ # Return most recent data points
484
+ recent = data["data"][:12] # Last 12 periods
485
+ return {
486
+ "indicator": indicator.upper(),
487
+ "name": data.get("name", indicator),
488
+ "unit": data.get("unit", ""),
489
+ "data": [{"date": d["date"], "value": d["value"]} for d in recent]
490
+ }
491
+
492
+ return {"error": "No data returned", "raw": data}
493
+ except Exception as e:
494
+ return {"error": str(e)}
495
+
496
+
497
+ def get_intraday_data(symbol: str, interval: str = "5min") -> dict:
498
+ """Get intraday price data from Alpha Vantage."""
499
+ api_key = _get_alpha_vantage_key()
500
+ if not api_key:
501
+ return {"error": "Alpha Vantage API key not configured. Set ALPHA_VANTAGE_API_KEY in ~/.sigma/config.env"}
502
+
503
+ import requests
504
+
505
+ valid_intervals = ["1min", "5min", "15min", "30min", "60min"]
506
+ if interval not in valid_intervals:
507
+ return {"error": f"Invalid interval. Use: {valid_intervals}"}
508
+
509
+ try:
510
+ url = f"https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol={symbol}&interval={interval}&apikey={api_key}"
511
+ response = requests.get(url, timeout=10)
512
+ data = response.json()
513
+
514
+ if "Error Message" in data:
515
+ return {"error": data["Error Message"]}
516
+
517
+ time_series_key = f"Time Series ({interval})"
518
+ if time_series_key not in data:
519
+ return {"error": "No data returned. Check symbol or API limits.", "raw": data}
520
+
521
+ # Get last 20 candles
522
+ series = data[time_series_key]
523
+ candles = []
524
+ for timestamp, values in list(series.items())[:20]:
525
+ candles.append({
526
+ "timestamp": timestamp,
527
+ "open": float(values["1. open"]),
528
+ "high": float(values["2. high"]),
529
+ "low": float(values["3. low"]),
530
+ "close": float(values["4. close"]),
531
+ "volume": int(values["5. volume"])
532
+ })
533
+
534
+ return {
535
+ "symbol": symbol.upper(),
536
+ "interval": interval,
537
+ "candles": candles
538
+ }
539
+ except Exception as e:
540
+ return {"error": str(e)}
541
+
542
+
543
+ def get_market_news(tickers: str = "", topics: str = "") -> dict:
544
+ """Get market news and sentiment from Alpha Vantage."""
545
+ api_key = _get_alpha_vantage_key()
546
+ if not api_key:
547
+ return {"error": "Alpha Vantage API key not configured. Set ALPHA_VANTAGE_API_KEY in ~/.sigma/config.env"}
548
+
549
+ import requests
550
+
551
+ try:
552
+ url = f"https://www.alphavantage.co/query?function=NEWS_SENTIMENT&apikey={api_key}"
553
+ if tickers:
554
+ url += f"&tickers={tickers}"
555
+ if topics:
556
+ url += f"&topics={topics}"
557
+
558
+ response = requests.get(url, timeout=15)
559
+ data = response.json()
560
+
561
+ if "Error Message" in data:
562
+ return {"error": data["Error Message"]}
563
+
564
+ feed = data.get("feed", [])[:10] # Get top 10 news items
565
+
566
+ articles = []
567
+ for item in feed:
568
+ articles.append({
569
+ "title": item.get("title", ""),
570
+ "source": item.get("source", ""),
571
+ "time": item.get("time_published", ""),
572
+ "summary": item.get("summary", "")[:300] + "..." if item.get("summary") else "",
573
+ "sentiment": item.get("overall_sentiment_label", ""),
574
+ "sentiment_score": item.get("overall_sentiment_score", 0),
575
+ "tickers": [t["ticker"] for t in item.get("ticker_sentiment", [])[:3]]
576
+ })
577
+
578
+ return {
579
+ "articles": articles,
580
+ "query": {"tickers": tickers, "topics": topics}
581
+ }
582
+ except Exception as e:
583
+ return {"error": str(e)}
584
+
585
+
586
+ # ============================================================================
587
+ # EXA SEARCH TOOLS (Financial News, SEC Filings)
588
+ # ============================================================================
589
+
590
+ def _get_exa_key() -> Optional[str]:
591
+ """Get Exa API key from config."""
592
+ try:
593
+ from .config import get_settings
594
+ return get_settings().exa_api_key
595
+ except:
596
+ return None
597
+
598
+
599
+ def search_financial_news(query: str, num_results: int = 5) -> dict:
600
+ """Search for financial news using Exa."""
601
+ api_key = _get_exa_key()
602
+ if not api_key:
603
+ return {"error": "Exa API key not configured. Set EXA_API_KEY in ~/.sigma/config.env"}
604
+
605
+ import requests
606
+
607
+ try:
608
+ headers = {
609
+ "x-api-key": api_key,
610
+ "Content-Type": "application/json"
611
+ }
612
+
613
+ payload = {
614
+ "query": query,
615
+ "num_results": num_results,
616
+ "use_autoprompt": True,
617
+ "type": "neural",
618
+ "include_domains": [
619
+ "reuters.com", "bloomberg.com", "wsj.com", "cnbc.com",
620
+ "marketwatch.com", "ft.com", "seekingalpha.com", "yahoo.com/finance"
621
+ ]
622
+ }
623
+
624
+ response = requests.post(
625
+ "https://api.exa.ai/search",
626
+ headers=headers,
627
+ json=payload,
628
+ timeout=15
629
+ )
630
+
631
+ if response.status_code != 200:
632
+ return {"error": f"Exa API error: {response.status_code}", "details": response.text}
633
+
634
+ data = response.json()
635
+
636
+ results = []
637
+ for item in data.get("results", []):
638
+ results.append({
639
+ "title": item.get("title", ""),
640
+ "url": item.get("url", ""),
641
+ "published": item.get("publishedDate", ""),
642
+ "score": item.get("score", 0)
643
+ })
644
+
645
+ return {
646
+ "query": query,
647
+ "results": results
648
+ }
649
+ except Exception as e:
650
+ return {"error": str(e)}
651
+
652
+
653
+ def search_sec_filings(company: str, filing_type: str = "10-K", num_results: int = 3) -> dict:
654
+ """Search for SEC filings using Exa."""
655
+ api_key = _get_exa_key()
656
+ if not api_key:
657
+ return {"error": "Exa API key not configured. Set EXA_API_KEY in ~/.sigma/config.env"}
658
+
659
+ import requests
660
+
661
+ try:
662
+ headers = {
663
+ "x-api-key": api_key,
664
+ "Content-Type": "application/json"
665
+ }
666
+
667
+ query = f"{company} {filing_type} SEC filing site:sec.gov"
668
+
669
+ payload = {
670
+ "query": query,
671
+ "num_results": num_results,
672
+ "use_autoprompt": True,
673
+ "type": "neural",
674
+ "include_domains": ["sec.gov"]
675
+ }
676
+
677
+ response = requests.post(
678
+ "https://api.exa.ai/search",
679
+ headers=headers,
680
+ json=payload,
681
+ timeout=15
682
+ )
683
+
684
+ if response.status_code != 200:
685
+ return {"error": f"Exa API error: {response.status_code}"}
686
+
687
+ data = response.json()
688
+
689
+ results = []
690
+ for item in data.get("results", []):
691
+ results.append({
692
+ "title": item.get("title", ""),
693
+ "url": item.get("url", ""),
694
+ "published": item.get("publishedDate", "")
695
+ })
696
+
697
+ return {
698
+ "company": company,
699
+ "filing_type": filing_type,
700
+ "results": results
701
+ }
702
+ except Exception as e:
703
+ return {"error": str(e)}
704
+
705
+
706
+ def search_earnings_transcripts(company: str, num_results: int = 3) -> dict:
707
+ """Search for earnings call transcripts using Exa."""
708
+ api_key = _get_exa_key()
709
+ if not api_key:
710
+ return {"error": "Exa API key not configured. Set EXA_API_KEY in ~/.sigma/config.env"}
711
+
712
+ import requests
713
+
714
+ try:
715
+ headers = {
716
+ "x-api-key": api_key,
717
+ "Content-Type": "application/json"
718
+ }
719
+
720
+ query = f"{company} earnings call transcript Q4 2025"
721
+
722
+ payload = {
723
+ "query": query,
724
+ "num_results": num_results,
725
+ "use_autoprompt": True,
726
+ "type": "neural",
727
+ "include_domains": [
728
+ "seekingalpha.com", "fool.com", "reuters.com"
729
+ ]
730
+ }
731
+
732
+ response = requests.post(
733
+ "https://api.exa.ai/search",
734
+ headers=headers,
735
+ json=payload,
736
+ timeout=15
737
+ )
738
+
739
+ if response.status_code != 200:
740
+ return {"error": f"Exa API error: {response.status_code}"}
741
+
742
+ data = response.json()
743
+
744
+ results = []
745
+ for item in data.get("results", []):
746
+ results.append({
747
+ "title": item.get("title", ""),
748
+ "url": item.get("url", ""),
749
+ "published": item.get("publishedDate", "")
750
+ })
751
+
752
+ return {
753
+ "company": company,
754
+ "results": results
755
+ }
756
+ except Exception as e:
757
+ return {"error": str(e)}
758
+
759
+
434
760
  # ============================================================================
435
761
  # TOOL DEFINITIONS FOR LLM
436
762
  # ============================================================================
@@ -590,6 +916,98 @@ TOOLS = [
590
916
  }
591
917
  }
592
918
  },
919
+ # Alpha Vantage tools
920
+ {
921
+ "type": "function",
922
+ "function": {
923
+ "name": "get_economic_indicators",
924
+ "description": "Get economic indicators like GDP, inflation, unemployment, interest rates, CPI",
925
+ "parameters": {
926
+ "type": "object",
927
+ "properties": {
928
+ "indicator": {"type": "string", "enum": ["GDP", "INFLATION", "UNEMPLOYMENT", "INTEREST_RATE", "CPI", "RETAIL_SALES", "NONFARM_PAYROLL"], "description": "Economic indicator to retrieve"}
929
+ },
930
+ "required": ["indicator"]
931
+ }
932
+ }
933
+ },
934
+ {
935
+ "type": "function",
936
+ "function": {
937
+ "name": "get_intraday_data",
938
+ "description": "Get intraday price data with 1min, 5min, 15min, 30min, or 60min candles",
939
+ "parameters": {
940
+ "type": "object",
941
+ "properties": {
942
+ "symbol": {"type": "string", "description": "Stock ticker symbol"},
943
+ "interval": {"type": "string", "enum": ["1min", "5min", "15min", "30min", "60min"], "description": "Candle interval", "default": "5min"}
944
+ },
945
+ "required": ["symbol"]
946
+ }
947
+ }
948
+ },
949
+ {
950
+ "type": "function",
951
+ "function": {
952
+ "name": "get_market_news",
953
+ "description": "Get market news and sentiment for specific tickers or topics",
954
+ "parameters": {
955
+ "type": "object",
956
+ "properties": {
957
+ "tickers": {"type": "string", "description": "Comma-separated ticker symbols (e.g., AAPL,MSFT)"},
958
+ "topics": {"type": "string", "description": "Topics like: earnings, ipo, mergers, technology, finance"}
959
+ },
960
+ "required": []
961
+ }
962
+ }
963
+ },
964
+ # Exa Search tools
965
+ {
966
+ "type": "function",
967
+ "function": {
968
+ "name": "search_financial_news",
969
+ "description": "Search for financial news articles from major sources (Bloomberg, Reuters, WSJ, etc.)",
970
+ "parameters": {
971
+ "type": "object",
972
+ "properties": {
973
+ "query": {"type": "string", "description": "Search query for financial news"},
974
+ "num_results": {"type": "integer", "description": "Number of results (1-10)", "default": 5}
975
+ },
976
+ "required": ["query"]
977
+ }
978
+ }
979
+ },
980
+ {
981
+ "type": "function",
982
+ "function": {
983
+ "name": "search_sec_filings",
984
+ "description": "Search for SEC filings (10-K, 10-Q, 8-K, etc.) for a company",
985
+ "parameters": {
986
+ "type": "object",
987
+ "properties": {
988
+ "company": {"type": "string", "description": "Company name or ticker"},
989
+ "filing_type": {"type": "string", "enum": ["10-K", "10-Q", "8-K", "S-1", "DEF 14A"], "description": "Type of SEC filing", "default": "10-K"},
990
+ "num_results": {"type": "integer", "description": "Number of results", "default": 3}
991
+ },
992
+ "required": ["company"]
993
+ }
994
+ }
995
+ },
996
+ {
997
+ "type": "function",
998
+ "function": {
999
+ "name": "search_earnings_transcripts",
1000
+ "description": "Search for earnings call transcripts",
1001
+ "parameters": {
1002
+ "type": "object",
1003
+ "properties": {
1004
+ "company": {"type": "string", "description": "Company name or ticker"},
1005
+ "num_results": {"type": "integer", "description": "Number of results", "default": 3}
1006
+ },
1007
+ "required": ["company"]
1008
+ }
1009
+ }
1010
+ },
593
1011
  ]
594
1012
 
595
1013
 
@@ -606,6 +1024,14 @@ TOOL_FUNCTIONS = {
606
1024
  "compare_stocks": compare_stocks,
607
1025
  "get_market_overview": get_market_overview,
608
1026
  "get_sector_performance": get_sector_performance,
1027
+ # Alpha Vantage
1028
+ "get_economic_indicators": get_economic_indicators,
1029
+ "get_intraday_data": get_intraday_data,
1030
+ "get_market_news": get_market_news,
1031
+ # Exa Search
1032
+ "search_financial_news": search_financial_news,
1033
+ "search_sec_filings": search_sec_filings,
1034
+ "search_earnings_transcripts": search_earnings_transcripts,
609
1035
  }
610
1036
 
611
1037