feat: backfill missing PnL for submitted alphas
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Submitted PnL backfill covers the local library and preserves existing caches."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.alphas import upsert_alpha
|
||||
from app.models import Pnl
|
||||
from app.worldquant import WqError
|
||||
from tests.conftest import alpha
|
||||
from tests.test_jobs import ready_runner, result
|
||||
|
||||
URL = "/api/v1/sync-jobs"
|
||||
|
||||
|
||||
async def start(client):
|
||||
response = await client.post(URL, json={"kind": "pnl_backfill"})
|
||||
assert response.status_code == 202
|
||||
return response.json()
|
||||
|
||||
|
||||
async def test_backfill_all_submitted_beyond_page_limit_and_deduplicates(app, logged_in):
|
||||
runner = await ready_runner(app)
|
||||
async with runner.sessions() as db:
|
||||
for i in range(105):
|
||||
await upsert_alpha(db, alpha(
|
||||
f"ref{i:03}", status="DECOMMISSIONED" if i % 2 else "ACTIVE",
|
||||
hidden=bool(i % 2), settings={"region": "EUR" if i % 2 else "USA"},
|
||||
))
|
||||
for raw in [alpha("pending"), alpha("unknown", status=None), alpha("cached", status="ACTIVE")]:
|
||||
await upsert_alpha(db, raw)
|
||||
db.add(Pnl(alpha_id="cached", raw={"original": True}, points=[]))
|
||||
await db.commit()
|
||||
first = await start(logged_in)
|
||||
assert first["total"] == 105
|
||||
assert first["payload"]["alpha_ids"] == [f"ref{i:03}" for i in range(105)]
|
||||
# Another task can populate an item after this snapshot was fixed.
|
||||
async with runner.sessions() as db:
|
||||
db.add(Pnl(alpha_id="ref000", raw={"original": True}, points=[]))
|
||||
await db.commit()
|
||||
assert (await start(logged_in))["id"] == first["id"]
|
||||
calls = []
|
||||
original_pnl = runner.client.pnl
|
||||
|
||||
async def pnl(alpha_id):
|
||||
calls.append(alpha_id)
|
||||
return await original_pnl(alpha_id)
|
||||
|
||||
runner.client.pnl = pnl
|
||||
await runner.execute(first["id"])
|
||||
finished = await result(runner, first["id"])
|
||||
assert (finished.status, finished.processed, finished.failed) == ("completed", 105, 0)
|
||||
assert calls == [f"ref{i:03}" for i in range(1, 105)]
|
||||
async with runner.sessions() as db:
|
||||
assert await db.scalar(select(func.count()).select_from(Pnl)) == 106
|
||||
assert (await db.get(Pnl, "cached")).raw == {"original": True}
|
||||
assert (await db.get(Pnl, "ref000")).raw == {"original": True}
|
||||
empty = await start(logged_in)
|
||||
assert empty["status"] == "completed" and empty["total"] == 0
|
||||
assert empty["payload"]["alpha_ids"] == []
|
||||
|
||||
|
||||
async def test_backfill_keeps_progress_and_retries_only_unfinished_items(app, logged_in):
|
||||
runner = await ready_runner(app)
|
||||
async with runner.sessions() as db:
|
||||
for name in ("a", "b", "c"):
|
||||
await upsert_alpha(db, alpha(name, status="ACTIVE"))
|
||||
await db.commit()
|
||||
calls = []
|
||||
blocked = True
|
||||
original_pnl = runner.client.pnl
|
||||
|
||||
async def pnl(alpha_id):
|
||||
calls.append(alpha_id)
|
||||
if blocked and alpha_id == "b":
|
||||
raise WqError("无权访问", "access_denied")
|
||||
if blocked and alpha_id == "c":
|
||||
raise WqError("平台数据仍在准备,请稍后重试", "pending")
|
||||
return await original_pnl(alpha_id)
|
||||
|
||||
runner.client.pnl = pnl
|
||||
job = await start(logged_in)
|
||||
await runner.execute(job["id"])
|
||||
failed = await result(runner, job["id"])
|
||||
assert (failed.status, failed.processed, failed.failed) == ("failed", 1, 1)
|
||||
assert failed.checkpoint["alpha_id"] == "c"
|
||||
async with runner.sessions() as db:
|
||||
assert await db.get(Pnl, "a") is not None
|
||||
assert await db.get(Pnl, "b") is None
|
||||
assert await db.get(Pnl, "c") is None
|
||||
blocked = False
|
||||
assert (await logged_in.post(f"{URL}/{job['id']}/retry")).status_code == 200
|
||||
await runner.execute(job["id"])
|
||||
finished = await result(runner, job["id"])
|
||||
assert (finished.status, finished.processed, finished.failed) == ("completed", 3, 0)
|
||||
assert calls == ["a", "b", "c", "b", "c"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("extra", [
|
||||
{"alpha_ids": ["a"]}, {"submission": "UNSUBMITTED"}, {"date_from": "2025-01-01"},
|
||||
])
|
||||
async def test_backfill_rejects_client_scope(logged_in, extra):
|
||||
response = await logged_in.post(URL, json={"kind": "pnl_backfill", **extra})
|
||||
assert response.status_code == 422
|
||||
Reference in New Issue
Block a user