# Phase A: NT8 Data & Execution Adapters - Detailed Specification

**For:** Kilocode AI Agent (Autonomous Implementation)  
**Phase:** Phase A - Foundation  
**Components:** NT8DataAdapter Tests + NT8ExecutionAdapter  
**Estimated Time:** 4-5 hours  
**Mode:** Code Mode  

---

## 🎯 Objective

Build comprehensive unit tests for existing NT8DataAdapter/NT8DataConverter, then create the NT8ExecutionAdapter that handles real order submission to NinjaTrader 8.

---

## 📋 Task 1: NT8 Data Adapter Unit Tests (2 hours)

### Overview
The `NT8DataConverter.cs` already exists but has ZERO unit tests. Create comprehensive test coverage.

### Location
**Create:** `tests/NT8.Core.Tests/Adapters/NT8DataConverterTests.cs`

### Requirements

**Test Coverage Target:** 95%+ of NT8DataConverter methods

**Test Categories:**
1. ConvertBar (8 tests)
2. ConvertAccount (4 tests)
3. ConvertPosition (5 tests)
4. ConvertSession (4 tests)
5. ConvertContext (6 tests)

**Total:** 27 unit tests minimum

### Detailed Test Specifications

#### 1. ConvertBar Tests (8 tests)

```csharp
namespace NT8.Core.Tests.Adapters
{
    public class NT8DataConverterTests
    {
        // TEST 1: Happy path with valid ES bar
        [Fact]
        public void ConvertBar_WithValidESBar_ShouldCreateBarData()
        {
            // Input: symbol="ES", time=2026-02-17 09:30:00, OHLCV=4200/4210/4195/4208/10000, barSize=5
            // Expected: BarData with all properties matching, BarSize=TimeSpan.FromMinutes(5)
        }

        // TEST 2: Null/empty/whitespace symbol
        [Theory]
        [InlineData(null)]
        [InlineData("")]
        [InlineData("   ")]
        public void ConvertBar_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
        {
            // Expected: ArgumentException with parameter name "symbol"
        }

        // TEST 3: Invalid bar sizes (zero, negative)
        [Theory]
        [InlineData(0)]
        [InlineData(-1)]
        [InlineData(-60)]
        public void ConvertBar_WithInvalidBarSize_ShouldThrowArgumentException(int barSize)
        {
            // Expected: ArgumentException with parameter name "barSizeMinutes"
        }

        // TEST 4: Different timeframes (1min, 5min, 15min, 30min, 60min, 240min, daily)
        [Fact]
        public void ConvertBar_WithDifferentTimeframes_ShouldSetCorrectBarSize()
        {
            // Test each: 1, 5, 15, 30, 60, 240, 1440
            // Verify BarSize property matches TimeSpan.FromMinutes(input)
        }

        // TEST 5: High < Low scenario (invalid OHLC)
        [Fact]
        public void ConvertBar_WithHighLessThanLow_ShouldStillCreate()
        {
            // Note: BarData constructor should validate, but converter just passes through
            // Expected: May throw from BarData constructor OR create invalid bar
            // Document actual behavior
        }

        // TEST 6: Zero volume
        [Fact]
        public void ConvertBar_WithZeroVolume_ShouldCreateBar()
        {
            // Expected: Creates bar with Volume=0 (valid for some instruments/sessions)
        }

        // TEST 7: Negative prices
        [Fact]
        public void ConvertBar_WithNegativePrices_ShouldHandleCorrectly()
        {
            // For instruments like ZN that can have negative yields
            // Expected: Accepts negative prices
        }

        // TEST 8: Large volume values
        [Fact]
        public void ConvertBar_WithLargeVolume_ShouldHandleCorrectly()
        {
            // Volume = 10,000,000
            // Expected: Handles long values correctly
        }
    }
}
```

#### 2. ConvertAccount Tests (4 tests)

```csharp
// TEST 9: Valid account with positive values
[Fact]
public void ConvertAccount_WithPositiveValues_ShouldCreateAccountInfo()
{
    // Input: equity=100000, buyingPower=250000, dailyPnL=1250.50, maxDD=0.05
    // Expected: All properties match
}

// TEST 10: Negative daily P&L (losing day)
[Fact]
public void ConvertAccount_WithNegativePnL_ShouldHandleCorrectly()
{
    // Input: dailyPnL=-2500.75
    // Expected: DailyPnL property is negative
}

// TEST 11: Zero equity/buying power (margin call scenario)
[Fact]
public void ConvertAccount_WithZeroValues_ShouldCreateAccount()
{
    // Input: All zeros
    // Expected: Creates valid AccountInfo with zero values
}

// TEST 12: Very large equity values
[Fact]
public void ConvertAccount_WithLargeEquity_ShouldHandleCorrectly()
{
    // Input: equity=10,000,000
    // Expected: Handles large doubles correctly
}
```

