Clear trade history when resetting sim equity.
Deleting groups/fills/residuals and resetting strategy counters keeps funds reset a clean slate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -64,12 +64,42 @@ class Ledger:
|
||||
pass
|
||||
return equity
|
||||
|
||||
def clear_trade_history(self) -> None:
|
||||
"""清空交易记录与持仓痕迹(组/成交/残留/账本流水),仓位置 flat。"""
|
||||
now = int(time.time() * 1000)
|
||||
with self.db._lock:
|
||||
self.db._conn.execute("DELETE FROM fills")
|
||||
self.db._conn.execute("DELETE FROM residual_options")
|
||||
self.db._conn.execute("DELETE FROM groups")
|
||||
self.db._conn.execute("DELETE FROM ledger_entries")
|
||||
self.db._conn.execute(
|
||||
"""UPDATE positions SET
|
||||
group_id=NULL, perp_side=NULL, perp_qty_eth=0, perp_entry_px=NULL,
|
||||
option_inst_id=NULL, option_side=NULL, option_qty_eth=0,
|
||||
option_qty_contracts=0, option_entry_px=NULL, entry_index_px=NULL,
|
||||
initial_premium=0, exit_target_usdt=NULL, status='flat'
|
||||
WHERE id=1"""
|
||||
)
|
||||
self.db._conn.execute(
|
||||
"""UPDATE strategy_state SET
|
||||
rounds_done=0, window_key=NULL, rest_until_ms=NULL,
|
||||
last_error=NULL, phase=CASE WHEN running=1 THEN phase ELSE 'idle' END,
|
||||
updated_at_ms=?
|
||||
WHERE id=1""",
|
||||
(now,),
|
||||
)
|
||||
self.db._conn.execute(
|
||||
"DELETE FROM settings WHERE key=?", ("risk_last_k",)
|
||||
)
|
||||
self.db._conn.commit()
|
||||
|
||||
def reset_equity(self, amount: float, *, note: str = "重置模拟资金") -> float:
|
||||
"""将权益与可用资金重置为 amount(reserved 清零)。须在无持仓时调用。"""
|
||||
"""将权益与可用资金重置为 amount(reserved 清零),并清空交易记录。须在无持仓时调用。"""
|
||||
now = int(time.time() * 1000)
|
||||
amt = float(amount)
|
||||
if amt < 0:
|
||||
raise ValueError("模拟资金不能为负")
|
||||
self.clear_trade_history()
|
||||
with self.db._lock:
|
||||
self.db._conn.execute(
|
||||
"UPDATE ledger_meta SET equity=?, available=?, reserved=0, updated_at_ms=? WHERE id=1",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""模拟资金重置时同步清空交易记录。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.models.db import Database
|
||||
from app.sim.ledger import Ledger
|
||||
|
||||
|
||||
def test_reset_equity_clears_trade_history(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setenv("MODE", "SIM")
|
||||
db = Database(tmp_path / "reset.db")
|
||||
with db._lock:
|
||||
db._conn.execute(
|
||||
"""INSERT INTO groups(group_id, status, open_at_ms, close_at_ms, realized_pnl)
|
||||
VALUES ('G1','closed',1,2,-5.0)"""
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO fills(group_id, leg, action, side, inst_id, qty_eth,
|
||||
fill_px, fee, slip, notional, ts_ms)
|
||||
VALUES ('G1','perp','open','long','ETH-SWAP',1,2000,0.1,0,2000,1)"""
|
||||
)
|
||||
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 ('G1','OPT','call',2,10,'pending',1)"""
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO ledger_entries(group_id, kind, amount, balance_after, note, ts_ms)
|
||||
VALUES ('G1','pnl',-5,9995,'t',2)"""
|
||||
)
|
||||
db._conn.execute(
|
||||
"""UPDATE strategy_state SET rounds_done=3, window_key='w', rest_until_ms=99
|
||||
WHERE id=1"""
|
||||
)
|
||||
db._conn.commit()
|
||||
db.set_setting("risk_last_k", "1.5")
|
||||
|
||||
Ledger(db).reset_equity(12000.0, note="test reset")
|
||||
|
||||
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
|
||||
entries = db.fetchall("SELECT kind, amount FROM ledger_entries")
|
||||
assert len(entries) == 1
|
||||
assert entries[0]["kind"] == "reset"
|
||||
assert float(entries[0]["amount"]) == 12000.0
|
||||
pos = db.fetchone("SELECT status, group_id FROM positions WHERE id=1")
|
||||
assert pos["status"] == "flat"
|
||||
assert pos["group_id"] is None
|
||||
st = db.fetchone(
|
||||
"SELECT rounds_done, window_key, rest_until_ms FROM strategy_state WHERE id=1"
|
||||
)
|
||||
assert int(st["rounds_done"]) == 0
|
||||
assert st["window_key"] is None
|
||||
assert st["rest_until_ms"] is None
|
||||
assert db.get_setting("risk_last_k") is None
|
||||
led = db.fetchone("SELECT equity, available, reserved FROM ledger_meta WHERE id=1")
|
||||
assert float(led["equity"]) == 12000.0
|
||||
assert float(led["available"]) == 12000.0
|
||||
assert float(led["reserved"]) == 0.0
|
||||
db.close()
|
||||
@@ -419,7 +419,7 @@ export default function SettingsPage() {
|
||||
setStratOk(
|
||||
isLive
|
||||
? "策略参数已保存(实盘模式不改模拟资金)"
|
||||
: "策略参数已保存(切换交易所/改模拟资金需无持仓;切换后会重连行情)",
|
||||
: "策略参数已保存(改模拟资金会重置权益并清空交易记录,须无持仓;切换交易所后会重连行情)",
|
||||
);
|
||||
} catch (ex) {
|
||||
setErr(ex instanceof Error ? ex.message : String(ex));
|
||||
@@ -1405,7 +1405,7 @@ export default function SettingsPage() {
|
||||
<li>交易所:有持仓时不可切换。切换后套用该所合约并重连行情。</li>
|
||||
{mode !== "LIVE" ? (
|
||||
<li>
|
||||
模拟资金:仅 SIM 显示。保存后重置权益(须无持仓)。默认
|
||||
模拟资金:仅 SIM 显示。保存且金额变更后重置权益,并清空交易记录(须无持仓)。默认
|
||||
10000。
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user