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

141 lines
4.4 KiB
Markdown

# 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:
```csharp
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`
```csharp
using NT8.Core.Execution;
using Microsoft.Extensions.Logging.Abstractions;
```
### 2. Add private field alongside the other private fields
```csharp
private ExecutionCircuitBreaker _circuitBreaker;
```
### 3. Initialize in `InitializeSdkComponents()`, after `_positionSizer = new BasicPositionSizer(_logger);`
```csharp
_circuitBreaker = new ExecutionCircuitBreaker(
NullLogger<ExecutionCircuitBreaker>.Instance,
failureThreshold: 3,
timeout: TimeSpan.FromSeconds(30));
```
### 4. Gate `SubmitOrderToNT8()` — add check at top of the method
```csharp
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:
```csharp
// 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