#### 3. ConvertPosition Tests (5 tests)

```csharp
// TEST 13: Long position
[Fact]
public void ConvertPosition_WithLongPosition_ShouldCreatePosition()
{
    // Input: symbol="ES", quantity=2, avgPrice=4200.50, unrealizedPnL=250, realizedPnL=500
    // Expected: Quantity > 0
}

// TEST 14: Short position (negative quantity)
[Fact]
public void ConvertPosition_WithShortPosition_ShouldHandleNegativeQuantity()
{
    // Input: quantity=-1
    // Expected: Quantity < 0
}

// TEST 15: Flat position (zero quantity)
[Fact]
public void ConvertPosition_WithFlatPosition_ShouldHandleZeroQuantity()
{
    // Input: quantity=0, avgPrice=0
    // Expected: Creates valid flat position
}

// TEST 16: Invalid symbol
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("   ")]
public void ConvertPosition_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
{
    // Expected: ArgumentException with parameter "symbol"
}

// TEST 17: Negative unrealized P&L (losing position)
[Fact]
public void ConvertPosition_WithNegativeUnrealizedPnL_ShouldHandleCorrectly()
{
    // Input: unrealizedPnL=-350.25
    // Expected: UnrealizedPnL property is negative
}
```

#### 4. ConvertSession Tests (4 tests)

```csharp
// TEST 18: RTH session
[Fact]
public void ConvertSession_WithRTHSession_ShouldCreateMarketSession()
{
    // Input: start=09:30, end=16:00, isRth=true, name="RTH"
    // Expected: IsRth=true
}

// TEST 19: ETH session
[Fact]
public void ConvertSession_WithETHSession_ShouldCreateMarketSession()
{
    // Input: start=18:00, end=next day 09:30, isRth=false, name="ETH"
    // Expected: IsRth=false, handles overnight session
}

// TEST 20: Invalid session name
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("   ")]
public void ConvertSession_WithInvalidName_ShouldThrowArgumentException(string name)
{
    // Expected: ArgumentException with parameter "sessionName"
}

// TEST 21: End before start (invalid range)
[Fact]
public void ConvertSession_WithEndBeforeStart_ShouldThrowArgumentException()
{
    // Input: start=16:00, end=09:30
    // Expected: ArgumentException with parameter "sessionEnd"
}
```

#### 5. ConvertContext Tests (6 tests)

```csharp
// TEST 22: Valid context with all components
[Fact]
public void ConvertContext_WithValidInputs_ShouldCreateStrategyContext()
{
    // Input: All valid Position, Account, Session, CustomData with 2 entries
    // Expected: All properties populated, CustomData contains both entries
}

// TEST 23: Null custom data
[Fact]
public void ConvertContext_WithNullCustomData_ShouldCreateEmptyDictionary()
{
    // Input: customData=null
    // Expected: CustomData is non-null empty dictionary
}

// TEST 24: Invalid symbol
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("   ")]
public void ConvertContext_WithInvalidSymbol_ShouldThrowArgumentException(string symbol)
{
    // Expected: ArgumentException with parameter "symbol"
}

// TEST 25: Null position
[Fact]
public void ConvertContext_WithNullPosition_ShouldThrowArgumentNullException()
{
    // Expected: ArgumentNullException with parameter "currentPosition"
}

// TEST 26: Null account
[Fact]
public void ConvertContext_WithNullAccount_ShouldThrowArgumentNullException()
{
    // Expected: ArgumentNullException with parameter "account"
}

// TEST 27: Null session
[Fact]
public void ConvertContext_WithNullSession_ShouldThrowArgumentNullException()
{
    // Expected: ArgumentNullException with parameter "session"
}
```

### Implementation Notes

**Framework:**
- Use xUnit
- Use FluentAssertions for readable assertions
- Follow existing test patterns in `tests/NT8.Core.Tests`

