From 252deb5ec79bb46c5729090c08f25bb2a3bcb794 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Thu, 9 Jul 2026 22:33:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20paper=20positions/pending=20?= =?UTF-8?q?=E7=AB=AF=E7=82=B9(=E5=AE=9E=E8=B5=B0=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E7=94=A8,Phase=203c=20Live=E9=A1=B5=E5=90=8E=E7=AB=AF=E5=9F=BA?= =?UTF-8?q?=E7=A1=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_api/routes_paper.py | 21 ++++++++++++++ tests/api/test_paper_routes.py | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/sanguo_api/routes_paper.py b/sanguo_api/routes_paper.py index 6a9f810..c3d73df 100644 --- a/sanguo_api/routes_paper.py +++ b/sanguo_api/routes_paper.py @@ -115,6 +115,27 @@ def get_strategies(aid: int): return list_strategy_summary(_db_path["path"], aid) +@router.get("/paper/{aid}/positions", dependencies=[Depends(verify_token)]) +def get_positions(aid: int): + """当前持仓快照(实走监控,Phase 3c)。{symbol:{volume,frozen,avg_price}} → list。""" + from sanguo_trader.persistence import load_positions + + pos = load_positions(_db_path["path"], aid, "account") + return [ + {"symbol": sym, "volume": p["volume"], "frozen": p.get("frozen", 0), + "avg_price": p["avg_price"]} + for sym, p in pos.items() + ] + + +@router.get("/paper/{aid}/pending", dependencies=[Depends(verify_token)]) +def get_pending(aid: int): + """跨日 pending 订单(实走监控,Phase 3c)。""" + from sanguo_trader.persistence import load_pending_orders + + return load_pending_orders(_db_path["path"], aid) + + class _DataSourceWrapper: """包装 iter_bars/fetch_day 给 PaperEngine/live_orchestrator。""" diff --git a/tests/api/test_paper_routes.py b/tests/api/test_paper_routes.py index ef3091c..4ebfdee 100644 --- a/tests/api/test_paper_routes.py +++ b/tests/api/test_paper_routes.py @@ -90,3 +90,56 @@ def test_strategy_summary_aggregation(tmp_path): assert summary["s1"]["filled"] == 2 assert summary["s1"]["rejected"] == 1 assert summary["s2"]["filled"] == 1 + + +def test_positions_endpoint(tmp_path): + """Phase 3c:当前持仓快照({symbol:{volume,frozen,avg_price}} → list)。""" + from sanguo_trader.persistence import save_positions + + c, token = _client(tmp_path) + db = os.path.join(str(tmp_path), "p.db") + aid = c.post( + "/api/v1/paper/create", + json={"symbols": ["600000"], + "strategies": [{"name": "S", "symbol": "600000"}], + "start": "2024-01-01", "end": "2024-06-30"}, + headers=_auth(token), + ).json()["account_id"] + save_positions(db, aid, "account", + {"600000": {"volume": 100, "frozen": 0, "avg_price": 10.5}}, + "2024-01-15") + resp = c.get(f"/api/v1/paper/{aid}/positions", headers=_auth(token)) + assert resp.status_code == 200 + data = resp.json() + assert len(data) == 1 + assert data[0]["symbol"] == "600000" + assert data[0]["volume"] == 100 + assert data[0]["frozen"] == 0 + assert data[0]["avg_price"] == 10.5 + + +def test_pending_endpoint(tmp_path): + """Phase 3c:跨日 pending 订单(C-S3)。""" + from sanguo_trader.persistence import save_pending_orders + + c, token = _client(tmp_path) + db = os.path.join(str(tmp_path), "p.db") + aid = c.post( + "/api/v1/paper/create", + json={"symbols": ["600000"], + "strategies": [{"name": "S", "symbol": "600000"}], + "start": "2024-01-01", "end": "2024-06-30"}, + headers=_auth(token), + ).json()["account_id"] + save_pending_orders(db, aid, [ + {"strategy_id": "s1", "symbol": "600000", "side": "buy", + "price": 10.0, "volume": 100, "is_market": True, + "match_session": "next_open", "listing_days": 0}, + ]) + resp = c.get(f"/api/v1/paper/{aid}/pending", headers=_auth(token)) + assert resp.status_code == 200 + data = resp.json() + assert len(data) == 1 + assert data[0]["symbol"] == "600000" + assert data[0]["side"] == "buy" + assert data[0]["is_market"] is True