Files
qlib/backend/app/domain/repositories/market.py
T
Simon 195f5d41f4 perf(backend): 内存优化三项——全市场研究不再占满 8G
1) 数据装配流式+列裁剪:Repository 新增 stream_range_many_columns(只 SELECT
   所需列、SQL 侧转 REAL、yield_per 分批),引擎按 required_columns 取数
   (LocalEngine 仅 close+因子字段),消除 ORM/Decimal 全量物化;
2) 研究 Job 独立子进程执行(job.mode=subprocess):python -m app.cli.run_job
   在子进程内设 RLIMIT_AS 上限,OOM 归档 failed 而非拖垮 API worker;
   子进程异常退出由父进程补记 failed;并发上限 2;
3) 服务启动清理:残留 queued/running Job 标记 failed(防永久 running)。

实测同款全市场回测:uvicorn worker RSS 稳定 ~220MB,任务峰值内存由 4.1GB+
降至 ~470MB,24s 完成并归档(此前 43s 未完成即 OOM)。
新增/更新测试 96 passed,ruff 干净。
2026-09-06 22:12:44 +08:00

88 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Repository Protocol(Phase 1 数据层)。
业务层只依赖这些 Protocol;具体实现位于 infrastructure/persistence。
实体一律以 domain.entities 类型进出,禁止把 ORM Model 泄漏到上层。
"""
from __future__ import annotations
from collections.abc import Iterator, Sequence
from datetime import date
from typing import Protocol
from app.domain.entities.market import (
AdjustFactor,
DailyBar,
FinancialIndicator,
Stock,
SyncLog,
TradingCalendar,
)
class StockRepository(Protocol):
def get_by_symbol(self, symbol: str) -> Stock | None: ...
def list(self) -> list[Stock]: ...
def upsert_many(self, stocks: Sequence[Stock]) -> int:
"""批量写入,以 symbol 为幂等键,返回写入/更新的行数。"""
class TradingCalendarRepository(Protocol):
def upsert_many(self, days: Sequence[TradingCalendar]) -> int: ...
def list_range(self, start: date, end: date) -> list[TradingCalendar]: ...
def is_open(self, day: date) -> bool: ...
class DailyBarRepository(Protocol):
def upsert_many(self, bars: Sequence[DailyBar]) -> int: ...
def get_range(self, symbol: str, start: date, end: date) -> list[DailyBar]: ...
def get_range_many(self, symbols: Sequence[str], start: date, end: date) -> list[DailyBar]:
"""批量区间查询(研究服务装配面板用,避免逐只查询)。"""
def stream_range_many_columns(
self,
symbols: Sequence[str],
start: date,
end: date,
columns: Sequence[str],
) -> Iterator[tuple]:
"""流式(分批 yield)返回 symbol, trade_date(iso str), 数值列(float) 元组。
研究装配大数据面板专用:只 SELECT 所需列并在 SQL 侧转 REAL,
避免 ORM 对象 / Decimal 全量物化(内存大头,见内存优化专项)。
实现可选——ResearchService 会对缺失该方法的老实现回退到 get_range_many。
"""
def latest_date(self, symbol: str) -> date | None:
"""断点续传用:该股票本地已有数据的最新交易日。"""
class AdjustFactorRepository(Protocol):
def upsert_many(self, factors: Sequence[AdjustFactor]) -> int: ...
def get_range(self, symbol: str, start: date, end: date) -> list[AdjustFactor]: ...
class FinancialRepository(Protocol):
def upsert_many(self, rows: Sequence[FinancialIndicator]) -> int: ...
def list_announced(
self,
symbol: str,
as_of_date: date,
report_start: date | None = None,
) -> list[FinancialIndicator]:
"""只返回 announce_date <= as_of_date 的记录 —— 未来函数红线。"""
class SyncLogRepository(Protocol):
def add(self, log: SyncLog) -> SyncLog: ...
def recent(self, source: str | None = None, limit: int = 20) -> list[SyncLog]: ...