141 lines
4.4 KiB
Markdown
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
|