4.4 KiB
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 Openvoid OnSuccess()— call after a successful order submissionvoid OnFailure()— call after a failed order submissionvoid RecordOrderRejection(string reason)— call when NT8 rejects an ordervoid 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
ExecutionCircuitBreakeris instantiated inInitializeSdkComponents()SubmitOrderToNT8()checksShouldAllowOrder()before submitting — if false, prints message and returnsOnOrderUpdate()callsRecordOrderRejection()whenorderState == OrderState.RejectedOnSuccess()called after successful order submissionOnFailure()called in catch blockverify-build.batpasses 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