# 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
