Files
nt8-sdk/TASK-02-circuit-breaker.md
2026-02-24 15:00:41 -05:00

4.4 KiB

TASK-02: Wire ExecutionCircuitBreaker into NT8StrategyBase

File: src/NT8.Adapters/Strategies/NT8StrategyBase.cs Priority: CRITICAL Depends on: TASK-01 must be done first (file already open/modified) Estimated time: 45 min


Background

ExecutionCircuitBreaker at src/NT8.Core/Execution/ExecutionCircuitBreaker.cs is complete and tested. Its public API is:

  • bool ShouldAllowOrder() — returns false when circuit is Open
  • void OnSuccess() — call after a successful order submission
  • void OnFailure() — call after a failed order submission
  • void RecordOrderRejection(string reason) — call when NT8 rejects an order
  • void Reset() — resets to Closed state

The ExecutionCircuitBreaker constructor:

public ExecutionCircuitBreaker(
    ILogger<ExecutionCircuitBreaker> logger,
    int failureThreshold = 3,
    TimeSpan? timeout = null,
    TimeSpan? retryTimeout = null,
    int latencyWindowSize = 100,
    int rejectionWindowSize = 10)

Problem: It is never instantiated. NT8StrategyBase submits orders with no circuit breaker gate.


Exact Changes Required

1. Add using statement at top of NT8StrategyBase.cs

using NT8.Core.Execution;
using Microsoft.Extensions.Logging.Abstractions;

2. Add private field alongside the other private fields

private ExecutionCircuitBreaker _circuitBreaker;

3. Initialize in InitializeSdkComponents(), after _positionSizer = new BasicPositionSizer(_logger);

_circuitBreaker = new ExecutionCircuitBreaker(
    NullLogger<ExecutionCircuitBreaker>.Instance,
    failureThreshold: 3,
    timeout: TimeSpan.FromSeconds(30));

4. Gate SubmitOrderToNT8() — add check at top of the method

private void SubmitOrderToNT8(OmsOrderRequest request, StrategyIntent intent)
{
    // Circuit breaker gate
    if (_circuitBreaker != null && !_circuitBreaker.ShouldAllowOrder())
    {
        var state = _circuitBreaker.GetState();
        Print(string.Format("[SDK] Circuit breaker OPEN — order blocked: {0}", state.Reason));
        if (_logger != null)
            _logger.LogWarning("Circuit breaker blocked order: {0}", state.Reason);
        return;
    }

    try
    {
        // ... EXISTING submit logic unchanged ...
        var orderName = string.Format("SDK_{0}_{1}", intent.Symbol, DateTime.Now.Ticks);
        _executionAdapter.SubmitOrder(request, orderName);

        if (request.Side == OmsOrderSide.Buy)
        { ... existing EnterLong/EnterLongLimit/etc ... }
        else if (request.Side == OmsOrderSide.Sell)
        { ... existing EnterShort/etc ... }

        if (intent.StopTicks > 0)
            SetStopLoss(orderName, CalculationMode.Ticks, (int)intent.StopTicks, false);

        if (intent.TargetTicks.HasValue && intent.TargetTicks.Value > 0)
            SetProfitTarget(orderName, CalculationMode.Ticks, (int)intent.TargetTicks.Value);

        // Mark success after submission
        if (_circuitBreaker != null)
            _circuitBreaker.OnSuccess();
    }
    catch (Exception ex)
    {
        if (_circuitBreaker != null)
            _circuitBreaker.OnFailure();

        Print(string.Format("[SDK] SubmitOrderToNT8 failed: {0}", ex.Message));
        if (_logger != null)
            _logger.LogError("SubmitOrderToNT8 failed: {0}", ex.Message);
        throw;
    }
}

5. Wire rejections in OnOrderUpdate()

In the existing OnOrderUpdate() override, after the null/name checks, add:

// Record NT8 rejections in circuit breaker
if (orderState == NinjaTrader.Cbi.OrderState.Rejected && _circuitBreaker != null)
{
    var reason = string.Format("{0} {1}", errorCode, nativeError ?? string.Empty);
    _circuitBreaker.RecordOrderRejection(reason);
    Print(string.Format("[SDK] Order rejected by NT8: {0}", reason));
}

Acceptance Criteria

  • ExecutionCircuitBreaker is instantiated in InitializeSdkComponents()
  • SubmitOrderToNT8() checks ShouldAllowOrder() before submitting — if false, prints message and returns
  • OnOrderUpdate() calls RecordOrderRejection() when orderState == OrderState.Rejected
  • OnSuccess() called after successful order submission
  • OnFailure() called in catch block
  • verify-build.bat passes with zero errors
  • Existing 240+ tests still pass: dotnet test NT8-SDK.sln --verbosity minimal

Do NOT Change

  • ExecutionCircuitBreaker.cs — already correct, just use it
  • Any Core layer files
  • Any test files