**File Structure:**
```csharp
using System;
using System.Collections.Generic;
using Xunit;
using FluentAssertions;
using NT8.Adapters.NinjaTrader;
using NT8.Core.Common.Models;

namespace NT8.Core.Tests.Adapters
{
    /// <summary>
    /// Unit tests for NT8DataConverter
    /// </summary>
    public class NT8DataConverterTests
    {
        // All 27 tests here
    }
}
```

**Success Criteria:**
- [ ] All 27 tests implemented
- [ ] All tests pass
- [ ] Zero warnings
- [ ] Code coverage >95% for NT8DataConverter
- [ ] Follows existing test patterns

---

## 📋 Task 2: NT8ExecutionAdapter Implementation (2-3 hours)

### Overview
Create the adapter that handles REAL order submission to NinjaTrader 8.

### Location
**Create:** `src/NT8.Adapters/NinjaTrader/NT8ExecutionAdapter.cs`  
**Create:** `tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs`

### NT8ExecutionAdapter Specification

#### Class Structure

```csharp
using System;
using System.Collections.Generic;
using NT8.Core.Common.Models;
using NT8.Core.OMS;

namespace NT8.Adapters.NinjaTrader
{
    /// <summary>
    /// Adapter for executing orders through NinjaTrader 8 platform.
    /// Bridges SDK order requests to NT8 order submission and handles callbacks.
    /// Thread-safe for concurrent NT8 callbacks.
    /// </summary>
    public class NT8ExecutionAdapter
    {
        private readonly object _lock = new object();
        private readonly Dictionary<string, OrderTrackingInfo> _orderTracking;
        private readonly Dictionary<string, string> _nt8ToSdkOrderMap;
        
        /// <summary>
        /// Creates a new NT8 execution adapter.
        /// </summary>
        public NT8ExecutionAdapter()
        {
            _orderTracking = new Dictionary<string, OrderTrackingInfo>();
            _nt8ToSdkOrderMap = new Dictionary<string, string>();
        }
        
        // Methods defined below...
    }
    
    /// <summary>
    /// Internal class for tracking order state
    /// </summary>
    internal class OrderTrackingInfo
    {
        public string SdkOrderId { get; set; }
        public string Nt8OrderId { get; set; }
        public OrderRequest OriginalRequest { get; set; }
        public OrderState CurrentState { get; set; }
        public int FilledQuantity { get; set; }
        public double AverageFillPrice { get; set; }
        public DateTime LastUpdate { get; set; }
        public string ErrorMessage { get; set; }
    }
}
```

#### Method 1: SubmitOrder

```csharp
/// <summary>
/// Submit an order to NinjaTrader 8.
/// NOTE: This method accepts primitive parameters instead of NT8 Strategy object
/// to maintain testability and avoid NT8 DLL dependencies in core adapter.
/// The actual NT8StrategyBase will call NT8 methods and pass results here.
/// </summary>
/// <param name="request">SDK order request</param>
/// <param name="sdkOrderId">Unique SDK order ID</param>
/// <returns>Tracking info for the submitted order</returns>
/// <exception cref="ArgumentNullException">If request or orderId is null</exception>
/// <exception cref="InvalidOperationException">If order already exists</exception>
public OrderTrackingInfo SubmitOrder(OrderRequest request, string sdkOrderId)
{
    if (request == null)
        throw new ArgumentNullException("request");
    if (string.IsNullOrWhiteSpace(sdkOrderId))
        throw new ArgumentNullException("sdkOrderId");
    
    lock (_lock)
    {
        // Check if order already tracked
        if (_orderTracking.ContainsKey(sdkOrderId))
        {
            throw new InvalidOperationException(
                string.Format("Order {0} already exists", sdkOrderId));
        }
        
        // Create tracking info
        var trackingInfo = new OrderTrackingInfo
        {
            SdkOrderId = sdkOrderId,
            Nt8OrderId = null, // Will be set by NT8 callback
            OriginalRequest = request,
            CurrentState = OrderState.Pending,
            FilledQuantity = 0,
            AverageFillPrice = 0.0,
            LastUpdate = DateTime.UtcNow,
            ErrorMessage = null
        };
        
        _orderTracking[sdkOrderId] = trackingInfo;
        
        // NOTE: Actual NT8 submission happens in NT8StrategyBase
        // This adapter only tracks state
        
        return trackingInfo;
    }
}
```

#### Method 2: ProcessOrderUpdate

