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

5.7 KiB
Raw Blame History

Task 1 — Wire NT8OrderAdapter.ExecuteInNT8()

Priority: CRITICAL
Estimated time: 34 hours
Blocks: All backtest and live trading
Status: TODO


Problem

NT8OrderAdapter.ExecuteInNT8() in src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs is a stub.
It only logs to an internal list. The actual NT8 calls (EnterLong, EnterShort, SetStopLoss, SetProfitTarget) are in a commented-out block and never execute. This is why backtests show zero trades.


What Needs to Change

File: src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs

The adapter currently has no reference to the actual NinjaScript Strategy object. It needs a way to call NT8 managed order methods. The pattern used by NT8StrategyBase is the right model to follow.

Option A (Recommended): Inject a callback delegate so the adapter can call NT8 methods without directly holding a NinjaScript reference.

Add a new INT8ExecutionBridge interface:

// new file: src/NT8.Adapters/NinjaTrader/INT8ExecutionBridge.cs
namespace NT8.Adapters.NinjaTrader
{
    /// <summary>
    /// Provides NT8OrderAdapter access to NinjaScript execution methods.
    /// Implemented by NT8StrategyBase.
    /// </summary>
    public interface INT8ExecutionBridge
    {
        /// <summary>Submit a long entry with stop and target.</summary>
        void EnterLongManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize);

        /// <summary>Submit a short entry with stop and target.</summary>
        void EnterShortManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize);

        /// <summary>Exit all long positions.</summary>
        void ExitLongManaged(string signalName);

        /// <summary>Exit all short positions.</summary>
        void ExitShortManaged(string signalName);

        /// <summary>Flatten the full position immediately.</summary>
        void FlattenAll();
    }
}

Update NT8OrderAdapter constructor to accept INT8ExecutionBridge:

public NT8OrderAdapter(INT8ExecutionBridge bridge)
{
    if (bridge == null)
        throw new ArgumentNullException("bridge");
    _bridge = bridge;
    _executionHistory = new List<NT8OrderExecutionRecord>();
}

Implement ExecuteInNT8():

private void ExecuteInNT8(StrategyIntent intent, SizingResult sizing)
{
    if (intent == null)
        throw new ArgumentNullException("intent");
    if (sizing == null)
        throw new ArgumentNullException("sizing");

    var signalName = string.Format("SDK_{0}_{1}", intent.Symbol, intent.Side);

    if (intent.Side == Common.Models.OrderSide.Buy)
    {
        _bridge.EnterLongManaged(
            sizing.Contracts,
            signalName,
            intent.StopTicks,
            intent.TargetTicks.HasValue ? intent.TargetTicks.Value : 0,
            intent.TickSize);
    }
    else if (intent.Side == Common.Models.OrderSide.Sell)
    {
        _bridge.EnterShortManaged(
            sizing.Contracts,
            signalName,
            intent.StopTicks,
            intent.TargetTicks.HasValue ? intent.TargetTicks.Value : 0,
            intent.TickSize);
    }

    lock (_lock)
    {
        _executionHistory.Add(new NT8OrderExecutionRecord(
            intent.Symbol,
            intent.Side,
            intent.EntryType,
            sizing.Contracts,
            intent.StopTicks,
            intent.TargetTicks,
            DateTime.UtcNow));
    }
}

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

Implement INT8ExecutionBridge on NT8StrategyBase:

public class NT8StrategyBase : Strategy, INT8ExecutionBridge
{
    public void EnterLongManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize)
    {
        SetStopLoss(signalName, CalculationMode.Ticks, stopTicks, false);
        if (targetTicks > 0)
            SetProfitTarget(signalName, CalculationMode.Ticks, targetTicks);
        EnterLong(quantity, signalName);
    }

    public void EnterShortManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize)
    {
        SetStopLoss(signalName, CalculationMode.Ticks, stopTicks, false);
        if (targetTicks > 0)
            SetProfitTarget(signalName, CalculationMode.Ticks, targetTicks);
        EnterShort(quantity, signalName);
    }

    public void ExitLongManaged(string signalName)
    {
        ExitLong(signalName);
    }

    public void ExitShortManaged(string signalName)
    {
        ExitShort(signalName);
    }
    
    // FlattenAll already called in NT8 as: this.Account.Flatten(Instrument)
    // or: ExitLong(); ExitShort();
    public void FlattenAll()
    {
        ExitLong("EmergencyFlatten");
        ExitShort("EmergencyFlatten");
    }
}

Acceptance Criteria

  • NT8OrderAdapter takes INT8ExecutionBridge in its constructor
  • ExecuteInNT8() calls the bridge (no more commented-out code)
  • NT8StrategyBase implements INT8ExecutionBridge
  • OnOrderUpdate() callback in NT8OrderAdapter updates BasicOrderManager state (pass the fill back)
  • verify-build.bat passes
  • A backtest run on SimpleORBNT8 produces actual trades (not zero)

Files to Create/Modify

File Action
src/NT8.Adapters/NinjaTrader/INT8ExecutionBridge.cs CREATE
src/NT8.Adapters/NinjaTrader/NT8OrderAdapter.cs MODIFY — implement ExecuteInNT8(), update constructor
src/NT8.Adapters/Strategies/NT8StrategyBase.cs MODIFY — implement INT8ExecutionBridge

Do NOT Change

  • src/NT8.Core/OMS/BasicOrderManager.cs — the OMS is correct
  • src/NT8.Strategies/Examples/SimpleORBStrategy.cs — strategy logic is correct
  • Any existing test files