feat: Refactor services architecture and update project structure
- Remove Docker-based microservices (docker-compose.yml, Makefile, Dockerfiles) - Update start-dev.sh to use backend.app:app entry point - Add shared schema and client modules for service communication - Add team coordination modules (messenger, registry, task_delegator, coordinator) - Add evaluation hooks and skill adaptation hooks - Add skill template and gateway server - Update frontend WebSocket URL configuration - Add explain components for insider and technical analysis Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
shared/client/__init__.py
Normal file
12
shared/client/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Shared client package."""
|
||||
|
||||
from shared.client.trading_client import TradingServiceClient
|
||||
from shared.client.news_client import NewsServiceClient
|
||||
from shared.client.agent_client import AgentServiceClient
|
||||
|
||||
__all__ = [
|
||||
"TradingServiceClient",
|
||||
"NewsServiceClient",
|
||||
"AgentServiceClient",
|
||||
]
|
||||
211
shared/client/agent_client.py
Normal file
211
shared/client/agent_client.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Agent service client for agent orchestration and runtime operations."""
|
||||
|
||||
import json
|
||||
from typing import Any, AsyncIterator
|
||||
|
||||
import httpx
|
||||
import websockets
|
||||
|
||||
from shared.schema.signals import AgentStateData
|
||||
|
||||
|
||||
class AgentServiceClient:
|
||||
"""Async client for the Agent Service API."""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8000"):
|
||||
"""Initialize the client with a base URL.
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the agent service API.
|
||||
"""
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
|
||||
async def __aenter__(self) -> "AgentServiceClient":
|
||||
self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
|
||||
async def get_agents(self) -> dict:
|
||||
"""Get list of all registered agents.
|
||||
|
||||
Returns:
|
||||
Dictionary with agent list.
|
||||
"""
|
||||
response = await self._client.get("/api/agents")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_agent_status(self, agent_id: str) -> dict:
|
||||
"""Get status of a specific agent.
|
||||
|
||||
Args:
|
||||
agent_id: The agent identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with agent status.
|
||||
"""
|
||||
response = await self._client.get(f"/api/agents/{agent_id}/status")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def post_run_daily(
|
||||
self,
|
||||
tickers: list[str],
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
runtime_config: dict[str, Any] | None = None,
|
||||
) -> dict:
|
||||
"""Trigger a daily analysis run.
|
||||
|
||||
Args:
|
||||
tickers: List of stock tickers to analyze.
|
||||
start_date: Start date (YYYY-MM-DD).
|
||||
end_date: End date (YYYY-MM-DD).
|
||||
runtime_config: Optional runtime configuration.
|
||||
|
||||
Returns:
|
||||
Dictionary with run initiation response.
|
||||
"""
|
||||
payload = {
|
||||
"tickers": tickers,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
}
|
||||
if runtime_config:
|
||||
payload["runtime_config"] = runtime_config
|
||||
response = await self._client.post("/api/run/daily", json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_run_status(self, run_id: str) -> dict:
|
||||
"""Get status of a run.
|
||||
|
||||
Args:
|
||||
run_id: The run identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with run status.
|
||||
"""
|
||||
response = await self._client.get(f"/api/runs/{run_id}/status")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_run_result(self, run_id: str) -> AgentStateData:
|
||||
"""Get the result of a completed run.
|
||||
|
||||
Args:
|
||||
run_id: The run identifier.
|
||||
|
||||
Returns:
|
||||
AgentStateData with run results.
|
||||
"""
|
||||
response = await self._client.get(f"/api/runs/{run_id}/result")
|
||||
response.raise_for_status()
|
||||
return AgentStateData.model_validate(response.json())
|
||||
|
||||
async def get_run_logs(self, run_id: str) -> dict:
|
||||
"""Get logs for a run.
|
||||
|
||||
Args:
|
||||
run_id: The run identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with run logs.
|
||||
"""
|
||||
response = await self._client.get(f"/api/runs/{run_id}/logs")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def cancel_run(self, run_id: str) -> dict:
|
||||
"""Cancel a running task.
|
||||
|
||||
Args:
|
||||
run_id: The run identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with cancellation confirmation.
|
||||
"""
|
||||
response = await self._client.post(f"/api/runs/{run_id}/cancel")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_runtime_config(self) -> dict:
|
||||
"""Get current runtime configuration.
|
||||
|
||||
Returns:
|
||||
Dictionary with runtime config.
|
||||
"""
|
||||
response = await self._client.get("/api/runtime/config")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def update_runtime_config(self, config: dict[str, Any]) -> dict:
|
||||
"""Update runtime configuration.
|
||||
|
||||
Args:
|
||||
config: New runtime configuration.
|
||||
|
||||
Returns:
|
||||
Dictionary with updated config.
|
||||
"""
|
||||
response = await self._client.put("/api/runtime/config", json=config)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def websocket_connect(
|
||||
self,
|
||||
run_id: str | None = None,
|
||||
) -> AsyncIterator[dict]:
|
||||
"""Connect to WebSocket for real-time updates.
|
||||
|
||||
Args:
|
||||
run_id: Optional run ID to subscribe to.
|
||||
|
||||
Yields:
|
||||
Dictionary with WebSocket messages.
|
||||
"""
|
||||
ws_url = self.base_url.replace("http", "ws") + "/ws"
|
||||
if run_id:
|
||||
ws_url += f"?run_id={run_id}"
|
||||
|
||||
async with websockets.connect(ws_url) as ws:
|
||||
async for message in ws:
|
||||
yield json.loads(message)
|
||||
|
||||
async def get_pipeline_status(self) -> dict:
|
||||
"""Get current pipeline execution status.
|
||||
|
||||
Returns:
|
||||
Dictionary with pipeline status.
|
||||
"""
|
||||
response = await self._client.get("/api/pipeline/status")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def trigger_pipeline(
|
||||
self,
|
||||
pipeline_type: str,
|
||||
tickers: list[str],
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> dict:
|
||||
"""Trigger a pipeline execution.
|
||||
|
||||
Args:
|
||||
pipeline_type: Type of pipeline to run.
|
||||
tickers: List of tickers to process.
|
||||
config: Optional pipeline configuration.
|
||||
|
||||
Returns:
|
||||
Dictionary with pipeline trigger response.
|
||||
"""
|
||||
payload = {"pipeline_type": pipeline_type, "tickers": tickers}
|
||||
if config:
|
||||
payload["config"] = config
|
||||
response = await self._client.post("/api/pipeline/trigger", json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
130
shared/client/news_client.py
Normal file
130
shared/client/news_client.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""News service client for news enrichment operations."""
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class NewsServiceClient:
|
||||
"""Async client for the News Service API."""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8002"):
|
||||
"""Initialize the client with a base URL.
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the news service API.
|
||||
"""
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
|
||||
async def __aenter__(self) -> "NewsServiceClient":
|
||||
self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
|
||||
async def get_enriched_news(
|
||||
self,
|
||||
ticker: str,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
) -> dict:
|
||||
"""Get enriched news for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
start_date: Start date (YYYY-MM-DD).
|
||||
end_date: End date (YYYY-MM-DD).
|
||||
|
||||
Returns:
|
||||
Dictionary with enriched news data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
if end_date:
|
||||
params["end_date"] = end_date
|
||||
response = await self._client.get("/api/enriched-news", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_similar_days(
|
||||
self,
|
||||
ticker: str,
|
||||
date: str,
|
||||
n_similar: int = 5,
|
||||
) -> dict:
|
||||
"""Get similar trading days based on price patterns.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
date: Reference date (YYYY-MM-DD).
|
||||
n_similar: Number of similar days to return.
|
||||
|
||||
Returns:
|
||||
Dictionary with similar day data.
|
||||
"""
|
||||
params = {"ticker": ticker, "date": date, "n_similar": n_similar}
|
||||
response = await self._client.get("/api/similar-days", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_story(self, story_id: str) -> dict:
|
||||
"""Get a specific news story by ID.
|
||||
|
||||
Args:
|
||||
story_id: The story identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with story data.
|
||||
"""
|
||||
response = await self._client.get(f"/api/stories/{story_id}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def post_enrich(self, news_items: list[dict]) -> dict:
|
||||
"""Enrich news items with additional analysis.
|
||||
|
||||
Args:
|
||||
news_items: List of news items to enrich.
|
||||
|
||||
Returns:
|
||||
Dictionary with enriched news data.
|
||||
"""
|
||||
response = await self._client.post("/api/enrich", json=news_items)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_categories(self) -> dict:
|
||||
"""Get available news categories.
|
||||
|
||||
Returns:
|
||||
Dictionary with available categories.
|
||||
"""
|
||||
response = await self._client.get("/api/categories")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def search_news(
|
||||
self,
|
||||
query: str,
|
||||
ticker: str | None = None,
|
||||
limit: int = 10,
|
||||
) -> dict:
|
||||
"""Search news articles.
|
||||
|
||||
Args:
|
||||
query: Search query string.
|
||||
ticker: Optional ticker to filter by.
|
||||
limit: Maximum number of results.
|
||||
|
||||
Returns:
|
||||
Dictionary with search results.
|
||||
"""
|
||||
params = {"query": query, "limit": limit}
|
||||
if ticker:
|
||||
params["ticker"] = ticker
|
||||
response = await self._client.get("/api/search", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
207
shared/client/trading_client.py
Normal file
207
shared/client/trading_client.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Trading service client for market data operations."""
|
||||
|
||||
import httpx
|
||||
|
||||
from shared.schema.price import PriceResponse
|
||||
from shared.schema.financial import FinancialMetricsResponse, LineItemResponse
|
||||
from shared.schema.market import InsiderTradeResponse, CompanyFactsResponse
|
||||
from shared.schema.portfolio import Portfolio
|
||||
|
||||
|
||||
class TradingServiceClient:
|
||||
"""Async client for the Trading Service API."""
|
||||
|
||||
def __init__(self, base_url: str = "http://localhost:8001"):
|
||||
"""Initialize the client with a base URL.
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the trading service API.
|
||||
"""
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
|
||||
async def __aenter__(self) -> "TradingServiceClient":
|
||||
self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
if self._client:
|
||||
await self._client.aclose()
|
||||
|
||||
async def get_prices(
|
||||
self,
|
||||
ticker: str,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
) -> PriceResponse:
|
||||
"""Get price data for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
start_date: Start date (YYYY-MM-DD).
|
||||
end_date: End date (YYYY-MM-DD).
|
||||
|
||||
Returns:
|
||||
PriceResponse with price data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
if end_date:
|
||||
params["end_date"] = end_date
|
||||
response = await self._client.get("/api/prices", params=params)
|
||||
response.raise_for_status()
|
||||
return PriceResponse.model_validate(response.json())
|
||||
|
||||
async def get_news(
|
||||
self,
|
||||
ticker: str,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
) -> dict:
|
||||
"""Get news for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
start_date: Start date (YYYY-MM-DD).
|
||||
end_date: End date (YYYY-MM-DD).
|
||||
|
||||
Returns:
|
||||
Dictionary with news data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
if end_date:
|
||||
params["end_date"] = end_date
|
||||
response = await self._client.get("/api/news", params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_financials(
|
||||
self,
|
||||
ticker: str,
|
||||
period: str | None = None,
|
||||
limit: int | None = None,
|
||||
) -> FinancialMetricsResponse:
|
||||
"""Get financial metrics for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
period: Reporting period (e.g., "annual", "quarterly").
|
||||
limit: Maximum number of records to return.
|
||||
|
||||
Returns:
|
||||
FinancialMetricsResponse with financial data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if period:
|
||||
params["period"] = period
|
||||
if limit:
|
||||
params["limit"] = limit
|
||||
response = await self._client.get("/api/financials", params=params)
|
||||
response.raise_for_status()
|
||||
return FinancialMetricsResponse.model_validate(response.json())
|
||||
|
||||
async def get_insider_trades(
|
||||
self,
|
||||
ticker: str,
|
||||
limit: int | None = None,
|
||||
) -> InsiderTradeResponse:
|
||||
"""Get insider trades for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
limit: Maximum number of records to return.
|
||||
|
||||
Returns:
|
||||
InsiderTradeResponse with insider trade data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if limit:
|
||||
params["limit"] = limit
|
||||
response = await self._client.get("/api/insider-trades", params=params)
|
||||
response.raise_for_status()
|
||||
return InsiderTradeResponse.model_validate(response.json())
|
||||
|
||||
async def get_portfolio(self) -> Portfolio:
|
||||
"""Get the current portfolio.
|
||||
|
||||
Returns:
|
||||
Portfolio with current positions and cash.
|
||||
"""
|
||||
response = await self._client.get("/api/portfolio")
|
||||
response.raise_for_status()
|
||||
return Portfolio.model_validate(response.json())
|
||||
|
||||
async def post_trades(self, trades: list[dict]) -> dict:
|
||||
"""Submit trades for execution.
|
||||
|
||||
Args:
|
||||
trades: List of trade orders.
|
||||
|
||||
Returns:
|
||||
Dictionary with trade execution results.
|
||||
"""
|
||||
response = await self._client.post("/api/trades", json=trades)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def post_settle(self) -> dict:
|
||||
"""Settle all pending trades.
|
||||
|
||||
Returns:
|
||||
Dictionary with settlement results.
|
||||
"""
|
||||
response = await self._client.post("/api/settle")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_market_status(self) -> dict:
|
||||
"""Get current market status.
|
||||
|
||||
Returns:
|
||||
Dictionary with market status information.
|
||||
"""
|
||||
response = await self._client.get("/api/market/status")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def get_company_facts(self, ticker: str) -> CompanyFactsResponse:
|
||||
"""Get company facts for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
|
||||
Returns:
|
||||
CompanyFactsResponse with company information.
|
||||
"""
|
||||
response = await self._client.get(f"/api/company/{ticker}/facts")
|
||||
response.raise_for_status()
|
||||
return CompanyFactsResponse.model_validate(response.json())
|
||||
|
||||
async def get_line_items(
|
||||
self,
|
||||
ticker: str,
|
||||
statement_type: str | None = None,
|
||||
period: str | None = None,
|
||||
) -> LineItemResponse:
|
||||
"""Get line items (financial statement data) for a ticker.
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker symbol.
|
||||
statement_type: Type of statement (income, balance, cash_flow).
|
||||
period: Reporting period.
|
||||
|
||||
Returns:
|
||||
LineItemResponse with financial statement data.
|
||||
"""
|
||||
params = {"ticker": ticker}
|
||||
if statement_type:
|
||||
params["statement_type"] = statement_type
|
||||
if period:
|
||||
params["period"] = period
|
||||
response = await self._client.get("/api/line-items", params=params)
|
||||
response.raise_for_status()
|
||||
return LineItemResponse.model_validate(response.json())
|
||||
51
shared/schema/__init__.py
Normal file
51
shared/schema/__init__.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Shared schema package for EvoTraders services."""
|
||||
|
||||
from shared.schema.price import Price, PriceResponse
|
||||
from shared.schema.financial import (
|
||||
FinancialMetrics,
|
||||
FinancialMetricsResponse,
|
||||
LineItem,
|
||||
LineItemResponse,
|
||||
)
|
||||
from shared.schema.portfolio import Position, Portfolio
|
||||
from shared.schema.signals import (
|
||||
AnalystSignal,
|
||||
TickerAnalysis,
|
||||
AgentStateData,
|
||||
AgentStateMetadata,
|
||||
)
|
||||
from shared.schema.market import (
|
||||
InsiderTrade,
|
||||
InsiderTradeResponse,
|
||||
CompanyNews,
|
||||
CompanyNewsResponse,
|
||||
CompanyFacts,
|
||||
CompanyFactsResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Price
|
||||
"Price",
|
||||
"PriceResponse",
|
||||
# Financial
|
||||
"FinancialMetrics",
|
||||
"FinancialMetricsResponse",
|
||||
"LineItem",
|
||||
"LineItemResponse",
|
||||
# Portfolio
|
||||
"Position",
|
||||
"Portfolio",
|
||||
# Signals
|
||||
"AnalystSignal",
|
||||
"TickerAnalysis",
|
||||
"AgentStateData",
|
||||
"AgentStateMetadata",
|
||||
# Market
|
||||
"InsiderTrade",
|
||||
"InsiderTradeResponse",
|
||||
"CompanyNews",
|
||||
"CompanyNewsResponse",
|
||||
"CompanyFacts",
|
||||
"CompanyFactsResponse",
|
||||
]
|
||||
68
shared/schema/financial.py
Normal file
68
shared/schema/financial.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Financial-related schemas."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class FinancialMetrics(BaseModel):
|
||||
ticker: str
|
||||
report_period: str
|
||||
period: str
|
||||
currency: str
|
||||
market_cap: float | None
|
||||
enterprise_value: float | None
|
||||
price_to_earnings_ratio: float | None
|
||||
price_to_book_ratio: float | None
|
||||
price_to_sales_ratio: float | None
|
||||
enterprise_value_to_ebitda_ratio: float | None
|
||||
enterprise_value_to_revenue_ratio: float | None
|
||||
free_cash_flow_yield: float | None
|
||||
peg_ratio: float | None
|
||||
gross_margin: float | None
|
||||
operating_margin: float | None
|
||||
net_margin: float | None
|
||||
return_on_equity: float | None
|
||||
return_on_assets: float | None
|
||||
return_on_invested_capital: float | None
|
||||
asset_turnover: float | None
|
||||
inventory_turnover: float | None
|
||||
receivables_turnover: float | None
|
||||
days_sales_outstanding: float | None
|
||||
operating_cycle: float | None
|
||||
working_capital_turnover: float | None
|
||||
current_ratio: float | None
|
||||
quick_ratio: float | None
|
||||
cash_ratio: float | None
|
||||
operating_cash_flow_ratio: float | None
|
||||
debt_to_equity: float | None
|
||||
debt_to_assets: float | None
|
||||
interest_coverage: float | None
|
||||
revenue_growth: float | None
|
||||
earnings_growth: float | None
|
||||
book_value_growth: float | None
|
||||
earnings_per_share_growth: float | None
|
||||
free_cash_flow_growth: float | None
|
||||
operating_income_growth: float | None
|
||||
ebitda_growth: float | None
|
||||
payout_ratio: float | None
|
||||
earnings_per_share: float | None
|
||||
book_value_per_share: float | None
|
||||
free_cash_flow_per_share: float | None
|
||||
|
||||
|
||||
class FinancialMetricsResponse(BaseModel):
|
||||
financial_metrics: list[FinancialMetrics]
|
||||
|
||||
|
||||
class LineItem(BaseModel):
|
||||
ticker: str
|
||||
report_period: str
|
||||
period: str
|
||||
currency: str
|
||||
|
||||
# Allow additional fields dynamically
|
||||
model_config = {"extra": "allow"}
|
||||
|
||||
|
||||
class LineItemResponse(BaseModel):
|
||||
search_results: list[LineItem]
|
||||
64
shared/schema/market.py
Normal file
64
shared/schema/market.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Market data-related schemas."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class InsiderTrade(BaseModel):
|
||||
ticker: str
|
||||
issuer: str | None = None
|
||||
name: str | None = None
|
||||
title: str | None = None
|
||||
is_board_director: bool | None = None
|
||||
transaction_date: str | None = None
|
||||
transaction_shares: float | None = None
|
||||
transaction_price_per_share: float | None = None
|
||||
transaction_value: float | None = None
|
||||
shares_owned_before_transaction: float | None = None
|
||||
shares_owned_after_transaction: float | None = None
|
||||
security_title: str | None = None
|
||||
filing_date: str
|
||||
|
||||
|
||||
class InsiderTradeResponse(BaseModel):
|
||||
insider_trades: list[InsiderTrade]
|
||||
|
||||
|
||||
class CompanyNews(BaseModel):
|
||||
category: str | None = None
|
||||
ticker: str
|
||||
title: str
|
||||
related: str | None = None
|
||||
source: str
|
||||
date: str | None = None
|
||||
url: str
|
||||
summary: str | None = None
|
||||
|
||||
|
||||
class CompanyNewsResponse(BaseModel):
|
||||
news: list[CompanyNews]
|
||||
|
||||
|
||||
class CompanyFacts(BaseModel):
|
||||
ticker: str
|
||||
name: str
|
||||
cik: str | None = None
|
||||
industry: str | None = None
|
||||
sector: str | None = None
|
||||
category: str | None = None
|
||||
exchange: str | None = None
|
||||
is_active: bool | None = None
|
||||
listing_date: str | None = None
|
||||
location: str | None = None
|
||||
market_cap: float | None = None
|
||||
number_of_employees: int | None = None
|
||||
sec_filings_url: str | None = None
|
||||
sic_code: str | None = None
|
||||
sic_industry: str | None = None
|
||||
sic_sector: str | None = None
|
||||
website_url: str | None = None
|
||||
weighted_average_shares: int | None = None
|
||||
|
||||
|
||||
class CompanyFactsResponse(BaseModel):
|
||||
company_facts: CompanyFacts
|
||||
23
shared/schema/portfolio.py
Normal file
23
shared/schema/portfolio.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Portfolio-related schemas."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Position(BaseModel):
|
||||
"""Position information - for Portfolio mode"""
|
||||
|
||||
long: int = 0 # Long position quantity (shares)
|
||||
short: int = 0 # Short position quantity (shares)
|
||||
long_cost_basis: float = 0.0 # Long position average cost
|
||||
short_cost_basis: float = 0.0 # Short position average cost
|
||||
|
||||
|
||||
class Portfolio(BaseModel):
|
||||
"""Portfolio - for Portfolio mode"""
|
||||
|
||||
cash: float = 100000.0 # Available cash
|
||||
positions: dict[str, Position] = {} # ticker -> Position mapping
|
||||
# Margin requirement (0.0 means shorting disabled, 0.5 means 50% margin)
|
||||
margin_requirement: float = 0.0
|
||||
margin_used: float = 0.0 # Margin used
|
||||
18
shared/schema/price.py
Normal file
18
shared/schema/price.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Price-related schemas."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Price(BaseModel):
|
||||
open: float
|
||||
close: float
|
||||
high: float
|
||||
low: float
|
||||
volume: int
|
||||
time: str
|
||||
|
||||
|
||||
class PriceResponse(BaseModel):
|
||||
ticker: str
|
||||
prices: list[Price]
|
||||
41
shared/schema/signals.py
Normal file
41
shared/schema/signals.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Signal and analysis-related schemas."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from shared.schema.portfolio import Portfolio
|
||||
|
||||
|
||||
class AnalystSignal(BaseModel):
|
||||
signal: str | None = None
|
||||
confidence: float | None = None
|
||||
reasoning: dict | str | None = None
|
||||
# Extended fields for richer signal information
|
||||
reasons: list[str] | None = None # Core drivers/reasons for the signal
|
||||
risks: list[str] | None = None # Key risk factors
|
||||
invalidation: str | None = None # Conditions that would invalidate the thesis
|
||||
next_action: str | None = None # Suggested next action for PM
|
||||
# Valuation-related fields
|
||||
intrinsic_value: float | None = None # DCF intrinsic value
|
||||
fair_value_range: dict | None = None # {bear, base, bull} fair value range
|
||||
value_gap_pct: float | None = None # Value gap percentage
|
||||
valuation_methods: list[str] | None = None # List of valuation methods used
|
||||
max_position_size: float | None = None # For risk management signals
|
||||
|
||||
|
||||
class TickerAnalysis(BaseModel):
|
||||
ticker: str
|
||||
analyst_signals: dict[str, AnalystSignal] # agent_name -> signal mapping
|
||||
|
||||
|
||||
class AgentStateData(BaseModel):
|
||||
tickers: list[str]
|
||||
portfolio: Portfolio
|
||||
start_date: str
|
||||
end_date: str
|
||||
ticker_analyses: dict[str, TickerAnalysis] # ticker -> analysis mapping
|
||||
|
||||
|
||||
class AgentStateMetadata(BaseModel):
|
||||
show_reasoning: bool = False
|
||||
model_config = {"extra": "allow"}
|
||||
Reference in New Issue
Block a user