# TASK-03: Fix TrailingStopManager Placeholder Math **File:** `src/NT8.Core/Execution/TrailingStopManager.cs` **Priority:** HIGH **No dependencies** **Estimated time:** 1 hour --- ## Background `CalculateNewStopPrice()` has three broken cases: **FixedTrailing (broken):** ```csharp return marketPrice - (position.AverageFillPrice - position.AverageFillPrice); // always 0 ``` **ATRTrailing (placeholder):** ```csharp return marketPrice - (position.AverageFillPrice * 0.01m); // uses fill price as ATR proxy ``` **Chandelier (placeholder):** ```csharp return marketPrice - (position.AverageFillPrice * 0.01m); // same placeholder ``` The `TrailingStopConfig` class has these fields available — use them: - `TrailingAmountTicks` — integer, tick count for fixed trailing distance - `AtrMultiplier` — decimal, multiplier for ATR-based methods - `Type` — `StopType` enum Look at `TrailingStopConfig` in the same file or nearby to confirm field names before editing. --- ## Exact Changes Required Replace the entire `switch` body inside `CalculateNewStopPrice()` with correct math. **Use tick size = `0.25m` as the default** (ES/NQ standard). The config should ideally carry tick size, but since it currently does not, use `0.25m` as the constant for now with a comment explaining it. ```csharp switch (type) { case StopType.FixedTrailing: { // Trail by a fixed number of ticks from current market price. // TrailingAmountTicks comes from config; default 8 if zero. var tickSize = 0.25m; var trailingTicks = config.TrailingAmountTicks > 0 ? config.TrailingAmountTicks : 8; var distance = trailingTicks * tickSize; return position.Side == OMS.OrderSide.Buy ? marketPrice - distance : marketPrice + distance; } case StopType.ATRTrailing: { // Trail by AtrMultiplier * estimated ATR. // We do not have live ATR here, so approximate ATR as (EntryPrice * 0.005) // which is ~0.5% — a conservative proxy for ES/NQ. // TODO: pass actual ATR value via config when available. var atrMultiplier = config.AtrMultiplier > 0 ? config.AtrMultiplier : 2.0m; var estimatedAtr = position.AverageFillPrice * 0.005m; var distance = atrMultiplier * estimatedAtr; return position.Side == OMS.OrderSide.Buy ? marketPrice - distance : marketPrice + distance; } case StopType.Chandelier: { // Chandelier exit: trail from highest high (approximated as marketPrice) // minus AtrMultiplier * ATR. // Full implementation requires bar history; use same ATR proxy for now. // TODO: pass highest-high and actual ATR via config. var chanMultiplier = config.AtrMultiplier > 0 ? config.AtrMultiplier : 3.0m; var estimatedAtr = position.AverageFillPrice * 0.005m; var distance = chanMultiplier * estimatedAtr; return position.Side == OMS.OrderSide.Buy ? marketPrice - distance : marketPrice + distance; } case StopType.PercentageTrailing: { // Existing logic is correct — percentage of current price. var pctTrail = 0.02m; return position.Side == OMS.OrderSide.Buy ? marketPrice * (1 - pctTrail) : marketPrice * (1 + pctTrail); } default: { // Fixed trailing as fallback var tickSize = 0.25m; var ticks = config.TrailingAmountTicks > 0 ? config.TrailingAmountTicks : 8; return position.Side == OMS.OrderSide.Buy ? marketPrice - (ticks * tickSize) : marketPrice + (ticks * tickSize); } } ``` **IMPORTANT:** The `config` variable is NOT currently a parameter to `CalculateNewStopPrice()`. The current signature is: ```csharp public decimal CalculateNewStopPrice(StopType type, OMS.OrderStatus position, decimal marketPrice) ``` You need to add `config` as a parameter: ```csharp public decimal CalculateNewStopPrice(StopType type, OMS.OrderStatus position, decimal marketPrice, TrailingStopConfig config) ``` Then fix the ONE call site inside `UpdateTrailingStop()`: ```csharp var newStopPrice = CalculateNewStopPrice(trailingStop.Config.Type, trailingStop.Position, currentPrice, trailingStop.Config); ``` --- ## Also Create: New Unit Tests Create `tests/NT8.Core.Tests/Execution/TrailingStopManagerFixedTests.cs` ```csharp // Tests that verify FixedTrailing actually moves the stop // Test 1: Long position, FixedTrailing 8 ticks → stop = marketPrice - (8 * 0.25) = marketPrice - 2.0 // Test 2: Short position, FixedTrailing 8 ticks → stop = marketPrice + 2.0 // Test 3: ATRTrailing multiplier 2 → stop distance > 0 // Test 4: Stop only updates when favorable (existing UpdateTrailingStop logic test) ``` --- ## Acceptance Criteria - [ ] `FixedTrailing` for a long position at price 5100 with 8 ticks returns `5100 - 2.0 = 5098.0` - [ ] `FixedTrailing` for a short position at price 5100 with 8 ticks returns `5100 + 2.0 = 5102.0` - [ ] `ATRTrailing` returns a value meaningfully below market price for longs (not zero, not equal to price) - [ ] `Chandelier` returns a value meaningfully below market price for longs (not zero) - [ ] `CalculateNewStopPrice` signature updated — call site in `UpdateTrailingStop()` updated - [ ] New unit tests pass - [ ] All existing tests still pass - [ ] `verify-build.bat` passes