Imported from emfasys-labs/mytbot (
AGENTS.md). Install upstream withnpx skills add emfasys-labs/mytbot. Copyright stays with the author.
AGENTS.md
==========
This file is for Codex (Codex.ai).
Paste the contents of this file at the start of any Codex conversation
to instantly bring Codex up to speed on the project state.
Update the CURRENT STATE section after each work session.
PROJECT
mytbot — open-source autonomous multi-asset trading system under AGPL v3, published by Emfasys Labs, a division of Emfasys Ltd.
GitHub: https://github.com/emfasys-labs/mytbot.git
Owner: UK-based, trading stocks, bonds, ETFs, forex, crypto.
Primary broker: IBKR Pro. Crypto: Kraken + Binance.
ARCHITECTURE IN ONE PARAGRAPH
One-button system. python run.py starts everything — orchestrator brings up
Docker (Postgres, Redis), auto-discovers available brokers (skipping unavailable),
runs the trading loop and data pipeline, and exposes an API+WebSocket for the UI.
The UI has a single ON/OFF button (POST /system/start, POST /system/stop).
All brokers implement a single abstract interface in brokers/base.py.
Signals pass through an optional Signal Accumulation Engine (signals/accumulator.py) that maintains time-decayed, multi-source conviction per asset (quant + news + macro) before the signal engine emits a unified Signal.
The signal engine aggregates strategy outputs into a Signal (with legacy point-in-time AI modifier and optional accumulated overlay).
Every Signal passes through the risk engine (unconditional veto power).
Approved signals go to the execution engine which routes to the best broker.
Everything is logged. Runtime broker permissions support graceful fallback routing.
Risk parameters are managed by ParameterManager with layered overrides
(regime > AI > defaults) and bounded, auditable changes.
AI is local-first (rules → FinBERT → local LLM → optional paid fallback) — never places orders.
KEY FILES
run.py— THE entry point (python run.pystarts everything)system/orchestrator.py— state machine: OFF → STARTING → RUNNING → STOPPING → OFFsystem/dependency_manager.py— auto-start Postgres/Redis via Dockersystem/broker_manager.py— auto-discover and connect available brokerssystem/trading_loop/— controllable async trading loop package (TradingLoop,helpers.py)brokers/base.py— the adapter interface (FROZEN: backward-compatible optional fields only)brokers/registry.py— add new brokers here (one line)brokers/ibkr/adapter.py— IBKR + single-leg options (chain / qualify /Order.instrument_metadata)brokers/ibkr/universe.py— config-backed curated IBKR execution/discovery seedbrokers/ibkr/qualification.py— persisted IBKR contract qualification cachecore/instruments.py—OptionContractSpec(options as structured instruments)risk/options_env.py—ENABLE_OPTIONS/OPTIONS_*env merged into risk configbrokers/bybit/adapter.py— Bybit V5 (spot / USDT linear)brokers/_template/— copy this to add any new exchangerisk/engine.py— risk checks, kill switch, D115 FX + equity-index cluster capsrisk/intraday_derisk.py— D115 graduated portfolio-level derisk decision layerrisk/parameters.py— parameter manager (defaults + overrides + expiry)signals/accumulator.py— stateful time-decayed conviction per symbol (optional; YAML-gated)signals/anti_churn.py— D115 dedup / cross-strategy contradiction / post-fill cooldown gatesignals/engine.py— signal aggregation + accumulator integration + anti-churn wiringintelligence/trade_admission/— D196 pre-risk trade admission shadow ledger, diagnostics, outcome labelingstrategies/momentum.py— first strategy (momentum breakout)execution/engine.py— order placementexecution/router.py— smart order routingstorage/models.py— database schemaapi/server.py— FastAPI:/system/start,/system/stop,/system/status,/dashboard/snapshot,/pnl(week/month)api/pnl_periods.py— calendar week/month rollups overdaily_pnlfor/pnlsystem/dashboard_publish.py— persists allocator snapshot (dashboard.snapshotinControlState) for the UIconfig/risk_limits.yaml— all risk thresholds (editable without code change)config/m8_micro_live.yaml— optional micro-live profile (symbol/strategy/notional caps whenAPP_ENV=live)config/fundamentals.yaml— parameter defaults, absolute bounds, AI policyconfig/broker_permissions.yaml— runtime broker permission/fallback mapconfig/ibkr_universe.yaml— curated IBKR priority instruments; qualification still required before ordersconfig/data_pipeline.yaml— M2 symbols, intervals, News/FRED togglesconfig/ai.yaml— local-first AI config: providers, escalation policy, no daily capsconfig/session_exit_policy.yaml— broker-aware pre-close hold/trim/close policy by horizon and modeconfig/trade_admission.yaml— D196 Trade Admission Intelligence shadow/enforcement switchesconfig/profile_modes.yaml— D015 mode coefficients + emergency safety_bounds (allocator)config/allocation.yaml— D015 global opportunity replacement policy (validated byconfig/models.py)config/loaders.py—load_profile_modes()/load_allocation()core/models_runtime.py— runtime types: Opportunity, RegimeState, AllocationDecision, ExecutionPlancore/session_exit_policy.py— pre-close session-exit decision layer (hold,trim,close,defer)signals/volume_anomaly.py— D015 volume/flow features, detection vs scoring, YAML-weighted componentsignals/opportunity_engine.py— D015 opportunities; blends momentum proxy + volume component from M2 JSON + metadatarisk/regime_state.py— D015 market/regime context for allocator (stub)portfolio/allocation_engine.py— D015 global replacement allocator (stub)portfolio/balance.py— D222 canonical economic ledger, semantic HRP, factor admission, legacy reconciliationportfolio/target_ledger.py— D222 one absolute target owner across allocator/reserve pathsexecution/planner.py— D015 AllocationDecision → ExecutionPlandata/regime_metrics.py— cross-section feature fetch + aggregates for regimedata/feature_lookup.py— latestfeature_snapshots.featuresper symbolportfolio/d015_hold_switch.py— hold score + switching-cost penaltysignals/d015_weights.py— profile/regime dynamic coefficient resolversignals/opportunity_components.py— momentum/news/liquidity/structure component scoressystem/d015_shadow.py— optional per-signal D015 vs legacy log (envALLOCATOR_D015_SHADOW)core/signal_math.py— bounded_sigmoid, tanh_clip, normalize_zscore (Decimal)run_pipeline.py— M2: yfinance → features → Postgres; NewsAPI + FREDscripts/qualify_ibkr_universe.py— no-order IBKR contract qualification/cache warmerai/router.py— local-first AI router (drop-in for NewsClassifier)ai/providers/— provider implementations (rules, FinBERT, Ollama, Codex fallback)ai/escalation.py— necessity-based escalation engine (no hard daily caps)ai/schemas.py— shared AI types (ProviderResult, EscalationContext)ai/news_classifier.py— legacy Codex classifier (kept for backward compat)ai/pipeline.py— M6 orchestration: symbol news score + macro regimedocs/DECISIONS.md— architectural decision logdocs/BUILD_PLAN.md— milestones M1–M10 + task historydocs/NEW_MACHINE_SETUP.md— new PC / full reinstall (Python, Docker, Ollama, UI,.env)docs/M8_MICRO_LIVE.md— micro-live guardrails (APP_ENV=live, YAML profile)alembic/— DB migrations (URL from POSTGRES_* in env.py)tests/— pytest smoke tests.cursorrules— Cursor AI alignment rules
CURRENT STATE
- Milestone: Loss-attribution remediation (P0-P3) ✅
- Last completed task: D231-D237 — Full P0-P3 remediation of the D229 loss-attribution findings, implemented and verified against the live paper DB (read-only investigation first, then reversible code/schema changes;
python run.pyPID 76848 left running throughout, never stopped/restarted). D231 (P0.1):portfolio/global_edge_coordinator.py::propose_idle_loss_recycle_actionshad no minimum-holding-period protection (only a 15-min same-symbol cooldown) — 0% win rate, -$4,963.77 net, 25-min median hold by construction (filters exclusively for currently-underwater positions). Now gated through the samerisk/protective_exit_gate.pymin-hold logic every other exit path uses, via a new sharedstorage/fills_ledger.py::fills_age_seconds_by_symbol(). D232 (P1.3): partial trims (profit-harvest/capital-recycle/derisk) that would leave a sub-minimum-order-size remainder now close fully instead — the root cause of 210 open positions with a $942 median size, 28x under the orchestrator's own 2%-NAV floor. Fixed at the singleexecution/engine.py::_clamp_reduce_only_to_holdingschokepoint (same pattern as D165/D167.2). D233 (P1.4): edge-gate cost model raised from 30bps to 52bps round-trip (config/strategies.yaml::backtest) to match live-measured costs (scorecard foundportfolio_orchestratorat 51.3bps, 71% over the old assumption); re-ranscripts/run_edge_gate.py— all four proven weapons (trend_breakout/mean_reversion/trend_following/momentum_breakout) still clear the harsher bar with compressed margins. D234 (P1.5): newfills.opening_strategy/opening_signal_idcolumns (migrationd231a1b2c3d4, applied) trace a closing fill back to the strategy that opened the lot —fills.strategyon a close only ever named the EXIT mechanism (stop_loss_monitor/capital_recycle/etc.), so live expectancy for the four "proven" strategies was structurally unmeasurable (scripts/report_edge_scorecard.pyshowedlive_exp=0.00for all of them despite 620 combined opening fills). New ENTRY-STRATEGY LIVE EXPECTANCY table in the scorecard. D235 (P2):TradingLoop._orchestrator_strategy_trustno longer lets an optimistic backtest Kelly prior (backtest/edge_gate.py::edge_kelly_trust) amplify sizing above neutral (1.0x) without VERIFIED live evidence (enough closes, net-positive, PF>1) — previously an untested strategy could size up to 1.5x on backtest alone. Also fixed a silent key-mismatch bug:system/strategy_pnl_health.py::fetch_strategy_pnl_recentgrouped by the exit-mechanism name, not the entry strategy, so the posterior tilt had likely never engaged correctly in production; now grouped byopening_strategy. D236 (P3.7): new SYMBOL CHURN section in the scorecard flagging any(broker,symbol,day)with >5 fills — routinizes the manual-SQL discovery of AAPL (30 fills/8d), ETH-USD (55/8d) etc. D237 (P3.8):edge_gate_verdicts.jsonwas 3+ weeks stale and silently kept governing live sizing; newmax_verdict_age_daysconfig (default 14) triggers a runtime warning + scorecard header flag when exceeded. Full test suite: 2,187 passed, 3 skipped (from a 2,149 baseline). Seedocs/DECISIONS.mdD231-D237 for full detail on each. - Last completed task: D230 — Paper runtime restored by operator direction. The operator clarified that the intentionally stopped D229 runtime must remain on because this is a paper environment. Called
/system/startand verifiedstate=running,paper_mode=true,trading.running=true, pipeline running, all ten configured brokers connected and included, full accounting coverage, and no loop error. D229's loss attribution and design findings remain valid, but its mandatory shutdown/restart prohibition is superseded: paper trading stays active as the experiment and remediation environment unless the operator asks otherwise. No mass liquidation was performed. Seedocs/DECISIONS.mdD230. - Last completed task: D229 — Full loss attribution and paper-system warning. Operator challenged the dashboard's -£17,510.02 historical closed P&L. The value reconciles exactly to fills: -£10,912.51 gross realised P&L minus £6,597.51 fees across 1,175 fills. Net attribution: stop-loss -£12,820.49 (mostly D228 crystallising the already-losing legacy book), capital recycle -£4,963.77, portfolio orchestrator -£4,449.45, local paper repairs -£2,653.88, mean-reversion entry fees -£1,421.56, trend-following entry fees -£1,155.64, other paths about -£476, offset by profit harvest +£10,430.92. Largest symbol damage: MARA -£5,093, SMH -£2,743, AAPL -£2,515, AUDUSD -£2,022, ETH -£1,126, BTC -£949. The authoritative ledger still contains 210 open positions (the UI
/positionsresponse caps at 200), about £1.025m gross exposure and -£3,093 unrealised P&L: 109 Alpaca, 99 IBKR, one Kraken, one Capital.com. On 2026-07-03 the ledger recorded 15 fills around 00:09–00:49 London (nine stop-loss/profit-harvest actions, net about -£874); no fill occurred afterward, despite 308 loop iterations. The system was briefly stopped as a precaution, then restored by D230 following explicit operator direction. No mass liquidation was attempted. Seedocs/DECISIONS.mdD229. - Milestone: Protective-risk symmetry ✅
- Last completed task: D228 — Explicit stops can no longer be vetoed by minimum hold. Operator reported unrealised loss growing beyond -$10.3k. The D227 reserve fix had stopped new averaging down, but live logs proved a severe winner/loser asymmetry: profit harvest banked winners while both stop-loss and tier-2 intraday derisk repeatedly emitted valid actions and then suppressed them as
anti-churn:within_min_hold. MARA was down about -$4,992 / -10.3% and had breached its NAV-relative loss budget; numerous small equities were 10–19% down, yet the three-day strategy maturation window overruled their explicit 6% position stops. Protective min-hold is now scoped only to soft derisk/rebalance noise: explicit portfolio-budget stops and position stops always fire for every asset class, while structural/catastrophic/most-severe-tier exceptions remain. The first repaired run closed 26 breached paper positions reduce-only, including MARA and SMH, realising about -$11,464 gross with $84.81 fees and reducing unrealised P&L from about -$10,347 to +$928; this moved the already-existing economic loss rather than creating it. Live proof then exposed one stale-snapshot oversell: AAOI was closed correctly, but a later monitor pass used an obsolete positivePositionLogafter the authoritative fills ledger was flat and created a short. Removed that fallback—once fills exist,SUM(fills.signed_quantity)is unconditionally authoritative—and flattened only the accidental AAOI paper short. Final full suite: 2,149 passed, 3 skipped. Restartedpython run.pyPID 76848: running in paper mode, nine brokers included, no loop error, no negative ledger position, and no fill after the clean restart; IBKR Gateway/TWS remains externally unavailable on port 7497. Seedocs/DECISIONS.mdD228. - Milestone: Reserve-path churn containment ✅
- Last completed task: D227 — Negative-expectancy averaging-down bypass closed. Operator correctly challenged the roughly -$6.7k unrealised / -$5.3k daily net result and 200-position book. The D226 allocator fix worked, but a separate
portfolio_orchestrator_reservepath executed 21 directtrend_followingopens from 15:37–15:56, repeatedly adding to falling MARA, SMH, EEM, DAL, COPX, and AAPL. Its own learned metadata reported negative expected returns (for example MARA about-0.000457, EEM about-0.001740) and a below-threshold meta model, yet deployment-pressure relief converted those rejects into small paid exploration orders. Daily feature refresh timestamps also masqueraded as new bars, and reserve orders bypassedno_average_down. Fixed all three ownership points: mature non-positive expected-return buckets now receive zero capital and an enforced admission reject; pressure relief is disabled; reserve additions are blocked when the existing same-side position is underwater; feature timestamps are canonicalised to the actual timeframe bar; and the target ledger permits at most one reserve increase per symbol/bar. Focused verification: 89 passed; full suite: 2,147 passed, 3 skipped. Restartedpython run.pyPID 71676. First post-fix cycle: 420 candidates, 32 below-threshold candidates filtered, zero strategy opens/averaging-down fills; nine profit-harvest reductions realised about +$361 gross with $18 fees. Runtime is running with no loop error; nine brokers are included, while IBKR is externally unavailable because Gateway/TWS is not listening on127.0.0.1:7497. Seedocs/DECISIONS.mdD227. - Milestone: Loss-crystallisation control ✅
- Last completed task: D226 — Signal silence can no longer manufacture allocator exits. Operator reported continued losses. The book was initially about +$4.4k unrealised on the day, but the allocator had crystallised roughly -$1.29k across 21
portfolio_orchestratorfills and paid about $207 in allocator fees. Losing closes such as XLU, QCOM, COP, VCSH, KO, IYR, HYG, and XLP carriedorchestrator_reason=close, zero conviction, and no contributing strategy: a candidate disappearing from one cycle was being treated as a flat target and therefore as exit evidence once age/edge protection expired.OrchestratorConfig.close_on_signal_silencenow defaults false and the live YAML explicitly keeps it false; silence holds the position, while explicit opposing signals, stop-loss, derisk, session exits, reconciliation, and evidence-backed recycling retain authority. Profit-harvest repeat cooldown increased from 90 seconds to one hour after 41 profitable-but-fee-heavy trims in under an hour. Full suite: 2,144 passed, 3 skipped. Restartedpython run.pyas PID 71792 in paper mode. Post-restart evidence: first iteration complete, ten of ten brokers included with full coverage and no loop error; zero zero-conviction/strategy-less allocator reductions; the only allocator reduction had explicittrend_followingevidence and realised about +$85.94; initial profit banking realised about +$1,867 gross with about $33 fees. Later live marks moved the daily total back to about -$1,949 net without any additional fill, led by MARA, SMH, and EEM; their refreshed Yahoo/broker prices agree and their trend signals remain explicitly long, so that movement is market risk rather than the repaired forced-loss path. Seedocs/DECISIONS.mdD226. - Milestone: Full startup and runtime health repair ✅
- Last completed task: D225 — Full startup-health repair and final legacy balance reconciliation. Audited the supplied startup and found the system operational but not clean: balance invariants conflated policy-approved advisories with hard defects; the safe legacy plan still contained AGG/BND/VXUS; auto-training mishandled insufficient meta-label history and used
ridgefor a classification target; hosted local-reasoning had no credential; request logs exposed feed keys; expected Yahoo/IBKR discovery misses and startup coverage readiness produced false error/critical records; optional constituent sources were stale; and Bybit's derivatives open-order query omitted its settlement scope. Fixed each owning path, rebuilt the UI, restored local Ollamaqwen2.5:7b(startup plus real JSON scoring passed), and verified auto-training end-to-end with clean skips and four successful forecast models. The existing reduce-only, paper-only, one-action-per-cycle reconciliation removed AGG, BND, and VXUS (about -$214.27 realised, $20.30 simulated fees) while retaining IUSB/EFA. Final full suite: 2,143 passed, 3 skipped. Restartedpython run.pyas PID 59464: ten of ten brokers included, full coverage, pipeline and infrastructure healthy, all four AI stages active, no loop error, and freshruntime.invariants healthy=true / balance_healthy=true. Future logs redact secrets; feed keys exposed in historical logs still require external rotation. Seedocs/DECISIONS.mdD225. - Milestone: Broker credential rollback recovery ✅ 10/10
- Last completed task: D224 — Broker credential rollback investigated and all ten venues recovered. The dashboard correctly showed five brokers offline because
.envhad been replaced with an older 4,834-byte version at 2026-06-29 23:37. Cursor local-history metadata identifies the operation asUndo Reject Diff; the restored revision omitted Capital.com, Coinbase, IG, OANDA, and Trading 212 credential blocks, while/system/status::coverage.fullremained true only relative to the five credentials still configured. Recovered Capital.com, IG, and Trading 212 from the prior Cursor.envsnapshot and Coinbase from the original localDownloads/cdp_api_key.json, without printing secret values. The operator supplied separate OANDA tokens: read-only endpoint checks proved the first was live-only and the second practice-only, so each is stored in its matching environment slot and live OANDA trading remains disabled. Fixed OANDA credential resolution and broker discovery so tokens can no longer fall back across live/practice environments; focused verification: 37 passed. Restartedpython run.pyas PID 59564: all ten brokers are authenticated, connected, balance-ready, and included; accounting coverage is full with zero exclusions and no loop error. Seedocs/DECISIONS.mdD224. - Milestone: Portfolio economic-risk balance ✅
- Last completed task: D222 — Canonical economic portfolio balance and gradual legacy reconciliation. The first open-market audit after D221 found exact
AUDUSD/CMEvenue duplicates, near-identical bond/index/regional ETF stacks, FIDD behaving as a USD peg, extensive ETF/constituent overlap, 70% of deployed exposure at Alpaca, and crypto contributing about 49% of estimated risk from only 7.65% of NAV. Implemented all nine portfolio-balance controls: economic alias/venue aggregation; explicit alpha/hedge/cash/reserve roles; expanded ETF/factor look-through; semantic HRP blended with conviction; one absolute target ledger shared by primary and reserve paths; same-feature-bar reduction tombstones; all-asset venue consolidation; final risk and preflight overlap admission; broker-load-aware routing; balance-aware runtime invariants; fill-derived holding age; and a costed, one-action-per-cycle paper reconciliation plan with automatic live cleanup disabled. Full suite: 2,136 passed, 3 skipped. Restartedpython run.pyas PID 73924; ten of ten brokers are included, the first final-policy iteration completed, and there is no loop error. D222 paper reconciliation removed the redundant IBKRAUDUSDexpression (about +$7.54 realised, $6.76 fee) and theFIDD-USDcash-equivalent alpha position (about +$0.02 realised, $4.02 fee). The balance invariant remains intentionally unhealthy while the legacy book still contains theCMEvenue duplicate, true-substitute bond/ex-US stacks, broker concentration, crypto breadth, and two tiny remnants; its safe cleanup plan contains only those cases and no automatic sector/style liquidation. Seedocs/DECISIONS.mdD222. - Milestone: Publication readiness ✅
- Last completed task: D221 — Fifth clean live stability round. Operator requested another independent round after D220. Audited the untouched PID 60220 from iteration 13 through 15 with 150 endpoint probes across
/healthz,/system/status,/pnl,/positions, and/dashboard/snapshot; zero requests failed and maximum latency was 533ms. Final live audit found zero defect log signatures, no loop error, ten of ten brokers included, no disabled broker, all computed runtime invariants healthy, 384/384 filled orders linked to fills, zero working orders, no duplicate same-symbol venue exposure, no cash-equivalent position, and no new fills or fees. Unrealised P&L moved with market marks only; no loss was crystallised during the round. Sunday inactivity remained explicitly accounted for by closed equity/FX sessions and crypto edge gates. Seedocs/DECISIONS.mdD221. - Last completed task: D220 — Fourth clean round found and repaired expected Yahoo no-data error pollution. Operator requested one more clean round after D219. The initial audit ran across two fresh cycles and remained healthy for trading, broker coverage, API latency, accounting, and runtime invariants, but correctly failed the clean criterion because Yahoo discovery probes emitted repeated error-level
possibly delisted; no price data foundrecords for unsupported broker-catalogue symbols such asSYRUPUSD,SUIFDUSD, andSOLFDUSD.data/yfinance_fetch.pynow installs a narrow logging filter for only Yahoo's known no-data/delisted diagnostics; unexpected provider errors remain visible, while empty history continues to score zero and exclude the symbol. Addedtests/test_yfinance_fetch.py. Restartedpython run.pyPID 60220 and repeated the round from a new baseline: three live cycles, zero defect log signatures, zero endpoint failures, ten of ten brokers included, no disabled brokers, no loop error, zero invariant violations, zero new fills/fees, no working orders, no duplicate or cash-equivalent exposure, and 384/384 filled orders linked to fills. Full suite: 2,122 passed, 3 skipped. Seedocs/DECISIONS.mdD220. - Last completed task: D219 — Three consecutive clean stability rounds after proactive runtime fixes. Continued auditing until three complete post-fix rounds found zero new defects. Before restarting the clean count, fixed four additional runtime faults: instrument availability rebuilt each broker catalogue for every one of ~30k symbols and persisted each row with a separate statement; catalogue membership is now constant-time and availability writes use bounded bulk upserts.
/pnlsynchronously refreshed all broker balances after cache expiry; API NAV now serves the last coherent snapshot while one background refresh runs. Persisted coverage disables could strand a recovered broker after restart; risk broker gates now retain independent disable reasons, allowing coverage recovery to remove only its own gate without overriding manual gates. Trading 212 account-summary reads now have endpoint-specific caching and stale fallback on HTTP 429. Full suite: 2,119 passed, 3 skipped. Clean rounds then covered seven live iterations, 209 stressed/pnlreads plus final endpoint samples, scheduled and on-demand runtime invariants, broker coverage, logs, fill/order/position reconciliation, duplicate exposure, cash-equivalent exposure, and candidate-path explanations. Final state:running, paper mode, ten of ten brokers included, no disabled brokers, no loop/log errors, all invariant counts zero, 372/372 filled orders linked to fills, no new fills or fees, and final dashboard endpoints below 70ms. No new orders during the Saturday window is explained by closed equity/FX sessions, crypto edge gates, and same-feature-bar churn prevention. Currentpython run.pyPID 50668. Seedocs/DECISIONS.mdD219. - Last completed task: D218 — Proactive full-system money-path audit and continuous invariants. Operator correctly objected that defects were only being found after visible losses. Ran a proactive audit across live health, fill/position reconciliation, order lifecycle, churn, P&L attribution, admission learning, routing, paper execution, configuration, and the complete test suite. In addition to D217, fixed three more defects: (1) rerouted fills were labelled against the pre-route broker, leaving valid crypto outcomes
unpriced; outcome pricing now uses the actual matched fill broker, and 32 historical rows were repaired (34 positive,47 negative,360 not_executed, zero recentunpriced); (2) unsupported crypto could be probed through IBKR and synthetic paper mode could fill a product whose venue could not return a market price; router alternatives now honor symbol-specific broker translation and synthetic crypto opens fail closed on unavailable venue prices, while reduce-only exits remain allowed; (3) explicit empty OANDA credentials could inherit ambient practice credentials, defeating validation; explicit constructor credentials now fully override ambient token inheritance. Updated economic semantics forUSATcash-equivalent andWBETHwrapped ETH, then flattened those two invalid paper exposures locally (combined realised about +$6.42, zero fees). Addedsystem/runtime_invariants.py, run continuously by the trading loop and persisted asControlState(runtime.invariants), checking fills-to-position equality, filled orders without fills, stale working orders, and recent unpriced outcomes; unhealthy reports are logged as errors and attached to heartbeat telemetry. Full suite: 2,114 passed, 3 skipped. Restartedpython run.pyPID 55364; liveruntime.invariantspublishedhealthy=truewith all counts zero, ten brokers active, no loop error. Earlier three-cycle soak had no new strategy fills/fees, idle recycle, unpriced rows, stale orders, ledger mismatch, or unsupported-symbol errors. Seedocs/DECISIONS.mdD218. - Last completed task: D217 — Unconditional idle-loss crystallisation replaced with learned successor evidence. Operator reported that loss kept increasing. Live audit showed the main deterioration was not ordinary marking: eight
idle_loss_recyclecloses in six hours crystallised about -$1,006 realised P&L plus $31 closing fees (INJ,WLD,ZEC,XTZ,KSM,LDO,MAV,KITE). The loop then spent the released crypto venue room on many small, weak candidates. Root cause: D207/D216 required a losing position and low remaining-edge proxy, but still did not require a concrete superior replacement, so deployment pressure alone could manufacture realised losses.GlobalEdgeCoordinator.propose_idle_loss_recycle_actions()now requires named replacement evidence with positive matured expected return, matching asset-class capital, and expected advantage greater than held remaining edge plus switching cost. Both active allocator paths derive that evidence from the live trade-admission model; abstaining or negative-expectancy buckets cannot trigger this discretionary close. Normal stop-loss, risk derisk, profit-harvest, session exits, and evidence-backed rotation are unchanged. Restartedpython run.py(PID 58184). Verification: 141 passed, compile clean; two complete live iterations had zero recycle fills, no new fees or realised loss, runtime healthy with ten brokers and no loop error. Current unrealised P&L continued moving with crypto prices, but was no longer forcibly crystallised. Seedocs/DECISIONS.mdD217. - Last completed task: D216 — Learned reserve sizing, recycle churn, and paper P&L marks repaired. Operator reported "only losses." Live audit proved four bugs: reserve outcome learning reduced metadata targets but quantity math cancelled the multiplier (USDT requested ~$5.8k but filled $33.1k; WBTC requested ~$2.0k but filled $11.8k); idle-loss recycling closed any tiny red tick regardless of remaining edge/switching cost and then reopened the same daily signal; cash-equivalent stablecoins and wrapped BTC were treated as independent directional exposure; and paper APIs replaced the loop's persisted ACGL venue mark ($97.555, about -$35) with an older feature close ($94.33, about -$1,993). Fixed reserve quantity as learned remaining target / price; added
core/instrument_semantics.pyfor cash-equivalent role and wrapped-underlying canonicalisation; excluded cash equivalents in candidate, primary, and reserve paths; made cull re-entry wait at least one live feature interval; made idle-loss recycle require remaining edge <= fee-adjusted switching cost; made closed-market marks preserve the last executable quote; and made paper/positions//pnltrust persisted loop marks. Removed existing USDT/RLUSD/WBTC paper positions reduce-only. Live proof: corrected RLUSD filled exactly its learned $5,738 target before expanded semantic filtering; six final cycles ran without errors; stablecoins loggedineligible_instrument_role; same-bar recycled signals loggedculled_without_new_feature_bar; no repeated recycle/reopen fills; ACGL displays about -$35; open unrealised about +$1,010; current-day realised + unrealised - fees about +$950. Historical TWR remains about -0.59% and was not erased. Current process:python run.pyPID 12104, paper, nine brokers active. Verification: 162 passed, 1 skipped. Seedocs/DECISIONS.mdD216. - Last completed task: D215 — Ranked-universe deployment starvation repaired. Operator reported that roughly 9% capital use was still not useful. Live audit found four linked causes: the loop selected the first 500 feature symbols alphabetically before intersecting ranked tiers;
trend_followingrequired 201 rows while the loop loaded 200 and therefore could never emit; newly ranked names had only one month of daily history; and Yahoo provisional volume-only rows with non-finite OHLC were persisted as the latest bar, disabling all strategies for affected equities. Fixed ranked-symbol availability queries, added shared enabled-strategy history resolution (strategies/history_requirements.py), expanded live feature windows automatically, added one-attempt-per-process history warm-up for insufficient ranked names, rejected/filtered incomplete OHLC at ingest/validation/read boundaries, and removed 3,826 malformed stored rows. Added early portfolio/reserve session checks so closed-market candidates remain audited without expensive broker preflight. Live proof after final restart: cycles generated 43, 43, 16, 43, 43 candidates with no error, no fills, and no new fees; history-ready symbols rose 255 → 323 and malformed rows stayed at zero; allocator saw 37 intents/33 orders and about $1.15M aggregate target demand, but correctly deferred equity/FX because it was Saturday; BTC/ETH/LINK were already above their learned negative-outcome targets. Gross live exposure remains about 9.54% until actionable non-crypto venues reopen. Current process:python run.pyPID 20700, paper, nine brokers active; Capital.com excluded for invalid API key. Verification slices: 93 passed, 1 skipped, 80 passed, 73 passed, reserve preflight 9 passed. Seedocs/DECISIONS.mdD215. - Last completed task: D211-D214 — Fee-churn incident repaired with learned absolute targets, reserve netting, recycle memory, coalesced NAV reads, and authoritative paper venue consolidation. Live audit found today's main loss mechanism: about -$2.1k realised P&L plus $2.49k fees over 186 fills. Reserve orders repeatedly submitted full targets; the active orchestrator dropped deployment/recycle context; 135 executed opens were
unpriced; monitors flooded broker balance/price APIs; and crypto consolidation queried adapters that cannot see synthetic paper positions. Fixes: both primary and reserve paths apply payoff-aware learning to absolute targets before calculating gaps; allocator/reserve paths share persisted replacement context and candidate metadata; outcome labels usePositionLogmarks and historical rows were relabelled (201 trained rows, 26 positive/175 negative); the model uses pooled strategy/asset evidence and shrinking1/sqrt(n)exploration; models load before the first cycle; post-learning adjustments reuse the allocator's NAV-relative rebalance band; runtime NAV reads are coalesced; paper/positionsand/pnluse local persisted marks; crypto consolidation readsPositionLog. Live proof: BTC loggedtarget already met; XRP converged from about $49.8k to $21.7k near its learned target; the last verified cycle evaluated all reserve candidates as target-met and executed zero orders; no fill occurred after the final restart;/pnlnow responds in about 95ms. Current process:python run.pyPID 25972, paper, ten brokers. Verification culminated in 192 passed, execution 48 passed, reserve 61 passed, and API/P&L 54 passed, 1 skipped. Seedocs/DECISIONS.mdD211-D214. - Last completed task: D210 — Continuous monitor/fix loop: reserve multi-open, neutral-news sizing, harvest churn, and crypto venue room. Operator asked Codex to keep checking until resolved instead of waiting for repeated prompts. Continued live monitoring after flat/underdeployed state. Fixed three active blockers: (1)
system/trading_loop/loop.py::_run_orchestrator_reserve_candidates()now honorsglobal_edge.max_actions_per_tickand can execute multiple independent reserve candidates per loop instead of returning after the first success; (2)intelligence/trade_admission/policy.pyno longer applies directional-news size haircuts for neutral/supportive signed news (news_directional >= 0.5), soai_news_score=0stops shrinking orders; adverse news still produces continuous sizing penalties; (3)execution/engine.pyreroutes new-symbol crypto paper opens to a synthetic venue with better effective deploy room before clamping, while same-symbol consolidation remains stronger so one symbol is not fragmented across brokers; (4)risk/profit_harvest.pysuppresses young immaterial voluntary profit-harvest trims to prevent immediate churn. Restartedpython run.py(PID 14716). Verification:pytest tests/test_execution_engine.py tests/test_trade_admission.py tests/test_global_edge_preflight.py -q70 passed, earlier profit-harvest/wave2 slices passed,py_compileclean. Live validation: runtimerunning, paper, 10 broker adapters connected, pipeline/news healthy, no loop error; latest checked loopgenerated=4 executed=2; BTC initially routed to Kraken then reroutedkraken -> binancebecause Binance had better effective paper room; profit-harvest logsSUPPRESSED (young_immaterial_profit)for fresh small winners. Current paper book at last check:ADA-USDon Binance,BTC-USDon Binance,ETH-USDon Kraken; no AAPL. Held cash/deployment rose from zero/flat to about $61.3k cash at work; remaining underdeployment is due the current candidate set and active trained-meta skips for AAPL/XRP rather than broken execution. Seedocs/DECISIONS.mdD208-D210. - Last completed task: D207 — Underdeployed loser dead-zone recycle wired into orchestrator. Operator said the system was stable but useless: about 23.5% capital used and still losing. Live audit showed infra healthy, but after D206 the active
portfolio_orchestratorpath was rejecting most new opens while still holding losing AUDUSD/XRP positions and emitting no reduce/close actions. Root cause: global-edge capital recycle existed, but the active orchestrator path bypassed it, so the book could sit underdeployed with weak losers indefinitely. AddedGlobalEdgeCoordinator.propose_idle_loss_recycle_actions()governed byconfig/global_edge.yaml::capital_recycle; it only considers holdings with negative live unrealised return, ranks by weakest expected remaining edge, then worse return/larger notional, and emits normal reduce-onlytrim_symbolactions withcapital_recycle_reason=idle_loss_recycle. Wired it into both global-edge and activeportfolio_orchestratorticks when no other reduce action exists, with boot-warmup suppression preserved and full signal/preflight/risk/admission/execution path retained. Restartedpython run.py(PID 47452). Verification: focused tests 139 passed,py_compileclean. Live validation: iteration 2 closed stuck AUDUSD reduce-only (sell 351901 @ 0.68961240, realised about -$780.83, fee about $182.01); iteration 3 closed stuck XRP-USD reduce-only (sell 47610.46789290 @ 1.04322699, realised about -$230.30, fee about $37.25). Latest/positionsis empty;/pnlhas unrealised0, realised about-$281.78, fees about$1,274.92, 74 trades; runtime isrunning, paper, 10 brokers connected, pipeline running, no loop error. Seedocs/DECISIONS.mdD207. - Last completed task: D206 — Allocator-open meta gate + crypto dust-room routing fixed. Operator asked why the system was still losing and not opening more positions. Live audit showed infra healthy, but bad allocator opens were previously bypassing the active trained meta-label gate because allocator-selected opens were treated as shadow/exempt (
meta_label_kept=false,meta_label_shadow=true). This allowed AAPL to be bought again and let weak BTC/ETH/XRP candidates reach execution. Fixedsignals/engine.pyso only true operator/reduce exits are exempt; allocatoropen_strategycandidates now enforce the trained gate. Also fixedexecution/engine.pycrypto no-native-paper routing: venue deploy room that is above zero but rounds to zero cents is now treated as exhausted before clamping, so BTC/ETH can reroute to another crypto venue with room instead of becomingzero_notional_after_accounting_rounding; no-fallback skips now reportcrypto_venue_room_below_accounting_minimum. Flattened the 2-share AAPL remnant earlier viascripts/flatten_local_paper_book.py --apply --brokers ibkr --symbols AAPL. Restartedpython run.py(PID 47236). Verification: focused tests 70 passed,py_compileclean; post-fix loop iteration 2 completed with no error. Since restart:110 no_setup,2 edge_gate_blocked,2 filtered_signal_engine/meta_label_below_threshold, no admission rows/fills in the first checked loop. AAPL correctly blocked (0.2249 < 0.228); BTC/ETH also blocked (0.1759/0.1344 < 0.228). Current latest paper book at 2026-06-26 16:15 UTC: AUDUSD long about -$532 unrealised, XRP-USD long about -$139, no AAPL. Seedocs/DECISIONS.mdD206. - Last completed task: D205 — Poor-performance incident controls fixed. Operator asked why performance remained extremely poor. Live audit found infra healthy but behaviour leaking money: TWR about -0.42% since 2026-06-25, net trading P&L about -$5.26k, all-time fees about $1.89k / 145 fills, AAPL historical damage, and continuing AUDUSD/XRP unrealised loss. Root causes were churn/fees, profit-harvest selling gross winners without proving close-leg fee coverage, adaptive tuner exploring or increasing risk after losing/no-evidence rewards, and trade-admission learning that waited for longest horizon and could label still-open executed trades as fee-only losses. Fixes:
TunableParam.loss_guard_direction; adaptive optimiser now takes one bounded de-risk step on negative reward; tuner skips no-fill attribution windows;config/adaptive_tuner.yamldeclares loss directions; profit-harvest suppresses cost-not-covered reduce orders; trade-admission labels at first matured horizon, marks opens to market fromprice_history/feature_snapshots, and labels unavailable mark rows asunpricedso they do not train. Recomputed recent ledger:not_executed=625,unpriced=53,negative=27,pending=48. Restartedpython run.py(PID 51844). Verification: focused tests 44 passed (known AsyncMock warnings),py_compileclean; runtime paper, 10/10 brokers, pipeline running. - Last completed task: D204 — Direct-news hard veto replaced by tunable learning penalty. Operator correctly rejected the D203 hard-coded direct-news veto approach. Removed direct-news hard rejection from
risk/engine.pyand removednegative_direct_news_long/positive_direct_news_shortexplicit reject logic fromintelligence/trade_admission/policy.py.feature_builder.pynow derivesnews_directionalfrom signedai_news_scoreand side;policy.pyblends absolute news evidence with directional evidence throughAdmissionConfig.directional_news_weightand applies continuousdirectional_news_size_adjustmentsizing haircuts instead of blocking. Addeddirectional_news_weighttoconfig/trade_admission.yaml, addedtrade_admission.directional_news_weighttoconfig/adaptive_tuner.yaml, taught adaptive tuner defaults to loadtrade_admissionconfig, and wired live tuner overrides intoTradeAdmissionService.apply_live_overrides()from the trading loop. Restartedpython run.py(PID 25256). Verification: runtimerunning, 10/10 brokers connected, pipeline running;directional_news_weight=0.5; tuner containstrade_admission.directional_news_weight; focused tests 54 passed, 1 skipped,py_compileclean. Seedocs/DECISIONS.mdD204. - Last completed task: D203 — Direct negative news veto + AAPL paper flatten. Operator reported AAPL was bought again while the UI showed negative company/news impact. Root cause: signed direct
ai_news_scoreexisted, but trade admission scored absolute news magnitude as generic evidence, so negative company news could strengthen a candidate instead of blocking a long. Added signed direct-news features inintelligence/trade_admission/feature_builder.py, active admission vetoes inpolicy.py(negative_direct_news_long,positive_direct_news_short), and a final risk-engine hard veto inrisk/engine.py; reduce-only exits remain allowed. Extendedscripts/flatten_local_paper_book.py/system/local_paper_flatten.pywith--symbolsand flattened onlyibkr:AAPLfrom the local paper ledger (sell 893 @ 275.21, reduce-only tombstone). Restartedpython run.py(PID 45400). Verification:/positionsand dashboard snapshot show no AAPL; runtimerunning, 10/10 brokers connected, pipeline running; focused tests 64 passed, 1 skipped,py_compileclean. Seedocs/DECISIONS.mdD203. - Last completed task: D202 — Paper book/P&L wiped again and restarted from clean slate. Operator asked to wipe everything again and start from scratch. Stopped system via
/system/stopand waited forstate=off, then ranpython scripts/reset_trading_data.py --execute. Script truncatedorders,positions,fills,daily_pnl,signals,risk_decisions,strategy_candidate_log,thesis_log,anomaly_log; deleted trading-derivedcontrol_statekeys; removeddata/runtime/risk_state.jsonanddata/runtime/paper_wallet.json. Restarted via/system/start; runtime isstate=running, 10/10 brokers included, coverage full,trading_running=true, pipeline running, first clean iteration completed at2026-06-25T19:06:50+01:00, no loop error. Verification:/positionsreturns[];/pnlreturns realised/unrealised/fees/trades all zero with TWR0.0; DB counts after restartpositions=0,fills=0,orders=0,daily_pnl=1(today's zero baseline row). D201 active gates remain enabled. - Last completed task: D201 — Shadow governance promoted to active paper gates. Operator asked not to keep newly developed controls in shadow mode. Promoted trained meta-label gating (
config/strategies.yaml:use_trained_meta_labeler: true,trained_meta_labeler_shadow: false), enabled active trade-admission sizing/blocking (config/trade_admission.yaml:shadow_only: false,block_new_opens: true,allow_size_haircuts: true), and made the regime transition detector affect allocator exposure by tighteningRegimeState.drawdown_throttlewhen non-shadow stress probability is emitted (config/regime_models.yaml::transition_detector.shadow_only: false). Bridged execution microstructure evidence into trade admission by mappingmicrostructure_shadow_labeltomicrostructure_label; disabled read-only shadow diagnostics that did not affect order flow (FUSION_SHADOW=0, demandlearned_graph_shadow.enabled=false,transition_policy_shadow.enabled=false). Tests:python -m py_compile intelligence\trade_admission\feature_builder.py execution\microstructure_shadow.py risk\regime_state.py signals\engine.py;pytest tests\test_trade_admission.py tests\test_wave2_wiring.py tests\test_phase_c_regime_transition.py -q27 passed. Restartedpython run.py(PID 39352); runtime isstate=running, 10/10 brokers connected,can_trade=true, first loop completed. Current paper book remains AAPL/XRP/YM from before this fix;/pnltoday about -$8.2k net trading P&L with AAPL/YM carrying most unrealised loss. Seedocs/DECISIONS.mdD201. - Last completed task: D200 — Trade admission now actively blocks bad new opens after AAPL loss. Operator asked why unrealised P&L went from about +$2k to -$5k and why a negative AAPL signal did not prevent buying Apple. Live audit after D199 reset: current paper book had
AAPLlong on Capital.com (881.81542670 shares, avg 280.37, mark ~277.97, unrealised about -$2.1k),YM=Flong on IBKR (5 contracts, unrealised about -$3.2k), andXRP-USDlong on Binance (about +$271). AAPL was opened at 2026-06-25 13:41 UTC byportfolio_orchestrator/mean-reversion despitemeta_label_kept=false,meta_label_reason=below_threshold, andtrade_admission_decision=defer/prior_trade_filter_dropbecause trade admission was shadow-only (shadow_only: true,block_new_opens: false). Stopped the system immediately (state=off). Changedconfig/trade_admission.yamltoshadow_only: falseandblock_new_opens: true; reduce-only/close actions remain allowed. Tests:pytest tests/test_trade_admission.py -q7 passed,py_compileclean. Seedocs/DECISIONS.mdD200. - Last completed task: D199 — Operator-requested paper book/P&L reset. Operator clarified "wipe everything" meant current open positions and P&L, not repo/config. Stopped the system first; confirmed
paper_mode=trueand/positionssource was localposition_log. Initially reset core local paper ledgers (positions,fills,orders,daily_pnl) and stale UI/runtime state, then operator remembered the repo reset tool; ranpython scripts/reset_trading_data.pydry-run andpython scripts/reset_trading_data.py --execute. The script truncatedorders,positions,fills,daily_pnl,signals,risk_decisions,strategy_candidate_log,thesis_log,anomaly_log; deleted trading-derivedcontrol_statekeys while preservingauto_training.last_run_atandsystem.capital_allocation; removeddata/runtime/risk_state.jsonanddata/runtime/paper_wallet.json. Verification:/positionsreturns[];/pnlreturns realised/unrealised/fees/trades all zero; all reset tables are0; only preserved control keys remain; bot intentionally left OFF after reset. - Last completed task: D198 — Crypto same-symbol venue consolidation after ADA fragmentation. Operator asked why ADA was open on Binance, Bybit, and Kraken. Live audit showed one ADA target was repeatedly topped up: Binance filled first, then the no-native-paper crypto execution path rerouted additive ADA orders to Bybit/Kraken when per-venue synthetic paper room was exhausted. Risk capped total ADA exposure, but no guard enforced one same-symbol venue expression. Fixed
execution/engine.py: additive no-native-paper crypto orders now inspect existing Binance/Bybit/Kraken positions; if the symbol is already held, adds route only to the dominant existing venue, and if that venue was already attempted/full the add is skipped instead of opening a fresh broker row. Reduce-only/close orders remain exempt. Also skip accounting-zero crypto dust orders before they create filled zero-notional rows. Tests:python -m py_compile execution\engine.py tests\test_execution_engine.py;pytest tests\test_execution_engine.py -q45 passed. Seedocs/DECISIONS.mdD198. - Last completed task: D197 — Trade admission schema drift fixed; orders flowing again. Live health audit on 2026-06-25 showed the system itself was healthy (
state=running, paper, 10/10 brokers connected/balance-ready, Postgres/Redis healthy, AI/news fresh, pipeline running), but orchestrator orders were failing afterRISK APPROVEDbecause the existingtrade_admission_logtable lacked new nullable JSON columnsoutcome_horizonsandoutcome_labels. Applied live DBALTER TABLE ... ADD COLUMN IF NOT EXISTSfor both, added the same self-healing patch instorage.db._ensure_additive_schema_patches(), and verified the next loop executed/fill path recovered: iteration #4 generated 9, executed 4 paper fills (XLE,USO,GLD,ADA-USD).python -m py_compile storage/db.pypassed;/diagnostics/trade-admissionworks again;/system/statusreturns no errors. Seedocs/DECISIONS.mdD197. - Last completed task: D196 — Trade Admission Intelligence shadow ledger. Built a myTbot-native pre-risk admission layer from scratch. New
intelligence/trade_admission/package provides schema/config, feature extraction, conservative admission decisions, persistence, outcome labeling, and diagnostics. Addedstorage.models.TradeAdmissionLogto preserve the exact executable-candidate context and downstream status.TradingLoopnow evaluates built signals in both legacy and global/orchestrated chokepoints before risk, stamps admission metadata, updates ledger rows for risk rejection / execution skip / fill, and labels due outcomes from matched fills in the heartbeat. Added/diagnostics/trade-admissionandconfig/trade_admission.yaml; default is enabled + shadow-only, with enforcement switches off, so live behavior is unchanged unless explicitly activated. Tests:tests/test_trade_admission.py,tests/test_strategy_candidate_flow.py,tests/test_sqlite_lite_backend.py9 passed;py_compileclean. Seedocs/DECISIONS.mdD196. - Last completed task: D195 — Flow-neutral TWR implemented as primary percentage return. Operator asked for percentage return (IRR/HPR/TWR) and chose TWR. Added
api.pnl_periods.time_weighted_return_from_daily_rows()and/pnl.metrics.twr: daily trading P&L =realised - fees + change_in_unrealised; any remaining NAV movement is classified as external flow, so adding/removing brokers/paper balances does not count as performance. UI performance strip now showsTWRas the primary percentage return with subtitlesince <date> · net <P&L>. Current live/pnl.metrics.twr: about -0.74% since 2026-06-18, net trading P&L about -$8.9k, external flow about +$105k. Teststests/test_twr_metrics.py3 passed,py_compileclean, UInpm run buildpassed. Restartedpython run.py(PID 57192). Seedocs/DECISIONS.mdD195. - Last completed task: D194 — Separate broker NAV plumbing from trading performance stats. Operator pointed out the dashboard's
NAV Δ +8.1% since 18 Junwas caused by newly connected brokers/paper balances and must not be treated as performance. Fixedui/src/app/redesign/dashboard.tsx: removed the NAV-history return/Sharpe/DD helper from the performance strip so broker activation/deactivation cannot manufacture returns. D195 then replaced the temporary realised-P&L card with flow-neutralTWR. Sharpe and trade drawdown stay suppressed (needs 20 days) until a proper trading-return series exists; NAV remains an account-state metric elsewhere. UI build passed (npm run build). Seedocs/DECISIONS.mdD194. - Last completed task: D190 — Dynamic profit harvest + FX P&L correction + no-static-threshold rule. Operator reiterated the ban on absolute trading parameters after the book failed to harvest/recycle while fixed 8%/30% gates blocked action and USDJPY P&L was displayed in quote currency. Added mandatory rule 9 to
AGENTS.mdand created.cursorrules: no fixed trading thresholds/magic market parameters except mathematical identities, external protocol/accounting constraints, UI/cadence settings, or documented safety bounds; touched absolute thresholds must be replaced with dynamic variables. Addedcore/pnl.py::unrealised_pnl_account_currency()and wired/positions, live broker position fallback, global-edge book building, and D015 portfolio bridge so FX quote-currency P&L such as USDJPY is converted to account currency. Reworkedrisk/profit_harvest.resolve_harvest_thresholds()to derive partial/full profit bands from realised volatility and mode coefficients only; fixed NAV profit floors and peak-lock NAV floors removed; absolute per-position profit overrides are ignored/audited. Replacedconfig/global_edge.yaml::capital_recycle.take_profit_pct: 0.08withtake_profit_edge_multiplier, so capital recycle banks winners only when unrealised return beats live remaining-edge estimate. Tests:tests/test_pnl_helpers.py,tests/test_profit_harvest.py,tests/test_global_edge_coordinator.py60 passed (known AsyncMock warnings in profit-harvest harness); compile clean. Restartedpython run.py(PID 14004); live/positionsshowsUSDJPY unrealised_pnl=-1.38USD instead of raw-223JPY, system running with 10 brokers andcapital_pct=1.0. - Last completed task: D189 — OANDA broker adapter (tenth venue). REST forex adapter
brokers/oanda/adapter.py(Bearer token, practice/live v20 API). Wired registry, broker_manager, Connect Hub, permissions (forex only), router, canonical FX symbols. NeedsOANDA_API_TOKENin.env; optionalOANDA_ACCOUNT_ID. Teststests/test_oanda_adapter.py. Restartpython run.pyfor tenth broker badge. Seedocs/DECISIONS.mdD189. - Last completed task: D188 — Coinbase Advanced Trade broker adapter (ninth venue). REST crypto spot adapter
brokers/coinbase/adapter.py+brokers/coinbase/auth.py(CDP ES256 JWT per request,/api/v3/brokerage). Wired registry, broker_manager, Connect Hub, permissions (crypto only), router, canonicalBTC-USDproducts. NeedsCOINBASE_API_KEY(full organizations/... path) +COINBASE_API_SECRET(EC PEM,\nok) in.env. Live probe: connect OK, 10 accounts, 117 products. Teststests/test_coinbase_adapter.py5 passed. Restartpython run.pyfor ninth broker badge. Seedocs/DECISIONS.mdD188. - Last completed task: D187 — IG Markets broker adapter (eighth venue). REST spread-bet/CFD adapter
brokers/ig/adapter.py(Version-2 session auth, demo/live base URLs, epic search,POST /positions/otc+ confirms,DELETE /positions/otccloses). Wired registry, broker_manager, Connect Hub, permissions, router, canonical symbols. NeedsIG_API_KEY,IG_IDENTIFIER(username), andIG_PASSWORD(IG login password) in.env. Teststests/test_ig_adapter.py4 passed. Restartpython run.pyfor eighth broker badge. Seedocs/DECISIONS.mdD187. - Last completed task: D184 — Real logo URLs for positions and Universe instruments. Operator clarified generated avatars are not enough and asked for real logos. Clearbit is not viable in 2026 (free Logo API sunset Dec 2025), so added API-key-free real image sources with fallback: official-domain favicons via Google's favicon proxy for companies/funds, CoinCap public icon CDN for crypto, FlagCDN for FX base-currency flags, and CME favicon for futures. New
core/instrument_profiles.pycentralizes domain/logo mappings,logo_url_for_symbol(),with_logo(), crypto display names, company/fund domains, FX flags, futures logos./positionsprofiles now callwith_logo();universe/snapshot_service.pyemitslogo_urlfrom the same helper. Fixed collision whereCVX-USDwas incorrectly named Chevron by old catalogue matching; now crypto rows use crypto display names (CVX-USD->Convex Finance). Verification:python -m py_compile core/instrument_profiles.py api/server.py universe/snapshot_service.py; UInpm run buildpassed; restartedpython run.py(PID 50396);/positionsemits live logo URLs for CME/FlagCDN/USCF/SSGA;/intelligence/universesample emits Apple/Coinbase/Delta favicons, CoinCap BTC/CVX icons
Truncated - read the full file at https://github.com/emfasys-labs/mytbot/blob/30a4448720cf835c4ce5f9ba93e39645c3ce0040/AGENTS.md.
