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/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())
|