Add delete for closed trade records.
Only closed groups can be removed from local history; equity is unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""删除单条已平仓交易记录。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.api.trades import delete_group
|
||||
from app.models.db import Database
|
||||
|
||||
|
||||
def _seed_closed(db: Database, gid: str = "G-DEL-1") -> None:
|
||||
with db._lock:
|
||||
db._conn.execute(
|
||||
"""INSERT INTO groups(group_id, status, open_at_ms, close_at_ms, realized_pnl)
|
||||
VALUES (?, 'closed', 1, 2, 1.5)""",
|
||||
(gid,),
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth,
|
||||
fill_px, fee, slip, notional, ts_ms)
|
||||
VALUES (?, 'option', 'open', 'buy', 'ETH-C', 1, 10, 0.1, 0, 10, 1)""",
|
||||
(gid,),
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO residual_options(
|
||||
group_id, option_inst_id, option_side, option_qty_eth,
|
||||
option_entry_px, status, created_at_ms
|
||||
) VALUES (?, 'ETH-C', 'call', 1, 10, 'pending', 1)""",
|
||||
(gid,),
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO ledger_entries(group_id, kind, amount, balance_after, note, ts_ms)
|
||||
VALUES (?, 'pnl', 1.5, 10001.5, 't', 2)""",
|
||||
(gid,),
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
|
||||
def test_delete_closed_group(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
db = Database(tmp_path / "del.db")
|
||||
monkeypatch.setattr("app.api.trades.get_db", lambda: db)
|
||||
_seed_closed(db)
|
||||
|
||||
out = asyncio.run(delete_group("G-DEL-1", _user="t"))
|
||||
assert out["ok"] is True
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM groups")["c"] == 0
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM fills")["c"] == 0
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM residual_options")["c"] == 0
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM ledger_entries")["c"] == 0
|
||||
db.close()
|
||||
|
||||
|
||||
def test_delete_open_group_refused(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
db = Database(tmp_path / "del_open.db")
|
||||
monkeypatch.setattr("app.api.trades.get_db", lambda: db)
|
||||
with db._lock:
|
||||
db._conn.execute(
|
||||
"""INSERT INTO groups(group_id, status, open_at_ms)
|
||||
VALUES ('G-OPEN', 'open', 1)"""
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
with pytest.raises(HTTPException) as ei:
|
||||
asyncio.run(delete_group("G-OPEN", _user="t"))
|
||||
assert ei.value.status_code == 409
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM groups")["c"] == 1
|
||||
db.close()
|
||||
|
||||
|
||||
def test_delete_active_position_refused(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
db = Database(tmp_path / "del_pos.db")
|
||||
monkeypatch.setattr("app.api.trades.get_db", lambda: db)
|
||||
_seed_closed(db, "G-POS")
|
||||
with db._lock:
|
||||
# status closed but still referenced (edge)
|
||||
db._conn.execute(
|
||||
"UPDATE positions SET group_id=?, status='open' WHERE id=1",
|
||||
("G-POS",),
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
with pytest.raises(HTTPException) as ei:
|
||||
asyncio.run(delete_group("G-POS", _user="t"))
|
||||
assert ei.value.status_code == 409
|
||||
assert db.fetchone("SELECT COUNT(*) AS c FROM groups")["c"] == 1
|
||||
db.close()
|
||||
Reference in New Issue
Block a user