Skip to content

Commit d79ca7d

Browse files
committed
Add VRP integration test with 100% field coverage
Mirrors the discipline already in place for ExposureSummary: every leaf field declared in VrpResponse is referenced by at least one assertion. Catches silent-null traps proactively (response.z_score → vrp.z_score, response.put_vrp → directional.downside_vrp, response.net_gex → regime.net_gex, response.harvest_score → gex_conditioned.harvest_score). Live-vs-historical structural diffs encoded: - macro.fed_funds: present on live, absent on historical - macro.hy_spread: live currently null (data gap), historical populated - vrp.z_score / percentile / regime.vrp_regime / strategy_scores / net_harvest_score: nullable on historical with insufficient warmup Verified passing against the live API.
1 parent c127f53 commit d79ca7d

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

tests/test_integration.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,92 @@ def test_vrp_returns_full_payload(fa):
718718
assert "vix" in r["macro"]
719719

720720

721+
def test_vrp_every_field_declared_in_poco_must_be_referenced(fa):
722+
"""Every leaf field declared in VrpResponse must be referenced.
723+
724+
100% field-coverage discipline (mirrors the ExposureSummary contract).
725+
Live-specific:
726+
- macro.fed_funds is present (live-only field).
727+
- macro.hy_spread may currently be null (data-pipeline gap).
728+
"""
729+
r = fa.vrp("SPY")
730+
731+
# ── top-level scalars ──
732+
assert r["symbol"] == "SPY"
733+
assert isinstance(r["underlying_price"], (int, float)) and r["underlying_price"] > 0
734+
assert isinstance(r["as_of"], str) and r["as_of"]
735+
assert isinstance(r["market_open"], bool)
736+
assert isinstance(r["variance_risk_premium"], (int, float))
737+
assert isinstance(r["convexity_premium"], (int, float))
738+
assert isinstance(r["fair_vol"], (int, float))
739+
assert isinstance(r["dealer_flow_risk"], (int, float))
740+
assert isinstance(r["net_harvest_score"], (int, float))
741+
assert isinstance(r["warnings"], list)
742+
# Customer traps: these MUST NOT be top-level
743+
assert "z_score" not in r
744+
assert "percentile" not in r
745+
assert "atm_iv" not in r
746+
assert "net_gex" not in r
747+
assert "put_vrp" not in r and "call_vrp" not in r
748+
assert "harvest_score" not in r # under gex_conditioned
749+
750+
# ── vrp.* core block ──
751+
core = r["vrp"]
752+
for k in ("atm_iv", "rv_5d", "rv_10d", "rv_20d", "rv_30d",
753+
"vrp_5d", "vrp_10d", "vrp_20d", "vrp_30d"):
754+
assert isinstance(core[k], (int, float)), f"vrp.{k}"
755+
assert isinstance(core["z_score"], (int, float))
756+
assert isinstance(core["percentile"], int) and 0 <= core["percentile"] <= 100
757+
assert isinstance(core["history_days"], int)
758+
759+
# ── directional ──
760+
d = r["directional"]
761+
for k in ("put_wing_iv_25d", "call_wing_iv_25d",
762+
"downside_rv_20d", "upside_rv_20d",
763+
"downside_vrp", "upside_vrp"):
764+
assert isinstance(d[k], (int, float)), f"directional.{k}"
765+
766+
# ── term_vrp[] ──
767+
term = r["term_vrp"]
768+
assert isinstance(term, list) and len(term) > 0
769+
first = term[0]
770+
assert isinstance(first["dte"], int)
771+
for k in ("iv", "rv", "vrp"):
772+
assert isinstance(first[k], (int, float)), f"term_vrp[0].{k}"
773+
774+
# ── gex_conditioned ──
775+
gex_c = r["gex_conditioned"]
776+
assert isinstance(gex_c["regime"], str)
777+
assert isinstance(gex_c["harvest_score"], (int, float))
778+
assert isinstance(gex_c["interpretation"], str)
779+
780+
# ── vanna_conditioned ──
781+
vanna_c = r["vanna_conditioned"]
782+
assert isinstance(vanna_c["outlook"], str)
783+
assert isinstance(vanna_c["interpretation"], str)
784+
785+
# ── regime (net_gex lives HERE) ──
786+
reg = r["regime"]
787+
assert isinstance(reg["gamma"], str)
788+
assert isinstance(reg["vrp_regime"], str)
789+
assert isinstance(reg["net_gex"], (int, float))
790+
assert isinstance(reg["gamma_flip"], (int, float))
791+
792+
# ── strategy_scores ──
793+
ss = r["strategy_scores"]
794+
for k in ("short_put_spread", "short_strangle", "iron_condor", "calendar_spread"):
795+
assert isinstance(ss[k], int) and 0 <= ss[k] <= 100, f"strategy_scores.{k}"
796+
797+
# ── macro (live-specific includes fed_funds) ──
798+
m = r["macro"]
799+
for k in ("vix", "vix_3m", "vix_term_slope", "dgs10"):
800+
assert isinstance(m[k], (int, float)), f"macro.{k}"
801+
# hy_spread present but may be null on live currently
802+
assert "hy_spread" in m and (m["hy_spread"] is None or isinstance(m["hy_spread"], (int, float)))
803+
# fed_funds is the live-only macro field
804+
assert "fed_funds" in m and isinstance(m["fed_funds"], (int, float))
805+
806+
721807
# Issue #1 — Nested response structures. Customer accessed
722808
# response["z_score"] directly and got None. The field lives at
723809
# response["vrp"]["z_score"]. Tests assert the documented nesting.

0 commit comments

Comments
 (0)