v1.0.0 to v2.0.0

61 added, 33 removed. Audit A to A.

---
name: explainability-for-live-trading-signals
- description: Use when deploying ML trading models to decompose raw live signal predictions
- into local feature attributions (SHAP/contributions) and generate human-readable
- audit explanations
+ description: Use when deploying ML trading models to reconcile local feature attributions
+ (SHAP/contributions) against the score the model actually emitted, rank the drivers,
+ and write a human-readable audit record for every live signal
domain: algorithmic-trading
subdomain: financial-ml
tags:
- financial-ml
- explainable-ai
- shap-values
- signal-attribution
- compliance-audit
brokers_frameworks:
- SHAP
- Captum
- scikit-learn
- XGBoost
- Custom Explainers
- version: "1.0.0"
+ version: "2.0.0"
author: algo-trading-skills-contributors
license: Apache-2.0
---
## When to Use
- Invoke this whenever deploying Machine Learning (ML) or complex quantitative models for live signal generation. Black-box ML models (XGBoost, Random Forests, Neural Networks) that output raw probability or score signals (e.g. $+0.82$ Buy) without feature attribution create compliance vulnerabilities and lack trader trust. If an unexpected order fires during market stress, risk officers must instantly identify which input features drove the decision. Generating local feature attribution contributions ($\phi_i$), top driver rankings, and structured human-readable natural language audit reports for every live signal is mandatory.
+ Invoke this whenever a black-box or complex quantitative model emits live trading signals and someone — a risk officer during an incident, a model-governance reviewer, a supervisor requesting records — will later need to know *which inputs produced this specific score*. A raw $+0.82$ Buy with no attribution is unreviewable after the fact.
+ The skill consumes an attribution vector $\{\phi_i\}$ produced by an upstream explainer (SHAP, Captum, an EBM shape function, or a linear model's $w_i x_i$), **reconciles it against the score the model actually emitted**, ranks the drivers, narrates them, and writes an append-only audit record. Reconciliation is the point: an attribution vector that does not reproduce the model's output is not an explanation of that signal, and a log full of unreconciled explanations is worse than no log because it looks authoritative.
+
+ ## When NOT to Use
+
+ - **As a substitute for computing attributions.** This module does not run SHAP. If you have no $\phi_i$, you have nothing to reconcile.
+ - **As evidence that a model is correct, safe, or causal.** $\phi_i$ measures a feature's contribution to *this model's* output under the chosen background distribution. It says nothing about whether the feature causes the price move or whether the model is right. Attribution is not causation, and a coherent explanation of a bad signal is still a bad signal.
+ - **As a pre-trade risk control.** Explanations describe; they do not block. Exposure caps, drawdown limits and kill switches must sit out-of-band — see `kill-switch-and-drawdown-circuit-breakers`.
+ - **On the latency-critical path without measuring it.** Exact per-signal TreeSHAP costs real time. Where the explanation is for post-hoc audit, compute and log it asynchronously — see `model-inference-latency-budget-for-live-trading`.
+ - **To detect model decay.** Reconciliation catches a broken *explainer*, not a stale *model*. Use `model-staleness-detection` and `feature-importance-drift-monitoring`.
+ - **As an immutable ledger.** `log_explainable_signal` appends JSONL to a local file. Retention-grade immutability needs WORM storage, object-lock, or a hash-chained ledger.
+
## Prerequisites
- - Model feature input dictionary or vector $\{f_1: v_1, f_2: v_2, \dots, f_M: v_M\}$.
- - Model baseline value (expected prediction value $E[Y]$).
- - Feature attribution weights or SHAP values.
+ - The feature vector actually fed to the model for this instance, $\{f_1: v_1, \dots, f_M: v_M\}$.
+ - The local attributions $\phi_i$ for the same instance.
+ - The explainer's base value $\phi_0 = E[f(z)]$ — for SHAP, `explainer.expected_value` **from the same explainer instance** that produced the $\phi_i$.
+ - **The score the model actually emitted**, $\hat{Y}$, in the same output space as $\phi_0$ and $\phi_i$.
## Workflow
- 1. **Capture Live Model Prediction & Input Features**:
- - Ingest raw model output prediction score $\hat{Y}$ and feature values $X_t$.
+ 1. **Capture the prediction and the features together**:
+ - Record $\hat{Y}$ as emitted, plus $X_t$ as fed. Do not re-run the model to obtain $\hat{Y}$ for the log — a re-run can silently use a different model version than the one that traded.
- 2. **Compute Local Feature Attributions ($\phi_i$)**:
- - Decompose prediction score relative to model base value:
- $$\hat{Y} = \text{BaseValue} + \sum_{i=1}^M \phi_i$$
+ 2. **Obtain local attributions in a known output space**:
+ - **Decision point — establish the output space before anything else.** `shap.TreeExplainer` on an XGBoost `binary:logistic` model explains the **raw margin (log-odds)** by default, so $\phi_0 + \sum \phi_i$ equals the log-odds, *not* the probability from `predict_proba`. Reconcile in margin space, or construct the explainer with `model_output="probability"` (which requires `feature_perturbation="interventional"`) and reconcile in probability space. Never mix.
+ - **Decision point — the base value is not a free parameter.** `interventional` and `tree_path_dependent` perturbation give different `expected_value`s *and* different $\phi_i$. Pass the `expected_value` of the instance that produced these $\phi_i$; a hand-chosen constant will fail the gate, which is the correct outcome.
- 3. **Extract Top Positive & Negative Drivers**:
- - Rank features by contribution magnitude $|\phi_i|$.
- - Identify top bullish factors ($\phi_i > 0$) and top bearish factors ($\phi_i < 0$).
+ 3. **Run the additivity gate**:
+ $$\hat{Y} \stackrel{?}{=} \phi_0 + \sum_{i=1}^M \phi_i \quad\text{within}\quad \text{abs\_tol} + \text{rel\_tol}\cdot|\hat{Y}|$$
+ - **Decision point — a gate failure is a model-governance incident, not a warning.** It means the attribution vector belongs to a different model version, a different feature vector, or a different output space. `explain_signal` does not raise (that would destroy the evidence); it returns `reconciled=False` with a signed `reconciliation_error` and an `UNRECONCILED` banner on the summary. **Gate on `explanation.reconciled` before showing anyone the drivers**, and record the failure either way.
+ - **Decision point — loosen tolerances deliberately, never disable the gate.** Float32 or GPU-trained tree ensembles genuinely need slack; shap's own TreeExplainer assertion allows ~1% relative. Raise `reconciliation_rel_tol` explicitly and record that you did.
- 4. **Generate Human-Readable Audit Explanation**:
- - Format natural language audit string:
- `"BUY signal (+0.82) triggered primarily by High RSI Momentum (+0.35) and Low Volatility (+0.25), offset by Negative Sentiment (-0.08)."`
+ 4. **Rank drivers and disclose what was left out**:
+ - Sort by $|\phi_i|$; ties break by feature name so the same input always yields the same record.
+ - **Decision point — top-$N$ is a summary, not the explanation.** With 200 features, three drivers can be a few percent of total attribution magnitude. Report `attribution_coverage` and `residual_contribution` alongside them, and keep the full $\{\phi_i\}$ vector in the record.
- 5. **Write Compliance Audit Log (`log_explainable_signal`)**:
- - Store signal timestamp, prediction score, base value, feature contribution dictionary, and natural language summary into an immutable JSON compliance log.
+ 5. **Narrate by alignment with the signal, not by sign**:
+ - On a BUY, positive contributions *drove* it and negative ones *offset* it. On a SELL, the negative contributions drove it. Labelling every negative contribution as an "offset" tells an incident reviewer the opposite of what happened.
- > Full step-by-step procedure with broker-specific detail: see `references/workflows.md`.
- > Broker/framework coverage table for this skill: see `references/standards.md`.
+ 6. **Write the compliance record (`log_explainable_signal`)**:
+ - Append a single JSONL line carrying the UTC timestamp (epoch **and** ISO-8601), symbol, action, the emitted score, the reconstructed score, the base value, the reconciliation verdict, the ranked drivers, and the **complete** contribution vector — a reviewer must be able to re-derive $\phi_0 + \sum \phi_i$ from the record alone.
+ - Pass `executed_action` where the strategy's real decision is known, so the record proves the explanation belongs to the trade that happened.
+
+ > Full step-by-step procedure: see `references/workflows.md`.
+ > Attribution methods and regulatory touchpoints: see `references/standards.md`.
> Printable pre-flight checklist: see `assets/checklist.md`.
## Common Pitfalls
- - **Black-Box Production Signals**: Transmitting raw model prediction scores to execution algorithms without feature attribution logging.
- - **Global vs Local Attribution Confusion**: Relying on global feature importance (e.g. Gini importance) rather than local instance-level contributions for specific live signals.
- - **Unvalidated Feature Contribution Sums**: Failing to verify that base value plus sum of feature attributions equals the total model output score ($\text{BaseValue} + \sum \phi_i = \hat{Y}$).
+ - **Deriving the score from the attributions.** If the pipeline computes $\hat{Y} := \phi_0 + \sum \phi_i$ instead of comparing against the emitted score, the additivity check can never fail — a broken explainer produces a confident, well-formatted explanation of a signal the model never emitted, and any BUY/SELL label derived from that reconstruction is fiction. Take $\hat{Y}$ from the model.
+ - **Reconciling across output spaces.** Log-odds $\phi_i$ against a `predict_proba` output is the single most common silent failure; $\sigma(\phi_0 + \sum\phi_i) = p$, but $\phi_0 + \sum\phi_i \neq p$.
+ - **Mismatched feature names.** An attribution keyed `rsi` against a feature vector keyed `rsi_14` is an explainer/feature-store drift bug. Defaulting the missing value to $0.0$ writes a *false input value* into a compliance record; reject the mismatch instead.
+ - **Silent NaN.** A single `NaN` contribution makes the sum `NaN`; `NaN >= buy_threshold` and `NaN <= sell_threshold` are both `False`, so a corrupt attribution vector lands in the log as a clean `HOLD`. Reject non-finite values at the boundary.
+ - **Global importance in place of local attribution.** Gini/split-count importance is a property of the trained model, not of this instance. It cannot answer "why did *this* order fire".
+ - **A scale-blind materiality cut-off.** A hard-coded $\pm 0.001$ floor drops every driver of a model scored in basis points. Default to keeping all non-zero contributions; set a threshold only when a sampling explainer (KernelSHAP) genuinely produces noise, and set it relative to the score's scale.
+ - **Presenting top-$N$ as the whole story.** Without coverage and residual, three drivers out of two hundred read as a complete explanation.
+ - **Naive local timestamps.** A local-time stamp cannot be aligned with order records kept under MiFID II RTS 25 clock-synchronisation rules and shifts silently across a DST boundary. Stamp in UTC.
+ - **Holding the caller's dict by reference.** Storing `contributions_dict` without copying lets the caller mutate a written audit record after the fact.
+ - **Calling an append-only file immutable.** Say what it is; a reviewer who believes the log is tamper-evident when it is not draws the wrong conclusion from it.
## Verification
- - Submit synthetic feature vector and verify `LiveSignalExplainer` computes exact feature contributions summing to model output score.
- - Verify natural language audit report correctly identifies top positive and top negative feature drivers.
- - Verify `log_explainable_signal()` writes structured JSON audit entry.
- - Run unit test suite `python scripts/test_signal_explainer.py` and confirm 100% pass rate.
+ - Feed a linear model's exact attributions ($\phi_i = w_i x_i$, $\phi_0 = b$) with the true $\hat Y$: confirm `reconciled` is `True`, `reconciliation_error` $\approx 0$, and `attribution_coverage` matches the hand-computed $|\phi|$ share of the listed drivers.
+ - **Negative case (the one that matters):** hold the attributions fixed and pass a different `model_prediction`. Confirm `reconciled` is `False`, the error equals the hand-computed difference, `signal_action` follows the *emitted* score rather than the reconstruction, and the summary starts with `UNRECONCILED`.
+ - Confirm log-odds contributions reconciled against $\sigma(\cdot)$ of their own sum fail the gate.
+ - Confirm a SELL signal names its negative contributions under "driven by", not "offset by".
+ - Confirm NaN/Inf contributions, an empty attribution vector, and a contribution naming an unknown feature each raise `SignalExplainerError`; confirm inverted thresholds (`sell_threshold >= buy_threshold`) raise at construction.
+ - Confirm the JSONL record contains `all_contributions` and `base_value`, and that `base_value + sum(all_contributions.values())` re-derives `score` from the persisted line alone.
+ - Run `python -m unittest discover -s skills/explainability-for-live-trading-signals/scripts` and confirm 100% pass rate.
## Related Skills
+ - `explainable-boosting-machines-for-regulated-signals`
+ - `model-card-documentation-for-trading-models`
+ - `feature-importance-drift-monitoring`
- `feature-store-for-live-and-backtest-parity`
- - `ensemble-signal-combination-without-overfitting`
- - `regime-detection-for-strategy-switching`
- ---
+ - `structured-logging-for-post-incident-forensics`