mirror of
https://github.com/The-Swarm-Corporation/AutoHedge.git
synced 2026-10-02 05:24:49 +08:00
[New tools] [Jupiter, polygon, massive] [Remove ticker agent]
This commit is contained in:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Run AutoHedge CLI with: python -m autohedge"""
|
||||
|
||||
from autohedge.cli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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
@@ -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,5 +1,4 @@
|
||||
swarms
|
||||
tickr-agent
|
||||
pydantic
|
||||
loguru
|
||||
swarm-models
|
||||
|
||||
Reference in New Issue
Block a user