```csharp
/// <summary>
/// Process order update callback from NinjaTrader 8.
/// Called by NT8StrategyBase.OnOrderUpdate().
/// </summary>
/// <param name="nt8OrderId">NT8's order ID</param>
/// <param name="sdkOrderId">SDK's order ID (from order name/tag)</param>
/// <param name="orderState">NT8 order state</param>
/// <param name="filled">Filled quantity</param>
/// <param name="averageFillPrice">Average fill price</param>
/// <param name="errorCode">Error code if rejected</param>
/// <param name="errorMessage">Error message if rejected</param>
public void ProcessOrderUpdate(
    string nt8OrderId,
    string sdkOrderId,
    string orderState,
    int filled,
    double averageFillPrice,
    int errorCode,
    string errorMessage)
{
    if (string.IsNullOrWhiteSpace(sdkOrderId))
        return; // Ignore orders not from SDK
    
    lock (_lock)
    {
        if (!_orderTracking.ContainsKey(sdkOrderId))
        {
            // Order not tracked, ignore
            return;
        }
        
        var info = _orderTracking[sdkOrderId];
        
        // Map NT8 order ID
        if (!string.IsNullOrWhiteSpace(nt8OrderId) && info.Nt8OrderId == null)
        {
            info.Nt8OrderId = nt8OrderId;
            _nt8ToSdkOrderMap[nt8OrderId] = sdkOrderId;
        }
        
        // Update state
        info.CurrentState = MapNT8OrderState(orderState);
        info.FilledQuantity = filled;
        info.AverageFillPrice = averageFillPrice;
        info.LastUpdate = DateTime.UtcNow;
        
        // Handle errors
        if (errorCode != 0 && !string.IsNullOrWhiteSpace(errorMessage))
        {
            info.ErrorMessage = string.Format("[{0}] {1}", errorCode, errorMessage);
            info.CurrentState = OrderState.Rejected;
        }
    }
}
```

#### Method 3: ProcessExecution

```csharp
/// <summary>
/// Process execution (fill) callback from NinjaTrader 8.
/// Called by NT8StrategyBase.OnExecutionUpdate().
/// </summary>
/// <param name="nt8OrderId">NT8 order ID</param>
/// <param name="executionId">NT8 execution ID</param>
/// <param name="price">Fill price</param>
/// <param name="quantity">Fill quantity</param>
/// <param name="time">Execution time</param>
public void ProcessExecution(
    string nt8OrderId,
    string executionId,
    double price,
    int quantity,
    DateTime time)
{
    if (string.IsNullOrWhiteSpace(nt8OrderId))
        return;
    
    lock (_lock)
    {
        // Map NT8 order ID to SDK order ID
        if (!_nt8ToSdkOrderMap.ContainsKey(nt8OrderId))
            return; // Not our order
        
        var sdkOrderId = _nt8ToSdkOrderMap[nt8OrderId];
        if (!_orderTracking.ContainsKey(sdkOrderId))
            return;
        
        var info = _orderTracking[sdkOrderId];
        
        // Update fill info
        // Note: NT8 may send multiple execution callbacks for partial fills
        // We track cumulative filled quantity via ProcessOrderUpdate
        
        info.LastUpdate = time;
        
        // Update state based on filled quantity
        if (info.FilledQuantity >= info.OriginalRequest.Quantity)
        {
            info.CurrentState = OrderState.Filled;
        }
        else if (info.FilledQuantity > 0)
        {
            info.CurrentState = OrderState.PartiallyFilled;
        }
    }
}
```

#### Method 4: CancelOrder

```csharp
/// <summary>
/// Request to cancel an order.
/// NOTE: Actual cancellation happens in NT8StrategyBase via CancelOrder().
/// This method validates and marks order for cancellation.
/// </summary>
/// <param name="sdkOrderId">SDK order ID to cancel</param>
/// <returns>True if cancel request accepted, false if order can't be cancelled</returns>
public bool CancelOrder(string sdkOrderId)
{
    if (string.IsNullOrWhiteSpace(sdkOrderId))
        throw new ArgumentNullException("sdkOrderId");
    
    lock (_lock)
    {
        if (!_orderTracking.ContainsKey(sdkOrderId))
            return false; // Order not found
        
        var info = _orderTracking[sdkOrderId];
        
        // Check if order is in cancellable state
        if (info.CurrentState == OrderState.Filled ||
            info.CurrentState == OrderState.Cancelled ||
            info.CurrentState == OrderState.Rejected)
        {
            return false; // Already in terminal state
        }
        
        // Mark as pending cancellation
        // Actual state change happens in ProcessOrderUpdate callback
        info.LastUpdate = DateTime.UtcNow;
        
        return true;
    }
}
```

