malkhut(T4): Strategy DSL v2 + generator + supporting modules
Strategy DSL v2 (dsl.py): 40+ action primitives, 40+ market sensors, 12 comparison operators, 16 builtins, full parser. Strategy Generator (generator.py): genetic programming evolution — crossover, mutation, tournament selection, pool management. Supporting: discrepancy tracking, execution quality, hooks, feature importance, observability, parallel eval, auto-rollback, stress testing, structured observations, trajectory recording.
This commit is contained in:
120
MALKHUT/malkhut/training/structured_obs.py
Normal file
120
MALKHUT/malkhut/training/structured_obs.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
Structured Observability — per-decision feature attribution and metrics.
|
||||
|
||||
Tracks:
|
||||
- Which features drove each decision
|
||||
- Feature importance over time
|
||||
- Decision quality metrics
|
||||
- Regime-specific performance
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
||||
|
||||
from malkhut.state import MarketWorldState
|
||||
from malkhut.actions import FulfilmentAction, PlannedPolicy, RiskDecision
|
||||
from malkhut.features import DefaultFeatureExtractor, FeatureExtractor
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DecisionMetrics:
|
||||
"""Per-decision metrics."""
|
||||
ts_ns: int
|
||||
symbol: str
|
||||
action_kind: str
|
||||
approved: bool
|
||||
plan_latency_ns: int
|
||||
entropy: float
|
||||
sims: int
|
||||
feature_attribution: Mapping[str, float]
|
||||
regime: str
|
||||
|
||||
|
||||
class StructuredObservability:
|
||||
"""
|
||||
Structured observability with per-decision feature attribution.
|
||||
|
||||
Tracks which features drive decisions and computes aggregate metrics.
|
||||
"""
|
||||
|
||||
def __init__(self, feature_extractor: Optional[FeatureExtractor] = None) -> None:
|
||||
self._extractor = feature_extractor or DefaultFeatureExtractor()
|
||||
self._decisions: list[DecisionMetrics] = []
|
||||
self._feature_importance: Dict[str, List[float]] = defaultdict(list)
|
||||
self._regime_performance: Dict[str, List[float]] = defaultdict(list)
|
||||
self._total_decisions = 0
|
||||
|
||||
def record_decision(
|
||||
self,
|
||||
state: MarketWorldState,
|
||||
planned: PlannedPolicy,
|
||||
decision: RiskDecision,
|
||||
plan_ns: int,
|
||||
regime: str = "unknown",
|
||||
) -> None:
|
||||
"""Record a decision with full feature attribution."""
|
||||
fv = self._extractor.extract(state).values
|
||||
|
||||
# Compute feature attribution (which features are "active")
|
||||
attribution = {}
|
||||
for name, value in fv.items():
|
||||
if abs(value) > 1e-6:
|
||||
attribution[name] = value
|
||||
|
||||
metrics = DecisionMetrics(
|
||||
ts_ns=state.ts_ns,
|
||||
symbol=state.venue.symbol,
|
||||
action_kind=planned.selected_action.kind.value,
|
||||
approved=decision.approved,
|
||||
plan_latency_ns=plan_ns,
|
||||
entropy=planned.diagnostics.get("entropy", 0.0),
|
||||
sims=planned.diagnostics.get("sims", 0),
|
||||
feature_attribution=attribution,
|
||||
regime=regime,
|
||||
)
|
||||
|
||||
self._decisions.append(metrics)
|
||||
self._total_decisions += 1
|
||||
|
||||
# Track feature importance
|
||||
for name, value in attribution.items():
|
||||
self._feature_importance[name].append(value)
|
||||
|
||||
# Track regime performance
|
||||
self._regime_performance[regime].append(1.0 if decision.approved else 0.0)
|
||||
|
||||
def get_feature_importance(self, top_n: int = 10) -> List[Tuple[str, float]]:
|
||||
"""Get top N features by average absolute value."""
|
||||
scores = []
|
||||
for name, values in self._feature_importance.items():
|
||||
avg = sum(abs(v) for v in values) / len(values)
|
||||
scores.append((name, avg))
|
||||
scores.sort(key=lambda x: x[1], reverse=True)
|
||||
return scores[:top_n]
|
||||
|
||||
def get_regime_approval_rate(self, regime: str) -> float:
|
||||
"""Get approval rate for a specific regime."""
|
||||
approvals = self._regime_performance.get(regime, [])
|
||||
if not approvals:
|
||||
return 0.0
|
||||
return sum(approvals) / len(approvals)
|
||||
|
||||
@property
|
||||
def total_decisions(self) -> int:
|
||||
return self._total_decisions
|
||||
|
||||
@property
|
||||
def avg_latency_ns(self) -> float:
|
||||
if not self._decisions:
|
||||
return 0.0
|
||||
return sum(d.plan_latency_ns for d in self._decisions) / len(self._decisions)
|
||||
|
||||
@property
|
||||
def avg_entropy(self) -> float:
|
||||
if not self._decisions:
|
||||
return 0.0
|
||||
return sum(d.entropy for d in self._decisions) / len(self._decisions)
|
||||
Reference in New Issue
Block a user