feat(exec): bybit paper-envelope helper (testnet-only) + venue in book-state

This commit is contained in:
jgrusewski
2026-07-11 00:58:52 +02:00
parent 8e54d6dc1d
commit 4837a1fe8e
2 changed files with 47 additions and 5 deletions

View File

@@ -15,11 +15,22 @@ def crypto_envelope(repo: Any, equity: float) -> float:
return min(float(repo.current_allocation().get("bybit_4edge", 0.0)), float(equity))
def paper_or_b2a_crypto_envelope(repo: Any, equity: float, paper_envelope: float, is_paper: bool) -> float:
"""PAPER-validation envelope (min(paper, equity)) when `paper_envelope > 0`, bypassing B2a — refused on a
non-testnet account (never widen real-capital exposure). Otherwise the B2a/B3-gated `crypto_envelope`."""
if paper_envelope > 0.0:
if not is_paper:
raise ValueError("paper-envelope refused: bybit account is not testnet/paper")
return min(float(paper_envelope), float(equity))
return crypto_envelope(repo, equity)
def build_crypto_pos_state(positions: dict[str, float], marks: dict[str, float], envelope: float,
funding_cursor_ts: int) -> dict[str, Any]:
funding_cursor_ts: int, venue: str = "") -> dict[str, Any]:
return {"positions": {s: float(q) for s, q in positions.items()},
"marks": {s: float(m) for s, m in marks.items()},
"envelope": float(envelope), "funding_cursor_ts": int(funding_cursor_ts)}
"envelope": float(envelope), "funding_cursor_ts": int(funding_cursor_ts),
"venue": str(venue)}
def compute_crypto_exec_return(pos_prior: dict[str, Any], today_marks: dict[str, float],
@@ -76,11 +87,13 @@ class _CryptoExecBookingStrategy:
def record_bybit_exec_track(repo: Any, exec_return: float | None, new_positions: dict[str, float],
today_marks: dict[str, float], envelope: float, funding_cursor_ts: int,
today: str, at: dt.datetime) -> None:
today: str, at: dt.datetime, venue: str = "") -> None:
"""Book `exec_return` as an observed forward_record for `bybit_4edge_exec` via A's run_track (anchor +
record + nav/summary), then carry the next run's basis (positions/marks/envelope/funding cursor) under the
book-state key `bybit_4edge_exec_pos` (SEPARATE from run_track's own book_state)."""
book-state key `bybit_4edge_exec_pos` (SEPARATE from run_track's own book_state). `venue` is recorded on
the exec book-state."""
from fxhnt.application.forward_engine import run_track
run_track(repo, "bybit_4edge_exec", _CryptoExecBookingStrategy(exec_return), today=today, at=at)
repo.set_book_state("bybit_4edge_exec_pos",
build_crypto_pos_state(new_positions, today_marks, envelope, funding_cursor_ts), at=at)
build_crypto_pos_state(new_positions, today_marks, envelope, funding_cursor_ts, venue),
at=at)

View File

@@ -97,3 +97,32 @@ def test_apply_fills_rejects_unknown_side():
fills = [{"symbol": "SOLUSDT", "side": "HOLD", "qty": 2.0}]
with pytest.raises(ValueError, match="apply_fills: unexpected fill side"):
apply_fills(current, fills)
def test_build_crypto_pos_state_persists_venue():
from fxhnt.application.bybit_exec_record import build_crypto_pos_state
st = build_crypto_pos_state({"BTCUSDT": 1.0}, {"BTCUSDT": 60000.0}, 5000.0, 123, venue="bybit-testnet")
assert st["venue"] == "bybit-testnet" and st["funding_cursor_ts"] == 123
def test_paper_or_b2a_crypto_envelope_paper_mode():
from fxhnt.application.bybit_exec_record import paper_or_b2a_crypto_envelope
class _Repo:
def current_allocation(self):
raise AssertionError("B2a must not be read in paper mode")
assert paper_or_b2a_crypto_envelope(_Repo(), equity=8000.0, paper_envelope=10000.0, is_paper=True) == 8000.0
def test_paper_or_b2a_crypto_envelope_refuses_non_testnet():
import pytest
from fxhnt.application.bybit_exec_record import paper_or_b2a_crypto_envelope
with pytest.raises(ValueError, match="paper-envelope refused"):
paper_or_b2a_crypto_envelope(object(), equity=8000.0, paper_envelope=10000.0, is_paper=False)
def test_paper_or_b2a_crypto_envelope_default_reads_b2a():
from fxhnt.application.bybit_exec_record import paper_or_b2a_crypto_envelope
class _Repo:
def current_allocation(self):
return {"bybit_4edge": 3000.0}
assert paper_or_b2a_crypto_envelope(_Repo(), equity=8000.0, paper_envelope=0.0, is_paper=True) == 3000.0