#### Method 5: GetOrderStatus

```csharp
/// <summary>
/// Get current status of an order.
/// </summary>
/// <param name="sdkOrderId">SDK order ID</param>
/// <returns>Order status or null if not found</returns>
public OrderStatus GetOrderStatus(string sdkOrderId)
{
    if (string.IsNullOrWhiteSpace(sdkOrderId))
        return null;
    
    lock (_lock)
    {
        if (!_orderTracking.ContainsKey(sdkOrderId))
            return null;
        
        var info = _orderTracking[sdkOrderId];
        
        return new OrderStatus(
            orderId: info.SdkOrderId,
            state: info.CurrentState,
            symbol: info.OriginalRequest.Symbol,
            side: info.OriginalRequest.Side,
            quantity: info.OriginalRequest.Quantity,
            type: info.OriginalRequest.Type,
            filled: info.FilledQuantity,
            averageFillPrice: info.FilledQuantity > 0 ? (double?)info.AverageFillPrice : null,
            message: info.ErrorMessage,
            timestamp: info.LastUpdate
        );
    }
}
```

#### Helper Method: MapNT8OrderState

```csharp
/// <summary>
/// Maps NinjaTrader 8 order state strings to SDK OrderState enum.
/// </summary>
/// <param name="nt8State">NT8 order state as string</param>
/// <returns>Mapped SDK OrderState</returns>
private OrderState MapNT8OrderState(string nt8State)
{
    if (string.IsNullOrWhiteSpace(nt8State))
        return OrderState.Unknown;
    
    // NT8 order states: https://ninjatrader.com/support/helpGuides/nt8/?orderstate.htm
    switch (nt8State.ToUpperInvariant())
    {
        case "ACCEPTED":
        case "WORKING":
            return OrderState.Working;
        
        case "FILLED":
            return OrderState.Filled;
        
        case "PARTFILLED":
        case "PARTIALLYFILLED":
            return OrderState.PartiallyFilled;
        
        case "CANCELLED":
        case "CANCELED":
            return OrderState.Cancelled;
        
        case "REJECTED":
            return OrderState.Rejected;
        
        case "PENDINGCANCEL":
            return OrderState.Working; // Still working until cancelled
        
        case "PENDINGCHANGE":
        case "PENDINGSUBMIT":
            return OrderState.Pending;
        
        default:
            return OrderState.Unknown;
    }
}
```

### Unit Tests for NT8ExecutionAdapter

**Create:** `tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs`

**Test Count:** 15 tests minimum

```csharp
public class NT8ExecutionAdapterTests
{
    // TEST 1: Submit valid order
    [Fact]
    public void SubmitOrder_WithValidRequest_ShouldCreateTrackingInfo()
    
    // TEST 2: Submit duplicate order
    [Fact]
    public void SubmitOrder_WithDuplicateOrderId_ShouldThrowInvalidOperationException()
    
    // TEST 3: Submit with null request
    [Fact]
    public void SubmitOrder_WithNullRequest_ShouldThrowArgumentNullException()
    
    // TEST 4: Process order update - Working state
    [Fact]
    public void ProcessOrderUpdate_WithWorkingState_ShouldUpdateState()
    
    // TEST 5: Process order update - Filled state
    [Fact]
    public void ProcessOrderUpdate_WithFilledState_ShouldMarkFilled()
    
    // TEST 6: Process order update - Rejected with error
    [Fact]
    public void ProcessOrderUpdate_WithRejection_ShouldSetErrorMessage()
    
    // TEST 7: Process execution - Full fill
    [Fact]
    public void ProcessExecution_WithFullFill_ShouldMarkFilled()
    
    // TEST 8: Process execution - Partial fill
    [Fact]
    public void ProcessExecution_WithPartialFill_ShouldMarkPartiallyFilled()
    
    // TEST 9: Cancel order - Valid
    [Fact]
    public void CancelOrder_WithWorkingOrder_ShouldReturnTrue()
    
    // TEST 10: Cancel order - Already filled
    [Fact]
    public void CancelOrder_WithFilledOrder_ShouldReturnFalse()
    
    // TEST 11: Get order status - Exists
    [Fact]
    public void GetOrderStatus_WithExistingOrder_ShouldReturnStatus()
    
    // TEST 12: Get order status - Not found
    [Fact]
    public void GetOrderStatus_WithNonExistentOrder_ShouldReturnNull()
    
    // TEST 13: NT8 order state mapping
    [Theory]
    [InlineData("ACCEPTED", OrderState.Working)]
    [InlineData("FILLED", OrderState.Filled)]
    [InlineData("CANCELLED", OrderState.Cancelled)]
    [InlineData("REJECTED", OrderState.Rejected)]
    public void MapNT8OrderState_WithKnownStates_ShouldMapCorrectly(string nt8State, OrderState expected)
    
    // TEST 14: Thread safety - Concurrent submissions
    [Fact]
    public void SubmitOrder_WithConcurrentCalls_ShouldBeThreadSafe()
    
    // TEST 15: Multiple executions for same order
    [Fact]
    public void ProcessExecution_WithMultipleCallsForSameOrder_ShouldAccumulate()
}
```

