diff --git a/keel/agent.py b/keel/agent.py index a0c76937..19e3d4e1 100644 --- a/keel/agent.py +++ b/keel/agent.py @@ -658,7 +658,13 @@ def run_once( # PAPER path instead, which never touches the broker. Constructed once per cycle and # rehydrated from the orders table, so a per-cycle agent resumes its open positions. paper_trader = PaperTrader(repo) if config.auto_trade.mode == "paper" else None - log_event(logger, logging.INFO, "agent.mode_resolved", mode=mode) + # What this cycle actually DID, for the log line and `LoopResult`. `_effective_mode` + # returns the executor string `"confirm"` for any non-live config -- paper included -- + # so reporting it verbatim told the user a confirm-mode (live) run happened when the + # cycle only ever hit the paper path. `executor.execute`/`_handle_exits` still get the + # real executor `mode`; only what we surface to the user changes. + reported_mode = "paper" if paper_trader is not None else mode + log_event(logger, logging.INFO, "agent.mode_resolved", mode=reported_mode) max_age_sec = config.auto_trade.interval_sec * FEED_STALENESS_CYCLES finest = _finest_granularity(granularities) @@ -792,7 +798,7 @@ def run_once( ts=now_ts, skipped=False, skip_reason=None, - mode=mode, + mode=reported_mode, polled=polled, products=products, stale_products=stale_products, diff --git a/tests/test_agent.py b/tests/test_agent.py index 7b5e5dd9..75f1d792 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -387,6 +387,22 @@ def test_paper_mode_places_nothing_even_when_autonomous(repo): assert broker.place_calls == [], "paper mode must never reach the broker" +def test_paper_mode_is_reported_as_paper_not_confirm(repo): + """The loop summary must name the real operating mode. + + Paper routes to the paper path and never touches the broker, but `_effective_mode` returns + the executor string `"confirm"` for any non-live config -- so a paper cycle used to report + `mode=confirm`, telling the user a confirm-mode (live) run happened when nothing did. The + reported mode must say `"paper"`. + """ + broker = FakeBroker(series={(PRODUCT, Granularity.ONE_DAY): [_candle(0, "100")]}) + config = _config(auto_trade=AutoTradeConfig(mode="paper", interval_sec=50_000)) + + result = run_once(broker, repo, config, now_ts=90_000) + + assert result.mode == "paper" + + # -- run_once: EXIT wiring on a held position ---------------------------------------------------