Files
worldquant-alpha-system/backend/app/research/provenance.py
T

71 lines
2.7 KiB
Python
Raw Normal View History

"""Read provenance from saved results, retaining every experiment for an Alpha."""
from collections import defaultdict
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy import func, select
from ..models import Alpha, BacktestItem, BacktestResult, BacktestRun
def saved_sources():
"""Only persisted results establish provenance; pending platform IDs do not."""
return (
select(BacktestResult, BacktestItem, BacktestRun)
.select_from(BacktestResult)
.join(BacktestItem, BacktestResult.item_id == BacktestItem.id)
.join(BacktestRun, BacktestItem.run_id == BacktestRun.id)
)
def source_alpha_ids(source=None, source_reference=None, research_id=None, backtest_run_id=None):
"""An IN subquery keeps list counts and exports independent of source multiplicity."""
query = saved_sources().with_only_columns(BacktestResult.alpha_id)
for key, value in (("kind", source), ("reference", source_reference), ("research_id", research_id)):
if value:
query = query.where(BacktestRun.source[key].as_string() == value)
if backtest_run_id:
query = query.where(BacktestRun.id == backtest_run_id)
return query
async def source_kinds(db, alpha_ids=None):
query = saved_sources().with_only_columns(BacktestResult.alpha_id, BacktestRun.source["kind"].as_string())
if alpha_ids is not None:
query = query.where(BacktestResult.alpha_id.in_(alpha_ids))
values = defaultdict(list)
for alpha_id, kind in await db.execute(query.distinct()):
if kind:
values[alpha_id].append(kind)
return {alpha_id: sorted(kinds) for alpha_id, kinds in values.items()}
async def alpha_sources(db, alpha_id, limit=25, offset=0):
if not await db.get(Alpha, alpha_id):
raise HTTPException(404, "Alpha 尚未同步")
query = saved_sources().where(BacktestResult.alpha_id == alpha_id)
total = await db.scalar(select(func.count()).select_from(query.subquery()))
rows = await db.execute(
query.order_by(BacktestResult.observed_at.desc(), BacktestResult.item_id).limit(limit).offset(offset)
)
return jsonable_encoder(
{
"alpha_id": alpha_id,
"total": total,
"limit": limit,
"offset": offset,
"items": [
{
"backtest_run_id": run.id,
"name": run.name,
"source": run.source,
"item_id": item.id,
"client_item_id": item.client_item_id,
"observed_at": result.observed_at,
}
for result, item, run in rows
],
}
)