#!/usr/bin/env python3 # ============================================================================== # COPYRIGHT NO ONE EVERYWHERE LLC (WYOMING HOLDING COMPANY) # PROJECT: SOVEREIGN STACK # This artifact is entirely proprietary and cryptographically proven. # Open-Source usage requires explicit permission from Brandon Scott Schneider. # ============================================================================== """Regret, surprise, and counterfactual mechanics for the legal OmniToken action bot. These mechanisms answer three questions that the compliance front layer alone cannot answer: 1. **Surprise**: how far did this candidate deviate from what we expected? 2. **Regret**: did our decision cost us (or save us) relative to the best alternative we could have taken? 3. **Counterfactual**: what would have happened if we had chosen differently? The market simulation lane uses these to tune entry/exit thresholds. The compliance lane uses them to tune *refusal* thresholds — when the bot chooses *not* to act, it should still learn whether that refusal was correct. Design principles (from BEHAVIORAL_FORMALISM and MARKET_SIMULATION_REGRET_COMPRESSION_BRIDGE): - Every decision emits a predicted value and a realized value - Surprise = log(1 + |predicted - realized|) — bounded, doesn't blow up - Regret = max(0, best_alternative - chosen) — standard external regret - Counterfactuals enumerate the actions we didn't take and estimate their value - Threshold adaptation learns from missed opportunities vs bad actions """ from __future__ import annotations import math from dataclasses import dataclass from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple # ── Surprise ───────────────────────────────────────────────────────────────── def log_surprise(predicted: float, actual: float) -> float: """Log-scaled surprise: 0 = no surprise, higher = more deviation. Uses log(1 + |delta|) to prevent outlier blowup. """ return math.log1p(abs(actual - predicted)) # ── Regret ─────────────────────────────────────────────────────────────────── @dataclass class RegretRecord: """A single decision and its regret outcome.""" candidate_id: str decision: str # prepare_submit | hold_for_review | freeze_and_escalate | block_submit expected_value: float realized_value: float best_alternative_value: float surprise: float timestamp_utc: str @property def decision_regret(self) -> float: """Opportunity cost vs the best alternative we didn't take.""" return max(0.0, self.best_alternative_value - self.realized_value) @property def is_missed_opportunity(self) -> bool: """We blocked/held but the alternative would have been profitable.""" return ( self.decision in ("block_submit", "hold_for_review", "freeze_and_escalate") and self.best_alternative_value > self.realized_value ) @property def is_bad_action(self) -> bool: """We acted and the alternative would have been better.""" return self.decision == "prepare_submit" and self.best_alternative_value > self.realized_value def compute_regret( chosen_value: float, alternative_values: Sequence[float], ) -> Tuple[float, float]: """Return (regret, best_alternative_value). Standard external-regret: max(0, best_alt - chosen). """ if not alternative_values: return 0.0, 0.0 best_alt = max(alternative_values) return max(0.0, best_alt - chosen_value), best_alt # ── Counterfactual Analysis ────────────────────────────────────────────────── @dataclass class CounterfactualScenario: """One alternative action we didn't take, with estimated outcomes.""" alternative_action: str estimated_value: float estimated_risk: float rationale: str def generate_counterfactuals( candidate: Mapping[str, Any], chosen_action: str, front_layer: Mapping[str, Any], ) -> List[CounterfactualScenario]: """Bounded counterfactual alternatives for a candidate. For each action we *didn't* take, estimate what would have happened based on observable features and front-layer flags. Estimates are deliberately conservative — the bot should not fantasize about profits it cannot verify. """ alternatives: List[CounterfactualScenario] = [] economic_purpose = str(front_layer.get("economic_purpose", "unknown")) risk_flags = sum( 1 for k in ( "retail_disadvantage_flag", "manipulation_risk_flag", "wash_trading_risk_flag", "spoofing_pattern_flag", "user_order_targeting_flag", ) if front_layer.get(k) ) if chosen_action != "prepare_submit": # If risk flags are high, estimated value of submitting is low submit_value = 0.0 if risk_flags >= 2 else 0.3 submit_risk = 0.8 if risk_flags >= 2 else 0.3 alternatives.append(CounterfactualScenario( alternative_action="prepare_submit", estimated_value=submit_value, estimated_risk=submit_risk, rationale=( f"Submitting would expose the candidate to downstream markets; " f"{risk_flags} abuse risk flags present. " f"Economic purpose: {economic_purpose}." ), )) if chosen_action != "hold_for_review": hold_value = 0.5 if risk_flags == 0 else 0.2 hold_risk = 0.1 alternatives.append(CounterfactualScenario( alternative_action="hold_for_review", estimated_value=hold_value, estimated_risk=hold_risk, rationale=( "Holding for review defers the decision but preserves optionality; " "review cost is bounded and the candidate remains observable." ), )) if chosen_action != "freeze_and_escalate": freeze_value = 0.1 if risk_flags >= 3 else 0.0 freeze_risk = 0.05 alternatives.append(CounterfactualScenario( alternative_action="freeze_and_escalate", estimated_value=freeze_value, estimated_risk=freeze_risk, rationale=( "Freezing prevents any downstream harm but incurs escalation cost; " "only justified when risk flags are numerous or critical." ), )) if chosen_action != "block_submit": alternatives.append(CounterfactualScenario( alternative_action="block_submit", estimated_value=0.0, estimated_risk=0.0, rationale=( "Blocking eliminates risk but also eliminates any possible benefit; " "appropriate only when the candidate fails hard jurisdictional or " "asset-whitelist checks." ), )) return alternatives # ── Adaptive Threshold Tuning from Counterfactual Regret ───────────────────── @dataclass class ThresholdState: """Tracks how action thresholds adapt based on regret signals.""" # Mirrors MarketActionPolicy structure # RSC-1 follow-up: initialize above 0.0 so missed-opportunity decrements (-0.0001/event) # have room to operate. At 0.0 (the clamp floor), every missed-opportunity decrement is # silently discarded — the entry bar cannot self-loosen from baseline. # 0.005 = 0.5% improvement required initially; ≈50 missed-opp events to reach neutral. entry_improvement_fraction: float = 0.005 max_loss_fraction: float = 0.05 expected_slippage_fraction: float = 0.0025 # Regret tracking total_regret: float = 0.0 missed_opportunities: int = 0 bad_actions: int = 0 total_decisions: int = 0 learning_rate: float = 0.01 def update(self, record: RegretRecord) -> None: self.total_decisions += 1 self.total_regret += record.decision_regret if record.is_missed_opportunity: self.missed_opportunities += 1 # Too conservative: loosen loss tolerance, lower entry bar self.max_loss_fraction += self.learning_rate * record.decision_regret # RSC-1 fix: entry_improvement_fraction was never updated self.entry_improvement_fraction -= self.learning_rate * 0.01 if record.is_bad_action: self.bad_actions += 1 # Too aggressive: tighten loss tolerance, raise slippage expectation, raise entry bar self.max_loss_fraction -= self.learning_rate * record.decision_regret self.expected_slippage_fraction += self.learning_rate * 0.5 # RSC-1 fix: entry_improvement_fraction was never updated self.entry_improvement_fraction += self.learning_rate * 0.01 # Clamp to sane bounds. # RSC-B: once entry_improvement_fraction reaches 0.0, missed-opportunity # decrements (-0.0001/event) are silently absorbed. After ~50 missed-opp # events from the 0.005 baseline the threshold is fully loosened and the # learning signal is structurally saturated — the counter keeps incrementing # but threshold state no longer changes. This is intentional: the system # is already maximally permissive on entries. The saturation is observable # via missed_opportunities count vs the ~50-event horizon. self.max_loss_fraction = max(0.01, min(0.20, self.max_loss_fraction)) self.expected_slippage_fraction = max(0.001, min(0.05, self.expected_slippage_fraction)) self.entry_improvement_fraction = max(0.0, min(0.10, self.entry_improvement_fraction)) def regret_rate(self) -> float: return self.total_regret / max(self.total_decisions, 1) def snapshot(self) -> Dict[str, Any]: return { "entry_improvement_fraction": round(self.entry_improvement_fraction, 6), "max_loss_fraction": round(self.max_loss_fraction, 6), "expected_slippage_fraction": round(self.expected_slippage_fraction, 6), "total_regret": round(self.total_regret, 6), "regret_per_decision": round(self.regret_rate(), 6), "missed_opportunities": self.missed_opportunities, "bad_actions": self.bad_actions, "total_decisions": self.total_decisions, "learning_rate": self.learning_rate, } # ── Action Record Enrichment ──────────────────────────────────────────────── def enrich_action_with_regret_surprise_counterfactual( action: Dict[str, Any], candidate: Mapping[str, Any], predicted_value: float, realized_value: float, alternative_values: Sequence[float], ) -> Dict[str, Any]: """Add regret, surprise, and counterfactual fields to an action record. Called *after* compliance front layer produces its decision but *before* the action is written to the output log. """ surprise = log_surprise(predicted_value, realized_value) counterfactuals = generate_counterfactuals( candidate, action.get("action", "hold_for_review"), action, ) # RSC-A fix: best_alternative_value and best_alternative_action must come from # the same source. Previously regret used external alternative_values while # best_alternative_action used internal counterfactual estimates — the two could # point to different scenarios. Fix: derive alternative_values from counterfactuals # when available so both fields are consistent by construction. External # alternative_values is kept as API fallback when no counterfactuals exist. cf_values = [cf.estimated_value for cf in counterfactuals] regret_values = cf_values if cf_values else list(alternative_values) regret, best_alt = compute_regret(realized_value, regret_values) enriched = dict(action) enriched["predicted_value"] = round(predicted_value, 6) enriched["realized_value"] = round(realized_value, 6) enriched["surprise"] = round(surprise, 6) enriched["regret"] = round(regret, 6) enriched["best_alternative_value"] = round(best_alt, 6) enriched["best_alternative_action"] = ( max(counterfactuals, key=lambda c: c.estimated_value).alternative_action if counterfactuals else "none" ) enriched["counterfactuals"] = [ { "alternative_action": cf.alternative_action, "estimated_value": round(cf.estimated_value, 6), "estimated_risk": round(cf.estimated_risk, 6), "rationale": cf.rationale, } for cf in counterfactuals ] enriched["is_missed_opportunity"] = ( action.get("action") in ("block_submit", "hold_for_review", "freeze_and_escalate") and best_alt > realized_value ) enriched["is_bad_action"] = ( action.get("action") == "prepare_submit" and best_alt > realized_value ) return enriched def surprise_regret_summary(actions: Sequence[Mapping[str, Any]]) -> Dict[str, Any]: """Summary statistics for surprise and regret across a batch of actions.""" surprises = [float(a.get("surprise", 0.0)) for a in actions] regrets = [float(a.get("regret", 0.0)) for a in actions] missed = sum(1 for a in actions if a.get("is_missed_opportunity")) bad = sum(1 for a in actions if a.get("is_bad_action")) def _pct(vals, p): if not vals: return 0.0 s = sorted(vals) return s[min(int(len(s) * p), len(s) - 1)] n = max(len(actions), 1) return { "surprise_mean": round(sum(surprises) / n, 6), "surprise_median": round(_pct(surprises, 0.5), 6), "surprise_p90": round(_pct(surprises, 0.9), 6), "regret_mean": round(sum(regrets) / n, 6), "regret_median": round(_pct(regrets, 0.5), 6), "regret_p90": round(_pct(regrets, 0.9), 6), "missed_opportunities": missed, "bad_actions": bad, "missed_opportunity_rate": round(missed / n, 6), "bad_action_rate": round(bad / n, 6), }