MK2 trailing-stop + scale-out implementation
Implements the corpus-validated strategy reversal documented in 2026-05-14-trailing-stop-corpus-validation. Companion to that page - this one captures the as-built engineering.
Commit: dc73132 on cDSe2403/-MK2.1 + main, 2026-05-14 17:35 CT.
Codex handoff: plans/2026-05-14-codex-handoff-trailing-stop-mk2.md
Task: #104
Reverses: memory feedback_no_hwm_trailing_language
What shipped
Behavior is gated. Default TRAILING_ENABLED=false keeps the engine byte-identical to prior main (single-shot +10% LMT TP). Setting TRAILING_ENABLED=true engages scale-out + trailing.
When trailing is ON
-
TP LMT sizing (
mixins.py:_place_tp_limit): place LMT formax(1, int(remaining_qty * SCALE_OUT_PCT)). Default 50% of contracts. The other half stays exposed to the trail. -
HWM tracking (
manager.py:_process_price_tick): whenoption_mid >= avg_cost * (1 + TRAIL_ENGAGEMENT_PCT)(default +5%), start tracking high-water mark. Persistpos.high_water_markandpos.hwm_pcton each new high. -
Trail exit fire: when
option_mid <= pos.high_water_mark * (1 - TRAIL_PCT)(default 5% give-back), fireMarketOrder SELLvia_close_remaining(pos, option_price, "TRAIL_EXIT"). -
Engagement gate prevents noise triggers: HWM does not engage until option mid has crossed +5% above entry. Before engagement, trail logic is dormant.
-
TP fill handling: filled half-TP does NOT finalize the whole position. Engine continues tracking HWM and trail-managing the remainder. (This was a bug-class the original implementation could have hit; codex caught and handled it.)
Downstream propagation
TRAIL_EXIT is a new exit_reason value, propagated to:
scripts/build_daily_report.py- label, color (#4fa3a5), counterscripts/reporting_common.py- CSS class for HTML reportssrc/cortana/alerts/telegram.py- human label “Trail”src/cortana/storage/decision_logger.py-hit_tp = 1for TRAIL_EXIT (trail only fires after profitable engagement, so it’s a TP-equivalent for ML labeling)
Config (env vars override config.json defaults)
| Key | Default | Range | Meaning |
|---|---|---|---|
TRAILING_ENABLED | false | bool | Master switch |
TRAIL_PCT | 0.05 | (0, 1) | Give-back from HWM that triggers exit |
SCALE_OUT_PCT | 0.5 | (0, 1) | Fraction of position to LMT at +10% |
TRAIL_ENGAGEMENT_PCT | 0.05 | (0, 2) | How far above entry option mid must reach before HWM starts tracking |
Required logging (validation hooks)
At INFO level on every engaged trade:
HWM_ENGAGED position=X entry=Y option_mid=Z- first engagementHWM_UPDATE position=X new_hwm=Y pct_above_entry=Z%- new highTRAIL_TRIGGERED position=X hwm=Y current=Z trail_pct=W% trigger_price=V- exit fire
These are how Friday’s session will be validated against the corpus prediction.
Tests (all passing)
tests/test_position_manager.py- 98 tests, includes 4 trail-specific scenarios:- regression with
TRAILING_ENABLED=false(byte-identical to prior behavior) - trail engagement sequence (entry → HWM → trail fire)
- SL fires before HWM engagement (trail bypassed)
- TRAIL_EXIT distinct from PRICE_STOP
- regression with
tests/test_config.py- 28 tests including env-var binding for the 4 new keys
Combined run: 99 passed in 63s (pytest tests/test_position_manager.py tests/test_config.py -q).
Deploy + revert
Friday 2026-05-15 deploy (target):
- Set
TRAILING_ENABLED=trueinscripts/com.cortanaroi.mk2.plistenv block - Run E2E pre-flight at 08:08 CT
- Engine kicks 08:10 CT
- First entry should show TP LMT for ~50 contracts (half of nominal 100)
- Monitor first 3-5 trades for
HWM_ENGAGED/TRAIL_TRIGGEREDlog lines
Revert path (single flag):
- Set
TRAILING_ENABLED=false - Restart engine
- Instant return to fixed +10% LMT behavior, no code rollback needed
Residual risk (from Codex’s own report, 8/10 confidence)
Main residual risk is broker event timing around partial TP fills during cancel races, but the code now handles the normal filled half-TP path and keeps the trail-managed remainder live.
This is the same class of race that bit position #356 (inverted-position story) on 2026-05-11. The implementation handles the normal path. If a cancel-race happens, watch for EMERGENCY_EXIT_MANUAL_REQUIRED logs as the existing safety net - flatten_all.py is still available as the manual force-close.
Memory links
- 2026-05-14-trailing-stop-corpus-validation - the empirical case for the change
- 2026-05-14-sl-and-peak-stop-corpus-validation - why SL stays at -25%
- feedback_no_hwm_trailing_language - reversed by this change
- project_pm_ibkr_exit_invariant - the broker-truth invariant that all exit paths must honor
- Task #104 - this implementation