117 lines
4.8 KiB
Python
117 lines
4.8 KiB
Python
"""/factor/eval/* 端点:leaderboard 扁平化/tiles/排序/详情/提交校验."""
|
|
import sys, os
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from sanguo_factor import eval_store
|
|
from sanguo_api import routes_factor
|
|
from sanguo_api.app import create_app
|
|
from sanguo_api.auth import hash_password
|
|
|
|
|
|
@pytest.fixture()
|
|
def eval_db(tmp_path):
|
|
db = str(tmp_path / "factor_eval.db")
|
|
eval_store.init_db(db)
|
|
routes_factor.set_eval_db_path(db)
|
|
return db
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path):
|
|
db_path = str(tmp_path / "test.db")
|
|
app = create_app(
|
|
db_path=db_path,
|
|
auth_config={
|
|
"username": "admin",
|
|
"password_hash": hash_password("admin"),
|
|
"jwt_secret": "test-secret",
|
|
"expire_minutes": 60,
|
|
},
|
|
max_workers=1,
|
|
)
|
|
# Set eval db path
|
|
eval_db_path = str(tmp_path / "factor_eval.db")
|
|
eval_store.init_db(eval_db_path)
|
|
from sanguo_orchestrator.runner import resolve_eval_db
|
|
routes_factor.set_eval_db_path(resolve_eval_db(db_path))
|
|
return TestClient(app)
|
|
|
|
|
|
@pytest.fixture()
|
|
def token(client):
|
|
return client.post("/api/v1/auth/login", json={"username": "admin", "password": "admin"}).json()["token"]
|
|
|
|
|
|
def _seed(db):
|
|
run_id = eval_store.create_run(db, label="冒烟", universe="custom", symbols_count=50,
|
|
factors_total=3, start="2024-01-01", end="2024-12-31", params={})
|
|
eval_store.save_results(db, run_id, [
|
|
{"factor": "alpha2", "category": "alpha101", "expression": "e2",
|
|
"metrics": {"turnover": 0.2, "1": {"ic_mean": 0.05, "icir": 0.4, "t_stat": 3.0,
|
|
"win_rate": 0.6, "ls_annual": 0.12, "conclusion": "effective",
|
|
"monthly_ic": [{"month": "2024-01", "ic": 0.05}]},
|
|
"5": {}, "10": {}}},
|
|
{"factor": "kmid", "category": "alpha158", "expression": "ek",
|
|
"metrics": {"turnover": 0.5, "1": {"ic_mean": -0.01, "icir": -0.05, "t_stat": -0.4,
|
|
"win_rate": 0.45, "ls_annual": -0.02, "conclusion": "eliminated",
|
|
"monthly_ic": []}, "5": {}, "10": {}}},
|
|
{"factor": "alpha6", "category": "alpha101", "expression": "e6",
|
|
"metrics": {"turnover": 0.3, "1": {"ic_mean": 0.03, "icir": 0.2, "t_stat": 1.7,
|
|
"win_rate": 0.55, "ls_annual": 0.06, "conclusion": "watch",
|
|
"monthly_ic": []}, "5": {}, "10": {}}},
|
|
])
|
|
eval_store.finish_run(db, run_id, "done", 3)
|
|
return run_id
|
|
|
|
|
|
def test_leaderboard_default_latest_run(client, token, eval_db):
|
|
_seed(eval_db)
|
|
r = client.get("/api/v1/factor/eval/leaderboard", headers={"Authorization": f"Bearer {token}"})
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["tiles"]["factors_total"] == 3
|
|
assert body["tiles"]["effective"] == 1
|
|
assert body["tiles"]["watch"] == 1
|
|
assert body["tiles"]["top_ls_factor"] == "alpha2"
|
|
# 默认 abs_icir desc: alpha2(0.4) > alpha6(0.2) > kmid(0.05)
|
|
assert [x["factor"] for x in body["rows"]] == ["alpha2", "alpha6", "kmid"]
|
|
row = body["rows"][0]
|
|
for key in ("factor", "category", "expression", "ic_mean", "icir", "t_stat",
|
|
"win_rate", "ls_annual", "turnover", "conclusion", "monthly_ic"):
|
|
assert key in row
|
|
|
|
|
|
def test_leaderboard_filter_and_period(client, token, eval_db):
|
|
_seed(eval_db)
|
|
r = client.get("/api/v1/factor/eval/leaderboard", params={"category": "alpha158"}, headers={"Authorization": f"Bearer {token}"})
|
|
assert [x["factor"] for x in r.json()["rows"]] == ["kmid"]
|
|
r = client.get("/api/v1/factor/eval/leaderboard", params={"search": "alpha6"}, headers={"Authorization": f"Bearer {token}"})
|
|
assert [x["factor"] for x in r.json()["rows"]] == ["alpha6"]
|
|
|
|
|
|
def test_detail(client, token, eval_db):
|
|
_seed(eval_db)
|
|
r = client.get("/api/v1/factor/eval/detail", params={"factor": "kmid"}, headers={"Authorization": f"Bearer {token}"})
|
|
assert r.status_code == 200
|
|
assert r.json()["category"] == "alpha158"
|
|
assert "1" in r.json()["metrics"]
|
|
assert client.get("/api/v1/factor/eval/detail", params={"factor": "nope"}, headers={"Authorization": f"Bearer {token}"}).status_code == 404
|
|
|
|
|
|
def test_runs_list(client, token, eval_db):
|
|
_seed(eval_db)
|
|
r = client.get("/api/v1/factor/eval/runs", headers={"Authorization": f"Bearer {token}"})
|
|
assert len(r.json()["runs"]) == 1
|
|
|
|
|
|
def test_submit_validation_error(client, token):
|
|
# categories 与 factors 双空 → 422/400
|
|
r = client.post("/api/v1/factor/eval/submit",
|
|
json={"start": "2018-01-01", "end": "2026-06-30"},
|
|
headers={"Authorization": f"Bearer {token}"})
|
|
assert r.status_code in (400, 422)
|