Initial commit of integrated agent system
This commit is contained in:
34
backend/apps/__init__.py
Normal file
34
backend/apps/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Application surfaces for progressive service extraction."""
|
||||
|
||||
from .agent_service import app as agent_app
|
||||
from .agent_service import create_app as create_agent_app
|
||||
from .news_service import app as news_app
|
||||
from .news_service import create_app as create_news_app
|
||||
from .openclaw_service import app as openclaw_app
|
||||
from .openclaw_service import create_app as create_openclaw_app
|
||||
from .runtime_service import app as runtime_app
|
||||
from .runtime_service import create_app as create_runtime_app
|
||||
from .trading_service import app as trading_app
|
||||
from .trading_service import create_app as create_trading_app
|
||||
from .cors import add_cors_middleware, get_cors_origins
|
||||
|
||||
app = agent_app
|
||||
create_app = create_agent_app
|
||||
|
||||
__all__ = [
|
||||
"app",
|
||||
"create_app",
|
||||
"agent_app",
|
||||
"create_agent_app",
|
||||
"news_app",
|
||||
"create_news_app",
|
||||
"openclaw_app",
|
||||
"create_openclaw_app",
|
||||
"runtime_app",
|
||||
"create_runtime_app",
|
||||
"trading_app",
|
||||
"create_trading_app",
|
||||
"add_cors_middleware",
|
||||
"get_cors_origins",
|
||||
]
|
||||
89
backend/apps/agent_service.py
Normal file
89
backend/apps/agent_service.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Agent control-plane FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
|
||||
from backend.api import agents_router, guard_router, workspaces_router
|
||||
from backend.agents import AgentFactory, WorkspaceManager, get_registry
|
||||
|
||||
# Global instances (initialized on startup)
|
||||
agent_factory: AgentFactory | None = None
|
||||
workspace_manager: WorkspaceManager | None = None
|
||||
|
||||
|
||||
def create_app(project_root: Path | None = None) -> FastAPI:
|
||||
"""Create the agent control-plane app."""
|
||||
resolved_project_root = project_root or Path(__file__).resolve().parents[2]
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""Initialize workspace and registry state for the control plane."""
|
||||
global agent_factory, workspace_manager
|
||||
|
||||
workspace_manager = WorkspaceManager(project_root=resolved_project_root)
|
||||
agent_factory = AgentFactory(project_root=resolved_project_root)
|
||||
agent_factory.workspaces_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
registry = get_registry()
|
||||
print("✓ 大时代 API started")
|
||||
print(f" - Workspaces root: {agent_factory.workspaces_root}")
|
||||
print(f" - Registered agents: {registry.get_agent_count()}")
|
||||
|
||||
yield
|
||||
|
||||
print("✓ 大时代 API shutting down")
|
||||
|
||||
app = FastAPI(
|
||||
title="大时代 Agent Service",
|
||||
description="REST API for the 大时代 multi-agent control plane",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
add_cors_middleware(app)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, object]:
|
||||
"""Health check endpoint."""
|
||||
registry = get_registry()
|
||||
return {
|
||||
"status": "healthy",
|
||||
"version": "0.1.0",
|
||||
"agents_registered": registry.get_agent_count(),
|
||||
"workspaces_available": (
|
||||
len(workspace_manager.list_workspaces())
|
||||
if workspace_manager
|
||||
else 0
|
||||
),
|
||||
}
|
||||
|
||||
@app.get("/api/status")
|
||||
async def api_status() -> dict[str, object]:
|
||||
"""Get API status and registry information."""
|
||||
registry = get_registry()
|
||||
return {
|
||||
"status": "operational",
|
||||
"registry": registry.get_stats(),
|
||||
}
|
||||
|
||||
app.include_router(workspaces_router)
|
||||
app.include_router(agents_router)
|
||||
app.include_router(guard_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
30
backend/apps/cors.py
Normal file
30
backend/apps/cors.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Shared CORS configuration for all microservice apps."""
|
||||
|
||||
import os
|
||||
from typing import Sequence
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
|
||||
def get_cors_origins() -> Sequence[str]:
|
||||
"""Get allowed CORS origins from environment variable.
|
||||
|
||||
Defaults to ["*"] for backward compatibility.
|
||||
Set CORS_ALLOWED_ORIGINS env var (comma-separated) in production.
|
||||
"""
|
||||
origins = os.getenv("CORS_ALLOWED_ORIGINS", "").strip()
|
||||
if not origins:
|
||||
return ["*"]
|
||||
return [o.strip() for o in origins.split(",") if o.strip()]
|
||||
|
||||
|
||||
def add_cors_middleware(app: "FastAPI") -> None:
|
||||
"""Add CORS middleware to app with environment-configured origins."""
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=get_cors_origins(),
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
122
backend/apps/frontend_service.py
Normal file
122
backend/apps/frontend_service.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Production frontend service.
|
||||
|
||||
Serves the built frontend static files on port 80 (configurable via
|
||||
FRONTEND_PORT) and proxies API / WebSocket requests to backend services.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import websockets
|
||||
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import FileResponse, Response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"
|
||||
|
||||
AGENT_SERVICE_URL = os.getenv("AGENT_SERVICE_URL", "http://localhost:8000")
|
||||
RUNTIME_SERVICE_URL = os.getenv("RUNTIME_SERVICE_URL", "http://localhost:8003")
|
||||
GATEWAY_WS_URL = os.getenv("GATEWAY_WS_URL", "")
|
||||
|
||||
app = FastAPI(title="EvoTraders Frontend")
|
||||
|
||||
|
||||
async def _resolve_gateway_ws_url() -> str:
|
||||
"""Resolve the Gateway WebSocket URL dynamically from runtime API."""
|
||||
if GATEWAY_WS_URL:
|
||||
return GATEWAY_WS_URL
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
resp = await client.get(f"{RUNTIME_SERVICE_URL}/api/runtime/gateway/port")
|
||||
data = resp.json()
|
||||
if data.get("is_running") and data.get("port"):
|
||||
url = f"ws://localhost:{data['port']}"
|
||||
logger.info(f"[Frontend] Resolved gateway URL: {url}")
|
||||
return url
|
||||
except Exception as e:
|
||||
logger.warning(f"[Frontend] Failed to resolve gateway port: {e}")
|
||||
fallback = f"ws://localhost:{os.getenv('GATEWAY_PORT', '8765')}"
|
||||
logger.info(f"[Frontend] Using fallback gateway URL: {fallback}")
|
||||
return fallback
|
||||
|
||||
|
||||
# ── API reverse proxy ────────────────────────────────────────────────
|
||||
@app.api_route(
|
||||
"/api/{path:path}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||
)
|
||||
async def proxy_api(request: Request, path: str):
|
||||
"""Forward /api/* requests to the appropriate backend service."""
|
||||
target = RUNTIME_SERVICE_URL if path.startswith("runtime/") else AGENT_SERVICE_URL
|
||||
body = await request.body()
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
resp = await client.request(
|
||||
method=request.method,
|
||||
url=f"{target}/api/{path}",
|
||||
content=body,
|
||||
headers={
|
||||
k: v
|
||||
for k, v in request.headers.items()
|
||||
if k.lower() not in ("host", "transfer-encoding")
|
||||
},
|
||||
)
|
||||
return Response(
|
||||
content=resp.content,
|
||||
status_code=resp.status_code,
|
||||
headers=dict(resp.headers),
|
||||
)
|
||||
|
||||
|
||||
# ── WebSocket proxy ──────────────────────────────────────────────────
|
||||
@app.websocket("/ws")
|
||||
async def proxy_ws(ws: WebSocket):
|
||||
"""Proxy WebSocket connections to the Gateway (port resolved dynamically)."""
|
||||
gateway_url = await _resolve_gateway_ws_url()
|
||||
await ws.accept()
|
||||
upstream = None
|
||||
try:
|
||||
upstream = await websockets.asyncio.client.connect(gateway_url)
|
||||
|
||||
async def client_to_upstream():
|
||||
try:
|
||||
while True:
|
||||
data = await ws.receive_text()
|
||||
await upstream.send(data)
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
|
||||
async def upstream_to_client():
|
||||
try:
|
||||
async for msg in upstream:
|
||||
if isinstance(msg, str):
|
||||
await ws.send_text(msg)
|
||||
else:
|
||||
await ws.send_bytes(msg)
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
pass
|
||||
|
||||
await asyncio.gather(client_to_upstream(), upstream_to_client())
|
||||
except Exception as e:
|
||||
logger.warning(f"[Frontend] WebSocket proxy error: {e}")
|
||||
finally:
|
||||
if upstream:
|
||||
await upstream.close()
|
||||
|
||||
|
||||
# ── Static files (SPA fallback) ─────────────────────────────────────
|
||||
if FRONTEND_DIST.is_dir():
|
||||
|
||||
@app.get("/{full_path:path}")
|
||||
async def serve_spa(full_path: str):
|
||||
"""Serve static files; fall back to index.html for SPA routing."""
|
||||
file_path = FRONTEND_DIST / full_path
|
||||
if full_path and file_path.is_file():
|
||||
return FileResponse(file_path)
|
||||
return FileResponse(FRONTEND_DIST / "index.html")
|
||||
|
||||
154
backend/apps/news_service.py
Normal file
154
backend/apps/news_service.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""News and explain FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Depends, FastAPI, Query
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
|
||||
from backend.data.market_store import MarketStore
|
||||
from backend.domains import news as news_domain
|
||||
|
||||
|
||||
def get_market_store() -> MarketStore:
|
||||
"""Get the MarketStore singleton dependency."""
|
||||
return MarketStore.get_instance()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the news/explain service app."""
|
||||
app = FastAPI(
|
||||
title="大时代 News Service",
|
||||
description="Read-only news enrichment and explain service surface extracted from the monolith",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
add_cors_middleware(app)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, str]:
|
||||
return {"status": "healthy", "service": "news-service"}
|
||||
|
||||
@app.get("/api/enriched-news")
|
||||
async def api_get_enriched_news(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str | None = Query(None),
|
||||
end_date: str | None = Query(None),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_enriched_news(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/news-for-date")
|
||||
async def api_get_news_for_date(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
date: str = Query(...),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_for_date(
|
||||
store,
|
||||
ticker=ticker,
|
||||
date=date,
|
||||
limit=limit,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/news-timeline")
|
||||
async def api_get_news_timeline(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_timeline(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/categories")
|
||||
async def api_get_categories(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str | None = Query(None),
|
||||
end_date: str | None = Query(None),
|
||||
limit: int = Query(200, ge=1, le=1000),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_news_categories(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/similar-days")
|
||||
async def api_get_similar_days(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
date: str = Query(...),
|
||||
n_similar: int = Query(5, ge=1, le=20),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_similar_days_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
date=date,
|
||||
n_similar=n_similar,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/stories/{ticker}")
|
||||
async def api_get_story(
|
||||
ticker: str,
|
||||
as_of_date: str = Query(...),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_story_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
as_of_date=as_of_date,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
@app.get("/api/range-explain")
|
||||
async def api_get_range_explain(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
article_ids: list[str] = Query(default=[]),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
store: MarketStore = Depends(get_market_store),
|
||||
) -> dict[str, Any]:
|
||||
return news_domain.get_range_explain_payload(
|
||||
store,
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
article_ids=article_ids,
|
||||
limit=limit,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8002)
|
||||
49
backend/apps/openclaw_service.py
Normal file
49
backend/apps/openclaw_service.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Read-only OpenClaw CLI FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from backend.api import openclaw_router
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
from backend.api.openclaw import get_openclaw_cli_service
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the OpenClaw service app."""
|
||||
app = FastAPI(
|
||||
title="大时代 OpenClaw Service",
|
||||
description="Read-only OpenClaw CLI integration service surface",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
add_cors_middleware(app)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check(
|
||||
service=Depends(get_openclaw_cli_service),
|
||||
) -> dict[str, object]:
|
||||
return service.health()
|
||||
|
||||
@app.get("/api/status")
|
||||
async def api_status(
|
||||
service=Depends(get_openclaw_cli_service),
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"status": "operational",
|
||||
"service": "openclaw-service",
|
||||
"openclaw": service.health(),
|
||||
}
|
||||
|
||||
app.include_router(openclaw_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8004)
|
||||
62
backend/apps/runtime_service.py
Normal file
62
backend/apps/runtime_service.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dedicated runtime service FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from backend.api import runtime_router
|
||||
from backend.api.runtime import get_runtime_state
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the runtime service app."""
|
||||
app = FastAPI(
|
||||
title="大时代 Runtime Service",
|
||||
description="Runtime lifecycle and gateway service surface extracted from the monolith",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
add_cors_middleware(app)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, object]:
|
||||
"""Health check for the runtime service."""
|
||||
runtime_state = get_runtime_state()
|
||||
process = runtime_state.gateway_process
|
||||
is_running = process is not None and process.poll() is None
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "runtime-service",
|
||||
"gateway_running": is_running,
|
||||
"gateway_port": runtime_state.gateway_port,
|
||||
}
|
||||
|
||||
@app.get("/api/status")
|
||||
async def api_status() -> dict[str, object]:
|
||||
"""Service-level status payload for runtime orchestration."""
|
||||
runtime_state = get_runtime_state()
|
||||
process = runtime_state.gateway_process
|
||||
is_running = process is not None and process.poll() is None
|
||||
return {
|
||||
"status": "operational",
|
||||
"service": "runtime-service",
|
||||
"runtime": {
|
||||
"gateway_running": is_running,
|
||||
"gateway_port": runtime_state.gateway_port,
|
||||
"has_runtime_manager": runtime_state.runtime_manager is not None,
|
||||
},
|
||||
}
|
||||
|
||||
app.include_router(runtime_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8003)
|
||||
136
backend/apps/trading_service.py
Normal file
136
backend/apps/trading_service.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Trading data FastAPI surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
from backend.apps.cors import add_cors_middleware
|
||||
|
||||
from backend.domains import trading as trading_domain
|
||||
from shared.schema import (
|
||||
CompanyNewsResponse,
|
||||
FinancialMetricsResponse,
|
||||
InsiderTradeResponse,
|
||||
LineItemResponse,
|
||||
PriceResponse,
|
||||
)
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the trading data service app."""
|
||||
app = FastAPI(
|
||||
title="大时代 Trading Service",
|
||||
description="Read-only trading data service surface extracted from the monolith",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
add_cors_middleware(app)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check() -> dict[str, str]:
|
||||
"""Health check endpoint."""
|
||||
return {"status": "healthy", "service": "trading-service"}
|
||||
|
||||
@app.get("/api/prices", response_model=PriceResponse)
|
||||
async def api_get_prices(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
start_date: str = Query(...),
|
||||
end_date: str = Query(...),
|
||||
) -> PriceResponse:
|
||||
payload = trading_domain.get_prices_payload(
|
||||
ticker=ticker,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
return PriceResponse(ticker=payload["ticker"], prices=payload["prices"])
|
||||
|
||||
@app.get("/api/financials", response_model=FinancialMetricsResponse)
|
||||
async def api_get_financials(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
end_date: str = Query(...),
|
||||
period: str = Query("ttm"),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
) -> FinancialMetricsResponse:
|
||||
payload = trading_domain.get_financials_payload(
|
||||
ticker=ticker,
|
||||
end_date=end_date,
|
||||
period=period,
|
||||
limit=limit,
|
||||
)
|
||||
return FinancialMetricsResponse(financial_metrics=payload["financial_metrics"])
|
||||
|
||||
@app.get("/api/news", response_model=CompanyNewsResponse)
|
||||
async def api_get_news(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
end_date: str = Query(...),
|
||||
start_date: str | None = Query(None),
|
||||
limit: int = Query(1000, ge=1, le=5000),
|
||||
) -> CompanyNewsResponse:
|
||||
payload = trading_domain.get_news_payload(
|
||||
ticker=ticker,
|
||||
end_date=end_date,
|
||||
start_date=start_date,
|
||||
limit=limit,
|
||||
)
|
||||
return CompanyNewsResponse(news=payload["news"])
|
||||
|
||||
@app.get("/api/insider-trades", response_model=InsiderTradeResponse)
|
||||
async def api_get_insider_trades(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
end_date: str = Query(...),
|
||||
start_date: str | None = Query(None),
|
||||
limit: int = Query(1000, ge=1, le=5000),
|
||||
) -> InsiderTradeResponse:
|
||||
payload = trading_domain.get_insider_trades_payload(
|
||||
ticker=ticker,
|
||||
end_date=end_date,
|
||||
start_date=start_date,
|
||||
limit=limit,
|
||||
)
|
||||
return InsiderTradeResponse(insider_trades=payload["insider_trades"])
|
||||
|
||||
@app.get("/api/market/status")
|
||||
async def api_get_market_status() -> dict[str, Any]:
|
||||
"""Return current market status using the existing market service logic."""
|
||||
return trading_domain.get_market_status_payload()
|
||||
|
||||
@app.get("/api/market-cap")
|
||||
async def api_get_market_cap(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
end_date: str = Query(...),
|
||||
) -> dict[str, Any]:
|
||||
"""Return market cap for one ticker/date."""
|
||||
return trading_domain.get_market_cap_payload(
|
||||
ticker=ticker,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
@app.get("/api/line-items", response_model=LineItemResponse)
|
||||
async def api_get_line_items(
|
||||
ticker: str = Query(..., min_length=1),
|
||||
line_items: list[str] = Query(...),
|
||||
end_date: str = Query(...),
|
||||
period: str = Query("ttm"),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
) -> LineItemResponse:
|
||||
payload = trading_domain.get_line_items_payload(
|
||||
ticker=ticker,
|
||||
line_items=line_items,
|
||||
end_date=end_date,
|
||||
period=period,
|
||||
limit=limit,
|
||||
)
|
||||
return LineItemResponse(search_results=payload["search_results"])
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8001)
|
||||
Reference in New Issue
Block a user