Risk Controls¶
SafeBroker is the pre-trade control layer around a live broker. It validates each order before submission, persists risk state across restarts, and keeps the kill switch sticky until you clear it.
What Is Enforced Today¶
At order submission time, SafeBroker currently enforces:
- position and exposure caps
- per-order notional and share limits
- order-rate limiting and duplicate-order suppression
- asset allow/block lists
- price-deviation checks for priced orders
- stale-data rejection via
max_data_staleness_seconds - daily-loss rejection via
max_daily_loss - drawdown-triggered kill-switch activation via
max_drawdown_pct - shadow-mode execution through
VirtualPortfolio
The market-price checks now use the latest cached market snapshot from the engine instead of a placeholder fallback for first-entry trades.
LiveRiskConfig¶
from ml4t.live import LiveRiskConfig
config = LiveRiskConfig(
max_position_value=25_000,
max_position_shares=1_000,
max_total_exposure=100_000,
max_positions=10,
max_order_value=5_000,
max_order_shares=250,
max_orders_per_minute=5,
max_daily_loss=2_000,
max_drawdown_pct=0.10,
max_price_deviation_pct=0.05,
max_data_staleness_seconds=60,
dedup_window_seconds=1.0,
allowed_assets={"SPY", "QQQ"},
blocked_assets={"GME"},
shadow_mode=True,
kill_switch_enabled=False,
fail_on_reconciliation_mismatch=True,
state_file=".ml4t_risk_state.json",
journal_file=".ml4t_execution_journal.jsonl",
)
Control Groups¶
Position Limits¶
max_position_valuemax_position_sharesmax_total_exposuremax_positions
Order Limits¶
max_order_valuemax_order_sharesmax_orders_per_minutededup_window_seconds
Loss And Safety Limits¶
max_daily_lossmax_drawdown_pctmax_price_deviation_pctmax_data_staleness_secondsallowed_assetsblocked_assets
Execution And Persistence¶
shadow_modekill_switch_enabledfail_on_reconciliation_mismatchstate_filejournal_file
Persisted State¶
The risk state file persists more than just the kill switch. It now captures:
- current trading date and daily counters
- daily-loss baseline through
session_start_equity - persisted position and pending-order snapshots from the last clean disconnect
- kill-switch state and reason
That persisted snapshot is used again on the next SafeBroker.connect() to generate the startup reconciliation report.
If fail_on_reconciliation_mismatch=True, a non-clean reconciliation raises ReconciliationMismatchError and blocks startup outside shadow mode.
Execution Journal¶
SafeBroker also writes a JSONL execution journal. By default it lives next to the risk-state file and includes:
- order submissions and shadow fills
- manual or automatic kill-switch events
- startup reconciliation outcomes
- engine health transitions and recovery attempts when the broker is used through
LiveEngine
Set journal_file explicitly when you want the journal stored elsewhere.
Shadow Mode¶
Shadow mode is the recommended first deployment step:
In shadow mode:
- all normal risk checks still run
- orders are marked filled virtually
VirtualPortfoliotracks positions and cash locally- no real broker order is submitted
Kill Switch¶
The kill switch can be activated manually or by a loss breach:
Kill-switch state is persisted in state_file, so it survives process restarts until it is manually cleared.
SafeBroker Usage¶
from ml4t.live import IBBroker, LiveRiskConfig, SafeBroker
raw_broker = IBBroker(port=7497)
safe_broker = SafeBroker(
raw_broker,
LiveRiskConfig(
shadow_mode=True,
max_position_value=10_000,
max_order_value=2_500,
max_daily_loss=500,
max_data_staleness_seconds=60,
),
)
Inside Strategy.on_data(...), order placement stays synchronous because LiveEngine passes a thread-safe broker wrapper to the strategy:
def on_data(self, timestamp, data, context, broker):
if broker.get_position("AAPL") is None:
broker.submit_order("AAPL", 10)
# Pending orders can also be updated through the sync wrapper.
# broker.replace_order("ML4T-1", limit_price=189.5)
Errors¶
Risk violations raise RiskLimitError:
from ml4t.live import RiskLimitError
try:
await safe_broker.submit_order_async("AAPL", 10_000)
except RiskLimitError as exc:
print(f"blocked: {exc}")
Recommended Progression¶
- Start in shadow mode.
- Validate order flow, virtual positions, and runtime health.
- Move to paper credentials with conservative limits.
- Check
ml4t-live statusand startup reconciliation before going live. - Increase size only after repeated clean starts and expected fills.