Plan 003 steps 1-3. - Loop exit(0) after a SUCCESSFUL EOD flatten (TRADING_EXIT_AFTER_EOD_FLATTEN, default on); compose restarts fresh. Restart-churn guard: _rebuild_daily_state detects today's eod_flatten event and seeds _session_closed/_eod_done so a post-flatten restart neither re-flattens nor re-exits. SIGTERM = clean drain. - Exit manager: _FETCH_FAILED sentinel distinguishes a failed get_orders from a genuinely-empty snapshot. A failed fetch skips naked-stop enforcement (catastrophe still exits, re-fetching); an empty-but-successful snapshot needs TWO consecutive reads before 'naked' is trusted (Alpaca silently returns [] for symbols=). Costs ~one 12s pass on the all-naked case only; isolated naked positions unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""Daily self-restart: the process is ONE trading session (plan 003, steps 1-2).
|
|
|
|
After a SUCCESSFUL EOD flatten the loop requests a clean exit and compose
|
|
restarts it fresh; a post-flatten restart must neither re-flatten nor re-exit
|
|
(today's eod_flatten event in the store is the truth); a FAILED flatten keeps
|
|
retrying inside the window.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
import run_live_paper as rlp
|
|
from alpaca_paper.execution import AlpacaPaperExecutor
|
|
from storage.event_schema import EventMode, RiskRuleTriggeredEvent
|
|
from storage.event_store import EventStore
|
|
from trading_execution import ExecutionSettings, TradingExecutionService
|
|
|
|
|
|
class _FakeClient:
|
|
def submit_order(self, **kw):
|
|
return {}
|
|
|
|
def get_account(self):
|
|
return {"equity": "100000", "last_equity": "100000"}
|
|
|
|
|
|
def _svc(store):
|
|
return TradingExecutionService(
|
|
store, executor=AlpacaPaperExecutor(store, client=_FakeClient()),
|
|
settings=ExecutionSettings(), session_id="t",
|
|
)
|
|
|
|
|
|
def _emit_eod_flatten(store, errors):
|
|
store.emit(RiskRuleTriggeredEvent(
|
|
timestamp=datetime.now(), mode=EventMode.PAPER, correlation_id="t",
|
|
message="End-of-day flatten (eod_flatten)",
|
|
rule_type="eod_flatten", rule_value=0.0,
|
|
current_state={"cancelled_entries": 0,
|
|
"closed_positions": [] if errors else ["AAA"],
|
|
"errors": errors},
|
|
action_taken="closed_session",
|
|
))
|
|
|
|
|
|
def test_eod_exit_flag(monkeypatch):
|
|
"""_request_exit_after_eod: ok+flag-on -> exit requested; failed flatten or
|
|
flag-off -> not (a failed flatten must keep retrying inside the window)."""
|
|
rlp._clean_exit_requested.clear()
|
|
monkeypatch.setenv("TRADING_EXIT_AFTER_EOD_FLATTEN", "1")
|
|
assert rlp._request_exit_after_eod(True) is True
|
|
assert rlp._clean_exit_requested.is_set()
|
|
|
|
rlp._clean_exit_requested.clear()
|
|
assert rlp._request_exit_after_eod(False) is False
|
|
assert not rlp._clean_exit_requested.is_set()
|
|
|
|
monkeypatch.setenv("TRADING_EXIT_AFTER_EOD_FLATTEN", "0")
|
|
assert rlp._request_exit_after_eod(True) is False
|
|
assert not rlp._clean_exit_requested.is_set()
|
|
rlp._clean_exit_requested.clear()
|
|
|
|
|
|
def test_boot_after_eod_flatten():
|
|
"""Restart-churn guard: with today's SUCCESSFUL eod_flatten event in the
|
|
store, a fresh service boots session-closed (no re-entry) and reports
|
|
done-today (the loop then neither re-flattens nor re-exits)."""
|
|
store = EventStore(":memory:")
|
|
_emit_eod_flatten(store, errors=[])
|
|
svc = _svc(store)
|
|
assert svc._session_closed is True
|
|
assert svc.eod_flatten_done_today() is True
|
|
|
|
|
|
def test_boot_after_failed_eod_flatten_still_retries():
|
|
"""A FAILED flatten blocks re-entry but must NOT read as done — the fresh
|
|
process retries the flatten inside the window exactly as before."""
|
|
store = EventStore(":memory:")
|
|
_emit_eod_flatten(store, errors=["close AAA: 403"])
|
|
svc = _svc(store)
|
|
assert svc._session_closed is True
|
|
assert svc.eod_flatten_done_today() is False
|
|
|
|
|
|
def test_boot_fresh_day_not_closed():
|
|
store = EventStore(":memory:")
|
|
svc = _svc(store)
|
|
assert svc._session_closed is False
|
|
assert svc.eod_flatten_done_today() is False
|