Files
xiaobaifupan/next/backend/features/market/service.py
T

149 lines
5.7 KiB
Python

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.features.market.insights import MarketInsightService
from backend.features.market.insights.service import MarketInsightError
from backend.features.market.sync import MarketSnapshotService, SnapshotSyncError
from backend.http.errors import AppError
class MarketService:
def __init__(
self,
gateway: DataGateway,
snapshots: MarketSnapshotService,
insights: MarketInsightService,
) -> None:
self._gateway = gateway
self._snapshots = snapshots
self._insights = insights
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)
def sync_snapshot(self, requested_date: str | None = None) -> dict[str, Any]:
return self._call(self._snapshots.sync, requested_date)
def workspace(self, key: str, requested_date: str | None = None) -> dict[str, Any]:
return self._call(self._snapshots.workspace, key, requested_date)
def rotation_members(
self, sector_name: str, requested_date: str | None = None
) -> dict[str, Any]:
trade_date, representative = self._call(
self._snapshots.rotation_member_target, requested_date, sector_name
)
return self._call(
self._gateway.sector_members, trade_date, sector_name, representative
)
def insight(
self, key: str, requested_date: str | None, user_id: int
) -> dict[str, Any]:
return self._call(self._insights.workspace, key, requested_date, user_id=user_id)
def sync_insight(
self, key: str, requested_date: str | None, user_id: int
) -> dict[str, Any]:
return self._call(
self._insights.workspace, key, requested_date, user_id=user_id, force=True
)
def theme_detail(self, identifier: str, requested_date: str | None = None) -> dict[str, Any]:
return self._call(self._insights.theme_detail, identifier, requested_date)
def save_seat_alias(
self, seat_name: str, alias_name: str, user_id: int
) -> dict[str, str]:
return self._call(self._insights.save_seat_alias, seat_name, alias_name, user_id)
@staticmethod
def _call(function, *args, **kwargs):
try:
return function(*args, **kwargs)
except (
MarketDataUnavailable,
ProviderError,
DataQualityError,
SnapshotSyncError,
MarketInsightError,
) 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,
}