Interactive Brokers Client Services
Jun 17 12:40 AM
Message Center Notification
Interactive Brokers ticket response regarding Kanex account messaging.
Joaquín Medina
Jun 17 12:34 AM
RE: Re:[## 708 ##] RE: RMA for EXT-4KHD70M
AVIT Vision requests credit memo for destroyed equipment per RMA 708.
Microsoft Outlook
Jun 16 10:36 PM
Undeliverable: FW: TIER 3 Trending Stocks Part 2 (1 New Red Candle): $DELL, $OSCR, $ONDS, $ZETA, $LMND and $TMC (June 17, 2026-daily)
Microsoft Outlook: Undeliverable: FW: TIER 3 Trending Stocks Part 2 (1 New Red
Interactive Brokers Client Services
Jun 16 9:59 PM
Security Notice for User k******8: Verify Log In
Fake Interactive Brokers security alert requesting account verification.
Microsoft Outlook
Jun 16 9:38 PM
Undeliverable: FW: $QCOM (June 17, 2026-daily)
Microsoft Outlook: Undeliverable: FW: $QCOM (June 17, 2026-daily)
TDhruv Sharma
Jun 16 9:05 PM
[Teams oneOnOne] (Teams DM)
Dhruv Sharma: [Teams oneOnOne] (Teams DM)
Fabiola Hernandez
Jun 16 6:55 PM
Tomorrow is Spirit Day – Souvenir T-Shirt Day
Spirit Day reminder: kids invited to wear souvenir t-shirt tomorrow at camp.
Derrick
Jun 16 6:19 PM
Request for More Info: EXT-USBC2XI-100M - Dual-Host USB 3.2 Gen 1 Extender over CAT6a — USB-C & USB-B Inputs, 100m/328ft
Product inquiry for USB extender; requesting pricing, specs, availability.
Sydnee Agent (AI)
Jun 16 6:00 PM
[Spread Cal] 2026-06-17 — per-stock max_spread_bps results
Sydnee Agent (AI): [Spread Cal] 2026-06-17 — per-stock max_spread_bps results
Sydnee Agent (AI)
Jun 16 5:40 PM
[Calibration Daily] 2026-06-17
Sydnee Agent (AI): [Calibration Daily] 2026-06-17
Sydnee Agent (AI)
Jun 16 5:30 PM
Sydnee algo daily — dev $-2,699 · prod $+0 · 6d window
Sydnee Agent (AI): Sydnee algo daily — dev $-2,699 · prod $+0 · 6d window
Find My
Jun 16 5:24 PM
A sound was played on Kelvin’s iPad mini.
Find My: A sound was played on Kelvin’s iPad mini.
Sammy Cemo, Matt Pourcho and Anthony DeLorenzo
Jun 16 4:04 PM
New Pricing | Fortune 40 Credit | STNL Industrial | 11 Yr. WALT | SoCal
⚠ PHISHING: employee impersonation: display name matches 'anthony' but sender is cbre.com
Microsoft Store
Jun 16 3:23 PM
Meet the new Surface lineup—with limited-time offers
Microsoft Store: Meet the new Surface lineup—with limited-time offers
[email protected]
Jun 16 2:34 PM
New Funding Program
Funding broker offering working capital solutions and consolidation services.
Sammy Cemo and Anthony DeLorenzo
Jun 16 2:03 PM
Single-Story Owner-User Offering in Newport Beach
⚠ PHISHING: employee impersonation: display name matches 'anthony' but sender is cbre.com
Aarti Gupta
Jun 16 1:53 PM
Re: CR-TOUCH6R 6" Widescreen Touch Panel with Knob, RS232/RS485 & PoE
Aarti Gupta: Re: CR-TOUCH6R 6" Widescreen Touch Panel with Knob, RS232/RS
[email protected]
Jun 16 1:43 PM
Action required: Your June booking bonuses expire soon
⚠ PHISHING: phishing subject pattern: 'Action required' from external sender vacationoffer.com
TAnthony Patino
Jun 16 1:30 PM
[Teams oneOnOne] (Teams DM)
LK account overdrawn; bank fee applied, needs replenishment before QB charge.
TAnthony Patino
Jun 16 1:27 PM
[Teams oneOnOne] (Teams DM)
Anthony Patino sent a brief Teams DM referencing 'LK'.
TAnthony Patino
Jun 16 1:27 PM
[Teams oneOnOne] (Teams DM)
Anthony Patino reports insufficient funds issue.
Amazon Payments
Jun 16 12:44 PM
Action requise sur le compte Amazon Payments
Fake Amazon Payments suspension notice in French requesting account verification.
Nick Daniel
Jun 16 11:04 AM
Quick question
TriNet HR solution sales inquiry.
Aarti Gupta
Jun 16 10:04 AM
Re: Order help #1227
Aarti Gupta: Re: Order help #1227
Aarti Gupta
Jun 16 10:03 AM
Re: Order help #1227
Aarti Gupta: Re: Order help #1227
Aarti Gupta
Jun 16 9:59 AM
EXT-USBCPD4K-70M 18Gbps USB-C 4K60 HDBaseT 3.0 Extender with 100W PD (70m)
Aarti Gupta: EXT-USBCPD4K-70M 18Gbps USB-C 4K60 HDBaseT 3.0 Extender with
Sean McGinley
Jun 16 9:58 AM
Order help #1227
Customer inquiry about HDMI extender transmitter compatibility for order #1227.
IBKR FYI
Jun 16 9:51 AM
FYI: Upcoming Exchange Holidays
Exchange holiday notice: MIAX, NASDAQ, NYSE closed June 19.
IBKR FYI
Jun 16 9:27 AM
FYI: Upcoming Exchange Holidays
NASDAQ/MIAX exchange holiday June 19, 2026 — no trading.
Hims
Jun 16 9:02 AM
Kelvin - action required on your account
⚠ PHISHING: phishing subject pattern: 'action required' from external sender icloud.com
[email protected]
Jun 16 9:02 AM
You have a new estimate
Suspicious medical estimate link from Providence—verify legitimacy before clicking.
Mail Delivery System
Jun 16 8:20 AM
Mail delivery failed [Invoice #36077 PO: SH260526762J]
Mail delivery failed for invoice #36077 to Mike Vanderkamp; recipient address rejected.
Mail Delivery System
Jun 16 8:19 AM
Mail delivery failed [Invoice #36078 PO: SH260430677J]
Mail delivery failed: [email protected] → [email protected] for invoice #36078.
Mail Delivery System
Jun 16 8:19 AM
Mail delivery failed [Invoice #36079 PO: SH2606101007J]
Mail delivery failure: invoice to [email protected] rejected by recipient server.
TDhruv Sharma
Jun 16 8:16 AM
[Teams oneOnOne] (Teams DM)
Dhruv Sharma: [Teams oneOnOne] (Teams DM)
Zoho Campaigns
Jun 16 7:55 AM
Campaign "NEW USB-C + USB-B Dual-Host Extender · 100m" has been successfully Sent - Zoho Campaigns
Zoho Campaigns notification: USB-C/USB-B extender product campaign sent successfully.
Ali Pacheco
Jun 16 7:53 AM
Request for More Info: EXT-USBC2XI-100M - Dual-Host USB 3.2 Gen 1 Extender over CAT6a — USB-C & USB-B Inputs, 100m/328ft
Product inquiry for USB 3.2 extender from external vendor.
KanexPro
Jun 16 7:51 AM
NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro: NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro
Jun 16 7:50 AM
NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro: NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro
Jun 16 7:50 AM
NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro: NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro
Jun 16 7:49 AM
NEW USB-C + USB-BDual-Host Extender · 100m
KanexPro: NEW USB-C + USB-BDual-Host Extender · 100m
TDhruv Sharma
Jun 16 7:33 AM
[Teams oneOnOne] (Teams DM)
Dhruv Sharma: [Teams oneOnOne] (Teams DM)
TDhruv Sharma
Jun 16 7:32 AM
[Teams oneOnOne] (Teams DM)
Dhruv Sharma: [Teams oneOnOne] (Teams DM)
Bank of America
Jun 16 6:16 AM
Your available account balance is low
Bank of America: Your available account balance is low
Let's Talk Supply Chain
Jun 16 6:01 AM
Are you ready?
Suspicious meeting confirmation from unknown sender with obfuscation.
Interactive Brokers Client Services
Jun 16 5:18 AM
Message Center Notification
Interactive Brokers ticket response regarding Kanex account notification.
Benjamin & Williams
Jun 16 5:03 AM
Commercial Claim Discovery Documents Our file:D-8222 Debtor: VICTORIA ROPA ELEGANTE
Fake debt collector demanding payment on unknown commercial claim within 24h.
IBKR FYI
Jun 16 5:01 AM
FYI: Option Expiration Notification
Oracle options expiring 18JUN2026 — immediate action required.
IBKR FYI
Jun 16 5:00 AM
FYI: Option Expiration Notification
MSTR call option expiring 18JUN2026; action needed if extending position.
Sydnee Agent (AI)
Jun 16 4:25 AM
Sydnee nightly — exit_flow audit 2026-06-16 — 0P0 8P1 6R
Sydnee Agent (AI): Sydnee nightly — exit_flow audit 2026-06-16 — 0P0 8P1 6R
Sydnee nightly — config_drift audit 2026-05-31 — 0P0 4P1 8R
AI verdict
employee
high
· confidence: high
· by internal-exempt
“Sydnee Agent (AI): Sydnee nightly — config_drift audit 2026-05-31 — 0P0 4P1 8R”
Reasoning: @sydnee.ai is a protected domain — hard exemption
Sydnee nightly — config_drift audit — 2026-05-31
P0 findings: 0 P1 findings: 4 Risks: 8
- Area: Config Drift (Sunday theme)
- Bugs found (P0 / P1): 0 / 4 (0 new, 4 carried)
- Risks noted: 9 (1 new, 8 carried)
Cross-check: `git log --oneline -30` and `docs/strategy_decisions.md` reviewed before filing.
`docs/audit_2026-05-25_config_drift.md` (last config-drift) and `docs/audit_2026-05-31_weekly.md`
reviewed. No P1 or RISK items from previous audits were resolved in the intervening commits
(last code commit `0f470d7` feat(scalp); all items confirmed still present in current code).
---
Full report (dev branch): https://github.com/kanex1/sydnee.signals/blob/dev/docs/audit_2026-05-31_config_drift.md
Reply FROM [email protected] to [email protected] to request fixes, e.g.:
"code_task on sydnee-signals-dev: apply fix for the P0 about RVOL threshold in bot.py"
Sydnee Agent will propose + you APPROVE (or plain 'approve') + auto-push to dev.
--- Full audit below (first 12 KB) ---
# Nightly Audit 2026-05-31 — CONFIG DRIFT
## Summary
- Area: Config Drift (Sunday theme)
- Bugs found (P0 / P1): 0 / 4 (0 new, 4 carried)
- Risks noted: 9 (1 new, 8 carried)
Cross-check: `git log --oneline -30` and `docs/strategy_decisions.md` reviewed before filing.
`docs/audit_2026-05-25_config_drift.md` (last config-drift) and `docs/audit_2026-05-31_weekly.md`
reviewed. No P1 or RISK items from previous audits were resolved in the intervening commits
(last code commit `0f470d7` feat(scalp); all items confirmed still present in current code).
---
## Fixed Since Last Config-Drift Audit (2026-05-25)
*(No config-drift items resolved. 36 code commits landed since 2026-05-25 — all feature or
bug-fix work, none touching the carried config issues below.)*
---
## Findings
### BUG [P1] (carried from 2026-05-24): `TV_BXT_MAX_AGE_SEC` env var documented but never implemented
**File:** `bot.py:3681`
**Evidence:**
```python
# bot.py:3681 — _tv_bxt_signal() docstring only
"""... 0 (none). Optionally ignore stale signals via TV_BXT_MAX_AGE_SEC."""
# grep "TV_BXT_MAX_AGE_SEC" bot.py → single hit, docstring only — no os.environ.get call
```
`tv_bxt_signal:{sym}` Redis key has no TTL. A stale Friday-close signal persists through the
weekend. On Monday's first loop tick the bot acts on it before the first real webhook arrives.
**Impact:** TV-BXt model (SNDK, real money since 2026-05-01) can enter based on a signal
up to 65+ hours stale, potentially in the wrong direction vs. the Monday-gap open.
**Fix:** Add age check in `_tv_bxt_signal()` using `tv_bxt_signal_ts:{sym}` Redis key, or
set `EX` TTL on the webhook write at `bot.py:11832–11843`.
---
### BUG [P1] (carried from 2026-05-17): `REVERSE_TIME_DECAY` uses `== "1"` — ignores `true`/`yes`
**File:** `bot.py:5749`
**Evidence:**
```python
# bot.py:5749 — sole check point for the flag
if os.environ.get("REVERSE_TIME_DECAY", "0") == "1" and _in_rth:
```
Every other boolean env flag in bot.py uses `.lower() in ("1", "true", "yes")`.
`REVERSE_TIME_DECAY=true` (YAML-style compose) silently has no effect.
**Impact:** If dev docker-compose is edited to `REVERSE_TIME_DECAY: "true"`, the morning size
ramp is silently disabled with no diagnostic.
**Fix:** Change to `os.environ.get("REVERSE_TIME_DECAY", "").lower() in ("1", "true", "yes")`.
---
### BUG [P1] (carried from 2026-05-17): `daily_loss_limit` equals `capital` — DLL disabled
**File:** `config.json:3,6`; `core/risk.py:105`
**Evidence:**
```json
{ "capital": 50000, "daily_loss_limit": 50000 }
```
```python
# core/risk.py:105 — can_trade() gate
if self.state.realized_pnl <= -self.cfg.daily_loss_limit:
return False, ...
```
DLL fires only after a full $50,000 intraday loss — 100% drawdown. Bot is live on real money.
The DLL is also the backstop that the TV-BXt SHIELD exemption (`8535f8e`) relies on.
**Fix:** `"daily_loss_limit": 1500` (3% of $50K). Also set `core/risk.py:RiskConfig.daily_loss_limit`
dataclass default to match (currently `1_500.0`, which would be correct — just fix config.json).
---
### BUG [P1] (carried from 2026-05-31 weekly): `TV_BXT_TRAIL_AGAINST` default mismatch — live=`1.0` vs history endpoint=`1.5`
**File:** `bot.py:3750` vs `bot.py:14062`
**Evidence:**
```python
# bot.py:3750 — _obv_trail_stop_hit() LIVE TRADING path:
agnst = float(os.environ.get("TV_BXT_TRAIL_AGAINST", "1.0"))
# bot.py:14062 — api_trade_obv_trail() history REPLAY endpoint:
agnst = float(os.environ.get("TV_BXT_TRAIL_AGAINST", "1.5"))
```
When `TV_BXT_TRAIL_AGAINST` is unset (the default path), the live engine trails at 1.0×ATR
against-trend, but the OBV-trail history endpoint reconstructs at 1.5×ATR. Every closed-trade
stop timeline shown on the dashboard reflects a 50% wider trail than what actually ran.
**Impact:** All historical stop-analysis on trade cards is wrong when the env var is unset.
Post-trade reviews ("was the stop too tight?") draw wrong conclusions.
**Fix:** Change `bot.py:14062` from `"1.5"` to `"1.0"`. One-line fix.
---
### RISK (new): `TV_BXT_NLV_PCT = 50.0` sizing bypass absent from `strategy_decisions.md`
**File:** `bot.py:6885–6898`; `docs/strategy_decisions.md`
**Evidence:**
```python
# bot.py:6885-6898 — TV-BXt sizing override
# Bypasses MAX_POSITION_PCT and all earlier multipliers.
_tv_pct = float(os.environ.get("TV_BXT_NLV_PCT", "50.0"))
_tv_shares = int((nlv * _tv_pct / 100.0) / price)
```
`TV_BXT_NLV_PCT = 50.0` (default) sizes each TV-BXt entry to 50% of NLV — $25,000 per trade on
a $50K account. This explicitly bypasses `MAX_POSITION_PCT` (10% cap), ATR-risk sizing, and all
conviction multipliers. Only the `buying_power_pct` margin cap (bot.py:6913) and the DLL SHIELD
remain active.
No entry exists in `docs/strategy_decisions.md` for this sizing decision. The quarantine guard
was raised 25%→55% (`a4a5277`) to accommodate it, but the underlying `TV_BXT_NLV_PCT=50.0`
decision — bypassing the normal 10% NLV position cap — has no structured hypothesis, backtest,
or validation record.
**Impact:** If `TV_BXT_NLV_PCT` is accidentally left at default in a fresh staging reprovisioning,
or if SNDK adds a second symbol to `TV_BXT_SYMBOLS`, the bot can open two $25K positions
simultaneously on a $50K account. No post-trade review record exists to catch if this sizing
is underperforming vs. the ATR-risk approach.
**Fix:** Add a `strategy_decisions.md` entry for the TV-BXt 50% NLV sizing: hypothesis,
approval date, expected outcome, and validation criteria.
---
### RISK (carried from 2026-05-25): `config.json:max_concurrent_positions: 50` disables concurrent-position gate
**File:** `config.json:24`; `core/risk.py:114`; `bot.py:6781`
**Evidence:**
```json
"max_concurrent_positions": 50
```
```python
# bot.py:6781 — gate in live path
allowed, reason = self.risk.can_trade()
# core/risk.py:114 — fires only when open_positions >= 50
```
With 8 symbols in the watchlist, maximum realistic concurrent positions = 8–12. Gate never fires.
The concurrent-position circuit breaker is effectively off on real money.
**Fix:** `"max_concurrent_positions": 5` (or 8 at most for an 8-symbol book).
---
### RISK (carried from 2026-05-25): `config.json:risk_per_trade_pct: 2.0` bypassed by env-var sizing path
**File:** `config.json:7`; `core/risk.py:19`; `bot.py:6179–6210`
**Evidence:**
```json
"risk_per_trade_pct": 2.0
```
```python
# bot.py:6179-6210 — live sizing path (not config.json):
sym_risk_pct = max(1.0, min(3.0, float(os.environ.get("RISK_ATR_BUDGET", "12.0")) / daily_atr_pct))
risk_amount = min(risk_amount, nlv * float(os.environ.get("MAX_RISK_PCT", "0.015")))
```
Config.json value is read only by the backtester (`core/risk.py:position_size()`). Live sizing
reads `RISK_ATR_BUDGET` and `MAX_RISK_PCT` env vars — not `risk_per_trade_pct`. Three sources,
three values: `config.json` = 2.0%, `core/risk.py` default = 1.5%, live `MAX_RISK_PCT` default = 1.5%,
dev override = 3.0%.
**Impact:** An operator reading `config.json` to understand per-trade risk sees 2.0% — but actual
live cap is the `MAX_RISK_PCT` env var (default 1.5%).
**Fix:** Either (a) add a comment to `config.json` noting `risk_per_trade_pct` is backtester-only
and live sizing is env-driven, or (b) wire live sizing to read this value as the base.
---
### RISK (carried from 2026-05-24): Oracle `HEALTH_PORT` and bot `DASH_PORT` both default to `8083`
**Files:** `oracle/config.py:56`; `bot.py:184`; `Dockerfile.oracle:12`; `Dockerfile:20`
```python
# oracle/config.py:56
HEALTH_PORT = int(os.environ.get("ORACLE_HEALTH_PORT", "8083"))
# bot.py:184
DASH_PORT = int(os.environ.get("DASH_PORT", "8083"))
```
Both services `EXPOSE 8083`. Silent dashboard outage if oracle gets the host-8083 binding
first on a compose-with-ports deploy.
**Fix:** Change `ORACLE_HEALTH_PORT` default to `8084` in `oracle/config.py:56` and `Dockerfile.oracle:12`.
---
### RISK (carried from 2026-05-24): `config.json:atr_stop_multiplier` (1.5) diverges from code fallback (2.0)
**Files:** `config.json:8`; `bot.py:750`
```python
# bot.py:750
self.atr_stop_mult = self.cfg.get("atr_stop_multiplier", 2.0)
```
Removing or misspelling the config key silently widens stops by 33% with no log or alert.
**Fix:** Add an inline comment to `config.json` documenting that the calibrated value is 1.5 and
the code fallback is 2.0, to flag the divergence clearly on review.
---
### RISK (carried from 2026-05-25): `pages.py:1448` hardcodes `"TradingView webhook (SNDK only)"` model label
**File:** `pages.py:1448`
```javascript
var nm = { ..., tv: 'TradingView webhook (SNDK only)' }[m];
```
`TV_BXT_SYMBOLS` env controls the actual symbol list; the dashboard label is static. If a second
symbol is added to `TV_BXT_SYMBOLS`, the label goes stale immediately.
**Fix:** Pass `TV_BXT_SYMBOLS` value through a Jinja2 template variable into the JS map.
---
### RISK (carried from 2026-05-17): `sentiment.cashtags` seed mismatches trading `symbols`
**File:** `config.json:2,37`
```json
"symbols": ["WDC", "STX", "MU", "SNDK", "APP", "META", "TSLA", "PLTR"],
"cashtags": ["APP", "KORU", "MU", "SNDK", "STX", "WDC"]
```
`META`, `TSLA`, `PLTR` are traded but absent from the cashtag seed — sentiment bypassed for
~15 min post-startup on those symbols. `KORU` is in the seed but not traded.
**Fix:**
```json
"cashtags": ["APP", "META", "MU", "PLTR", "SNDK", "STX", "TSLA", "WDC"]
```
---
### RISK (carried from 2026-05-25): ~40 undocumented feature-flag env vars
**Files:** `bot.py` (multiple); `oracle/config.py`; no `.env.example`
30+ env vars flagged in 2026-05-25 audit have grown by 9 more this week (`TV_BXT_DISABLE_ALGO_STOPS`,
`TV_BXT_FLOW_CONFIRM`, `TV_BXT_FLOW_RATIO_MIN`, `TV_BXT_FLOW_MIN`, `TV_BXT_VOL_MIN`,
`TV_WEBHOOK_ALLOWED_IPS`, `SCALP_SIGNAL_LOG`, `TAPE_SCANNER_SYMBOLS`, `SCALP_SIGNAL_LOG_SEC`).
None are in `.env.example` (no such file exists). A fresh staging reprovision would silently
default all of these, breaking the configured dev A/B stack.
`TV_BXT_DISABLE_ALGO_STOPS` (a stop-loss kill switch) is particularly dangerous if
accidentally left set — no operator reading CLAUDE.md or a `.env.example` would know it exists.
**Fix:** Add `.env.example` listing all strategy flags with expected dev/prod values. Highlight
`TV_BXT_DISABLE_ALGO_STOPS` as a kill switch, not a tuning parameter.
---
## OK (checked, working or recently fixed)
- **RVOL blowoff ceiling:** CLAUDE.md RVOL table correctly shows `> 5.0x` ceiling for RSI-extreme
skip (updated after 2026-04-15 strategy decision; code at `bot.py:6027` matches). ✓
- **CLAUDE.md Entry Time Blockers table:** Correctly reflects close-hour BXt hard block and
off-hours RSI-extreme toggle (default OFF). Both entries now present since 2026-05-19 correction. ✓
- **Off-hours RSI extreme toggle Redis default:** `_is_rsi_extreme_offhours_enabled()` returns
`False` when Redis key absent (`bot.py:10527`). Correct fail-safe behavior. ✓
- **`TV_BXT_DISABLE_ALGO_STOPS` boolean handling:** Uses `.lower() in ("1", "true", "yes")`
(`bot.py:3967`). Consistent with project convention. ✓
- **New scalp/tape scanner env vars:** `SCALP_SIGNAL_LOG` and related flags use
`.lower() not in ("1", "true", "yes")` / `.lower() in (...)` patterns correctly. ✓
- **`TV_BXT_FLOW_CONFIRM` boolean handling:** Uses `.lower() in ("1", "true", "yes")`
(`bot.py:4000`). Consistent. ✓
- **`TV_BXT_NLV_PCT` buying_power_pct backstop:** After the sizing override at `bot.py:6891–6898`,
the margin check at `bot.py:6913` (`max_cost = bp * self._buying_power_pct`) still caps shares.
TV-BXt sizing is bounded by available buying power; not a fully unconstrained path. ✓
- **Oracle `REDIS_MIRROR` guard:** Gated on non-empty var; no crash when unset. ✓
- **`signal_tf: 5` / `bias_tfs: [15]`:** Match bot.py defaults. ✓
- **`rsi_upper: 65` / `rsi_lower: 35`:** Match bot.py defaults. ✓
- **`TV_BXT_SECRET` reject-empty guard:** `if not expected: abort(403)` (`bot.py:11839`). ✓
- **`BXT_USE_15M_BIAS` default `"true"`:** Matches strategy