fix(HEL-494): 修复个股缺失指标、问天遮罩、四爻外显并回补250日K
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
c8a9376adb
commit
3e828b346c
@@ -491,24 +491,96 @@ class Pipeline:
|
||||
)
|
||||
return sorted(str(row["cal_date"]) for row in rows)
|
||||
|
||||
def backfill_daily_history(
|
||||
self,
|
||||
end_date: str | None = None,
|
||||
trading_days: int | None = None,
|
||||
force: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Incrementally publish official daily bars for the website K-line window.
|
||||
|
||||
Same-day EOD still uses the atomic A-group. History backfill publishes
|
||||
daily (with adj_factor) first so K-line coverage cannot be blocked by
|
||||
the looser moneyflow universe, then valuation and moneyflow/auction
|
||||
best-effort. Complete daily dates are skipped unless ``force``.
|
||||
"""
|
||||
end = yyyymmdd(end_date or self.clock())
|
||||
limit = int(trading_days or self.settings.daily_history_trading_days)
|
||||
open_dates = self.open_trade_dates(end, limit)
|
||||
if not open_dates:
|
||||
return {
|
||||
"start": None,
|
||||
"end": end,
|
||||
"requested_days": 0,
|
||||
"published": [],
|
||||
"skipped": [],
|
||||
"failed": [{"error": "calendar has no open dates on or before end"}],
|
||||
"ok": False,
|
||||
}
|
||||
start = open_dates[0]
|
||||
published: list[dict[str, Any]] = []
|
||||
skipped: list[str] = []
|
||||
failed: list[dict[str, Any]] = []
|
||||
for day in open_dates:
|
||||
if not force and self.active_batch("daily", day):
|
||||
skipped.append(day)
|
||||
continue
|
||||
try:
|
||||
daily = self.run_dataset("daily", day)
|
||||
datasets = {"daily": daily.get("state")}
|
||||
try:
|
||||
valuation = self.run_dataset("valuation", day)
|
||||
datasets["valuation"] = valuation.get("state")
|
||||
except Exception as exc:
|
||||
datasets["valuation"] = f"failed:{exc}"[:180]
|
||||
for name in ("moneyflow", "auction"):
|
||||
try:
|
||||
extra = self.run_dataset(name, day)
|
||||
datasets[name] = extra.get("state")
|
||||
except Exception as exc:
|
||||
datasets[name] = f"failed:{exc}"[:180]
|
||||
published.append({"trade_date": day, "datasets": datasets})
|
||||
except Exception as exc:
|
||||
failed.append({"trade_date": day, "error": str(exc)})
|
||||
return {
|
||||
"start": start,
|
||||
"end": end,
|
||||
"requested_days": len(open_dates),
|
||||
"published": published,
|
||||
"skipped": skipped,
|
||||
"failed": failed,
|
||||
"ok": not failed,
|
||||
}
|
||||
|
||||
def backfill_history(
|
||||
self,
|
||||
trade_date: str | None = None,
|
||||
calendar_start: str | None = None,
|
||||
index_days: int | None = None,
|
||||
daily_days: int | None = None,
|
||||
codes: tuple[str, ...] | None = None,
|
||||
force: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Idempotent calendar + website-index history backfill."""
|
||||
"""Idempotent calendar + stock daily + website-index history backfill."""
|
||||
day = yyyymmdd(trade_date or self.clock())
|
||||
calendar = self.ingest_reference(day, start=calendar_start)
|
||||
daily = self.backfill_daily_history(
|
||||
end_date=day,
|
||||
trading_days=daily_days,
|
||||
force=force,
|
||||
)
|
||||
index = self.backfill_index_history(
|
||||
end_date=day,
|
||||
trading_days=index_days,
|
||||
codes=codes,
|
||||
force=force,
|
||||
)
|
||||
return {"calendar": calendar, "index_daily": index, "ok": bool(index.get("ok"))}
|
||||
return {
|
||||
"calendar": calendar,
|
||||
"daily": daily,
|
||||
"index_daily": index,
|
||||
"ok": bool(daily.get("ok")) and bool(index.get("ok")),
|
||||
}
|
||||
|
||||
def backfill_index_history(
|
||||
self,
|
||||
@@ -803,10 +875,7 @@ class Pipeline:
|
||||
"published_rows": len(published),
|
||||
"upstream_rows": 0,
|
||||
}
|
||||
listed = self.db.fetchone(
|
||||
"SELECT COUNT(*) AS n FROM stock_master WHERE list_status = 'L'",
|
||||
)
|
||||
listed_n = int((listed or {}).get("n") or 0)
|
||||
listed_n = self._listed_count(day)
|
||||
floor = float(self.settings.quality.get("daily_row_ratio") or 0.98)
|
||||
if listed_n and len(upstream) / listed_n < floor:
|
||||
return {
|
||||
@@ -1314,14 +1383,33 @@ class Pipeline:
|
||||
if isinstance(item, dict) and item.get("state") == "failed"
|
||||
]
|
||||
|
||||
def _listed_count(self, trade_date: str = "") -> int:
|
||||
"""Count listed names that already existed on ``trade_date``.
|
||||
|
||||
Historical daily bars must not be judged against later IPOs, or a
|
||||
correct past session fails the 0.98 row-ratio gate.
|
||||
"""
|
||||
day = yyyymmdd(trade_date) if trade_date else ""
|
||||
if day:
|
||||
listed = self.db.fetchone(
|
||||
"""
|
||||
SELECT COUNT(*) AS n FROM stock_master
|
||||
WHERE list_status = 'L'
|
||||
AND (list_date IS NULL OR TRIM(list_date) = '' OR list_date <= ?)
|
||||
""",
|
||||
(day,),
|
||||
)
|
||||
else:
|
||||
listed = self.db.fetchone(
|
||||
"SELECT COUNT(*) AS n FROM stock_master WHERE list_status = 'L'"
|
||||
)
|
||||
return int((listed or {}).get("n") or 0)
|
||||
|
||||
def validate(self, dataset: str, batch_id: str, trade_date: str, rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
quality = self.settings.quality
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
listed = self.db.fetchone(
|
||||
"SELECT COUNT(*) AS n FROM stock_master WHERE list_status = 'L'",
|
||||
)
|
||||
listed_n = int((listed or {}).get("n") or 0)
|
||||
listed_n = self._listed_count(trade_date)
|
||||
row_n = len(rows)
|
||||
if dataset == "limit_events":
|
||||
keys = [(row.get("ts_code"), row.get("trade_date"), row.get("limit_type")) for row in rows]
|
||||
|
||||
Reference in New Issue
Block a user