VWAP entry gate on the live auto path + concentrate boot-guard
Wire the one validated selection signal (above-VWAP breakouts reach +1R ~1.5x as often, n=3,110 from the labeler lift report) into the LIVE auto entry path (request_approvals_for_ready_signals) — NOT the dead fast submit_breakout_now path where the cap and rank logic got stranded. - setup_evaluator already computes above_vwap/levels.vwap; surface them in the setup dict (also an observability win) and pass through query_ready_signals_snapshot. - New ExecutionSettings.require_above_vwap (TRADING_REQUIRE_ABOVE_VWAP, env default on). When a ready signal is below session VWAP: always emit a rule_type=vwap_below event (shadow measurement); only SKIP the entry when the flag is on. Fail-open: above_vwap=None (old signals) is never blocked. - Boot log now prints the entry-gate state, and WARNS if TRADING_CONCENTRATE_TOP_N>0 (that rank switch only affects the dead fast path — see the rank-scaling audit). Tests: gate skips below-VWAP when enforced, shadow-logs+allows when off, allows above-VWAP, and fail-opens on the missing field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac1ac50c9e
commit
e3ac89c2f8
5 files changed
+101
-6
No files matched your search
@@ -66,7 +66,17 @@ class Clock:
|
||||
self.t += timedelta(minutes=minutes)
|
||||
|
||||
|
||||
def emit_signal(store, symbol="GOOD", entry=14.0, stop=13.45):
|
||||
def emit_signal(store, symbol="GOOD", entry=14.0, stop=13.45,
|
||||
above_vwap=None, vwap=None):
|
||||
signal_data = {
|
||||
"entry_price": entry,
|
||||
"stop_loss_price": stop,
|
||||
"quality_score": 0.7,
|
||||
}
|
||||
if above_vwap is not None:
|
||||
signal_data["above_vwap"] = above_vwap
|
||||
signal_data["vwap"] = vwap if vwap is not None else (
|
||||
round(entry - 1.0, 2) if above_vwap else round(entry + 1.0, 2))
|
||||
store.emit(
|
||||
SignalReadyEvent(
|
||||
timestamp=T0,
|
||||
@@ -76,11 +86,7 @@ def emit_signal(store, symbol="GOOD", entry=14.0, stop=13.45):
|
||||
symbol=symbol,
|
||||
signal_type="bull_flag",
|
||||
confidence=0.9,
|
||||
signal_data={
|
||||
"entry_price": entry,
|
||||
"stop_loss_price": stop,
|
||||
"quality_score": 0.7,
|
||||
},
|
||||
signal_data=signal_data,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -209,6 +215,43 @@ def test_daily_cap_never_blocks_manual_approval(store):
|
||||
assert len(executor.executed) == 2 # cap ignored for manual override
|
||||
|
||||
|
||||
def test_vwap_gate_skips_below_vwap_when_enforced(store):
|
||||
emit_signal(store, symbol="LOWV", entry=14.0, stop=13.45, above_vwap=False, vwap=15.0)
|
||||
service = make_service(store, require_above_vwap=True)
|
||||
created = service.request_approvals_for_ready_signals()
|
||||
assert created == [] # entry skipped
|
||||
assert query_approval_queue(store) == []
|
||||
vb = [json.loads(e["payload_json"]) for e in store.query_events(event_type="risk_rule_triggered")
|
||||
if json.loads(e["payload_json"])["rule_type"] == "vwap_below"]
|
||||
assert len(vb) == 1 and vb[0]["action_taken"] == "skipped_entry"
|
||||
|
||||
|
||||
def test_vwap_gate_shadow_logs_but_allows_when_off(store):
|
||||
emit_signal(store, symbol="LOWV", entry=14.0, stop=13.45, above_vwap=False, vwap=15.0)
|
||||
service = make_service(store, require_above_vwap=False)
|
||||
created = service.request_approvals_for_ready_signals()
|
||||
assert len(created) == 1 # still entered (shadow mode)
|
||||
vb = [json.loads(e["payload_json"]) for e in store.query_events(event_type="risk_rule_triggered")
|
||||
if json.loads(e["payload_json"])["rule_type"] == "vwap_below"]
|
||||
assert len(vb) == 1 and vb[0]["action_taken"] == "shadow_logged"
|
||||
|
||||
|
||||
def test_vwap_gate_allows_above_vwap(store):
|
||||
emit_signal(store, symbol="HIV", entry=14.0, stop=13.45, above_vwap=True, vwap=13.0)
|
||||
service = make_service(store, require_above_vwap=True)
|
||||
created = service.request_approvals_for_ready_signals()
|
||||
assert len(created) == 1
|
||||
assert not [e for e in store.query_events(event_type="risk_rule_triggered")
|
||||
if json.loads(e["payload_json"])["rule_type"] == "vwap_below"]
|
||||
|
||||
|
||||
def test_vwap_gate_fail_open_on_missing_field(store):
|
||||
# signals lacking the above_vwap field (old signals) must NEVER be blocked
|
||||
emit_signal(store, symbol="OLDV", entry=14.0, stop=13.45) # no above_vwap
|
||||
service = make_service(store, require_above_vwap=True)
|
||||
assert len(service.request_approvals_for_ready_signals()) == 1
|
||||
|
||||
|
||||
def test_no_duplicate_requests_for_same_symbol(store):
|
||||
emit_signal(store)
|
||||
service = make_service(store)
|
||||
|
||||
Reference in new issue
Block a user