When to Use
Use this skill when a trading system has pre-trade or intra-trade risk controls (position limits, daily loss limits, price collars, message-rate limits, kill switches) that a human can override, and you need the override to leave an evidence trail rather than a shrug. The engine records each bypass in an append-only SHA-256 chain, classifies its severity, and flags the patterns that make a bypass suspicious on its face: an unauthorised principal, a missing justification, an authoriser who is also the requester, an override of a critical control with no expiry, and a record whose event time runs ahead of the recording clock.
It is worth being precise about whose obligation this serves, because the skill's own domain is one where vague appeals to "the regulators" are common and wrong:
- US broker-dealers with market access are subject to SEA Rule 15c3-5. The rule requires a documented system of risk management controls under the broker-dealer's "direct and exclusive control". SEC Division of Trading and Markets FAQ No. 18 addresses the override case directly: where a threshold is reached and orders are rejected, the firm may raise the threshold in accordance with supervisory procedures, and "the reasons for such modifications should be documented and retained as part of the broker-dealer's books and records."
- EEA and UK investment firms engaged in algorithmic trading are subject to RTS 6. Article 15(6) is the override provision, and it is stricter than an allowlist: override arrangements apply "in relation to a specific trade on a temporary basis and in exceptional circumstances" and are "subject to verification by the risk management function and authorisation by a designated individual" — two distinct actors, a bounded scope, and a bounded duration.
Everyone else — a proprietary trader, a fund's internal system, an individual running an algorithm — has no override-logging rule pointed at them, and should use this as good operational hygiene rather than as compliance evidence.
When NOT to Use
- As an enforcement gate. This engine records that a bypass happened; it does not decide whether one may happen. Authorisation enforcement belongs in the risk control itself, upstream. A system that lets
log_bypassreturn before checking whether the principal was allowed has already permitted the trade. - As the system of record. The hash chain is held in process memory. It is tamper-evident, not immutable: anything that can rewrite the process can recompute the chain. Persist entries and publish the chain head to append-only storage — see Common Pitfalls.
- For automated risk-limit recalibration. A scheduled or model-driven limit change is a configuration change with its own approval path, not an emergency override. Logging it here buries the genuine emergencies in noise. See
risk-control-configuration-change-approval-workflow. - As a substitute for the escalation itself. Recording a CRITICAL bypass is not the same as telling anyone. Wire the flags into an alerting path — see
risk-limit-breach-escalation-matrix. - To generate compliance assertions for a regime the firm is not in scope of. The engine cannot tell whether you are a broker-dealer with market access or an EEA investment firm; it will happily produce an authoritative-looking report either way.
Prerequisites
- Bypass event details:
event_id, timezone-aware ISO-8601timestamp_iso,bypassed_control,original_limit_value,override_value,authorized_by,justification. Timestamps must carry an explicit UTC offset; naive local times are rejected because they cannot be ordered across a DST transition. - Ideally also
requested_by,risk_function_verifier, andexpires_at_iso— the three fields that let the record answer the questions RTS 6 Article 15(6) actually asks. They default to empty for backward compatibility, which means an incomplete record still logs; it simply proves less. - The firm's own authorised-principal list. The module default (
risk_officer,cro,head_of_trading,system_admin) is an illustrative example, not a policy. - Calibrated engine options.
min_justification_charsdefaults to 5 and has no regulatory basis;require_risk_function_verificationandrequire_expiry_for_criticaldefault toFalsebecause they encode jurisdiction-specific RTS 6 expectations. Firms in scope of RTS 6 should turn both on. - Append-only storage for the persisted chain (WORM, an object store with object-lock, or a write-only sink the trading host cannot delete from).
Workflow
- Capture the Bypass at the Point It Happens, Not Afterwards: Call
log_bypasson the same code path that applies the override, and passrecorded_atexplicitly so the result is reproducible. Recording later loses exactly what a forensic reader needs: the gap between when the bypass occurred and when it was written down. That gap is itself a signal, which is why the engine storestimestamp_isoandrecorded_at_isoseparately and flags an event timestamp that runs ahead of the recording clock. - Let Structural Failures Raise — Never Swallow Them: A missing
event_id, an unparseable or timezone-naive timestamp, or a resubmitted id carrying different content raisesRiskBypassAuditError. Fix the record and resubmit. Catching the exception and continuing produces the one outcome the trail exists to prevent: a bypass that happened and was never recorded. - Treat a Retry as a Retry, Not a Second Bypass: Resubmitting an identical
event_idwith identical content returns the original entry unchanged. One override that was written twice because an acknowledgement was lost must appear once, or the counts a regulator reads are wrong. - Classify Severity from Registered Control Names:
CRITICALfor controls whose bypass removes a capital-protection or halt mechanism (kill switch, loss limits, VaR limit, margin-call halt);HIGHfor the RTS 6 Article 15(1) mandated pre-trade controls (price collars, maximum order values, maximum order volumes, message limits). The"LIMIT"/"CAP"substring rule is only a fallback for names you have not registered — register the firm's real control names rather than relying on it. - Flag the Governance Failures, Not Just the Unauthorised Ones: An override authorised by someone on the allowlist can still be improper. Self-authorisation (requester equals authoriser) defeats the separation RTS 6 Article 1(c) requires "to ensure that unauthorised trading activity cannot be concealed", and an open-ended override of a critical control is a permanent disablement wearing an override's clothes.
- Verify and Publish the Chain: Call
verify_integrity()before relying on a report, and publishchain_head_hashto append-only storage on a cadence. A chain nobody anchors externally proves only that the entries are consistent with each other, which is exactly what a tamperer would also arrange. - Generate the Report — Which Reads Verdicts, Never Re-derives Them:
generate_audit_report()reproduces the severity and suspicion verdict fixed at log time, together withintegrity_verifiedand the chain head.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Calling a Hash Chain "Immutable": A SHA-256 chain makes an edit detectable by anyone holding an earlier chain head. It does nothing against an attacker who edits a record and recomputes every hash after it — which is trivial when the whole chain lives in one process. Immutability comes from where the record is written, not from how it is hashed. Under SEA Rule 17a-4(f)(2)(i)(A) the electronic record must be preserved either in a non-rewriteable, non-erasable (WORM) format or under the audit-trail alternative, which requires a complete time-stamped trail of all modifications and deletions, the date and time of each create/modify/delete action, and the identity of the individual responsible. Publish chain heads out to that storage; do not claim the in-memory object satisfies it.
- Re-deriving the Verdict at Report Time: If the report recomputes severity or suspicion instead of reading what was decided at log time, one event can carry two different verdicts inside one regulatory record. That is worse than either verdict alone, and it is invisible until a regulator reads both. The engine computes once, stores, and reports the stored value — and the hash covers the verdict, so a later reclassification is detectable rather than silent.
- Treating an Allowlist as Segregation of Duties: An allowlist answers "may this person authorise overrides?" It does not answer "did the person who wanted the override approve their own request?" A head of trading who requests and authorises their own limit increase passes an allowlist check and fails RTS 6 Article 1(c). Populate
requested_by; without it the self-authorisation check has nothing to compare. - Logging the Override but Not Its Duration: RTS 6 Article 15(6) permits overrides "in relation to a specific trade on a temporary basis". An entry with no
expires_at_isocannot distinguish a five-minute exception for one block trade from a kill switch that has been off since March. Setrequire_expiry_for_critical=Trueand record the expiry. - Accepting a Justification Because It Is Long Enough:
min_justification_charscatches an empty field and nothing else. "Need more room" clears the default threshold and explains nothing. No length check can assess adequacy; the control is human review of the flagged entries, on a cadence, by someone who did not authorise them. - Swallowing
RiskBypassAuditErrorin the Order Path: The temptation under load is to wrap the logging call in a bareexceptso a malformed audit record can never block a trade. That converts a data-entry bug into a missing audit record on precisely the events most likely to be malformed — the rushed, unusual, suspicious ones. Route the failure to an operator instead. - Timezone-Naive Timestamps: An audit trail is evidence because it can be ordered. Naive local timestamps are ambiguous for one hour every autumn, and that hour is not reliably the quiet one. The engine rejects them.
- Assuming the Retention Period: RTS 6 Article 28(3)'s five years applies to the HFT order records of Article 28, not to override records; those fall under the firm's general MiFID retention (Delegated Regulation (EU) 2017/565 Article 72 — five years, extendable to seven at a competent authority's request). A US broker-dealer's periods come from SEA Rule 17a-4 instead. Confirm the applicable period with counsel rather than copying a number from a code comment.
Verification
- Log a
SPREAD_VETObypass and confirmlog_bypassreturnsMEDIUMandgenerate_audit_report().entries[0].severityis alsoMEDIUM— the two must never diverge. - Log a bypass by
unknown_userwith an empty justification and confirm the report entry still carries a non-Noneflag_reasonnaming both defects, and thatjustification=Noneproduces a report rather than anAttributeError. - Log
MAX_ORDER_VALUEand confirmHIGH, notMEDIUM— it is an RTS 6 Article 15(1) mandated control whose name contains neither "LIMIT" nor "CAP". - Log two events, mutate a stored event in place, and confirm
verify_integrity()returns(False, reason)naming the affectedevent_idand the report setsintegrity_verified=False. - Resubmit an identical event and confirm the total stays at 1; resubmit the same
event_idwith a differentoverride_valueand confirmRiskBypassAuditError. - Set
requested_byequal toauthorized_byand confirm the self-authorisation flag fires even though the principal is on the allowlist. - Submit a timezone-naive
timestamp_isoand confirmRiskBypassAuditError, and that the rejected event did not advancechain_head_hash. - Run
python -m unittest discover -s skills/risk-control-bypass-audit-logging/scriptsand confirm a 100% pass rate.
Related Skills
risk-control-configuration-change-approval-workflowkill-switch-and-drawdown-circuit-breakerssec-rule-15c3-5-risk-controls-usmifid-ii-algo-trading-compliance-eurisk-limit-breach-escalation-matrixstructured-logging-for-post-incident-forensicsrecord-retention-periods-by-jurisdictionemergency-manual-override-access-control