mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 21:24:16 +08:00
像其他能力一样可路由: 声明 full_minute 数据集并在设置页路由, 即接入 盘中全市场分钟落盘服务 (与 TickFlow Expert 同一能力键 intraday.universe)。 - policy: _DATASET_CAP_MAP 增补 full_minute → INTRADAY_UNIVERSE, 自定义源 声明且被路由时自动补授能力, 服务门控对两类源统一口径 - capabilities 注册表: full_minute 从不可路由 (field=None) 改为 full_minute_data_provider 偏好路由; preferences/settings API 收发新字段 - 插件契约: get_intraday_batch (修复轮, 未实现回退 get_minute 当日窗口) + get_intraday_latest (稳态增量, 可选; 未实现降级仅修复轮, 节奏下限 60s) - YAML 声明式源: full_minute 数据集与 minute 同形, loader 白名单放行, 源编辑器 UI 可配置; 仅修复轮语义 - kline_sync 边界: fetch_intraday_custom_batch / _fetch_intraday_custom_latest, 帧过北京墙钟守卫, 与 TickFlow 边界函数同纪律 - minute_refresh: 删「自定义分钟源让位」逻辑, 改按路由取数; status 增 provider / provider_effective / repair_only - 前端: 监控页卡片按能力驱动, 数据源页路由矩阵, api.ts 类型 - 测试: 门控/路由/降级/增广新增用例, 后端 1332 全过 - 文档: plugin-development / custom-data-source / configuration / features / CONTRIBUTING 契约细化 (AI 可照文档接入)
143 lines
6.0 KiB
Python
143 lines
6.0 KiB
Python
"""能力标准统一: 自定义/插件数据源能力增广回归测试。
|
|
|
|
对应 _augment_custom_sources 的数据集→能力映射 (daily/adj_factor/minute/financial/full_minute):
|
|
某数据集的当前 provider 非 tickflow 且声明了该数据集 → grant 对应能力;
|
|
取数路由仍按 preferences 分流, 不会误调 TickFlow。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from app.tickflow.capabilities import Cap, CapabilityLimits, CapabilitySet
|
|
from app.tickflow.policy import _augment_custom_sources
|
|
|
|
|
|
def _set_providers(monkeypatch, *, daily="tickflow", adj="tickflow",
|
|
minute="tickflow", financial="tickflow", full_minute="tickflow") -> None:
|
|
"""mock preferences 各数据集 provider getter。"""
|
|
from app.services import preferences
|
|
monkeypatch.setattr(preferences, "get_daily_data_provider", lambda: daily)
|
|
monkeypatch.setattr(preferences, "get_adj_factor_provider", lambda: adj)
|
|
monkeypatch.setattr(preferences, "get_minute_data_provider", lambda: minute)
|
|
monkeypatch.setattr(preferences, "get_financial_provider", lambda: financial)
|
|
monkeypatch.setattr(preferences, "get_full_minute_data_provider", lambda: full_minute)
|
|
|
|
|
|
def _set_datasets(monkeypatch, datasets: set[str]) -> None:
|
|
"""mock provider_has_dataset: 非 tickflow provider 对给定数据集返回 True。"""
|
|
monkeypatch.setattr(
|
|
"app.data_providers.custom.provider_has_dataset",
|
|
lambda name, ds: name != "tickflow" and ds in datasets,
|
|
)
|
|
|
|
|
|
def test_daily_custom_source_grants_daily_batch(monkeypatch):
|
|
_set_providers(monkeypatch, daily="mock_src")
|
|
_set_datasets(monkeypatch, {"daily"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert capset.has(Cap.KLINE_DAILY_BATCH)
|
|
# 未声明其他数据集 → 不补
|
|
assert not capset.has(Cap.ADJ_FACTOR)
|
|
assert not capset.has(Cap.KLINE_MINUTE_BATCH)
|
|
assert not capset.has(Cap.FINANCIAL)
|
|
|
|
|
|
def test_adj_custom_source_grants_adj_factor(monkeypatch):
|
|
"""adj 显式路由到声明除权的自定义源 → 补授能力 (跟随日K已下线, 独立判定)。"""
|
|
_set_providers(monkeypatch, adj="mock_src")
|
|
_set_datasets(monkeypatch, {"adj_factor"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert capset.has(Cap.ADJ_FACTOR)
|
|
|
|
|
|
def test_full_minute_custom_source_grants_intraday_universe(monkeypatch):
|
|
"""全量分钟: 声明 full_minute 数据集且被路由 → 补授 INTRADAY_UNIVERSE,
|
|
minute_refresh 服务门控与 TickFlow Expert 口径统一。"""
|
|
_set_providers(monkeypatch, full_minute="mock_src")
|
|
_set_datasets(monkeypatch, {"full_minute"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert capset.has(Cap.INTRADAY_UNIVERSE)
|
|
# 声明了 full_minute 不等于声明 minute → 不补逐标的分钟K能力
|
|
assert not capset.has(Cap.KLINE_MINUTE_BATCH)
|
|
|
|
|
|
def test_full_minute_dataset_without_routing_not_granted(monkeypatch):
|
|
"""源声明了 full_minute 但路由仍是 tickflow → 不增广 (TickFlow 档位自决)。"""
|
|
_set_providers(monkeypatch, full_minute="tickflow")
|
|
_set_datasets(monkeypatch, {"full_minute"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert not capset.has(Cap.INTRADAY_UNIVERSE)
|
|
|
|
|
|
def test_minute_custom_source_grants_minute_batch(monkeypatch):
|
|
"""原有 minute 增广行为保持。"""
|
|
_set_providers(monkeypatch, minute="mock_src")
|
|
_set_datasets(monkeypatch, {"minute"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert capset.has(Cap.KLINE_MINUTE_BATCH)
|
|
|
|
|
|
def test_financial_custom_source_grants_financial(monkeypatch):
|
|
_set_providers(monkeypatch, financial="mock_src")
|
|
_set_datasets(monkeypatch, {"financial"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert capset.has(Cap.FINANCIAL)
|
|
|
|
|
|
def test_provider_active_but_dataset_not_declared_no_grant(monkeypatch):
|
|
"""provider 被选为当前源但未声明该数据集 → 不 grant (回退 TickFlow 语义)。"""
|
|
_set_providers(monkeypatch, daily="mock_src", minute="mock_src",
|
|
adj="mock_src", financial="mock_src")
|
|
_set_datasets(monkeypatch, set()) # 什么都不声明
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert not capset.has(Cap.KLINE_DAILY_BATCH)
|
|
assert not capset.has(Cap.ADJ_FACTOR)
|
|
assert not capset.has(Cap.KLINE_MINUTE_BATCH)
|
|
assert not capset.has(Cap.FINANCIAL)
|
|
|
|
|
|
def test_tickflow_active_no_grant(monkeypatch):
|
|
"""全部数据集仍走 tickflow → 不补任何能力。"""
|
|
_set_providers(monkeypatch) # 默认全 tickflow
|
|
_set_datasets(monkeypatch, {"daily", "adj_factor", "minute", "financial"})
|
|
capset = CapabilitySet()
|
|
_augment_custom_sources(capset)
|
|
assert not capset.has(Cap.KLINE_DAILY_BATCH)
|
|
assert not capset.has(Cap.ADJ_FACTOR)
|
|
assert not capset.has(Cap.KLINE_MINUTE_BATCH)
|
|
assert not capset.has(Cap.FINANCIAL)
|
|
|
|
|
|
def test_grant_does_not_override_tickflow_limits(monkeypatch):
|
|
"""grant 不覆盖 TickFlow 已有能力及其限制。"""
|
|
_set_providers(monkeypatch, minute="mock_src")
|
|
_set_datasets(monkeypatch, {"minute"})
|
|
capset = CapabilitySet()
|
|
capset.grant(Cap.KLINE_MINUTE_BATCH, CapabilityLimits(rpm=30, batch=100))
|
|
_augment_custom_sources(capset)
|
|
lim = capset.limits(Cap.KLINE_MINUTE_BATCH)
|
|
assert lim is not None and lim.rpm == 30 and lim.batch == 100
|
|
|
|
|
|
def test_update_data_providers_refreshes_capability_snapshot(monkeypatch):
|
|
"""切换数据源后 app.state.capabilities 快照应刷新 (读缓存+增广, 无网络)。"""
|
|
from app.api import settings as settings_api
|
|
|
|
monkeypatch.setattr("app.services.preferences.save", lambda upd: None)
|
|
sentinel = CapabilitySet()
|
|
monkeypatch.setattr(settings_api, "detect_capabilities", lambda: sentinel)
|
|
|
|
mock_request = MagicMock()
|
|
settings_api.update_data_providers(
|
|
MagicMock(model_dump=lambda exclude_none: {"daily_data_provider": "mock_src"}),
|
|
mock_request,
|
|
)
|
|
assert mock_request.app.state.capabilities is sentinel
|