[New tools] [Jupiter, polygon, massive] [Remove ticker agent]

This commit is contained in:
Kye Gomez
2026-02-17 13:56:27 -08:00
parent 553a898ba6
commit a24c2dbb27
10 changed files with 1173 additions and 235 deletions
-1
View File
@@ -107,7 +107,6 @@ MIT License. See [LICENSE](LICENSE) for details.
## Acknowledgments
- [Swarms](https://swarms.ai) for the AI agent framework
- [Tickr Agent](https://github.com/The-Swarm-Corporation/tickr-agent) for market data integration
---
+6
View File
@@ -0,0 +1,6 @@
"""Run AutoHedge CLI with: python -m autohedge"""
from autohedge.cli import main
if __name__ == "__main__":
main()
+212
View File
@@ -0,0 +1,212 @@
"""
AutoHedge CLI — welcome screen and interactive REPL.
"""
import os
import sys
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.columns import Columns
from rich import box
try:
from importlib.metadata import version as _version
VERSION = _version("autohedge")
except Exception:
VERSION = "0.1.2"
console = Console()
# ASCII art: minimal "hedge" / chart vibe
BANNER_ART = r"""
▄▄▄▄▄▄▄
█████████
▐▀▄▄▄▄▄▀▌
▀ ▀
▄▄ ▀▄▀ ▄▄
"""
TIPS = [
"Enter a task to run (e.g. 'Analyze NVDA for 50k allocation')",
"Type 'stocks AAPL,MSFT' to set tickers, then run a task",
"Type 'quit' or 'exit' to leave",
"Type 'help' or '?' for commands",
]
RECENT_FILE = Path.home() / ".autohedge" / "recent_tasks.txt"
MAX_RECENT = 5
def _get_recent_tasks() -> list[str]:
if not RECENT_FILE.exists():
return []
try:
lines = RECENT_FILE.read_text().strip().splitlines()
return [
ln.strip() for ln in lines[-MAX_RECENT:] if ln.strip()
]
except Exception:
return []
def _append_recent(task: str) -> None:
try:
RECENT_FILE.parent.mkdir(parents=True, exist_ok=True)
recent = _get_recent_tasks()
if task in recent:
recent.remove(task)
recent.append(task)
RECENT_FILE.write_text("\n".join(recent[-MAX_RECENT:]))
except Exception:
pass
def _welcome() -> None:
cwd = Path.cwd()
try:
cwd_str = cwd.relative_to(Path.home())
cwd_display = f"~/{cwd_str}"
except ValueError:
cwd_display = str(cwd)
welcome = Text("Welcome to AutoHedge", style="bold orange1")
subtitle = Text(
f"v{VERSION} · {cwd_display}",
style="dim",
)
tips_text = Text(
"Tips for getting started\n", style="bold orange1"
)
tips_text.append(" — ".join(TIPS[:2]) + "\n", style="dim")
tips_text.append(" — ".join(TIPS[2:]), style="dim")
recent = _get_recent_tasks()
recent_heading = Text("Recent activity\n", style="bold orange1")
if recent:
recent_body = Text("\n".join(recent[-3:]), style="dim")
else:
recent_body = Text("No recent activity", style="dim")
left = Text()
left.append(welcome)
left.append("\n\n")
left.append(BANNER_ART, style="cyan")
left.append("\n")
left.append(subtitle)
right = Text()
right.append(tips_text)
right.append("\n")
right.append("─" * 50 + "\n", style="dim")
right.append(recent_heading)
right.append(recent_body)
# Two-column layout inside one panel (Claude Code style)
left_panel = Panel(
left,
box=box.MINIMAL,
padding=(0, 1),
border_style="dim",
expand=False,
)
right_panel = Panel(
right,
box=box.MINIMAL,
padding=(0, 1),
border_style="dim",
expand=True,
)
cols = Columns(
[left_panel, right_panel], expand=True, equal=False
)
console.print(
Panel(
cols,
title=f"[bold]AutoHedge v{VERSION}[/]",
title_align="left",
border_style="cyan",
padding=(0, 1),
)
)
def run_repl(default_stocks: list[str] | None = None) -> None:
stocks = default_stocks or ["NVDA"]
_welcome()
while True:
try:
prompt = Text("> ", style="bold cyan")
console.print(prompt, end="")
line = input().strip()
except (EOFError, KeyboardInterrupt):
console.print("\n[dim]Goodbye.[/]")
break
if not line:
continue
lower = line.lower()
if lower in ("quit", "exit", "q"):
console.print("[dim]Goodbye.[/]")
break
if lower in ("help", "?", "h"):
for t in TIPS:
console.print(f" [dim]·[/] {t}")
continue
if lower.startswith("stocks "):
raw = line[6:].strip()
stocks = [
s.strip().upper()
for s in raw.replace(",", " ").split()
if s.strip()
]
console.print(
f"[dim]Stocks set to: {', '.join(stocks)}[/]"
)
continue
# Treat as task
task = line
_append_recent(task)
try:
from autohedge import AutoHedge
system = AutoHedge(stocks=stocks)
console.print("[dim]Running...[/]")
result = system.run(task=task)
console.print(
Panel(
str(result)[:2000],
title="Result",
border_style="green",
)
)
except Exception as e:
console.print(f"[red]Error: {e}[/]")
def main() -> None:
"""Entry point for the AutoHedge CLI."""
default_stocks = os.environ.get("AUTOHEDGE_STOCKS")
if default_stocks:
stocks = [
s.strip().upper()
for s in default_stocks.split(",")
if s.strip()
]
else:
stocks = None
run_repl(default_stocks=stocks)
sys.exit(0)
if __name__ == "__main__":
main()
+14 -232
View File
@@ -1,34 +1,17 @@
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
from typing import List, Optional
from loguru import logger
from pydantic import BaseModel
from swarms import Agent, Conversation
from tickr_agent.main import TickrAgent
from swarms import Conversation
from autohedge.prompts import (
DIRECTOR_PROMPT,
EXECUTION_ORDER_PROMPT,
EXECUTION_PROMPT,
QUANT_ANALYSIS_PROMPT,
QUANT_PROMPT,
RISK_ASSESSMENT_PROMPT,
RISK_PROMPT,
SENTIMENT_PROMPT,
DIRECTOR_THESIS_PROMPT,
DIRECTOR_DECISION_PROMPT,
)
sentiment_agent = Agent(
agent_name="Sentiment-Agent",
system_prompt=SENTIMENT_PROMPT,
model_name="gpt-4o-mini",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
from autohedge.workers import (
ExecutionAgent,
QuantAnalyst,
RiskManager,
TradingDirector,
)
@@ -52,209 +35,6 @@ class AutoHedgeOutputMain(BaseModel):
logs: List[AutoHedgeOutput] = None
class RiskManager:
def __init__(self):
self.risk_agent = Agent(
agent_name="Risk-Manager",
system_prompt=RISK_PROMPT,
model_name="groq/deepseek-r1-distill-llama-70b",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def assess_risk(
self, stock: str, thesis: str, quant_analysis: str
) -> str:
prompt = RISK_ASSESSMENT_PROMPT.format(
stock=stock, thesis=thesis, quant_analysis=quant_analysis
)
assessment = self.risk_agent.run(prompt)
return assessment
class ExecutionAgent:
def __init__(self):
self.execution_agent = Agent(
agent_name="Execution-Agent",
system_prompt=EXECUTION_PROMPT,
model_name="groq/deepseek-r1-distill-llama-70b",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def generate_order(
self, stock: str, thesis: Dict, risk_assessment: Dict
) -> str:
prompt = EXECUTION_ORDER_PROMPT.format(
stock=stock,
thesis=thesis,
risk_assessment=risk_assessment,
)
order = self.execution_agent.run(prompt)
return order
class TradingDirector:
"""
Trading Director Agent responsible for generating trading theses and coordinating strategy.
Attributes:
director_agent (Agent): Swarms agent for thesis generation
tickr (TickrAgent): Agent for market data collection
output_dir (Path): Directory for storing outputs
Methods:
generate_thesis: Generates trading thesis for a given stock
save_output: Saves thesis to JSON file
"""
def __init__(
self,
stocks: List[str],
output_dir: str = "outputs",
cryptos: List[str] = None,
):
logger.info("Initializing Trading Director")
self.director_agent = Agent(
agent_name="Trading-Director",
system_prompt=DIRECTOR_PROMPT,
model_name="groq/deepseek-r1-distill-llama-70b",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
# self.crypto_agent = CryptoAgentWrapper()
def generate_thesis(
self,
task: str = "Generate a thesis for the stock",
stock: str = None,
crypto: str = None,
) -> str:
"""
Generate trading thesis for a given stock.
Args:
stock (str): Stock ticker symbol
Returns:
TradingThesis: Generated thesis
"""
logger.info(f"Generating thesis for {stock}")
self.tickr = TickrAgent(
stocks=[stock],
max_loops=1,
workers=10,
retry_attempts=1,
context_length=16000,
)
try:
market_data = self.tickr.run(
f"{task} Analyze current market conditions and key metrics for {stock}"
)
prompt = DIRECTOR_THESIS_PROMPT.format(
task=task, stock=stock, market_data=market_data
)
thesis = self.director_agent.run(prompt)
return thesis, market_data
except Exception as e:
logger.error(
f"Error generating thesis for {stock}: {str(e)}"
)
raise
def make_decision(self, task: str, thesis: str, *args, **kwargs):
return self.director_agent.run(
DIRECTOR_DECISION_PROMPT.format(thesis=thesis, task=task)
)
def generate_thesis_crypto(
self,
task: str = None,
crypto: str = None,
):
logger.info(f"Generating thesis for {crypto}")
try:
market_data = self.crypto_agent.run(
crypto,
f"{task} Analyze current market conditions and key metrics for {crypto}",
)
prompt = DIRECTOR_THESIS_PROMPT.format(
task=task, stock=crypto, market_data=market_data
)
thesis = self.director_agent.run(prompt)
return thesis
except Exception as e:
logger.error(
f"Error generating thesis for {crypto}: {str(e)}"
)
raise
class QuantAnalyst:
"""
Quantitative Analysis Agent responsible for technical and statistical analysis.
Attributes:
quant_agent (Agent): Swarms agent for analysis
output_dir (Path): Directory for storing outputs
"""
def __init__(self, output_dir: str = "outputs"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
logger.info("Initializing Quant Analyst")
self.quant_agent = Agent(
agent_name="Quant-Analyst",
system_prompt=QUANT_PROMPT,
model_name="groq/deepseek-r1-distill-llama-70b",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def analyze(self, stock: str, thesis: str) -> str:
"""
Perform quantitative analysis for a stock.
Args:
stock (str): Stock ticker symbol
thesis (TradingThesis): Trading thesis
Returns:
QuantAnalysis: Quantitative analysis results
"""
logger.info(f"Performing quant analysis for {stock}")
try:
prompt = QUANT_ANALYSIS_PROMPT.format(
stock=stock, thesis=thesis
)
analysis = self.quant_agent.run(prompt)
return analysis
except Exception as e:
logger.error(
f"Error in quant analysis for {stock}: {str(e)}"
)
raise
class AutoHedge:
"""
Main trading system that coordinates all agents and manages the trading cycle.
@@ -334,14 +114,14 @@ class AutoHedge:
task=task, stock=stock
)
self.conversation.add_message(
self.conversation.add(
role=self.director.agent_name,
content=f"Stock: {stock}\nMarket Data: {market_data}\nThesis: {thesis}",
)
# Perform analysis
analysis = self.quant.analyze(
stock + market_data, thesis
stock + market_data, thesis, task=task
)
# setiment_analysis = sentiment_agent.run(
@@ -358,7 +138,7 @@ class AutoHedge:
# Assess risk
risk_assessment = self.risk.assess_risk(
stock + market_data, thesis, analysis
stock + market_data, thesis, analysis, task=task
)
self.conversation.add(
@@ -367,7 +147,7 @@ class AutoHedge:
# # Generate order if approved
order = self.execution.generate_order(
stock, thesis, risk_assessment
stock, thesis, risk_assessment, task=task
)
self.conversation.add(
@@ -378,7 +158,9 @@ class AutoHedge:
# Final decision
decision = self.director.make_decision(
order + market_data + risk_assessment, thesis
order + market_data + risk_assessment,
thesis,
user_task=task,
)
self.conversation.add(
+23
View File
@@ -0,0 +1,23 @@
from autohedge.tools.polygon_api import (
get_ticker_overview,
get_balance_sheets,
get_daily_ticker_summary,
)
from autohedge.tools.jupiter_search import search_tokens
from autohedge.tools.jupiter_price import get_token_price
from autohedge.tools.ultra_tools import (
execute_trade,
get_order,
get_holdings,
)
__all__ = [
"get_ticker_overview",
"get_balance_sheets",
"get_daily_ticker_summary",
"search_tokens",
"get_token_price",
"execute_trade",
"get_order",
"get_holdings",
]
+190
View File
@@ -0,0 +1,190 @@
"""
Stocks API client for ticker overview, balance sheets, and daily OHLC.
Uses Massive API (https://massive.com/docs). Set POLYGON_API_KEY and optionally
POLYGON_BASE_URL in .env.
"""
import json
import os
from typing import Any, Optional
import httpx
from loguru import logger
DEFAULT_BASE_URL = "https://api.massive.com"
BASE_URL = os.getenv("POLYGON_BASE_URL", DEFAULT_BASE_URL)
def _get_headers() -> dict[str, str]:
headers: dict[str, str] = {}
key = os.getenv("POLYGON_API_KEY")
if key:
headers["Authorization"] = f"Bearer {key}"
return headers
def _get(
path: str,
*,
params: Optional[dict[str, Any]] = None,
) -> str:
url = f"{BASE_URL.rstrip('/')}{path}"
try:
with httpx.Client(timeout=15) as client:
resp = client.get(
url,
params=params,
headers=_get_headers() or None,
)
resp.raise_for_status()
return json.dumps(resp.json())
except httpx.HTTPError as e:
logger.error(f"Polygon API request failed: {e}")
raise
def get_ticker_overview(
ticker: str, date: Optional[str] = None
) -> str:
"""
Get comprehensive details for a single ticker (company fundamentals, exchange,
identifiers, market cap, branding, etc.).
Parameters
----------
ticker : str
Case-sensitive ticker symbol (e.g. AAPL for Apple Inc.).
date : str, optional
Point-in-time date (YYYY-MM-DD) for ticker info. Defaults to most recent.
Returns
-------
str
JSON string of the response (results object with active, address, branding,
cik, description, market_cap, name, primary_exchange, etc.).
"""
if not ticker or not ticker.strip():
logger.warning("get_ticker_overview: ticker is empty")
return "{}"
path = f"/v3/reference/tickers/{ticker.strip()}"
params: dict[str, Any] = {}
if date:
params["date"] = date
return _get(path, params=params if params else None)
def get_balance_sheets(
*,
cik: Optional[str] = None,
tickers: Optional[str] = None,
tickers_any_of: Optional[str] = None,
period_end: Optional[str] = None,
period_end_gte: Optional[str] = None,
period_end_lte: Optional[str] = None,
filing_date: Optional[str] = None,
fiscal_year: Optional[float] = None,
fiscal_quarter: Optional[float] = None,
timeframe: Optional[str] = None,
limit: Optional[int] = None,
sort: Optional[str] = None,
) -> str:
"""
Get balance sheet data for public companies (quarterly/annual). Returns asset,
liability, and equity positions as of period end.
Parameters
----------
cik : str, optional
SEC Central Index Key (CIK).
tickers : str, optional
Filter by ticker(s).
tickers_any_of : str, optional
Comma-separated tickers; filter for any of these.
period_end : str, optional
Period end date (YYYY-MM-DD).
period_end_gte, period_end_lte : str, optional
Period end date range (YYYY-MM-DD).
filing_date : str, optional
SEC filing date (YYYY-MM-DD).
fiscal_year, fiscal_quarter : float, optional
Fiscal year and quarter (1–4).
timeframe : str, optional
'quarterly' or 'annual'.
limit : int, optional
Max results (default 100, max 50000).
sort : str, optional
Sort columns, e.g. 'period_end.desc'.
Returns
-------
str
JSON string of the response (results array and next_url if paginated).
"""
params: dict[str, Any] = {}
if cik is not None:
params["cik"] = cik
if tickers is not None:
params["tickers"] = tickers
if tickers_any_of is not None:
params["tickers.any_of"] = tickers_any_of
if period_end is not None:
params["period_end"] = period_end
if period_end_gte is not None:
params["period_end.gte"] = period_end_gte
if period_end_lte is not None:
params["period_end.lte"] = period_end_lte
if filing_date is not None:
params["filing_date"] = filing_date
if fiscal_year is not None:
params["fiscal_year"] = fiscal_year
if fiscal_quarter is not None:
params["fiscal_quarter"] = fiscal_quarter
if timeframe is not None:
params["timeframe"] = timeframe
if limit is not None:
params["limit"] = limit
if sort is not None:
params["sort"] = sort
return _get(
"/stocks/financials/v1/balance-sheets", params=params or None
)
def get_daily_ticker_summary(
stocks_ticker: str,
date: str,
adjusted: Optional[bool] = None,
) -> str:
"""
Get daily open/close (OHLC) and volume for a stock ticker on a given date.
Optionally includes pre-market and after-hours prices.
Parameters
----------
stocks_ticker : str
Case-sensitive ticker symbol (e.g. AAPL).
date : str
Date of the open/close in YYYY-MM-DD.
adjusted : bool, optional
If True, results are adjusted for splits; if False, not adjusted.
Default from API is adjusted.
Returns
-------
str
JSON string of the response (open, high, low, close, volume, afterHours,
preMarket, symbol, from, status).
"""
if not stocks_ticker or not stocks_ticker.strip():
logger.warning(
"get_daily_ticker_summary: stocks_ticker is empty"
)
return "{}"
if not date or not date.strip():
logger.warning("get_daily_ticker_summary: date is empty")
return "{}"
path = f"/v1/open-close/{stocks_ticker.strip()}/{date.strip()}"
params: dict[str, Any] = {}
if adjusted is not None:
params["adjusted"] = "true" if adjusted else "false"
return _get(path, params=params if params else None)
+259
View File
@@ -0,0 +1,259 @@
"""
Yahoo Finance API client for stock quotes, fundamentals, and historical OHLC.
Uses the yfinance package (https://github.com/ranaroussi/yfinance).
Handles Yahoo rate limits (429) by fetching history first and returning
partial data when quoteSummary fails.
"""
import json
import traceback
from typing import Any, Optional
import yfinance as yf
from loguru import logger
# Errors from yfinance when Yahoo returns 429 or non-JSON (rate limit / block)
_RATE_LIMIT_EXCEPTIONS: tuple = (json.JSONDecodeError,)
try:
import requests
_RATE_LIMIT_EXCEPTIONS = (
*_RATE_LIMIT_EXCEPTIONS,
requests.HTTPError,
)
except ImportError:
pass
def _df_to_json_serializable(df: Any) -> Any:
"""Convert DataFrame to a JSON-serializable structure (handles NaN/dates)."""
if df is None or (hasattr(df, "empty") and df.empty):
return None
try:
import pandas as pd
if isinstance(df, pd.DataFrame):
return json.loads(
df.to_json(orient="split", date_format="iso")
)
return df
except Exception:
return None
def _safe_info(
ticker: yf.Ticker,
) -> tuple[dict[str, Any], Optional[str]]:
"""
Get ticker.info; on 429/JSON error return {} and an error message.
Returns (info_dict, error_message_or_None).
"""
try:
info = ticker.info
return (info or {}), None
except _RATE_LIMIT_EXCEPTIONS as e:
logger.warning(
"Yahoo rate limit or invalid response (info): {}",
e,
)
return (
{},
"Rate limited or invalid response from Yahoo (429).",
)
except Exception as e:
logger.debug("get info failed: {}", e)
return {}, str(e)
def _safe_financials(ticker: yf.Ticker) -> dict[str, Any]:
"""
Get balance_sheet, income_stmt, cashflow, etc.; on failure return
dict with only keys that succeeded and an optional _error key.
"""
out: dict[str, Any] = {}
attrs = [
("balance_sheet", "balance_sheet"),
("quarterly_balance_sheet", "quarterly_balance_sheet"),
("income_stmt", "income_stmt"),
("quarterly_income_stmt", "quarterly_income_stmt"),
("cashflow", "cashflow"),
("quarterly_cashflow", "quarterly_cashflow"),
("recommendations", "recommendations"),
("calendar", "calendar"),
]
for name, attr in attrs:
try:
val = getattr(ticker, attr, None)
if val is not None:
if hasattr(val, "to_json"):
out[name] = _df_to_json_serializable(val)
else:
out[name] = val
except _RATE_LIMIT_EXCEPTIONS:
continue
except Exception:
continue
return out
def get_stock_quote(ticker: str) -> str:
"""
Get current quote for a symbol (price, volume, day range, etc.).
Fetches history first (chart API); info (quoteSummary) may be empty
if Yahoo rate-limits.
"""
if not ticker or not ticker.strip():
logger.warning("get_stock_quote: ticker is empty")
return "{}"
symbol = ticker.strip().upper()
try:
t = yf.Ticker(symbol)
hist = t.history(period="5d", interval="1d")
info, info_err = _safe_info(t)
out: dict[str, Any] = {"symbol": symbol, "info": info}
if info_err:
out["_warning"] = info_err
if hist is not None and not hist.empty:
out["history"] = _df_to_json_serializable(hist)
return json.dumps(out, default=str)
except Exception as e:
logger.error(
"get_stock_quote failed: {}\n{}",
e,
traceback.format_exc(),
)
return "{}"
def get_historical_prices(
ticker: str,
interval: str = "1d",
range_str: str = "1mo",
) -> str:
"""
Get historical OHLCV for a ticker.
Parameters
----------
ticker : str
Ticker symbol (e.g. AAPL).
interval : str, optional
Candle interval: 1m, 2m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo. Default 1d.
range_str : str, optional
Range: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max. Default 1mo.
Returns
-------
str
JSON string with history (dates, open, high, low, close, volume).
"""
if not ticker or not ticker.strip():
logger.warning("get_historical_prices: ticker is empty")
return "{}"
symbol = ticker.strip().upper()
try:
t = yf.Ticker(symbol)
hist = t.history(period=range_str, interval=interval)
out: dict[str, Any] = {"symbol": symbol}
out["history"] = _df_to_json_serializable(hist)
return json.dumps(out, default=str)
except Exception as e:
logger.error(
"get_historical_prices failed: {}\n{}",
e,
traceback.format_exc(),
)
return "{}"
def get_quote_summary(
ticker: str,
modules: Optional[list[str]] = None,
) -> str:
"""
Get quote summary (fundamentals, financials, profile, balance sheet,
income, cashflow). The modules argument is ignored. Uses safe fetchers
so rate limits (429) return partial data instead of failing.
"""
if not ticker or not ticker.strip():
logger.warning("get_quote_summary: ticker is empty")
return "{}"
symbol = ticker.strip().upper()
try:
t = yf.Ticker(symbol)
info, info_err = _safe_info(t)
out: dict[str, Any] = {"symbol": symbol, "info": info}
if info_err:
out["_warning"] = info_err
financials = _safe_financials(t)
out.update(financials)
return json.dumps(out, default=str)
except Exception as e:
logger.error(
"get_quote_summary failed: {}\n{}",
e,
traceback.format_exc(),
)
return "{}"
def get_all_stock_data(
ticker: str,
include_history: bool = True,
history_range: str = "1mo",
) -> str:
"""
Get all main data for a stock: current quote (info), historical OHLC,
and quote summary. Fetches history first (chart API); if Yahoo
rate-limits quoteSummary (429), still returns history and partial data.
"""
if not ticker or not ticker.strip():
logger.warning("get_all_stock_data: ticker is empty")
return "{}"
symbol = ticker.strip().upper()
try:
t = yf.Ticker(symbol)
# Fetch history first (chart API is less rate-limited than quoteSummary)
hist = t.history(period=history_range, interval="1d")
history_serialized = _df_to_json_serializable(hist)
info, info_err = _safe_info(t)
if info_err:
logger.warning(
"get_all_stock_data: info fetch failed: {}", info_err
)
quote_data: dict[str, Any] = {"symbol": symbol, "info": info}
if info_err:
quote_data["_warning"] = info_err
if include_history and history_serialized:
quote_data["history"] = history_serialized
summary_data: dict[str, Any] = {
"symbol": symbol,
"info": info,
}
if info_err:
summary_data["_warning"] = info_err
summary_data.update(_safe_financials(t))
out: dict[str, Any] = {
"symbol": symbol,
"quote": quote_data,
"quote_summary": summary_data,
}
if include_history:
out["history"] = history_serialized
return json.dumps(out, default=str)
except Exception as e:
logger.error(
"get_all_stock_data failed: {}\n{}",
e,
traceback.format_exc(),
)
return "{}"
if __name__ == "__main__":
print(get_all_stock_data("AAPL"))
+464
View File
@@ -0,0 +1,464 @@
"""
AutoHedge workers: Pydantic output models and agent classes for thesis
generation, risk assessment, execution, and quantitative analysis.
"""
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
from loguru import logger
from pydantic import BaseModel
from swarms import Agent
from autohedge.prompts import (
DIRECTOR_DECISION_PROMPT,
DIRECTOR_PROMPT,
DIRECTOR_THESIS_PROMPT,
EXECUTION_ORDER_PROMPT,
EXECUTION_PROMPT,
QUANT_ANALYSIS_PROMPT,
QUANT_PROMPT,
RISK_ASSESSMENT_PROMPT,
RISK_PROMPT,
SENTIMENT_PROMPT,
)
from autohedge.tools.yahoo_api import get_all_stock_data
sentiment_agent = Agent(
agent_name="Sentiment-Agent",
system_prompt=SENTIMENT_PROMPT,
model_name="gpt-4o-mini",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def _agent_context(task: Optional[str] = None) -> str:
"""Build context string with current time and task to prepend to agent prompts."""
now = datetime.now().isoformat()
task_str = task if task else "(none)"
return f"Current time: {now}\nTask: {task_str}\n\n"
class AutoHedgeOutput(BaseModel):
"""
Per-stock output from the AutoHedge pipeline for a single ticker.
Attributes
----------
id : str
Unique run identifier (hex UUID).
thesis : str, optional
Trading thesis for the stock.
risk_assessment : str, optional
Risk assessment text from the risk manager.
order : str, optional
Generated order / execution plan.
decision : str, optional
Director decision (e.g. hold, buy, sell).
timestamp : str
ISO timestamp when this output was produced.
current_stock : str
Ticker symbol this output refers to.
"""
id: str = uuid.uuid4().hex
thesis: Optional[str] = None
risk_assessment: Optional[str] = None
order: Optional[str] = None
decision: str = None
timestamp: str = datetime.now().isoformat()
current_stock: str
class AutoHedgeOutputMain(BaseModel):
"""
Top-level output from a full AutoHedge run (multiple stocks / task).
Attributes
----------
name : str, optional
Run or strategy name.
description : str, optional
Human-readable description of the run.
id : str
Unique run identifier (hex UUID).
stocks : list, optional
List of ticker symbols processed.
task : str, optional
User task or instruction for the run.
timestamp : str
ISO timestamp when the run completed.
logs : list of AutoHedgeOutput, optional
Per-stock results in order of processing.
"""
name: Optional[str] = None
description: Optional[str] = None
id: str = uuid.uuid4().hex
stocks: Optional[list] = None
task: Optional[str] = None
timestamp: str = datetime.now().isoformat()
logs: List[AutoHedgeOutput] = None
class RiskManager:
"""
Agent that assesses risk for a stock given a thesis and quant analysis.
Uses a dedicated Swarms agent (RISK_PROMPT) to produce a text risk
assessment from the trading thesis and quantitative analysis.
Attributes
----------
risk_agent : Agent
Swarms agent used for risk assessment.
"""
def __init__(self):
self.risk_agent = Agent(
agent_name="Risk-Manager",
system_prompt=RISK_PROMPT,
model_name="gpt-4.1",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def assess_risk(
self,
stock: str,
thesis: str,
quant_analysis: str,
task: Optional[str] = None,
) -> str:
"""
Produce a risk assessment for a stock given thesis and quant analysis.
Parameters
----------
stock : str
Ticker symbol.
thesis : str
Trading thesis text.
quant_analysis : str
Quantitative analysis text.
task : str, optional
User task or instruction (included in agent context with current time).
Returns
-------
str
Risk assessment text from the risk agent.
"""
prompt = _agent_context(task) + RISK_ASSESSMENT_PROMPT.format(
stock=stock, thesis=thesis, quant_analysis=quant_analysis
)
assessment = self.risk_agent.run(prompt)
return assessment
class ExecutionAgent:
"""
Agent that generates execution orders from thesis and risk assessment.
Uses a Swarms agent (EXECUTION_PROMPT) to turn a thesis and risk
assessment into a concrete order or execution plan.
Attributes
----------
execution_agent : Agent
Swarms agent used for order generation.
"""
def __init__(self):
self.execution_agent = Agent(
agent_name="Execution-Agent",
system_prompt=EXECUTION_PROMPT,
model_name="gpt-4.1",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def generate_order(
self,
stock: str,
thesis: Dict,
risk_assessment: Dict,
task: Optional[str] = None,
) -> str:
"""
Generate an execution order for a stock from thesis and risk data.
Parameters
----------
stock : str
Ticker symbol.
thesis : dict
Trading thesis (or serialized thesis data).
risk_assessment : dict
Risk assessment (or serialized risk data).
task : str, optional
User task or instruction (included in agent context with current time).
Returns
-------
str
Generated order text from the execution agent.
"""
prompt = _agent_context(task) + EXECUTION_ORDER_PROMPT.format(
stock=stock,
thesis=thesis,
risk_assessment=risk_assessment,
)
order = self.execution_agent.run(prompt)
return order
class TradingDirector:
"""
Coordinates strategy and generates trading theses using market data.
Uses a Swarms director agent and Yahoo Finance data to fetch market data,
then produces a trading thesis and can make follow-up decisions.
Attributes
----------
director_agent : Agent
Swarms agent for thesis generation and decisions.
"""
def __init__(
self,
stocks: List[str],
output_dir: str = "outputs",
cryptos: List[str] = None,
):
"""
Parameters
----------
stocks : list of str
Ticker symbols the director may analyze.
output_dir : str, optional
Directory for outputs (default "outputs").
cryptos : list of str, optional
Crypto symbols for crypto thesis (currently unused).
"""
logger.info("Initializing Trading Director")
self.director_agent = Agent(
agent_name="Trading-Director",
system_prompt=DIRECTOR_PROMPT,
model_name="gpt-4.1",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
# self.crypto_agent = CryptoAgentWrapper()
def generate_thesis(
self,
task: str = "Generate a thesis for the stock",
stock: str = None,
crypto: str = None,
) -> str:
"""
Generate trading thesis for a given stock.
Args:
stock (str): Stock ticker symbol
Returns:
TradingThesis: Generated thesis
"""
logger.info(f"Generating thesis for {stock}")
try:
market_data = get_all_stock_data(
stock, include_history=True
)
prompt = _agent_context(
task
) + DIRECTOR_THESIS_PROMPT.format(
task=task, stock=stock, market_data=market_data
)
thesis = self.director_agent.run(prompt)
return thesis, market_data
except Exception as e:
logger.error(
f"Error generating thesis for {stock}: {str(e)}"
)
raise
def make_decision(
self,
task: str,
thesis: str,
user_task: Optional[str] = None,
*args,
**kwargs,
):
"""
Run the director agent to make a decision given a task and thesis.
Parameters
----------
task : str
Order/context to evaluate (e.g. order + market_data + risk_assessment).
thesis : str
Trading thesis text.
user_task : str, optional
User task or instruction (included in agent context with current time).
*args, **kwargs
Passed through to the agent (e.g. for future options).
Returns
-------
str
Director decision output.
"""
prompt = _agent_context(
user_task
) + DIRECTOR_DECISION_PROMPT.format(thesis=thesis, task=task)
return self.director_agent.run(prompt)
def generate_thesis_crypto(
self,
task: str = None,
crypto: str = None,
):
"""
Generate a trading thesis for a crypto asset using the crypto agent.
Parameters
----------
task : str, optional
Analysis task or instruction.
crypto : str, optional
Crypto symbol (e.g. BTC, ETH).
Returns
-------
str
Generated thesis text.
Raises
------
Exception
If crypto_agent is not set or the run fails.
"""
logger.info(f"Generating thesis for {crypto}")
try:
market_data = self.crypto_agent.run(
crypto,
f"{task} Analyze current market conditions and key metrics for {crypto}",
)
prompt = _agent_context(
task
) + DIRECTOR_THESIS_PROMPT.format(
task=task, stock=crypto, market_data=market_data
)
thesis = self.director_agent.run(prompt)
return thesis
except Exception as e:
logger.error(
f"Error generating thesis for {crypto}: {str(e)}"
)
raise
class QuantAnalyst:
"""
Agent that performs quantitative (technical and statistical) analysis.
Uses a Swarms agent (QUANT_PROMPT) to analyze a stock in the context
of a trading thesis and produce structured quant analysis.
Attributes
----------
quant_agent : Agent
Swarms agent used for analysis.
output_dir : Path
Directory for saving outputs (created on init).
"""
def __init__(self, output_dir: str = "outputs"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
logger.info("Initializing Quant Analyst")
self.quant_agent = Agent(
agent_name="Quant-Analyst",
system_prompt=QUANT_PROMPT,
model_name="gpt-4.1",
output_type="str",
max_loops=1,
verbose=True,
context_length=16000,
)
def analyze(
self, stock: str, thesis: str, task: Optional[str] = None
) -> str:
"""
Perform quantitative analysis for a stock given a trading thesis.
Parameters
----------
stock : str
Stock ticker symbol.
thesis : str
Trading thesis text.
task : str, optional
User task or instruction (included in agent context with current time).
Returns
-------
str
Quantitative analysis text from the quant agent.
"""
logger.info(f"Performing quant analysis for {stock}")
try:
prompt = _agent_context(
task
) + QUANT_ANALYSIS_PROMPT.format(
stock=stock, thesis=thesis
)
analysis = self.quant_agent.run(prompt)
return analysis
except Exception as e:
logger.error(
f"Error in quant analysis for {stock}: {str(e)}"
)
raise
# -----------------------------------------------------------------------------
# Initialized workers and their agents (for discovery / iteration)
# -----------------------------------------------------------------------------
risk_manager = RiskManager()
execution_agent_instance = ExecutionAgent()
trading_director = TradingDirector(stocks=[])
quant_analyst = QuantAnalyst()
ALL_AGENTS = [
sentiment_agent, # Sentiment-Agent
risk_manager.risk_agent, # Risk-Manager
execution_agent_instance.execution_agent, # Execution-Agent
trading_director.director_agent, # Trading-Director
quant_analyst.quant_agent, # Quant-Analyst
]
+5 -1
View File
@@ -21,15 +21,19 @@ classifiers = [
"Programming Language :: Python :: 3.9"
]
[tool.poetry.scripts]
autohedge = "autohedge.cli:main"
[tool.poetry.dependencies]
python = "^3.10"
rich = "*"
swarms = "*"
tickr-agent="*"
pydantic = "*"
loguru = "*"
swarm-models = "*"
httpx = "*"
solders = "*"
yfinance = "*"
[tool.poetry.group.lint.dependencies]
-1
View File
@@ -1,5 +1,4 @@
swarms
tickr-agent
pydantic
loguru
swarm-models