146 lines
5.3 KiB
Python
146 lines
5.3 KiB
Python
"""模拟盘 API 路由测试(spec §10)。"""
|
|
import os
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from sanguo_api.app import create_app
|
|
from sanguo_api.auth import create_token, set_jwt_config
|
|
from sanguo_api.routes_paper import set_db_path
|
|
|
|
|
|
def _client(tmp_path):
|
|
set_jwt_config(secret="t", expire_minutes=60)
|
|
db = os.path.join(str(tmp_path), "p.db")
|
|
app = create_app(db_path=db)
|
|
set_db_path(db)
|
|
return TestClient(app), create_token("admin")
|
|
|
|
|
|
def _auth(token):
|
|
return {"Authorization": f"Bearer {token}"}
|
|
|
|
|
|
def test_create_paper(tmp_path):
|
|
c, token = _client(tmp_path)
|
|
resp = c.post(
|
|
"/api/v1/paper/create",
|
|
json={
|
|
"symbols": ["600000"],
|
|
"strategies": [{"name": "DoubleMa", "symbol": "600000",
|
|
"match_session": "next_open"}],
|
|
"start": "2024-01-01", "end": "2024-06-30",
|
|
"initial_capital": 1_000_000,
|
|
},
|
|
headers=_auth(token),
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "account_id" in resp.json()
|
|
|
|
|
|
def test_get_paper_and_empty_trades(tmp_path):
|
|
c, token = _client(tmp_path)
|
|
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"]
|
|
assert c.get(f"/api/v1/paper/{aid}", headers=_auth(token)).status_code == 200
|
|
assert c.get(f"/api/v1/paper/{aid}/trades", headers=_auth(token)).json() == []
|
|
assert c.get(f"/api/v1/paper/{aid}/equity", headers=_auth(token)).json() == []
|
|
|
|
|
|
def test_get_paper_404(tmp_path):
|
|
c, token = _client(tmp_path)
|
|
assert c.get("/api/v1/paper/999", headers=_auth(token)).status_code == 404
|
|
|
|
|
|
def test_unauthorized_401(tmp_path):
|
|
c, _ = _client(tmp_path)
|
|
assert c.get("/api/v1/paper/1").status_code == 401
|
|
|
|
|
|
def test_strategy_summary_aggregation(tmp_path):
|
|
"""C-S2 归因:分策略成交/拒单/费用聚合(spec §7)。"""
|
|
from sanguo_trader.persistence import init_db, save_account, save_trade
|
|
|
|
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"]
|
|
# 造假 2 笔成交 + 1 拒单(s1),1 成交(s2)
|
|
save_trade(db, aid, {"strategy_id": "s1", "symbol": "600000", "price": 10,
|
|
"volume": 100, "commission": 5, "transfer_fee": 0.02})
|
|
save_trade(db, aid, {"strategy_id": "s1", "symbol": "600000", "price": 11,
|
|
"volume": 100, "commission": 5, "stamp_duty": 0.55,
|
|
"transfer_fee": 0.02})
|
|
save_trade(db, aid, {"strategy_id": "s1", "symbol": "300750"},
|
|
rejected=True, reject_reason="limit_up_locked")
|
|
save_trade(db, aid, {"strategy_id": "s2", "symbol": "000001", "price": 15,
|
|
"volume": 100, "commission": 5})
|
|
resp = c.get(f"/api/v1/paper/{aid}/strategies", headers=_auth(token))
|
|
assert resp.status_code == 200
|
|
summary = {s["strategy_id"]: s for s in resp.json()}
|
|
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
|