Files
fxhnt/tests/unit/test_cli_execute_helpers.py
jgrusewski 4cf6de17ee feat(0b): remove dead Alpaca execution; IBKR is the sole equity executor (Broker port kept)
Alpaca's rebalancer CronJob has been suspended with 401'd keys since Task 4's
venue consolidation; IBKR paper is already the live forward-track venue for
the multi-strat book. This removes the dead code/config/manifests instead of
leaving them to rot, and relabels the multistrat/multistrat_exec registry
venues from the vestigial "alpaca-paper" to "ibkr-equity" (they execute on
IBKR paper, not Alpaca — exec_venue itself is still computed dynamically at
runtime).

- Delete src/fxhnt/adapters/broker/alpaca.py, AlpacaSettings + the `alpaca`
  config field; move the one real setting it carried (min_order_notional,
  a generic "skip sub-min-notional dust" floor for any future
  fractional-capable broker) onto ExecutionSettings.
- Remove the `--broker` option and its alpaca branches from `execute` and
  `execute-multistrat` (cli.py) — IBKR is now implicit; IbkrBroker was
  already the module-level import, so the branch was pure dead code once
  Alpaca is gone.
- Delete infra/k8s/jobs/fxhnt-alpaca-rebalancer.yaml and
  infra/k8s/secrets/alpaca-credentials.yaml; delete the superseded
  infra/k8s/services/multistrat-rebalancer.yaml (PVC + NetworkPolicy +
  CronJob), replaced by fxhnt-ibkr-rebalancer. Drop the now-invalid
  `--broker ibkr` argument from fxhnt-ibkr-rebalancer.yaml and
  fxhnt-ucits-rebalancer.yaml (the flag no longer exists).
- Delete tests/integration/test_alpaca_broker.py (tested the deleted
  adapter directly); add tests/unit/test_no_alpaca.py as a standing guard
  (no "alpaca" substring anywhere in src/fxhnt + IbkrBroker still imports).
- Update test_registry_multistrat_exec.py + test_cockpit_ssot.py (venue
  label) and test_cli_execute_helpers.py (drop the broker_name kwarg and
  AlpacaBroker fake, use IbkrBroker instead) to match.

MANUAL cluster ops (not run by CI, record only):
  kubectl delete cronjob/fxhnt-alpaca-rebalancer secret/alpaca-credentials -n foxhunt
  kubectl delete cronjob/multistrat-rebalancer networkpolicy/multistrat-rebalancer pvc/multistrat-state -n foxhunt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:17:04 +02:00

110 lines
4.5 KiB
Python

def test_venue_string():
from fxhnt.cli import _exec_venue_string
assert _exec_venue_string("ibkr", True) == "ibkr-paper"
assert _exec_venue_string("ibkr", False) == "ibkr-live"
assert _exec_venue_string("bybit", True) == "bybit-paper"
def test_resolve_paper_envelope():
import pytest
import typer
from fxhnt.cli import _resolve_paper_envelope
assert _resolve_paper_envelope(1_000_000.0, 0.0) == 1_000_000.0 # flag wins
assert _resolve_paper_envelope(0.0, 500_000.0) == 500_000.0 # config fallback
assert _resolve_paper_envelope(0.0, 0.0) == 0.0 # off
# a negative flag is an operator typo, not "unset" — must be rejected, not silently fall back to the
# config default (which would hide the typo behind a seemingly-valid run).
with pytest.raises(typer.BadParameter):
_resolve_paper_envelope(-5.0, 500_000.0)
def test_execute_bybit_command_registered():
from fxhnt.cli import app
names = {c.name for c in app.registered_commands}
assert "execute-bybit" in names
def test_execute_bybit_paper_envelope_refused_is_distinct_from_traceback(monkeypatch, tmp_path):
"""A paper-envelope ValueError refusal from `run_bybit_testnet` must surface as a clear, distinct
message + exit code 1 — NOT a raw traceback (the pre-fix behavior: `execute_bybit` had no try/except
at all, so the ValueError propagated uncaught)."""
from typer.testing import CliRunner
import fxhnt.cli as climod
dsn = f"sqlite:///{tmp_path / 'op.db'}"
monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
climod.get_settings.cache_clear()
def _boom(settings, *, paper_envelope, execute, log):
raise ValueError("paper-envelope refused: bybit account is not testnet/paper")
monkeypatch.setattr("fxhnt.application.bybit_testnet_run.run_bybit_testnet", _boom)
res = CliRunner().invoke(climod.app, ["execute-bybit", "--execute", "--paper-envelope", "5"])
climod.get_settings.cache_clear()
assert res.exit_code == 1, res.output
assert "paper-envelope refused" in res.output
assert "Traceback" not in res.output
def test_execute_multistrat_paper_envelope_refused_is_distinct_from_broker_error(monkeypatch, tmp_path, capsys):
"""A paper-envelope ValueError refusal from `plan_and_record` must be caught distinctly (BEFORE the
generic `except Exception`) and printed as a clear safety-refusal message — NOT the misleading
connectivity framing ('broker error: ... reachable? ... creds/gateway set?') the generic handler prints
for every other exception. No live broker/network: YahooDataProvider and IbkrBroker are faked."""
import numpy as np
import pytest
import typer
import fxhnt.cli as climod
from fxhnt.domain.models import PriceSeries
from fxhnt.ports.broker import AccountState
dsn = f"sqlite:///{tmp_path / 'op.db'}"
monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
climod.get_settings.cache_clear()
# 120 synthetic daily bars per fund instrument (needs >= 66 for target_weights to be non-empty).
rng = np.random.default_rng(7)
n = 120
dates = tuple(f"2026-{1 + i // 28:02d}-{1 + i % 28:02d}" for i in range(n))
class _FakeData:
def fetch(self, market): # noqa: ANN001, ANN201
close = 100.0 * np.cumprod(1 + rng.normal(0, 0.01, size=n))
return PriceSeries(market=market, dates=dates, close=close)
monkeypatch.setattr(climod, "YahooDataProvider", lambda: _FakeData())
class _FakeBroker:
name = "ibkr"
supports_fractional = False
def __enter__(self): # noqa: ANN201
return self
def __exit__(self, *a): # noqa: ANN002, ANN201
return False
def account_state(self): # noqa: ANN201
return AccountState(nlv=100_000.0, cash=100_000.0, is_paper=False)
monkeypatch.setattr(climod, "IbkrBroker", lambda *a, **k: _FakeBroker())
def _boom(**kwargs): # noqa: ANN003, ANN201
raise ValueError("paper-envelope refused: account is not paper (real-capital exposure guard)")
monkeypatch.setattr("fxhnt.application.multistrat_exec_record.plan_and_record", _boom)
with pytest.raises(typer.Exit) as ei:
climod.execute_multistrat(do_execute=True, live=False, max_gross=2.0,
paper_envelope=100.0, venue_kind="us")
climod.get_settings.cache_clear()
assert ei.value.exit_code == 1
out = capsys.readouterr().out
assert "paper-envelope refused" in out
assert "broker error" not in out
assert "reachable?" not in out