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/__init__.py +182 -6
- sigma/__main__.py +2 -2
- sigma/analytics/__init__.py +636 -0
- sigma/app.py +563 -898
- sigma/backtest.py +372 -0
- sigma/charts.py +407 -0
- sigma/cli.py +434 -0
- sigma/comparison.py +611 -0
- sigma/config.py +195 -0
- sigma/core/__init__.py +4 -17
- sigma/core/engine.py +493 -0
- sigma/core/intent.py +595 -0
- sigma/core/models.py +516 -125
- sigma/data/__init__.py +681 -0
- sigma/data/models.py +130 -0
- sigma/llm.py +401 -0
- sigma/monitoring.py +666 -0
- sigma/portfolio.py +697 -0
- sigma/reporting.py +658 -0
- sigma/robustness.py +675 -0
- sigma/setup.py +305 -402
- sigma/strategy.py +753 -0
- sigma/tools/backtest.py +23 -5
- sigma/tools.py +617 -0
- sigma/visualization.py +766 -0
- sigma_terminal-3.2.0.dist-info/METADATA +298 -0
- sigma_terminal-3.2.0.dist-info/RECORD +30 -0
- sigma_terminal-3.2.0.dist-info/entry_points.txt +6 -0
- sigma_terminal-3.2.0.dist-info/licenses/LICENSE +25 -0
- sigma/core/agent.py +0 -205
- sigma/core/config.py +0 -119
- sigma/core/llm.py +0 -794
- sigma/tools/__init__.py +0 -5
- sigma/tools/charts.py +0 -400
- sigma/tools/financial.py +0 -1457
- sigma/ui/__init__.py +0 -1
- sigma_terminal-2.0.2.dist-info/METADATA +0 -222
- sigma_terminal-2.0.2.dist-info/RECORD +0 -19
- sigma_terminal-2.0.2.dist-info/entry_points.txt +0 -2
- sigma_terminal-2.0.2.dist-info/licenses/LICENSE +0 -42
- {sigma_terminal-2.0.2.dist-info → sigma_terminal-3.2.0.dist-info}/WHEEL +0 -0
sigma/app.py
CHANGED
|
@@ -1,947 +1,612 @@
|
|
|
1
|
-
"""Sigma
|
|
1
|
+
"""Sigma v3.2.0 - Finance Research Agent."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
from typing import
|
|
7
|
-
import time
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Optional, List
|
|
8
7
|
|
|
9
|
-
from rich.
|
|
8
|
+
from rich.markdown import Markdown
|
|
10
9
|
from rich.panel import Panel
|
|
11
|
-
from rich.text import Text
|
|
12
10
|
from rich.table import Table
|
|
13
|
-
from rich.
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from textual import on, work
|
|
13
|
+
from textual.app import App, ComposeResult
|
|
14
|
+
from textual.binding import Binding
|
|
15
|
+
from textual.containers import Container, Horizontal, ScrollableContainer
|
|
16
|
+
from textual.widgets import Footer, Input, RichLog, Static
|
|
17
|
+
from textual.suggester import Suggester
|
|
18
|
+
|
|
19
|
+
from .config import LLMProvider, get_settings, save_api_key, AVAILABLE_MODELS
|
|
20
|
+
from .llm import get_llm
|
|
21
|
+
from .tools import TOOLS, execute_tool
|
|
22
|
+
from .backtest import run_backtest, get_available_strategies, BACKTEST_TOOL
|
|
18
23
|
|
|
19
|
-
from sigma.core.agent import SigmaAgent
|
|
20
|
-
from sigma.core.config import LLMProvider, get_settings
|
|
21
24
|
|
|
25
|
+
__version__ = "3.2.0"
|
|
26
|
+
SIGMA = "σ"
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
# Animated sigma logo frames for thinking state
|
|
29
|
+
THINKING_LOGO_FRAMES = [
|
|
30
|
+
"""[bold blue]
|
|
31
|
+
███████╗██╗ ██████╗ ███╗ ███╗ █████╗
|
|
32
|
+
██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗
|
|
33
|
+
███████╗██║██║ ███╗██╔████╔██║███████║
|
|
34
|
+
╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║
|
|
35
|
+
███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
|
|
36
|
+
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold blue]
|
|
37
|
+
[dim]analyzing[/dim]""",
|
|
38
|
+
"""[bold cyan]
|
|
39
|
+
███████╗██╗ ██████╗ ███╗ ███╗ █████╗
|
|
40
|
+
██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗
|
|
41
|
+
███████╗██║██║ ███╗██╔████╔██║███████║
|
|
42
|
+
╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║
|
|
43
|
+
███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
|
|
44
|
+
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold cyan]
|
|
45
|
+
[dim]analyzing.[/dim]""",
|
|
46
|
+
"""[bold white]
|
|
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]""",
|
|
62
|
+
]
|
|
24
63
|
|
|
25
|
-
#
|
|
26
|
-
|
|
64
|
+
# Welcome banner - clean design
|
|
65
|
+
WELCOME_BANNER = """
|
|
66
|
+
[bold blue]███████╗██╗ ██████╗ ███╗ ███╗ █████╗ [/bold blue]
|
|
67
|
+
[bold blue]██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗[/bold blue]
|
|
68
|
+
[bold blue]███████╗██║██║ ███╗██╔████╔██║███████║[/bold blue]
|
|
69
|
+
[bold blue]╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║[/bold blue]
|
|
70
|
+
[bold blue]███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║[/bold blue]
|
|
71
|
+
[bold blue]╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold blue]
|
|
27
72
|
|
|
28
|
-
|
|
29
|
-
BANNER = """[bold bright_blue]
|
|
30
|
-
███████╗██╗ ██████╗ ███╗ ███╗ █████╗
|
|
31
|
-
██╔════╝██║██╔════╝ ████╗ ████║██╔══██╗
|
|
32
|
-
███████╗██║██║ ███╗██╔████╔██║███████║
|
|
33
|
-
╚════██║██║██║ ██║██║╚██╔╝██║██╔══██║
|
|
34
|
-
███████║██║╚██████╔╝██║ ╚═╝ ██║██║ ██║
|
|
35
|
-
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝[/bold bright_blue]
|
|
73
|
+
[bold cyan]Finance Research Agent[/bold cyan] [dim]v3.2.0 | Native macOS[/dim]
|
|
36
74
|
"""
|
|
37
75
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
examples.add_row("")
|
|
113
|
-
examples.add_row("[dim]── Predictions & Sentiment ──[/dim]")
|
|
114
|
-
examples.add_row(" Predict AMZN price for next month")
|
|
115
|
-
examples.add_row(" What's the sentiment on TSLA?")
|
|
116
|
-
examples.add_row(" Price forecast for Bitcoin")
|
|
117
|
-
examples.add_row("")
|
|
118
|
-
examples.add_row("[dim]── Backtesting & Strategy ──[/dim]")
|
|
119
|
-
examples.add_row(" Create a MACD strategy backtest for AAPL")
|
|
120
|
-
examples.add_row(" Generate RSI mean reversion backtest for SPY")
|
|
121
|
-
examples.add_row(" List available backtest strategies")
|
|
122
|
-
examples.add_row("")
|
|
123
|
-
examples.add_row("[dim]── Market Overview ──[/dim]")
|
|
124
|
-
examples.add_row(" What are today's market movers?")
|
|
125
|
-
examples.add_row(" Show sector performance")
|
|
126
|
-
examples.add_row(" How are the major indices doing?")
|
|
127
|
-
|
|
128
|
-
console.print(examples)
|
|
129
|
-
console.print()
|
|
130
|
-
|
|
131
|
-
# Strategies
|
|
132
|
-
strategies = Table(title="[bold bright_cyan]Backtest Strategies[/bold bright_cyan]", box=ROUNDED, show_header=True, header_style="bold")
|
|
133
|
-
strategies.add_column("Strategy", style="bright_yellow")
|
|
134
|
-
strategies.add_column("Description", style="white")
|
|
135
|
-
|
|
136
|
-
strategies.add_row("sma_crossover", "Classic moving average crossover (fast/slow SMA)")
|
|
137
|
-
strategies.add_row("rsi_mean_reversion", "Buy oversold, sell overbought using RSI")
|
|
138
|
-
strategies.add_row("macd_momentum", "MACD histogram momentum strategy")
|
|
139
|
-
strategies.add_row("bollinger_bands", "Mean reversion at Bollinger Band extremes")
|
|
140
|
-
strategies.add_row("dual_momentum", "Absolute + relative momentum")
|
|
141
|
-
strategies.add_row("breakout", "Donchian channel breakout strategy")
|
|
142
|
-
|
|
143
|
-
console.print(strategies)
|
|
144
|
-
console.print()
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Tool name mappings for cleaner display
|
|
148
|
-
TOOL_DISPLAY_NAMES = {
|
|
149
|
-
"get_stock_quote": "Stock Quote",
|
|
150
|
-
"get_stock_history": "Price History",
|
|
151
|
-
"get_company_info": "Company Info",
|
|
152
|
-
"get_financial_statements": "Financials",
|
|
153
|
-
"get_analyst_recommendations": "Analyst Ratings",
|
|
154
|
-
"get_insider_trades": "Insider Trades",
|
|
155
|
-
"get_institutional_holders": "Institutions",
|
|
156
|
-
"get_earnings_calendar": "Earnings",
|
|
157
|
-
"get_options_chain": "Options Chain",
|
|
158
|
-
"get_dividends": "Dividends",
|
|
159
|
-
"compare_stocks": "Compare Stocks",
|
|
160
|
-
"get_market_movers": "Market Movers",
|
|
161
|
-
"get_sector_performance": "Sectors",
|
|
162
|
-
"get_market_indices": "Market Indices",
|
|
163
|
-
"calculate_portfolio_metrics": "Portfolio",
|
|
164
|
-
"search_stocks": "Stock Search",
|
|
165
|
-
"get_stock_news": "News",
|
|
166
|
-
"technical_analysis": "Technicals",
|
|
167
|
-
"generate_price_chart": "Price Chart",
|
|
168
|
-
"generate_comparison_chart": "Comparison",
|
|
169
|
-
"generate_rsi_chart": "RSI Chart",
|
|
170
|
-
"generate_sector_chart": "Sectors",
|
|
171
|
-
"list_backtest_strategies": "Strategies",
|
|
172
|
-
"generate_backtest": "Backtest",
|
|
173
|
-
"generate_custom_backtest": "Custom Backtest",
|
|
174
|
-
"price_forecast": "Forecast",
|
|
175
|
-
"sentiment_analysis": "Sentiment",
|
|
76
|
+
SYSTEM_PROMPT = """You are Sigma, a Finance Research Agent. You provide comprehensive market analysis, trading strategies, and investment insights.
|
|
77
|
+
|
|
78
|
+
CORE CAPABILITIES:
|
|
79
|
+
- Real-time market data analysis (quotes, charts, technicals)
|
|
80
|
+
- Fundamental analysis (financials, ratios, earnings)
|
|
81
|
+
- Technical analysis (RSI, MACD, Bollinger Bands, moving averages)
|
|
82
|
+
- Backtesting strategies (SMA crossover, RSI, MACD, Bollinger, momentum, breakout)
|
|
83
|
+
- Portfolio analysis and optimization
|
|
84
|
+
- Sector and market overview
|
|
85
|
+
- Insider and fund activity tracking
|
|
86
|
+
|
|
87
|
+
RESPONSE STYLE:
|
|
88
|
+
- Be concise and data-driven
|
|
89
|
+
- Lead with key insights, then supporting data
|
|
90
|
+
- Use tables for comparative data when appropriate
|
|
91
|
+
- Always cite specific numbers and metrics
|
|
92
|
+
- Provide actionable recommendations when asked
|
|
93
|
+
- Format currency and percentages properly
|
|
94
|
+
- Use STRONG BUY, BUY, HOLD, SELL, STRONG SELL ratings when appropriate
|
|
95
|
+
|
|
96
|
+
When users ask about stocks, always gather current data using your tools before responding."""
|
|
97
|
+
|
|
98
|
+
# Autocomplete suggestions
|
|
99
|
+
SUGGESTIONS = [
|
|
100
|
+
"analyze AAPL",
|
|
101
|
+
"analyze MSFT",
|
|
102
|
+
"analyze GOOGL",
|
|
103
|
+
"analyze NVDA",
|
|
104
|
+
"analyze TSLA",
|
|
105
|
+
"analyze META",
|
|
106
|
+
"analyze AMZN",
|
|
107
|
+
"compare AAPL MSFT GOOGL",
|
|
108
|
+
"compare NVDA AMD INTC",
|
|
109
|
+
"technical analysis of AAPL",
|
|
110
|
+
"technical analysis of SPY",
|
|
111
|
+
"backtest SMA crossover on AAPL",
|
|
112
|
+
"backtest RSI strategy on SPY",
|
|
113
|
+
"market overview",
|
|
114
|
+
"sector performance",
|
|
115
|
+
"get quote for AAPL",
|
|
116
|
+
"price of NVDA",
|
|
117
|
+
"fundamentals of MSFT",
|
|
118
|
+
"insider trading activity",
|
|
119
|
+
"institutional holders",
|
|
120
|
+
"analyst recommendations",
|
|
121
|
+
"/help",
|
|
122
|
+
"/clear",
|
|
123
|
+
"/keys",
|
|
124
|
+
"/models",
|
|
125
|
+
"/status",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class SigmaSuggester(Suggester):
|
|
130
|
+
"""Autocomplete suggester for Sigma."""
|
|
131
|
+
|
|
132
|
+
def __init__(self):
|
|
133
|
+
super().__init__(use_cache=True, case_sensitive=False)
|
|
134
|
+
|
|
135
|
+
async def get_suggestion(self, value: str) -> Optional[str]:
|
|
136
|
+
"""Get autocomplete suggestion."""
|
|
137
|
+
if not value or len(value) < 2:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
value_lower = value.lower()
|
|
141
|
+
for suggestion in SUGGESTIONS:
|
|
142
|
+
if suggestion.lower().startswith(value_lower):
|
|
143
|
+
return suggestion
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
CSS = """
|
|
148
|
+
Screen {
|
|
149
|
+
background: #0a0a0f;
|
|
176
150
|
}
|
|
177
151
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"get_analyst_recommendations": ">",
|
|
184
|
-
"get_insider_trades": ">",
|
|
185
|
-
"get_institutional_holders": ">",
|
|
186
|
-
"get_earnings_calendar": ">",
|
|
187
|
-
"get_options_chain": ">",
|
|
188
|
-
"get_dividends": ">",
|
|
189
|
-
"compare_stocks": ">",
|
|
190
|
-
"get_market_movers": ">",
|
|
191
|
-
"get_sector_performance": ">",
|
|
192
|
-
"get_market_indices": ">",
|
|
193
|
-
"calculate_portfolio_metrics": ">",
|
|
194
|
-
"search_stocks": ">",
|
|
195
|
-
"get_stock_news": ">",
|
|
196
|
-
"technical_analysis": ">",
|
|
197
|
-
"generate_price_chart": ">",
|
|
198
|
-
"generate_comparison_chart": ">",
|
|
199
|
-
"generate_rsi_chart": ">",
|
|
200
|
-
"generate_sector_chart": ">",
|
|
201
|
-
"list_backtest_strategies": ">",
|
|
202
|
-
"generate_backtest": ">",
|
|
203
|
-
"generate_custom_backtest": ">",
|
|
204
|
-
"price_forecast": ">",
|
|
205
|
-
"sentiment_analysis": ">",
|
|
152
|
+
* {
|
|
153
|
+
scrollbar-size: 1 1;
|
|
154
|
+
scrollbar-color: #3b82f6 30%;
|
|
155
|
+
scrollbar-color-hover: #60a5fa 50%;
|
|
156
|
+
scrollbar-color-active: #93c5fd 70%;
|
|
206
157
|
}
|
|
207
158
|
|
|
159
|
+
#main-container {
|
|
160
|
+
width: 100%;
|
|
161
|
+
height: 100%;
|
|
162
|
+
background: #0a0a0f;
|
|
163
|
+
}
|
|
208
164
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
symbol = args.get('symbol', '')
|
|
215
|
-
symbols = args.get('symbols', [])
|
|
216
|
-
query = args.get('query', '')
|
|
217
|
-
strategy = args.get('strategy', '')
|
|
218
|
-
|
|
219
|
-
if symbol and strategy:
|
|
220
|
-
return f"{icon} {display_name}", f"{symbol.upper()} - {strategy}"
|
|
221
|
-
elif symbol:
|
|
222
|
-
return f"{icon} {display_name}", symbol.upper()
|
|
223
|
-
elif symbols:
|
|
224
|
-
return f"{icon} {display_name}", ', '.join(s.upper() for s in symbols[:3])
|
|
225
|
-
elif query:
|
|
226
|
-
return f"{icon} {display_name}", query[:30]
|
|
227
|
-
else:
|
|
228
|
-
return f"{icon} {display_name}", None
|
|
165
|
+
#chat-area {
|
|
166
|
+
height: 1fr;
|
|
167
|
+
margin: 1 2;
|
|
168
|
+
background: #0a0a0f;
|
|
169
|
+
}
|
|
229
170
|
|
|
171
|
+
#chat-log {
|
|
172
|
+
background: #0a0a0f;
|
|
173
|
+
padding: 1 0;
|
|
174
|
+
}
|
|
230
175
|
|
|
231
|
-
|
|
232
|
-
|
|
176
|
+
#thinking-area {
|
|
177
|
+
height: auto;
|
|
178
|
+
width: 100%;
|
|
179
|
+
content-align: center middle;
|
|
180
|
+
background: #0a0a0f;
|
|
181
|
+
display: none;
|
|
182
|
+
padding: 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#thinking-area.visible {
|
|
186
|
+
display: block;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#input-area {
|
|
190
|
+
height: 5;
|
|
191
|
+
padding: 1 2;
|
|
192
|
+
background: #0d1117;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#input-row {
|
|
196
|
+
height: 3;
|
|
197
|
+
width: 100%;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#sigma-indicator {
|
|
201
|
+
width: 4;
|
|
202
|
+
height: 3;
|
|
203
|
+
content-align: center middle;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#prompt-input {
|
|
207
|
+
width: 1fr;
|
|
208
|
+
background: #1a1a2e;
|
|
209
|
+
border: solid #3b82f6;
|
|
210
|
+
color: #ffffff;
|
|
211
|
+
padding: 0 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#prompt-input:focus {
|
|
215
|
+
border: solid #60a5fa;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#prompt-input.-autocomplete {
|
|
219
|
+
border: solid #22c55e;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
Footer {
|
|
223
|
+
background: #0d1117;
|
|
224
|
+
height: 1;
|
|
225
|
+
dock: bottom;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Footer > .footer--highlight {
|
|
229
|
+
background: transparent;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Footer > .footer--key {
|
|
233
|
+
background: #1a1a2e;
|
|
234
|
+
color: #f59e0b;
|
|
235
|
+
text-style: bold;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Footer > .footer--description {
|
|
239
|
+
color: #6b7280;
|
|
240
|
+
}
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ThinkingDisplay(Static):
|
|
245
|
+
"""Animated sigma logo during thinking."""
|
|
246
|
+
|
|
247
|
+
def __init__(self, *args, **kwargs):
|
|
248
|
+
super().__init__(*args, **kwargs)
|
|
249
|
+
self.frame = 0
|
|
250
|
+
self.timer = None
|
|
251
|
+
|
|
252
|
+
def start(self):
|
|
253
|
+
"""Start the animation."""
|
|
254
|
+
self.add_class("visible")
|
|
255
|
+
self.frame = 0
|
|
256
|
+
self.update(Text.from_markup(THINKING_LOGO_FRAMES[0]))
|
|
257
|
+
self.timer = self.set_interval(0.3, self._animate)
|
|
258
|
+
|
|
259
|
+
def stop(self):
|
|
260
|
+
"""Stop the animation."""
|
|
261
|
+
if self.timer:
|
|
262
|
+
self.timer.stop()
|
|
263
|
+
self.timer = None
|
|
264
|
+
self.remove_class("visible")
|
|
265
|
+
self.update("")
|
|
266
|
+
|
|
267
|
+
def _animate(self):
|
|
268
|
+
"""Cycle through animation frames."""
|
|
269
|
+
self.frame = (self.frame + 1) % len(THINKING_LOGO_FRAMES)
|
|
270
|
+
self.update(Text.from_markup(THINKING_LOGO_FRAMES[self.frame]))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class SigmaIndicator(Static):
|
|
274
|
+
"""Pulsing sigma indicator."""
|
|
275
|
+
|
|
276
|
+
def __init__(self, *args, **kwargs):
|
|
277
|
+
super().__init__(*args, **kwargs)
|
|
278
|
+
self.active = False
|
|
279
|
+
self.frame = 0
|
|
280
|
+
self.timer = None
|
|
281
|
+
|
|
282
|
+
def on_mount(self):
|
|
283
|
+
self.update(Text.from_markup(f"[bold blue]{SIGMA}[/bold blue]"))
|
|
284
|
+
|
|
285
|
+
def set_active(self, active: bool):
|
|
286
|
+
self.active = active
|
|
287
|
+
if active and not self.timer:
|
|
288
|
+
self.timer = self.set_interval(0.2, self._pulse)
|
|
289
|
+
elif not active and self.timer:
|
|
290
|
+
self.timer.stop()
|
|
291
|
+
self.timer = None
|
|
292
|
+
self.update(Text.from_markup(f"[bold blue]{SIGMA}[/bold blue]"))
|
|
293
|
+
|
|
294
|
+
def _pulse(self):
|
|
295
|
+
colors = ["#3b82f6", "#60a5fa", "#93c5fd", "#60a5fa"]
|
|
296
|
+
self.frame = (self.frame + 1) % len(colors)
|
|
297
|
+
self.update(Text.from_markup(f"[bold {colors[self.frame]}]{SIGMA}[/bold {colors[self.frame]}]"))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class ChatLog(RichLog):
|
|
301
|
+
"""Chat log with rich formatting."""
|
|
302
|
+
|
|
303
|
+
def write_user(self, message: str):
|
|
304
|
+
self.write(Panel(
|
|
305
|
+
Text(message, style="white"),
|
|
306
|
+
title="[bold blue]You[/bold blue]",
|
|
307
|
+
border_style="blue",
|
|
308
|
+
padding=(0, 1),
|
|
309
|
+
))
|
|
310
|
+
|
|
311
|
+
def write_assistant(self, message: str):
|
|
312
|
+
self.write(Panel(
|
|
313
|
+
Markdown(message),
|
|
314
|
+
title=f"[bold cyan]{SIGMA} Sigma[/bold cyan]",
|
|
315
|
+
border_style="cyan",
|
|
316
|
+
padding=(0, 1),
|
|
317
|
+
))
|
|
318
|
+
|
|
319
|
+
def write_tool(self, tool_name: str):
|
|
320
|
+
self.write(Text.from_markup(f" [dim]{SIGMA} executing {tool_name}...[/dim]"))
|
|
321
|
+
|
|
322
|
+
def write_error(self, message: str):
|
|
323
|
+
self.write(Panel(Text(message, style="red"), title="[red]Error[/red]", border_style="red"))
|
|
324
|
+
|
|
325
|
+
def write_system(self, message: str):
|
|
326
|
+
self.write(Text.from_markup(f"[dim]{message}[/dim]"))
|
|
327
|
+
|
|
328
|
+
def write_welcome(self):
|
|
329
|
+
self.write(Text.from_markup(WELCOME_BANNER))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class SigmaApp(App):
|
|
333
|
+
"""Sigma Finance Research Agent."""
|
|
334
|
+
|
|
335
|
+
TITLE = "Sigma"
|
|
336
|
+
CSS = CSS
|
|
337
|
+
|
|
338
|
+
BINDINGS = [
|
|
339
|
+
Binding("ctrl+l", "clear", "Clear"),
|
|
340
|
+
Binding("ctrl+m", "models", "Models"),
|
|
341
|
+
Binding("ctrl+p", "palette", "palette", show=True),
|
|
342
|
+
Binding("escape", "cancel", show=False),
|
|
343
|
+
]
|
|
233
344
|
|
|
234
345
|
def __init__(self):
|
|
346
|
+
super().__init__()
|
|
235
347
|
self.settings = get_settings()
|
|
236
|
-
self.
|
|
237
|
-
self.
|
|
238
|
-
self.
|
|
239
|
-
self.
|
|
240
|
-
self.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def on_tool_end(self, name: str, result: Any, duration_ms: float):
|
|
263
|
-
"""Called when tool ends."""
|
|
264
|
-
self.tool_calls.append((name, duration_ms))
|
|
265
|
-
|
|
266
|
-
# Check for chart output
|
|
267
|
-
if isinstance(result, dict) and result.get("display_as_chart"):
|
|
268
|
-
self.chart_output = result.get("chart", "")
|
|
269
|
-
|
|
270
|
-
def on_thinking(self, content: str):
|
|
271
|
-
"""Called when agent is thinking."""
|
|
272
|
-
pass
|
|
273
|
-
|
|
274
|
-
def on_response(self, content: str):
|
|
275
|
-
"""Called with final response."""
|
|
276
|
-
pass
|
|
277
|
-
|
|
278
|
-
async def process_query(self, query: str):
|
|
279
|
-
"""Process a query."""
|
|
280
|
-
if self.agent is None:
|
|
281
|
-
self._init_agent()
|
|
282
|
-
|
|
283
|
-
assert self.agent is not None
|
|
284
|
-
|
|
285
|
-
self.tool_calls = []
|
|
286
|
-
self.start_time = time.time()
|
|
287
|
-
self.chart_output = None
|
|
288
|
-
|
|
289
|
-
console.print()
|
|
290
|
-
console.print(" [dim]╭─ Researching...[/dim]")
|
|
291
|
-
|
|
292
|
-
# Run agent
|
|
293
|
-
response = await self.agent.run(
|
|
294
|
-
query,
|
|
295
|
-
on_tool_start=self.on_tool_start,
|
|
296
|
-
on_tool_end=self.on_tool_end,
|
|
297
|
-
on_thinking=self.on_thinking,
|
|
298
|
-
on_response=self.on_response,
|
|
348
|
+
self.llm = None
|
|
349
|
+
self.conversation = []
|
|
350
|
+
self.is_processing = False
|
|
351
|
+
self.history: List[str] = []
|
|
352
|
+
self.history_idx = -1
|
|
353
|
+
|
|
354
|
+
def compose(self) -> ComposeResult:
|
|
355
|
+
yield Container(
|
|
356
|
+
ScrollableContainer(
|
|
357
|
+
ChatLog(id="chat-log", highlight=True, markup=True),
|
|
358
|
+
id="chat-area",
|
|
359
|
+
),
|
|
360
|
+
Static(id="thinking-area"),
|
|
361
|
+
Container(
|
|
362
|
+
Horizontal(
|
|
363
|
+
SigmaIndicator(id="sigma-indicator"),
|
|
364
|
+
Input(
|
|
365
|
+
placeholder="Ask about any stock, market, or strategy...",
|
|
366
|
+
id="prompt-input",
|
|
367
|
+
suggester=SigmaSuggester(),
|
|
368
|
+
),
|
|
369
|
+
id="input-row",
|
|
370
|
+
),
|
|
371
|
+
id="input-area",
|
|
372
|
+
),
|
|
373
|
+
id="main-container",
|
|
299
374
|
)
|
|
300
|
-
|
|
301
|
-
elapsed = time.time() - self.start_time
|
|
302
|
-
|
|
303
|
-
# Print data sources summary
|
|
304
|
-
if self.tool_calls:
|
|
305
|
-
console.print(f" [dim]╰─[/dim] [bright_green]Called {len(self.tool_calls)} data sources[/bright_green] [dim]in {elapsed:.1f}s[/dim]")
|
|
306
|
-
|
|
307
|
-
console.print()
|
|
308
|
-
|
|
309
|
-
# Print chart if generated
|
|
310
|
-
if self.chart_output:
|
|
311
|
-
console.print(self.chart_output)
|
|
312
|
-
console.print()
|
|
313
|
-
|
|
314
|
-
# Print response
|
|
315
|
-
self._display_response(response)
|
|
316
|
-
|
|
317
|
-
console.print()
|
|
375
|
+
yield Footer()
|
|
318
376
|
|
|
319
|
-
def
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
in_table = False
|
|
323
|
-
table_lines: list[str] = []
|
|
377
|
+
def on_mount(self):
|
|
378
|
+
chat = self.query_one("#chat-log", ChatLog)
|
|
379
|
+
chat.write_welcome()
|
|
324
380
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
in_table = True
|
|
330
|
-
table_lines = []
|
|
331
|
-
table_lines.append(line)
|
|
332
|
-
continue
|
|
333
|
-
elif in_table and line.strip():
|
|
334
|
-
if '|' in line:
|
|
335
|
-
table_lines.append(line)
|
|
336
|
-
continue
|
|
337
|
-
else:
|
|
338
|
-
self._print_table(table_lines)
|
|
339
|
-
in_table = False
|
|
340
|
-
table_lines = []
|
|
341
|
-
elif in_table and not line.strip():
|
|
342
|
-
self._print_table(table_lines)
|
|
343
|
-
in_table = False
|
|
344
|
-
table_lines = []
|
|
345
|
-
|
|
346
|
-
# Format and print the line
|
|
347
|
-
formatted = self._format_line(line)
|
|
348
|
-
if formatted is not None:
|
|
349
|
-
console.print(formatted)
|
|
381
|
+
provider = getattr(self.settings.default_provider, 'value', str(self.settings.default_provider))
|
|
382
|
+
chat.write_system(f"{SIGMA} Provider: {provider} | Model: {self.settings.default_model}")
|
|
383
|
+
chat.write_system(f"{SIGMA} Type /help for commands or ask anything about markets")
|
|
384
|
+
chat.write_system("")
|
|
350
385
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
self._print_table(table_lines)
|
|
386
|
+
self._init_llm()
|
|
387
|
+
self.query_one("#prompt-input", Input).focus()
|
|
354
388
|
|
|
355
|
-
def
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
level = len(header_match.group(1))
|
|
368
|
-
text = header_match.group(2)
|
|
369
|
-
if level == 1:
|
|
370
|
-
return f"\n[bold bright_white]{text}[/bold bright_white]"
|
|
371
|
-
elif level == 2:
|
|
372
|
-
return f"\n[bold bright_cyan]▸ {text}[/bold bright_cyan]"
|
|
373
|
-
else:
|
|
374
|
-
return f"\n[bold]{text}[/bold]"
|
|
375
|
-
|
|
376
|
-
# Bold-only lines
|
|
377
|
-
if stripped.startswith('**') and stripped.endswith('**') and stripped.count('**') == 2:
|
|
378
|
-
text = stripped[2:-2]
|
|
379
|
-
return f"\n[bold bright_yellow]▸ {text}[/bold bright_yellow]"
|
|
389
|
+
def _init_llm(self):
|
|
390
|
+
try:
|
|
391
|
+
self.llm = get_llm(self.settings.default_provider, self.settings.default_model)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
chat = self.query_one("#chat-log", ChatLog)
|
|
394
|
+
chat.write_error(f"LLM init failed: {e}")
|
|
395
|
+
chat.write_system("Use /keys to configure API keys")
|
|
396
|
+
|
|
397
|
+
@on(Input.Submitted)
|
|
398
|
+
def handle_input(self, event: Input.Submitted):
|
|
399
|
+
if self.is_processing:
|
|
400
|
+
return
|
|
380
401
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
bold_part = bold_start.group(1)
|
|
385
|
-
rest = bold_start.group(2)
|
|
386
|
-
rest = re.sub(r'\*\*(.+?)\*\*', r'[bold]\1[/bold]', rest)
|
|
387
|
-
rest = re.sub(r'`([^`]+)`', r'[bright_cyan]\1[/bright_cyan]', rest)
|
|
388
|
-
return f"[bold bright_green]{bold_part}[/bold bright_green]{rest}"
|
|
402
|
+
text = event.value.strip()
|
|
403
|
+
if not text:
|
|
404
|
+
return
|
|
389
405
|
|
|
390
|
-
#
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
bullet_text = re.sub(r'\*\*(.+?)\*\*', r'[bold]\1[/bold]', bullet_text)
|
|
394
|
-
bullet_text = re.sub(r'`([^`]+)`', r'[bright_cyan]\1[/bright_cyan]', bullet_text)
|
|
395
|
-
return f" [dim]•[/dim] {bullet_text}"
|
|
406
|
+
self.query_one("#prompt-input", Input).value = ""
|
|
407
|
+
self.history.append(text)
|
|
408
|
+
self.history_idx = len(self.history)
|
|
396
409
|
|
|
397
|
-
#
|
|
398
|
-
numbered = re.match(r'^(\d+)\.\s+(.+)$', stripped)
|
|
399
|
-
if numbered:
|
|
400
|
-
num = numbered.group(1)
|
|
401
|
-
text = numbered.group(2)
|
|
402
|
-
text = re.sub(r'\*\*(.+?)\*\*', r'[bold]\1[/bold]', text)
|
|
403
|
-
text = re.sub(r'`([^`]+)`', r'[bright_cyan]\1[/bright_cyan]', text)
|
|
404
|
-
return f" [bright_cyan]{num}.[/bright_cyan] {text}"
|
|
410
|
+
chat = self.query_one("#chat-log", ChatLog)
|
|
405
411
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
412
|
+
if text.startswith("/"):
|
|
413
|
+
self._handle_command(text, chat)
|
|
414
|
+
else:
|
|
415
|
+
chat.write_user(text)
|
|
416
|
+
self._process_query(text, chat)
|
|
417
|
+
|
|
418
|
+
def _handle_command(self, cmd: str, chat: ChatLog):
|
|
419
|
+
parts = cmd.lower().split()
|
|
420
|
+
command = parts[0]
|
|
421
|
+
args = parts[1:] if len(parts) > 1 else []
|
|
410
422
|
|
|
411
|
-
|
|
423
|
+
if command == "/help":
|
|
424
|
+
chat.write_system(f"""
|
|
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
|
+
""")
|
|
438
|
+
elif command == "/clear":
|
|
439
|
+
chat.clear()
|
|
440
|
+
self.conversation = []
|
|
441
|
+
chat.write_system("Chat cleared")
|
|
442
|
+
elif command == "/keys":
|
|
443
|
+
self._show_keys(chat)
|
|
444
|
+
elif command == "/models":
|
|
445
|
+
self._show_models(chat)
|
|
446
|
+
elif command == "/status":
|
|
447
|
+
self._show_status(chat)
|
|
448
|
+
elif command == "/backtest":
|
|
449
|
+
self._show_strategies(chat)
|
|
450
|
+
elif command == "/provider" and args:
|
|
451
|
+
self._switch_provider(args[0], chat)
|
|
452
|
+
elif command == "/model" and args:
|
|
453
|
+
self._switch_model(args[0], chat)
|
|
454
|
+
elif command.startswith("/setkey") and len(parts) >= 3:
|
|
455
|
+
self._set_key(parts[1], parts[2], chat)
|
|
456
|
+
else:
|
|
457
|
+
chat.write_error(f"Unknown command: {command}")
|
|
412
458
|
|
|
413
|
-
def
|
|
414
|
-
|
|
415
|
-
|
|
459
|
+
def _show_keys(self, chat: ChatLog):
|
|
460
|
+
chat.write_system(f"""
|
|
461
|
+
[bold]{SIGMA} API Keys[/bold]
|
|
462
|
+
Set key: /setkey <provider> <key>
|
|
463
|
+
|
|
464
|
+
Providers: google, openai, anthropic, groq, xai
|
|
465
|
+
Example: /setkey google AIzaSy...
|
|
466
|
+
""")
|
|
467
|
+
self._show_status(chat)
|
|
468
|
+
|
|
469
|
+
def _show_status(self, chat: ChatLog):
|
|
470
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
471
|
+
table.add_column("", style="bold")
|
|
472
|
+
table.add_column("")
|
|
473
|
+
|
|
474
|
+
provider = getattr(self.settings.default_provider, 'value', str(self.settings.default_provider))
|
|
475
|
+
table.add_row("Provider", provider)
|
|
476
|
+
table.add_row("Model", self.settings.default_model)
|
|
477
|
+
table.add_row("", "")
|
|
478
|
+
|
|
479
|
+
keys = [
|
|
480
|
+
("Google", self.settings.google_api_key),
|
|
481
|
+
("OpenAI", self.settings.openai_api_key),
|
|
482
|
+
("Anthropic", self.settings.anthropic_api_key),
|
|
483
|
+
("Groq", self.settings.groq_api_key),
|
|
484
|
+
("xAI", self.settings.xai_api_key),
|
|
485
|
+
]
|
|
486
|
+
for name, key in keys:
|
|
487
|
+
status = "[green]OK[/green]" if key else "[dim]--[/dim]"
|
|
488
|
+
table.add_row(f" {name}", Text.from_markup(status))
|
|
489
|
+
|
|
490
|
+
chat.write(Panel(table, title=f"[cyan]{SIGMA} Config[/cyan]", border_style="dim"))
|
|
491
|
+
|
|
492
|
+
def _show_models(self, chat: ChatLog):
|
|
493
|
+
table = Table(title=f"{SIGMA} Models", show_header=True, border_style="dim")
|
|
494
|
+
table.add_column("Provider", style="cyan")
|
|
495
|
+
table.add_column("Models")
|
|
496
|
+
for p, m in AVAILABLE_MODELS.items():
|
|
497
|
+
table.add_row(p, ", ".join(m))
|
|
498
|
+
chat.write(table)
|
|
499
|
+
|
|
500
|
+
def _show_strategies(self, chat: ChatLog):
|
|
501
|
+
strategies = get_available_strategies()
|
|
502
|
+
table = Table(title=f"{SIGMA} Strategies", show_header=True, border_style="dim")
|
|
503
|
+
table.add_column("Name", style="cyan")
|
|
504
|
+
table.add_column("Description")
|
|
505
|
+
for k, v in strategies.items():
|
|
506
|
+
table.add_row(k, v.get('description', ''))
|
|
507
|
+
chat.write(table)
|
|
508
|
+
|
|
509
|
+
def _switch_provider(self, provider: str, chat: ChatLog):
|
|
510
|
+
valid = ["google", "openai", "anthropic", "groq", "xai", "ollama"]
|
|
511
|
+
if provider not in valid:
|
|
512
|
+
chat.write_error(f"Invalid. Use: {', '.join(valid)}")
|
|
416
513
|
return
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
async def quick_chart(self, symbol: str, period: str = "3mo"):
|
|
445
|
-
"""Generate a quick chart."""
|
|
446
|
-
from sigma.tools.charts import create_price_chart
|
|
447
|
-
chart = create_price_chart(symbol, period)
|
|
448
|
-
console.print()
|
|
449
|
-
console.print(chart)
|
|
450
|
-
console.print()
|
|
451
|
-
|
|
452
|
-
async def quick_compare(self, symbols: list[str], period: str = "3mo"):
|
|
453
|
-
"""Generate a quick comparison chart."""
|
|
454
|
-
from sigma.tools.charts import create_comparison_chart
|
|
455
|
-
chart = create_comparison_chart(symbols, period)
|
|
456
|
-
console.print()
|
|
457
|
-
console.print(chart)
|
|
458
|
-
console.print()
|
|
459
|
-
|
|
460
|
-
def _handle_lean_command(self, args: list[str]):
|
|
461
|
-
"""Handle /lean commands for LEAN Engine backtesting."""
|
|
462
|
-
from sigma.tools.backtest import setup_lean_engine, run_lean_backtest, check_lean_status, get_available_strategies
|
|
463
|
-
|
|
464
|
-
if not args:
|
|
465
|
-
# Show help menu
|
|
466
|
-
strategies = get_available_strategies()
|
|
467
|
-
console.print()
|
|
468
|
-
console.print(Panel(
|
|
469
|
-
"\n".join([
|
|
470
|
-
"[bold cyan]LEAN Engine Backtesting[/bold cyan]",
|
|
471
|
-
"",
|
|
472
|
-
"[bold]Commands:[/bold]",
|
|
473
|
-
" /lean setup - Setup LEAN Engine (one-time)",
|
|
474
|
-
" /lean run <SYM> <STRAT> - Run comprehensive backtest",
|
|
475
|
-
" /lean status - Check LEAN setup status",
|
|
476
|
-
" /lean strategies - List available strategies",
|
|
477
|
-
"",
|
|
478
|
-
"[bold]Quick Start:[/bold]",
|
|
479
|
-
" /lean run AAPL sma_crossover",
|
|
480
|
-
" /lean run TSLA macd_momentum",
|
|
481
|
-
" /lean run NVDA rsi_mean_reversion",
|
|
482
|
-
"",
|
|
483
|
-
"[bold]Available Strategies:[/bold]",
|
|
484
|
-
*[f" [cyan]{name}[/cyan] - {s['description'][:50]}..." for name, s in list(strategies.items())[:3]],
|
|
485
|
-
" [dim]...use /lean strategies for full list[/dim]"
|
|
486
|
-
]),
|
|
487
|
-
title="[bold bright_cyan]LEAN Backtest Engine[/bold bright_cyan]",
|
|
488
|
-
border_style="bright_blue"
|
|
489
|
-
))
|
|
490
|
-
console.print()
|
|
514
|
+
try:
|
|
515
|
+
self.settings.default_provider = LLMProvider(provider)
|
|
516
|
+
if provider in AVAILABLE_MODELS:
|
|
517
|
+
self.settings.default_model = AVAILABLE_MODELS[provider][0]
|
|
518
|
+
self._init_llm()
|
|
519
|
+
chat.write_system(f"Switched to {provider}")
|
|
520
|
+
except Exception as e:
|
|
521
|
+
chat.write_error(str(e))
|
|
522
|
+
|
|
523
|
+
def _switch_model(self, model: str, chat: ChatLog):
|
|
524
|
+
self.settings.default_model = model
|
|
525
|
+
self._init_llm()
|
|
526
|
+
chat.write_system(f"Model: {model}")
|
|
527
|
+
|
|
528
|
+
def _set_key(self, provider: str, key: str, chat: ChatLog):
|
|
529
|
+
try:
|
|
530
|
+
save_api_key(LLMProvider(provider), key)
|
|
531
|
+
chat.write_system(f"{SIGMA} Key saved for {provider}")
|
|
532
|
+
if provider == getattr(self.settings.default_provider, 'value', ''):
|
|
533
|
+
self._init_llm()
|
|
534
|
+
except Exception as e:
|
|
535
|
+
chat.write_error(str(e))
|
|
536
|
+
|
|
537
|
+
@work(exclusive=True)
|
|
538
|
+
async def _process_query(self, query: str, chat: ChatLog):
|
|
539
|
+
if not self.llm:
|
|
540
|
+
chat.write_error("No LLM. Use /keys to configure.")
|
|
491
541
|
return
|
|
492
542
|
|
|
493
|
-
|
|
543
|
+
self.is_processing = True
|
|
544
|
+
thinking = self.query_one("#thinking-area", Static)
|
|
545
|
+
indicator = self.query_one("#sigma-indicator", SigmaIndicator)
|
|
494
546
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
547
|
+
# Start animated thinking display
|
|
548
|
+
thinking.add_class("visible")
|
|
549
|
+
frame = [0]
|
|
498
550
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
from rich.table import Table
|
|
503
|
-
table = Table(title="Available Backtest Strategies", border_style="bright_blue")
|
|
504
|
-
table.add_column("Strategy", style="cyan")
|
|
505
|
-
table.add_column("Description", style="dim")
|
|
506
|
-
table.add_column("Parameters", style="yellow")
|
|
507
|
-
|
|
508
|
-
for name, s in strategies.items():
|
|
509
|
-
params = ", ".join([f"{k}={v}" for k, v in s.get("default_params", {}).items()])
|
|
510
|
-
table.add_row(name, s["description"][:60], params[:40])
|
|
511
|
-
|
|
512
|
-
console.print(table)
|
|
513
|
-
console.print()
|
|
514
|
-
return
|
|
551
|
+
def animate():
|
|
552
|
+
thinking.update(Text.from_markup(THINKING_LOGO_FRAMES[frame[0]]))
|
|
553
|
+
frame[0] = (frame[0] + 1) % len(THINKING_LOGO_FRAMES)
|
|
515
554
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
console.print(Panel(
|
|
520
|
-
"\n".join([
|
|
521
|
-
f"[dim]Docker Installed:[/dim] {'[green]Yes[/green]' if status['docker_installed'] else '[red]No[/red]'}",
|
|
522
|
-
f"[dim]Docker Running:[/dim] {'[green]Yes[/green]' if status['docker_running'] else '[red]No[/red]'}",
|
|
523
|
-
f"[dim]LEAN Image:[/dim] {'[green]Pulled[/green]' if status['lean_image_pulled'] else '[yellow]Not pulled[/yellow]'}",
|
|
524
|
-
f"[dim]Workspace:[/dim] {'[green]Ready[/green]' if status['workspace_initialized'] else '[yellow]Not initialized[/yellow]'}",
|
|
525
|
-
"",
|
|
526
|
-
*status['instructions']
|
|
527
|
-
]),
|
|
528
|
-
title="[bold bright_cyan]LEAN Engine Status[/bold bright_cyan]",
|
|
529
|
-
border_style="bright_blue"
|
|
530
|
-
))
|
|
531
|
-
console.print()
|
|
532
|
-
return
|
|
555
|
+
animate()
|
|
556
|
+
timer = self.set_interval(0.3, animate)
|
|
557
|
+
indicator.set_active(True)
|
|
533
558
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
result = setup_lean_engine()
|
|
540
|
-
|
|
541
|
-
for step in result["steps_completed"]:
|
|
542
|
-
console.print(f" [green]✓[/green] {step}")
|
|
543
|
-
|
|
544
|
-
for error in result.get("errors", []):
|
|
545
|
-
console.print(f" [red]✗[/red] {error}")
|
|
546
|
-
|
|
547
|
-
console.print()
|
|
548
|
-
if result["success"]:
|
|
549
|
-
console.print(Panel(
|
|
550
|
-
"\n".join(result["next_steps"]),
|
|
551
|
-
title="[bold bright_green]Setup Complete[/bold bright_green]",
|
|
552
|
-
border_style="green"
|
|
553
|
-
))
|
|
554
|
-
else:
|
|
555
|
-
console.print(Panel(
|
|
556
|
-
"\n".join(result.get("next_steps", ["Setup failed. Check errors above."])),
|
|
557
|
-
title="[bold red]Setup Incomplete[/bold red]",
|
|
558
|
-
border_style="red"
|
|
559
|
-
))
|
|
560
|
-
console.print()
|
|
561
|
-
|
|
562
|
-
elif subcmd == "run":
|
|
563
|
-
if len(args) < 3:
|
|
564
|
-
console.print("\n [red]Usage:[/red] /lean run <symbol> <strategy>\n")
|
|
565
|
-
console.print(" [dim]Strategies:[/dim] sma_crossover, rsi_mean_reversion, macd_momentum, bollinger_bands, dual_momentum, breakout\n")
|
|
566
|
-
return
|
|
567
|
-
|
|
568
|
-
symbol = args[1].upper()
|
|
569
|
-
strategy = args[2].lower()
|
|
570
|
-
|
|
571
|
-
console.print()
|
|
572
|
-
console.print(f"[bright_cyan]Running Comprehensive Backtest: {symbol} - {strategy}[/bright_cyan]")
|
|
573
|
-
console.print()
|
|
559
|
+
try:
|
|
560
|
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
|
561
|
+
messages.extend(self.conversation)
|
|
562
|
+
messages.append({"role": "user", "content": query})
|
|
574
563
|
|
|
575
|
-
|
|
564
|
+
all_tools = TOOLS + [BACKTEST_TOOL]
|
|
576
565
|
|
|
577
|
-
|
|
578
|
-
|
|
566
|
+
async def on_tool(name: str, args: dict):
|
|
567
|
+
chat.write_tool(name)
|
|
568
|
+
if name == "run_backtest":
|
|
569
|
+
return run_backtest(**args)
|
|
570
|
+
return execute_tool(name, args)
|
|
579
571
|
|
|
580
|
-
|
|
572
|
+
response = await self.llm.generate(messages, tools=all_tools, on_tool_call=on_tool)
|
|
581
573
|
|
|
582
|
-
if
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
"\n".join([
|
|
589
|
-
f" [bold cyan]Initial Capital:[/bold cyan] {perf.get('initial_capital', 'N/A')}",
|
|
590
|
-
f" [bold cyan]Final Equity:[/bold cyan] {perf.get('final_equity', 'N/A')}",
|
|
591
|
-
f" [bold cyan]Total Return:[/bold cyan] {perf.get('total_return', 'N/A')}",
|
|
592
|
-
f" [bold cyan]Annual Return:[/bold cyan] {perf.get('annual_return', 'N/A')}",
|
|
593
|
-
f" [bold cyan]Buy & Hold:[/bold cyan] {perf.get('buy_hold_return', 'N/A')}",
|
|
594
|
-
f" [bold cyan]Alpha:[/bold cyan] {perf.get('alpha', 'N/A')}",
|
|
595
|
-
]),
|
|
596
|
-
title=f"[bold bright_green]Performance - {symbol} {strategy}[/bold bright_green]",
|
|
597
|
-
border_style="green"
|
|
598
|
-
))
|
|
599
|
-
|
|
600
|
-
# Risk metrics panel
|
|
601
|
-
risk = metrics.get("risk", {})
|
|
602
|
-
console.print(Panel(
|
|
603
|
-
"\n".join([
|
|
604
|
-
f" [bold yellow]Max Drawdown:[/bold yellow] {risk.get('max_drawdown', 'N/A')}",
|
|
605
|
-
f" [bold yellow]Volatility:[/bold yellow] {risk.get('volatility', 'N/A')}",
|
|
606
|
-
f" [bold yellow]Sharpe Ratio:[/bold yellow] {risk.get('sharpe_ratio', 'N/A')}",
|
|
607
|
-
f" [bold yellow]Sortino Ratio:[/bold yellow] {risk.get('sortino_ratio', 'N/A')}",
|
|
608
|
-
f" [bold yellow]Calmar Ratio:[/bold yellow] {risk.get('calmar_ratio', 'N/A')}",
|
|
609
|
-
]),
|
|
610
|
-
title="[bold bright_yellow]Risk Metrics[/bold bright_yellow]",
|
|
611
|
-
border_style="yellow"
|
|
612
|
-
))
|
|
613
|
-
|
|
614
|
-
# Trade statistics panel
|
|
615
|
-
trades = metrics.get("trades", {})
|
|
616
|
-
console.print(Panel(
|
|
617
|
-
"\n".join([
|
|
618
|
-
f" [bold magenta]Total Trades:[/bold magenta] {trades.get('total_trades', 'N/A')}",
|
|
619
|
-
f" [bold magenta]Win Rate:[/bold magenta] {trades.get('win_rate', 'N/A')}",
|
|
620
|
-
f" [bold magenta]Profit Factor:[/bold magenta] {trades.get('profit_factor', 'N/A')}",
|
|
621
|
-
f" [bold magenta]Avg Win:[/bold magenta] {trades.get('avg_win', 'N/A')}",
|
|
622
|
-
f" [bold magenta]Avg Loss:[/bold magenta] {trades.get('avg_loss', 'N/A')}",
|
|
623
|
-
f" [bold magenta]Avg Holding:[/bold magenta] {trades.get('avg_holding_days', 'N/A')} days",
|
|
624
|
-
]),
|
|
625
|
-
title="[bold bright_magenta]Trade Statistics[/bold bright_magenta]",
|
|
626
|
-
border_style="magenta"
|
|
627
|
-
))
|
|
628
|
-
|
|
629
|
-
# Display charts
|
|
630
|
-
charts = result.get("charts", {})
|
|
631
|
-
|
|
632
|
-
if charts.get("equity_curve"):
|
|
633
|
-
console.print()
|
|
634
|
-
console.print(charts["equity_curve"])
|
|
635
|
-
|
|
636
|
-
if charts.get("drawdown"):
|
|
637
|
-
console.print()
|
|
638
|
-
console.print(charts["drawdown"])
|
|
639
|
-
|
|
640
|
-
if charts.get("trade_pnl"):
|
|
641
|
-
console.print()
|
|
642
|
-
console.print(charts["trade_pnl"])
|
|
643
|
-
|
|
644
|
-
if charts.get("monthly_returns"):
|
|
645
|
-
console.print()
|
|
646
|
-
console.print(charts["monthly_returns"])
|
|
647
|
-
|
|
648
|
-
# Recent trades table
|
|
649
|
-
trade_list = result.get("trades", [])
|
|
650
|
-
if trade_list:
|
|
651
|
-
console.print()
|
|
652
|
-
from rich.table import Table
|
|
653
|
-
table = Table(title="Recent Trades", border_style="dim")
|
|
654
|
-
table.add_column("Date", style="dim")
|
|
655
|
-
table.add_column("Action", style="bold")
|
|
656
|
-
table.add_column("Price", justify="right")
|
|
657
|
-
table.add_column("Shares", justify="right")
|
|
658
|
-
table.add_column("P&L", justify="right")
|
|
659
|
-
|
|
660
|
-
for trade in trade_list[-10:]:
|
|
661
|
-
action_style = "green" if trade.get("action") == "BUY" else "red"
|
|
662
|
-
pnl = trade.get("pnl")
|
|
663
|
-
pnl_str = f"${pnl:+,.2f}" if pnl else "-"
|
|
664
|
-
pnl_style = "green" if pnl and pnl > 0 else "red" if pnl else "dim"
|
|
665
|
-
table.add_row(
|
|
666
|
-
trade.get("date", ""),
|
|
667
|
-
f"[{action_style}]{trade.get('action', '')}[/{action_style}]",
|
|
668
|
-
f"${trade.get('price', 0):,.2f}",
|
|
669
|
-
str(trade.get("shares", "")),
|
|
670
|
-
f"[{pnl_style}]{pnl_str}[/{pnl_style}]"
|
|
671
|
-
)
|
|
672
|
-
console.print(table)
|
|
673
|
-
|
|
674
|
-
# Monthly returns
|
|
675
|
-
monthly = result.get("monthly_returns", [])
|
|
676
|
-
if monthly:
|
|
677
|
-
console.print()
|
|
678
|
-
from rich.table import Table
|
|
679
|
-
mtable = Table(title="Monthly Returns", border_style="dim")
|
|
680
|
-
mtable.add_column("Month", style="dim")
|
|
681
|
-
mtable.add_column("Return", justify="right")
|
|
682
|
-
|
|
683
|
-
for m in monthly[-12:]:
|
|
684
|
-
ret = m.get("return", 0)
|
|
685
|
-
ret_style = "green" if ret > 0 else "red"
|
|
686
|
-
mtable.add_row(m.get("month", ""), f"[{ret_style}]{ret:+.2f}%[/{ret_style}]")
|
|
687
|
-
console.print(mtable)
|
|
688
|
-
|
|
689
|
-
# QuantConnect instructions
|
|
690
|
-
console.print()
|
|
691
|
-
console.print(Panel(
|
|
692
|
-
"\n".join(result.get("quantconnect_instructions", [])),
|
|
693
|
-
title="[bold bright_blue]QuantConnect Cloud (Institutional Data)[/bold bright_blue]",
|
|
694
|
-
border_style="blue"
|
|
695
|
-
))
|
|
696
|
-
|
|
574
|
+
if response:
|
|
575
|
+
chat.write_assistant(response)
|
|
576
|
+
self.conversation.append({"role": "user", "content": query})
|
|
577
|
+
self.conversation.append({"role": "assistant", "content": response})
|
|
578
|
+
if len(self.conversation) > 20:
|
|
579
|
+
self.conversation = self.conversation[-20:]
|
|
697
580
|
else:
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
581
|
+
chat.write_error("No response")
|
|
582
|
+
except Exception as e:
|
|
583
|
+
chat.write_error(str(e))
|
|
584
|
+
finally:
|
|
585
|
+
timer.stop()
|
|
586
|
+
thinking.remove_class("visible")
|
|
587
|
+
thinking.update("")
|
|
588
|
+
indicator.set_active(False)
|
|
589
|
+
self.is_processing = False
|
|
590
|
+
self.query_one("#prompt-input", Input).focus()
|
|
591
|
+
|
|
592
|
+
def action_clear(self):
|
|
593
|
+
chat = self.query_one("#chat-log", ChatLog)
|
|
594
|
+
chat.clear()
|
|
595
|
+
self.conversation = []
|
|
596
|
+
chat.write_system("Cleared")
|
|
597
|
+
|
|
598
|
+
def action_models(self):
|
|
599
|
+
self._show_models(self.query_one("#chat-log", ChatLog))
|
|
600
|
+
|
|
601
|
+
def action_cancel(self):
|
|
602
|
+
if self.is_processing:
|
|
603
|
+
self.is_processing = False
|
|
716
604
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
args = parts[1:] if len(parts) > 1 else []
|
|
722
|
-
|
|
723
|
-
if command in ["/quit", "/exit", "/q"]:
|
|
724
|
-
return False
|
|
725
|
-
|
|
726
|
-
elif command in ["/help", "/h", "/?"]:
|
|
727
|
-
print_help()
|
|
728
|
-
|
|
729
|
-
elif command == "/model":
|
|
730
|
-
if not args:
|
|
731
|
-
available = self.settings.get_available_providers()
|
|
732
|
-
console.print()
|
|
733
|
-
console.print(f" [dim]Current model:[/dim] [bright_cyan]{self._get_model_display()}[/bright_cyan]")
|
|
734
|
-
console.print(f" [dim]Available:[/dim] {', '.join(p.value for p in available)}")
|
|
735
|
-
console.print()
|
|
736
|
-
else:
|
|
737
|
-
try:
|
|
738
|
-
self.provider = LLMProvider(args[0].lower())
|
|
739
|
-
self.agent = None
|
|
740
|
-
console.print(f"\n [bright_green]✓[/bright_green] Switched to [bright_cyan]{self._get_model_display()}[/bright_cyan]\n")
|
|
741
|
-
except ValueError:
|
|
742
|
-
console.print(f"\n [red]✗[/red] Unknown provider: {args[0]}\n")
|
|
743
|
-
|
|
744
|
-
elif command == "/mode":
|
|
745
|
-
if not args:
|
|
746
|
-
console.print()
|
|
747
|
-
console.print(f" [dim]Current mode:[/dim] [bright_cyan]{self.mode}[/bright_cyan]")
|
|
748
|
-
console.print(f" [dim]Available:[/dim] default, technical, fundamental, quant")
|
|
749
|
-
console.print()
|
|
750
|
-
else:
|
|
751
|
-
mode = args[0].lower()
|
|
752
|
-
if mode in ["default", "technical", "fundamental", "quant"]:
|
|
753
|
-
self.mode = mode
|
|
754
|
-
console.print(f"\n [bright_green]✓[/bright_green] Switched to [bright_cyan]{mode}[/bright_cyan] mode\n")
|
|
755
|
-
else:
|
|
756
|
-
console.print(f"\n [red]✗[/red] Unknown mode: {mode}\n")
|
|
757
|
-
|
|
758
|
-
elif command == "/clear":
|
|
759
|
-
if self.agent:
|
|
760
|
-
self.agent.clear()
|
|
761
|
-
console.print("\n [bright_green]✓[/bright_green] Conversation cleared\n")
|
|
762
|
-
|
|
763
|
-
elif command == "/status":
|
|
764
|
-
console.print()
|
|
765
|
-
console.print(f" [dim]Provider:[/dim] [bright_cyan]{self.provider.value}[/bright_cyan]")
|
|
766
|
-
console.print(f" [dim]Model:[/dim] [bright_cyan]{self._get_model_display()}[/bright_cyan]")
|
|
767
|
-
console.print(f" [dim]Mode:[/dim] [bright_cyan]{self.mode}[/bright_cyan]")
|
|
768
|
-
available = self.settings.get_available_providers()
|
|
769
|
-
console.print(f" [dim]Available providers:[/dim] {', '.join(p.value for p in available)}")
|
|
770
|
-
if self.agent:
|
|
771
|
-
stats = self.agent.get_stats()
|
|
772
|
-
console.print(f" [dim]Tools called this session:[/dim] {stats['tools_called']}")
|
|
773
|
-
console.print()
|
|
774
|
-
|
|
775
|
-
elif command == "/chart":
|
|
776
|
-
if not args:
|
|
777
|
-
console.print("\n [red]Usage:[/red] /chart <symbol> [period]\n")
|
|
778
|
-
else:
|
|
779
|
-
symbol = args[0].upper()
|
|
780
|
-
period = args[1] if len(args) > 1 else "3mo"
|
|
781
|
-
try:
|
|
782
|
-
asyncio.get_event_loop().run_until_complete(self.quick_chart(symbol, period))
|
|
783
|
-
except RuntimeError:
|
|
784
|
-
asyncio.run(self.quick_chart(symbol, period))
|
|
785
|
-
|
|
786
|
-
elif command == "/compare":
|
|
787
|
-
if len(args) < 2:
|
|
788
|
-
console.print("\n [red]Usage:[/red] /compare <symbol1> <symbol2> ...\n")
|
|
789
|
-
else:
|
|
790
|
-
symbols = [s.upper() for s in args[:5]]
|
|
791
|
-
try:
|
|
792
|
-
asyncio.get_event_loop().run_until_complete(self.quick_compare(symbols))
|
|
793
|
-
except RuntimeError:
|
|
794
|
-
asyncio.run(self.quick_compare(symbols))
|
|
795
|
-
|
|
796
|
-
elif command == "/backtest":
|
|
797
|
-
if len(args) < 2:
|
|
798
|
-
console.print("\n [red]Usage:[/red] /backtest <symbol> <strategy>\n")
|
|
799
|
-
console.print(" [dim]Strategies:[/dim] sma_crossover, rsi_mean_reversion, macd_momentum, bollinger_bands, dual_momentum, breakout\n")
|
|
800
|
-
else:
|
|
801
|
-
symbol = args[0]
|
|
802
|
-
strategy = args[1]
|
|
803
|
-
try:
|
|
804
|
-
loop = asyncio.get_event_loop()
|
|
805
|
-
loop.run_until_complete(
|
|
806
|
-
self.process_query(f"Generate a {strategy} backtest for {symbol}")
|
|
807
|
-
)
|
|
808
|
-
except RuntimeError:
|
|
809
|
-
asyncio.run(
|
|
810
|
-
self.process_query(f"Generate a {strategy} backtest for {symbol}")
|
|
811
|
-
)
|
|
812
|
-
|
|
813
|
-
elif command == "/lean":
|
|
814
|
-
self._handle_lean_command(args)
|
|
815
|
-
|
|
816
|
-
else:
|
|
817
|
-
console.print(f"\n [red]Unknown command:[/red] {command}. Type /help for commands.\n")
|
|
818
|
-
|
|
819
|
-
return True
|
|
820
|
-
|
|
821
|
-
async def run(self):
|
|
822
|
-
"""Run the interactive loop."""
|
|
823
|
-
print_banner(self._get_model_display())
|
|
824
|
-
|
|
825
|
-
while True:
|
|
826
|
-
try:
|
|
827
|
-
# Professional prompt
|
|
828
|
-
prompt_line = f"[bold bright_yellow]σ[/bold bright_yellow] [dim]›[/dim] "
|
|
829
|
-
query = console.input(prompt_line).strip()
|
|
830
|
-
|
|
831
|
-
if not query:
|
|
832
|
-
continue
|
|
833
|
-
|
|
834
|
-
# Handle commands
|
|
835
|
-
if query.startswith("/"):
|
|
836
|
-
if not self.handle_command(query):
|
|
837
|
-
console.print("\n [dim]Goodbye! May your trades be ever profitable.[/dim] 📈\n")
|
|
838
|
-
break
|
|
839
|
-
continue
|
|
840
|
-
|
|
841
|
-
# Process query
|
|
842
|
-
await self.process_query(query)
|
|
843
|
-
|
|
844
|
-
except KeyboardInterrupt:
|
|
845
|
-
console.print("\n")
|
|
846
|
-
continue
|
|
847
|
-
except EOFError:
|
|
848
|
-
console.print("\n [dim]Goodbye! May your trades be ever profitable.[/dim] 📈\n")
|
|
849
|
-
break
|
|
850
|
-
except Exception as e:
|
|
851
|
-
console.print(f"\n [red]Error:[/red] {str(e)}\n")
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
def main():
|
|
855
|
-
"""Main entry point."""
|
|
856
|
-
import argparse
|
|
857
|
-
|
|
858
|
-
parser = argparse.ArgumentParser(
|
|
859
|
-
prog="sigma",
|
|
860
|
-
description="Sigma - Institutional-Grade Financial Research Agent",
|
|
861
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
862
|
-
epilog="""
|
|
863
|
-
Examples:
|
|
864
|
-
sigma Start interactive mode
|
|
865
|
-
sigma --setup Run setup wizard
|
|
866
|
-
sigma "Analyze AAPL" Direct query mode
|
|
867
|
-
sigma --version Show version
|
|
868
|
-
|
|
869
|
-
Inside Sigma:
|
|
870
|
-
/help Show all commands
|
|
871
|
-
/model openai Switch to OpenAI
|
|
872
|
-
/lean run TSLA macd Run backtest
|
|
873
|
-
"""
|
|
874
|
-
)
|
|
875
|
-
|
|
876
|
-
parser.add_argument(
|
|
877
|
-
"query",
|
|
878
|
-
nargs="?",
|
|
879
|
-
help="Direct query to analyze (optional)"
|
|
880
|
-
)
|
|
881
|
-
parser.add_argument(
|
|
882
|
-
"--setup",
|
|
883
|
-
action="store_true",
|
|
884
|
-
help="Run the setup wizard"
|
|
885
|
-
)
|
|
886
|
-
parser.add_argument(
|
|
887
|
-
"--reset",
|
|
888
|
-
action="store_true",
|
|
889
|
-
help="Reset configuration and run setup"
|
|
890
|
-
)
|
|
891
|
-
parser.add_argument(
|
|
892
|
-
"--version", "-v",
|
|
893
|
-
action="version",
|
|
894
|
-
version=f"Sigma {VERSION}"
|
|
895
|
-
)
|
|
896
|
-
parser.add_argument(
|
|
897
|
-
"--model", "-m",
|
|
898
|
-
choices=["openai", "anthropic", "google", "groq", "xai", "ollama"],
|
|
899
|
-
help="Override default AI model"
|
|
900
|
-
)
|
|
901
|
-
|
|
902
|
-
args = parser.parse_args()
|
|
903
|
-
|
|
904
|
-
# Handle setup
|
|
905
|
-
from sigma.setup import ensure_setup, run_setup, is_setup_complete, CONFIG_DIR
|
|
906
|
-
import shutil
|
|
907
|
-
|
|
908
|
-
if args.reset:
|
|
909
|
-
if CONFIG_DIR.exists():
|
|
910
|
-
shutil.rmtree(CONFIG_DIR)
|
|
911
|
-
run_setup(force=True)
|
|
912
|
-
elif args.setup:
|
|
913
|
-
run_setup(force=True)
|
|
914
|
-
else:
|
|
915
|
-
# Ensure setup is done
|
|
916
|
-
ensure_setup()
|
|
917
|
-
|
|
918
|
-
# Create UI with optional model override
|
|
919
|
-
ui = SigmaUI()
|
|
920
|
-
if args.model:
|
|
921
|
-
try:
|
|
922
|
-
ui.provider = LLMProvider(args.model)
|
|
923
|
-
ui.agent = None # Force agent reload
|
|
924
|
-
except ValueError:
|
|
925
|
-
pass
|
|
926
|
-
|
|
927
|
-
# Handle direct query or interactive mode
|
|
928
|
-
if args.query:
|
|
929
|
-
# Direct query mode
|
|
930
|
-
async def run_query():
|
|
931
|
-
print_banner(ui._get_model_display())
|
|
932
|
-
await ui.process_query(args.query)
|
|
933
|
-
|
|
934
|
-
try:
|
|
935
|
-
asyncio.run(run_query())
|
|
936
|
-
except KeyboardInterrupt:
|
|
937
|
-
console.print("\n")
|
|
938
|
-
else:
|
|
939
|
-
# Interactive mode
|
|
940
|
-
try:
|
|
941
|
-
asyncio.run(ui.run())
|
|
942
|
-
except KeyboardInterrupt:
|
|
943
|
-
console.print("\n [dim]Goodbye! May your trades be ever profitable.[/dim]\n")
|
|
605
|
+
|
|
606
|
+
def launch():
|
|
607
|
+
"""Launch Sigma."""
|
|
608
|
+
SigmaApp().run()
|
|
944
609
|
|
|
945
610
|
|
|
946
611
|
if __name__ == "__main__":
|
|
947
|
-
|
|
612
|
+
launch()
|