Announcement

Collapse
No announcement yet.

Partner 728x90

Collapse

Optimal Data Structure for Storing Multiple ATM Strategy/EntryOrder IDs

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Optimal Data Structure for Storing Multiple ATM Strategy/EntryOrder IDs

    What would be the optimal data structure for storing multiple unique IDs generated by GetAtmStrategyUniqueId()? Is there any benefit/drawback between utilizing a List<t> object vs simply an array data structure?

    The use-case here is for strategies placing orders utilizing the unmanaged approach (and ATM Templates) in which the following WILL occur with certainty:
    1) Consecutive triggers (whether same direction or opposing direction) occur on unique bars (so 1 trigger per bar, no simultaneous opposing direction orders are ever placed) BEFORE the target/stop OCO of the FIRST entry is hit.

    I have code that currently handles all relevant order management with the forced condition of only a SINGULAR ATM Strategy running at any given time. What I would like to do is utilize my existing structure and simply loop through either a List or Array to apply the processing logic to all actively running ATM Strategies and EntryOrders...

    So something along the lines of:
    Code:
    foreach(string atmStrategyId in ListAtmStrategyId)
    {
        // Execute logic
    }
    There's definitely benefit in utilizing the dynamic sizing of of the List object and associated methods, but wanted to get some confirmatory input.

    Thx.

    #2
    After some digging I decided to utilize a List.

    For the benefit of others I wanted to include my implementation for reference:

    Code:
    // Declare new class containing "fields" belonging to each ATM Strategy record
    public class atmStrategyRecord
    {[INDENT]public string fieldAtmStrategyId {get; set;}[/INDENT][INDENT]public string fieldEntryOrderId {get; set;}[/INDENT][INDENT]public int fieldEntryLongShort {get; set;}
    public int fieldEntryTriggerBar {get; set;}
    public bool fieldIsTradeTaken {get; set;}
    public bool fieldIsEntryCancelled {get; set;}
    public double fieldEntryOrderPriceSim {get; set;}
    public double fieldEntryOrderPriceActual {get; set;}
    public double fieldTargetAnchorPrice {get; set;}
    public double fieldStopAnchorPrice {get; set;}[/INDENT]
     }
    
    public class sStrategy : Strategy
    {[INDENT]// Create List object for simultaneous ATM Strategy handling
    private List<atmStrategyRecord> listAtmStrategies = new List<atmStrategyRecord>();
    
    [/INDENT][INDENT]protected override void OnBarUpdate()
    {[/INDENT][INDENT=2]// After calling AtmStrategyCreate(), add elements to listAtmStrategies
    listAtmStrategies.Add(new atmStrategyRecord
    {[/INDENT][INDENT=3]fieldAtmStrategyId = atmStrategyId,
    fieldEntryOrderId = entryOrderId,
    fieldEntryLongShort = entryLongShort,
    fieldEntryTriggerBar = entryTriggerBar,
    fieldIsTradeTaken = isTradeTaken,
    fieldIsEntryCancelled = isEntryCancelled,
    fieldEntryOrderPriceSim = entryOrderPriceSim,
    fieldEntryOrderPriceActual = double.NaN,
    fieldTargetAnchorPrice = targetAnchorPrice,
    fieldStopAnchorPrice = stopAnchorPrice[/INDENT][INDENT=2]});
    
    // Loop through all records contained in listAtmStrategies
    foreach(atmStrategyRecord r in listAtmStrategies)
    {[/INDENT][INDENT=3]// Entry order, Exit orders, and Termination management logic
    // Access/update relevant fields for given record via r.fieldname accessor[/INDENT][INDENT=2]}[/INDENT][INDENT]}[/INDENT]
      }

    Comment


      #3
      Hello TheFil,

      Thank you for sharing your thoughts and your script.

      I was able to find a similar forum thread regarding multiple unmanaged ATM strategy orders here:
      Howdy. I am new to attempting to use ATM strategies. I have been working with the &quot;sampleATMstrategy&quot; that ships with NT. I also recreated THIS (https://ninjatrader.com/support/helpGuides/nt8/atmstrategycreate.htm?zoom_highlightsub=atmstrategycreate) example, and have attached it below(not necessary to download),


      The user forrestang also settled on using a list to store important information related to ATM strategy orders.

      If you run into any specific questions or concerns as you test out your strategy, please let us know and we'd be glad to assist.

      Comment


        #4
        Thanks NinjaTrader_Emily.

        I actually do have a follow-up that is very similar to the callback issues in the post you linked (looping in NinjaTrader_ChrisL who assisted on that case).

        My structure differs from the linked post in that my loop runs AFTER AtmStrategyCreate() including successful confirmation of isAtmStrategyCreated == true.

        Below is the entirety of my long entry logic:
        Code:
        /// SIGNAL-PROCESSING: LONG -------------------------------------------------------------------------------------------------------------------------------- ///
        #region SignalProcessingLong[INDENT]// LONG CONDITION[/INDENT][INDENT]if[/INDENT][INDENT]([/INDENT][INDENT=2]// Ensure maxQuantity >= 1
        maxQuantity >= 1 &&
        // Confirm the targeted trade has not been cancelled and/or already placed intra-bar
        !isEntryCancelled && !isTradeTaken &&
        // Long Signal passed from iOrderflowClassifier()
        iOrderflowClassifier1.longSignal[refIndex] == true[/INDENT][INDENT])[/INDENT][INDENT]// END: LONG CONDITION
        
        {[/INDENT][INDENT=2]// Print signal latency: Time between the signal bar close as indicated by NT timestamp and the confirmation of the signal @ runtime
        PrintTo = PrintTo.OutputTab1; // Print all latencies to OutputTab1
        Print("Signal Long Latency: " + (stopWatch.ElapsedTicks*10).ToString("N0") + " ns");
        Print(">> " + DateTime.Now);
        
        // Long -- entryOrder at the signal threshold +/- offset ticks
        isAtmStrategyCreated = false; // Reset atm strategy created check to false
        atmStrategyId = GetAtmStrategyUniqueId();
        entryOrderId = GetAtmStrategyUniqueId();
        
        if (Close[refIndex] > Low[refIndex])
        {entryOrderPriceSim = Instrument.MasterInstrument.RoundToTickSize(Close[refIndex] + entryTickOffset*tickSize);}
        else
        {entryOrderPriceSim = Instrument.MasterInstrument.RoundToTickSize(Close[refIndex] - entryTickOffset*tickSize);}
        
        AtmStrategyCreate([/INDENT][INDENT=3]OrderAction.Buy, // P1 Action
        OrderType.Limit, // P2 orderType
        entryOrderPriceSim, // P3 limitPrice double (0 for Market orders)
        0, // P4 stopPrice double (0 for Market orders)
        TimeInForce.Day, // P5 TIF (Market orders ALWAYS TimeInForce.Day)
        entryOrderId, // P6 orderId string
        atmAutoScaleSelection, // P7 strategyTemplateName string (Must have been created in SuperDOM ATMStrategy Template)
        atmStrategyId, // P8 atmStrategyId string
        (atmCallbackErrorCode, atmCallBackId) => // P9 Action<Cbi.ErrorCode, string> callback
        {
        // Check atm strategy create DID NOT result in error AND requested atm strategy matches the id in callback
        if (atmCallbackErrorCode == ErrorCode.NoError && atmCallBackId == atmStrategyId)
        isAtmStrategyCreated = true;[/INDENT][INDENT=3]});[/INDENT][INDENT=2]
        // Print order latency: Time between the signal bar close as indicated by NT timestamp and the placement of an order @ runtime
        Print("Order Long Latency: " + (stopWatch.ElapsedTicks*10).ToString("N0") + " ns");
        Print(">> " + DateTime.Now);
        PrintTo = PrintTo.OutputTab2; // Revert all non-latency messages back to OutputTab2
        
        entryLongShort = 1; // Note the assignment of LONG
        entryTriggerBar = CurrentBar; // Assign for subsequent indexing
        isEntryCancelled = false; // At the point of submission, the entryOrder is live
        isTradeTaken = true; // Identifier to prevent multiple same-bar entries
        
        // Get theoretical target price as-if the trade were executed at entryOrderPriceSim
        targetAnchorPrice = Instrument.MasterInstrument.RoundToTickSize(entryO rderPriceSim + longTargetTicks*tickSize);
        
        // Get theoretical stop price as-if the trade were executed at entryOrderPriceSim
        stopAnchorPrice = Instrument.MasterInstrument.RoundToTickSize(entryO rderPriceSim - longStopTicks*tickSize);
        
        // Check that ATM was actually created before populating listAtmStrategies; onError proceed to TradeLifecycleManagement label
        if (!isAtmStrategyCreated)
        goto TradeLifecycleManagement;
        
        // Add elements to listAtmStrategies
        listAtmStrategies.Add(new atmStrategyRecord
        {[/INDENT][INDENT=3]fieldAtmStrategyId = atmStrategyId,
        fieldEntryOrderId = entryOrderId,
        fieldEntryLongShort = entryLongShort,
        fieldEntryTriggerBar = entryTriggerBar,
        fieldIsTradeTaken = isTradeTaken,
        fieldIsEntryCancelled = isEntryCancelled,
        fieldEntryOrderPriceSim = entryOrderPriceSim,
        fieldEntryOrderPriceActual = double.NaN,
        fieldTargetAnchorPrice = targetAnchorPrice,
        fieldStopAnchorPrice = stopAnchorPrice[/INDENT][INDENT=2]});[/INDENT][INDENT]}[/INDENT]
         
        
        /// END SIGNAL-PROCESSING: LONG ---------------------------------------------------------------------------------------------------------------------------- ///
        #endregion
        The purpose of my loop is to manage multiple ATM Strategies AFTER they've been created; namely entry order modification/cancellation, target/stop order modification, and ultimately termination upon fill of either leg of the associated OCO leg.

        Borrowing from the structure of @SampleAtmStrategy.cs, I also utilize the same strategy termination logic:
        Code:
        else if (atmStrategyId.Length > 0 && GetAtmStrategyMarketPosition(atmStrategyId) == Cbi.MarketPosition.Flat)
        The difference being that I pass r.fieldAtmStrategyId to GetAtmStrategyMarketPosition():
        Code:
        else if (GetAtmStrategyMarketPosition(r.fieldAtmStrategyId) == Cbi.MarketPosition.Flat)
        My question is whether there is a similar timing-related issue happening here as well? My foreach loop is running successfully and evaluating r.fieldEntryOrderId (for example) BUT it fails to resolve the expression:
        Code:
        (GetAtmStrategyMarketPosition(r.fieldAtmStrategyId) == Cbi.MarketPosition.Flat)
        to true upon the fill of either leg of the OCO. I have confirmed this with debug print statements contained within the block.

        Thoughts on this? Hopefully something simple I'm missing...
        Last edited by TheFil; 09-06-2022, 10:12 PM.

        Comment


          #5
          In reviewing my logs, I note there is 1ms timestamp differential between the Order update confirming the target/stop OCO fill and the subsequent Position update where position=flat & Operation=remove.

          Cbi.MarketPosition is reliant on the Position update.

          That 1ms is definitely long enough for a minimal element List to be iterated through to completion prior to the Position update being caught.

          Comment


            #6
            Below is an example log...

            24ms of time elapsed between the order fill and the PositionUpdate event
            151ms of time elapsed between the order fill and AtmStrategy.Terminate

            Click image for larger version

Name:	logLatency.png
Views:	143
Size:	1.27 MB
ID:	1214640
            Last edited by TheFil; 09-07-2022, 12:24 AM.

            Comment


              #7
              I have come full circle.

              I should have tested this first...

              Click image for larger version

Name:	atmStrategyCreateCode.png
Views:	131
Size:	594.3 KB
ID:	1214661

              As verified in the output window(s):

              Click image for larger version

Name:	outputWindowLatency.png
Views:	130
Size:	125.3 KB
ID:	1214662

              #1 executes BEFORE #2...

              Comment


                #8
                What would be the best way to incorporate an async await on isAtmStrategyCreated? That's in effect what's needed, from what I can tell?

                Comment


                  #9
                  NinjaTrader_ChrisL, could you assist me in properly creating a Task object which calls AtmStrategyCreate() and awaits the completion of the Task object prior to making reference to isAtmStrategyCreated?

                  I specifically don't want to utilize a return to the calling method OnBarUpdate() and I would also like to avoid any sort of hardcoded wait statement...

                  I'm not a proper c# developer so I'm stumbling through how to appropriately create the async Task without running into compilation errors

                  Comment


                    #10
                    Hello TheFil,

                    We do not recommend sleeping threads or attempting to make asynchronous tasks synchronous, or vice-versa.

                    We generally recommend implementing a timer to wait for the script to receive update from other parts.

                    For example, when the code reaches a certain part, start a timer, and then in the timer's event method, use TriggerCustomEvent to trigger code to respond to something in a "delayed" fashion so all needed updates are received.

                    I have attached an example for "delayed events" that could be used for reference.

                    Or, if it is within the scope of your script, to have code triggered from the callback.



                    Attached Files

                    Comment


                      #11
                      Thanks NinjaTrader_Jim,

                      I actually went a different route here...

                      After testing some additional behavior of AtmStrategyCreate() and what the callback/associated value assignment to isAtmStrategyCreated actually accomplish I was able to revert to the asynchronous processing of the callback while letting the application thread go ahead and process List.Add operations to my listAtmStrategies prior to the callback assigning a value of true to isAtmStrategyCreated.

                      Thanks for the reference code nonetheless.

                      Comment

                      Latest Posts

                      Collapse

                      Topics Statistics Last Post
                      Started by NullPointStrategies, Today, 05:17 AM
                      0 responses
                      50 views
                      0 likes
                      Last Post NullPointStrategies  
                      Started by argusthome, 03-08-2026, 10:06 AM
                      0 responses
                      126 views
                      0 likes
                      Last Post argusthome  
                      Started by NabilKhattabi, 03-06-2026, 11:18 AM
                      0 responses
                      69 views
                      0 likes
                      Last Post NabilKhattabi  
                      Started by Deep42, 03-06-2026, 12:28 AM
                      0 responses
                      42 views
                      0 likes
                      Last Post Deep42
                      by Deep42
                       
                      Started by TheRealMorford, 03-05-2026, 06:15 PM
                      0 responses
                      46 views
                      0 likes
                      Last Post TheRealMorford  
                      Working...
                      X