### Success Criteria

**For NT8ExecutionAdapter:**
- [ ] All public methods implemented
- [ ] Thread-safe with lock protection
- [ ] Comprehensive XML documentation
- [ ] C# 5.0 compliant (no modern syntax)
- [ ] Zero build warnings

**For Tests:**
- [ ] All 15 tests implemented
- [ ] All tests pass
- [ ] Code coverage >90% for NT8ExecutionAdapter
- [ ] Thread safety validated

---

## 🔄 Implementation Workflow

### Step 1: Create Test File (30 min)
1. Create `tests/NT8.Core.Tests/Adapters/` directory
2. Create `NT8DataConverterTests.cs`
3. Implement all 27 tests
4. Run tests - should all PASS (code already exists)

### Step 2: Verify Test Coverage (15 min)
```bash
dotnet test --collect:"XPlat Code Coverage"
# Verify >95% coverage for NT8DataConverter
```

### Step 3: Create NT8ExecutionAdapter (2 hours)
1. Create `src/NT8.Adapters/NinjaTrader/NT8ExecutionAdapter.cs`
2. Implement all methods per specification
3. Add XML documentation
4. Verify C# 5.0 compliance

### Step 4: Create Execution Adapter Tests (1 hour)
1. Create `tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs`
2. Implement all 15 tests
3. Run tests - should all PASS

### Step 5: Build & Verify (15 min)
```bash
dotnet build --configuration Release
dotnet test --configuration Release
.\verify-build.bat
```

### Step 6: Git Commit
```bash
git add tests/NT8.Core.Tests/Adapters/
git add src/NT8.Adapters/NinjaTrader/NT8ExecutionAdapter.cs
git commit -m "feat: Add NT8 adapter tests and execution adapter

- Added 27 unit tests for NT8DataConverter (>95% coverage)
- Implemented NT8ExecutionAdapter with order tracking
- Added 15 unit tests for NT8ExecutionAdapter (>90% coverage)
- Thread-safe order state management
- NT8 order state mapping
- C# 5.0 compliant

Phase A complete: Foundation adapters ready for NT8 integration"
```

---

## 📊 Deliverables Checklist

- [ ] `tests/NT8.Core.Tests/Adapters/NT8DataConverterTests.cs` (27 tests)
- [ ] `src/NT8.Adapters/NinjaTrader/NT8ExecutionAdapter.cs` (full implementation)
- [ ] `tests/NT8.Core.Tests/Adapters/NT8ExecutionAdapterTests.cs` (15 tests)
- [ ] All 42 new tests passing
- [ ] All 240+ existing tests still passing
- [ ] Zero build warnings
- [ ] Code coverage: >95% for DataConverter, >90% for ExecutionAdapter
- [ ] Git commit with clear message

---

## 🚨 Important Constraints

1. **C# 5.0 Only** - No:
   - `async/await`
   - String interpolation `$""`
   - Expression-bodied members `=>`
   - Pattern matching
   - Tuples
   - Use `string.Format()` instead of `$""`

2. **Thread Safety** - All shared state must use `lock (_lock)`

3. **Defensive Programming** - Validate all inputs, null checks

4. **XML Documentation** - All public members must have /// comments

5. **Test Patterns** - Follow existing test conventions in `tests/NT8.Core.Tests`

---

## 🎯 Success Metrics

**Definition of Done:**
- ✅ All 42 tests passing
- ✅ All existing 240+ tests still passing
- ✅ Build succeeds with zero warnings
- ✅ Code coverage targets met
- ✅ Thread safety verified
- ✅ C# 5.0 compliant
- ✅ Committed to Git

**Time Target:** 4-5 hours total

---

**READY FOR KILOCODE EXECUTION IN CODE MODE** ✅
