Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions scripts/reset_paper_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
touched — that is the audit trail. Only the live breaker / position / order
state is cleared; the ``orders`` table is operational, not the trail.

Usage:
Usage (SEMPRE como módulo, a partir da raiz do repo — `python scripts/reset_paper_state.py`
daria ``ModuleNotFoundError: src``, pois a raiz não estaria no ``sys.path``):
python -m scripts.reset_paper_state # prompts for confirmation
python -m scripts.reset_paper_state --yes # skip the prompt
python -m scripts.reset_paper_state --yes # skip the prompt (non-interactive)
python -m scripts.reset_paper_state --dry-run # report only, change nothing

Stop the orchestrator first (or restart it after) so it reloads the cleared
state instead of re-persisting the in-memory breaker:
Na VPS (dockerizado): pare o orchestrator para ele recarregar o estado limpo em vez
de re-persistir o breaker em memória; rode o reset DENTRO do container ``app`` (que
segue DE PÉ e compartilha o mesmo volume ``./data`` — ``exec`` exige um container
rodando, por isso mira ``app``, não o ``orchestrator`` parado). ``--yes`` é obrigatório
porque ``docker compose exec`` não tem TTY para o prompt (sem ele → EOFError):
docker compose -f docker-compose.vps.yml stop orchestrator
python -m scripts.reset_paper_state --yes
docker compose -f docker-compose.vps.yml exec app python -m scripts.reset_paper_state --yes
docker compose -f docker-compose.vps.yml start orchestrator
"""
from __future__ import annotations
Expand Down Expand Up @@ -145,9 +149,16 @@ def db_provider() -> Any:
return 0

if not args.yes:
reply = input(
"\nClear breaker + open-position book + orders? [y/N] "
).strip().lower()
try:
reply = input(
"\nClear breaker + open-position book + orders? [y/N] "
).strip().lower()
except EOFError:
# Sem TTY (ex.: `docker compose exec` sem -it) o prompt não pode ser
# respondido — aborta limpo apontando --yes, em vez de estourar um
# traceback de EOFError.
print("\nSem TTY para confirmar. Rode de novo com --yes para pular o prompt.")
return 1
if reply not in ("y", "yes"):
print("Aborted.")
return 1
Expand Down
44 changes: 43 additions & 1 deletion tests/test_reset_paper_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"""
from __future__ import annotations

from scripts.reset_paper_state import main as reset_main
from scripts.reset_paper_state import reset_paper_state
from src.core.ledger import TradingLedger
from src.hitl.orders import Order, OrderStore
from src.orchestration.position_store import (
Expand All @@ -16,7 +18,6 @@
load_circuit_state,
save_circuit_state,
)
from scripts.reset_paper_state import reset_paper_state

_POS = {
"symbol": "BTC/USDT", "side": "buy", "entry_price": 100.0, "quantity": 0.5,
Expand Down Expand Up @@ -161,3 +162,44 @@ def db():
assert result["dry_run"] is True
assert result["orders_cleared"] == 1 # what WOULD be removed
assert store.count() == 1 # ...but untouched


# --------------------------------------------------------------------- main() CLI
# M4: o ledger db E o app db derivam de LEDGER_DIR (get_db_path), então apontá-lo ao
# tmp isola os dois; main() lê esse mesmo estado.
def _seed_dirty(monkeypatch, tmp_path):
monkeypatch.setenv("LEDGER_DIR", str(tmp_path))
ledger = TradingLedger()

def db():
return ledger.db_path

PositionStore(db).upsert("o1", _POS)
save_circuit_state(db, 123.0, 6, -74.5)
return db


def test_main_yes_resets_without_prompting(tmp_path, monkeypatch):
db = _seed_dirty(monkeypatch, tmp_path)

def _no_input(*_a, **_k):
raise AssertionError("input() não deve ser chamado com --yes")

monkeypatch.setattr("builtins.input", _no_input)
assert reset_main(["--yes"]) == 0
assert PositionStore(db).count() == 0
assert load_circuit_state(db) is None


def test_main_eof_without_yes_aborts_cleanly(tmp_path, monkeypatch):
# M4: sem --yes e sem TTY (docker exec sem -it), input() levanta EOFError — o
# script deve abortar limpo (return 1, sem traceback) e NÃO tocar o estado.
db = _seed_dirty(monkeypatch, tmp_path)

def _raise_eof(*_a, **_k):
raise EOFError

monkeypatch.setattr("builtins.input", _raise_eof)
assert reset_main([]) == 1
assert PositionStore(db).count() == 1
assert load_circuit_state(db) is not None
Loading