rebuild(stage-5): establish market data gateway and charts
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from backend.data.gateway import DataGateway, MarketDataUnavailable
|
||||
from backend.data.providers.base import ProviderError
|
||||
from backend.data.quality import DataQualityError
|
||||
from backend.http.errors import AppError
|
||||
|
||||
|
||||
class MarketService:
|
||||
def __init__(self, gateway: DataGateway) -> None:
|
||||
self._gateway = gateway
|
||||
|
||||
def context(self, requested_date: str | None = None) -> dict[str, Any]:
|
||||
context = self._call(self._gateway.trade_context, requested_date)
|
||||
return _context(context)
|
||||
|
||||
def summary(self, requested_date: str | None = None) -> dict[str, Any]:
|
||||
result = self._call(self._gateway.summary, requested_date)
|
||||
return {"context": _context(result["context"]), "values": result["values"]}
|
||||
|
||||
def search(self, query: str) -> dict[str, Any]:
|
||||
normalized = " ".join(query.split())
|
||||
items = self._gateway.search(normalized) if normalized else ()
|
||||
labels = {"stock": "股票", "sector": "板块", "theme": "题材", "index": "指数"}
|
||||
groups = []
|
||||
for entity_type in ("stock", "sector", "theme", "index"):
|
||||
groups.append(
|
||||
{
|
||||
"entity_type": entity_type,
|
||||
"label": labels[entity_type],
|
||||
"items": [
|
||||
{
|
||||
"entity_type": item.entity_type,
|
||||
"identifier": item.identifier,
|
||||
"code": item.code,
|
||||
"name": item.name,
|
||||
"sector": item.sector,
|
||||
}
|
||||
for item in items
|
||||
if item.entity_type == entity_type
|
||||
],
|
||||
}
|
||||
)
|
||||
return {"query": normalized, "groups": groups}
|
||||
|
||||
def chart(self, entity_type: str, identifier: str, interval: str) -> dict[str, Any]:
|
||||
series = self._call(self._gateway.chart, entity_type, identifier, interval)
|
||||
return {
|
||||
"entity_type": series.entity.entity_type,
|
||||
"identifier": series.entity.identifier,
|
||||
"code": series.entity.code,
|
||||
"name": series.entity.name,
|
||||
"interval": series.interval,
|
||||
"trade_date": series.trade_date,
|
||||
"observed_at": series.metadata.observed_at,
|
||||
"previous_close": series.previous_close,
|
||||
"range_start": "09:30" if interval == "minute" else None,
|
||||
"range_end": "15:00" if interval == "minute" else None,
|
||||
"points": [
|
||||
{
|
||||
"time": point.time,
|
||||
"open": point.open,
|
||||
"high": point.high,
|
||||
"low": point.low,
|
||||
"close": point.close,
|
||||
"volume": point.volume,
|
||||
"amount": point.amount,
|
||||
"average": point.average,
|
||||
}
|
||||
for point in series.points
|
||||
],
|
||||
}
|
||||
|
||||
def refresh_reference(self) -> dict[str, int | str]:
|
||||
return self._call(self._gateway.refresh_reference)
|
||||
|
||||
@staticmethod
|
||||
def _call(function, *args):
|
||||
try:
|
||||
return function(*args)
|
||||
except (MarketDataUnavailable, ProviderError, DataQualityError) as exc:
|
||||
raise AppError("market_data_unavailable", str(exc), 503) from exc
|
||||
|
||||
|
||||
def _context(context) -> dict[str, Any]:
|
||||
return {
|
||||
"requested_date": context.requested_date,
|
||||
"actual_date": context.actual_date,
|
||||
"previous_date": context.previous_date,
|
||||
"observed_at": context.observed_at,
|
||||
"state": context.state.value if context.state else None,
|
||||
"carried_forward": context.carried_forward,
|
||||
"message": context.message,
|
||||
}
|
||||
Reference in New Issue
Block a user