Files
nt8-sdk/TASK_03_WIRE_CIRCUIT_BREAKER.md
2026-02-24 15:00:41 -05:00

3.6 KiB
Raw Permalink Blame History

Task 3 — Wire ExecutionCircuitBreaker

Priority: HIGH
Estimated time: 1.52 hours
Depends on: Task 1 (NT8StrategyBase changes)
Status: TODO


Problem

ExecutionCircuitBreaker in src/NT8.Core/Execution/ExecutionCircuitBreaker.cs is a complete, well-tested class.
It is never instantiated or connected to any live order flow. Orders are submitted regardless of latency or rejection conditions.


What Needs to Change

File: src/NT8.Adapters/Strategies/NT8StrategyBase.cs

Step 1: Add ExecutionCircuitBreaker as a field on NT8StrategyBase.

private ExecutionCircuitBreaker _circuitBreaker;

Step 2: Initialize it in OnStateChangeState.DataLoaded:

// Use Microsoft.Extensions.Logging NullLogger for now (or wire to BasicLogger)
_circuitBreaker = new ExecutionCircuitBreaker(
    new NullLogger<ExecutionCircuitBreaker>(),
    failureThreshold: 3,
    timeout: TimeSpan.FromSeconds(30));

Step 3: Gate ALL order submissions through the circuit breaker.
In the method that calls ExecuteIntent() (or wherever orders flow from strategy intent to the adapter), add:

private bool TrySubmitIntent(StrategyIntent intent, StrategyContext context)
{
    if (!_circuitBreaker.ShouldAllowOrder())
    {
        var state = _circuitBreaker.GetState();
        Print(string.Format("[NT8-SDK] Circuit breaker OPEN — order blocked. Reason: {0}", state.Reason));
        return false;
    }

    try
    {
        _orderAdapter.ExecuteIntent(intent, context, _strategyConfig);
        _circuitBreaker.OnSuccess();
        return true;
    }
    catch (Exception ex)
    {
        _circuitBreaker.OnFailure();
        _circuitBreaker.RecordOrderRejection(ex.Message);
        Print(string.Format("[NT8-SDK] Order execution failed: {0}", ex.Message));
        return false;
    }
}

Step 4: Wire OnOrderUpdate rejections back to the circuit breaker.
In NT8StrategyBase.OnOrderUpdate():

protected override void OnOrderUpdate(Order order, double limitPrice, double stopPrice,
    int quantity, int filled, double averageFillPrice,
    OrderState orderState, DateTime time, ErrorCode error, string nativeError)
{
    if (orderState == OrderState.Rejected)
    {
        if (_circuitBreaker != null)
        {
            _circuitBreaker.RecordOrderRejection(
                string.Format("NT8 rejected order: {0} {1}", error, nativeError));
        }
    }

    // Pass through to adapter for state tracking
    if (_orderAdapter != null)
    {
        _orderAdapter.OnOrderUpdate(
            order != null ? order.Name : "unknown",
            limitPrice, stopPrice, quantity, filled,
            averageFillPrice,
            orderState != null ? orderState.ToString() : "unknown",
            time, error.ToString(), nativeError ?? string.Empty);
    }
}

Acceptance Criteria

  • ExecutionCircuitBreaker is instantiated in NT8StrategyBase
  • All order submissions go through _circuitBreaker.ShouldAllowOrder() — if false, order is blocked and logged
  • NT8 order rejections call _circuitBreaker.RecordOrderRejection()
  • 3 consecutive rejections open the circuit breaker (blocks further orders for 30 seconds)
  • After 30 seconds, circuit breaker enters half-open and allows one test order
  • verify-build.bat passes

Files to Modify

File Action
src/NT8.Adapters/Strategies/NT8StrategyBase.cs Add circuit breaker field, initialize, gate submissions, wire rejections

Files to NOT Change

  • src/NT8.Core/Execution/ExecutionCircuitBreaker.cs — complete and correct, do not touch
  • Any test files