Announcement

Collapse

Looking for a User App or Add-On built by the NinjaTrader community?

Visit NinjaTrader EcoSystem and our free User App Share!

Have a question for the NinjaScript developer community? Open a new thread in our NinjaScript File Sharing Discussion Forum!
See more
See less

Partner 728x90

Collapse

Hosted Indicators in Multi-DataSeries Scripts

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

    Hosted Indicators in Multi-DataSeries Scripts

    Supplemental dataseries
    The AddDataSeries api allows scripts to load supplemental dataseries. These are price data streams that are distinct and separate from the chart or “primary” dataseries that the indicator/strategy is running on. The OHLCV of these supplemental series can be referenced by specifying the barsarray index.
    So if a script calls
    Code:
    AddDataSeries(“ES 09-21”);
    in the State.Configure event of a strategy that’s running on a chart of NQ 09-21, then the script can access the last price of NQ with Closes[0][0] and the last price of ES with Closes[1][0]. The “1” is the index of the 1st data series added with a call to AddDataSeries.

    By the way, as a side bar suggestion to this post it would be great if AddDataSeries returned the index of the bar series that is assigned to it.

    The other data points of the OHLCV supplemental series can be accessed in similar fashion:
    Opens[1][0], Highs[1][0], Lows[1][0], Volume[1][0].

    Now suppose a strategy uses indicators internally and the strategy wants the indicators to use the supplemental series. No problem, the NinjaScript code generator that creates the constructor script for its hosted indicators creates an overload for each indicator so that the strategy can specify which dataseries the hosted indicator should use:

    Code:
    public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
    {
    public Indicators.SMA SMA(int period)
    {
    return indicator.SMA(Input, period);
    }
    
    public Indicators.SMA SMA(ISeries<double> input , int period)
    {
    return indicator.SMA(input, period);
    }
    }

    Code:
    public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
    {
    public Indicators.ADX ADX(int period)
    {
    return indicator.ADX(Input, period);
    }
    Continuing with our example, to get the SMA of the ES:
    Code:
    SMA(Closes[1], 14);
    The Closes[1] parameter is passed into the SMA. Inside the SMA, this is referenced as the Input dataseries. The implementation of the SMA uses it to compute the moving average of the Input. In most cases the user wants the SMA of the closing price but they’re not limited to this. A user can pass in
    SMA(Volumes[0], 14) or SMA(Volumes[1], 14) to get the average volume of NQ and ES.

    Suppose the user wants the ADX of the supplemental dataseries:
    Code:
    public Indicators.ADX ADX(ISeries<double> input , int period)
    {
    return indicator.ADX(input, period);
    }
    ADX has the same overloaded constructor. But the implementation of ADX is not using the Input series that’s passed into this overloaded constructor. Instead it’s using the Close series. Also, it’s using the High and the Low and we haven’t discovered any means of supplying the supplemental series High or Low to a hosted script.
    Continuing with our example, to get the ADX of ES :

    Code:
    ADX(Closes[1], 14);
    But will this work? I don't think it will because ADX doesn't use it's Input. So how can scripts run indicators like ADX on supplemental series?

    There is a BarsInProgress property that indicates what the current bars in progress are in any given call to OnBarUpdate. If BarsInProgress is equal to 1 then a call to ADX(14) or ATR(14) returns the values for the BarsArray[1] ? If so is it valid to cache the references returned?

    Code:
    ADX adxForES;
    ATR adxForNQ;
    if(BarsInProgress == 0)
    adxForNQ = ADX(14);
    else
    if(BarsInProgress == 1)
    adxForES = ADX(14);
    Then, after doing the above, does it matter what the BIP are when using these references? For example if BIP is 0 can adxForES be used? I’m thinking not. I think that the Open, High, Low, Close Volume dataseries properties get connected to the BIP so if BIP is 0 using adxForES will cause the ADX it references to use OHLCV dataseries for the 0 BIP ( the NQ ).

    To use supplemental dataseries with hosted indicators, the hosting script MUST make sure that the hosted indicator is run in the context of the BIP that it's intended to run in. But how?
    I see there's an overload to the Update method. How is this to be used?

    Code:
    public void Update(int idx, int bip);
    I'm guessing
    Code:
    adxForES.Update(0,1);
    is the way to get the adxForES to run for BIP 1? What is that idx parameter for?


    Last edited by Brillo; 08-24-2021, 06:45 AM. Reason: Formatting paragraphs

    #2
    Hello Brillo,

    Thanks for your post.

    Several indicators are used in NT7 as input for other indicators. This functionality was not carried over into NT8. In most cases, the input will revert to the Close data series even if you select an indicator as the input series.

    Please see the attached ADxInputv1.1 indicator script which has been converted to function correctly when normal data is used as an input or another indicator is used as an input.

    Note that Input reads the input series and the OHLC PriceSeries will get the OHLC of the input data series.

    See this help guide page for more information: https://ninjatrader.com/support/help...nt8/?input.htm

    Let us know if we may assist further.

    Attached Files
    Brandon H.NinjaTrader Customer Service

    Comment


      #3
      Thank you for your response.
      There are 2 features/issues of the scripting api that I have raised in my post. You addressed one of them - the use of Input. The other is the BarsInProgress and how it relates to hosted indicators.
      Regarding the former - the use of Input. I examined the ADXInput sample you sent me. It can not be used to get the ADX to work correctly for a secondary price series. If the user of it passes in the barsarray for the secondary price series from my example, the ES that was loaded with AddDataSeries, as the Input, then this ADXInput will use the closes of the ES as the high, low and close for the ADX. The ADX needs the real High and Low for the ES. How can it get that?

      I brought up the BarsInProgress because I thought your answer was going to focus on that.

      I recall that when BarsInProgress is set for a secondary price series ( that is, it's not 0 ) then the Open, High, Low, Close and Volume properties will automatically be set to point to it upon entry into the OnBarUpdate() method.

      In other words, when BIP is = 1 your script will get the values for those bars in the properties: Open[0], High[0], Low[0], Close[0] and Volume[0], just as it would if it called Opens[1][0], Highs[1][0], Lows[1][0], Closes[1][0] and Volumes[1][0].

      Do I have this correct?

      If that is correct then it should be possible to run hosted indicators on the secondary price series as these indicators are coded to call the Open[0], High[0], Low[0], Close[0] and Volume[0]. The trick would be to make sure that when the hosted indicator's OnBarUpdate is called, it's being called with the the BIP that's the user of the indicator is expecting it to be called with. I try to explain this below...

      Given a single instance of a hosted indicator running in a host that gets price series for 2 data series ( in alternating, sequential callbacks into the hosts script OnBarUpdate with BIP == 0 and BIP == 1 ) ... how does the hosting script get the hosted indicator to store 2 different sets of data? I don't think that it can. I was confused by the Input because I thought that was a means to achieve the separation. I thought that I could create 2 different instances of the hosted indicator: one for BIP == 0 and another for BIP == 1 using the Input parameter and that everything would work. But I don't think that's the case.

      But perhaps, and this is where I'm looking for guidance, there is some other way to have 2 hosted indicators, one for each price series. And I thought that solution might be to take control over the Update for each instance. I see there's an Update overload that takes a BIP integer as a parameter. Can this be used? Something like this:

      Code:
      adxForBIP0 = ADX(14);
      adxForBIP1 = ADX(14);
      
      ...
      if(BarsInProgress == 0)
      {
         adxForBIP0.Update(0,0);
         // use adxForBIP0
         double adx0 = adxForBIP0[0];
      }
      else
      if(BarsInProgress == 1)
      {
         adxForBIP1.Update(0,1);
         // use adxForBIP0
         double adx1 = adxForBIP1[0];
      }
      
      // is it okay to use either during any BIP?
      adx0 = adxForBIP0[0];
      adx1 = adxForBIP1[0];
      Can you see what I mean by my example?

      Comment


        #4
        Hello Brillo,

        Thanks for your note.

        I understand that the ADX needs to use High and Low data, however, Input will not give you that data. This is correct since it is not programmed to support Input.

        The ADxInputv1.1 script shared in my previous post was created by my colleague Paul and allows for the use of another indicator as an input or normal data as an input.

        For indicators that cannot simply use Input, you would need to call the indicator in the BarsInProgress index for the series that you want the indicator calculated with.

        See the example code below for an example.

        Code:
        double adxForBIP0;
        double adxForBIP1;
        
        protected override void OnBarUpdate(){
        
        if(BarsInProgress == 0)
        {
            adxForBIP0 = ADX(20)[0];
        }
        else if(BarsInProgress == 1)
        {
            adxForBIP1 = ADX(20)[0];
        }
        Then you could use adxForBIP0 or adxForBIP1 in either BarsInProgress since it is a double variable set to the last available value now.

        Let us know if we may assist further.
        Brandon H.NinjaTrader Customer Service

        Comment


          #5
          Brandon,
          I have experimented further and I found a way to get it to work. I understand it better now. I attached the sample I used to figure it out. Here are the relevant parts:

          Code:
          else if (State == State.Configure)
          {
          String alternativeInstrument = "ES";
          String[] primaryInstrumentNameParts = Instrument.FullName.Split(' ');
          if (primaryInstrumentNameParts.Length > 1)
          {
          alternativeInstrument = alternativeInstrument + " " + primaryInstrumentNameParts[1];
          AddDataSeries(alternativeInstrument);
          }
          }
          
          protected override void OnBarUpdate()
          {
            if (CurrentBar < BarsRequiredToTrade)
              return;
          
          if(BarsInProgress==0)
          { 
            Print(Time[0] + " BIP0: " + BarsInProgress + " indy0: " + hostedIndy0[0] + " indy1: " + hostedIndy1[0]);
          }
          else
          {
            Print(Time[0] + " BIP1: " + BarsInProgress + " indy1: " + hostedIndy1[0] + " indy0: " + hostedIndy0[0]);
          }
          
          // This also works as expected:
          //Print(Time[0] + " BIPx: " + BarsInProgress + " indy0: " + hostedIndy0[0] + " indy1: " + hostedIndy1[0]);
          
          }
          I get output like this:

          10/2/2019 8:14:00 PM BIP0: 0 indy0: 2.59000116186294 indy1: 0.863373000867443
          10/2/2019 8:14:00 PM BIP1: 1 indy1: 0.863373000867443 indy0: 2.59000116186294
          10/2/2019 8:15:00 PM BIP0: 0 indy0: 2.53000107887273 indy1: 0.855274929376911
          10/2/2019 8:15:00 PM BIP1: 1 indy1: 0.855274929376911 indy0: 2.53000107887273

          I'm guessing that in addition to supplying the Input series that hosted indicators are also "subscribing" to the bar update of the series passed in as the input and consequently are only OnBarUpdate called for the BIP for that series.

          And the Open[0], High[0], Low[0], Close[0], Volume[0] that the hosted indy uses are only for that series.

          I'm sorry I didn't run this experiment before posting.
          Can you confirm that my inference of how it works from this sample is actually how it works? Again, I'm saying that it's not only the values of the Input itself that matter to the hosted indicator. The hosted indicator is ALSO ensured that it's OnBarUpdate will only happen for the bar series associated with the Input. And this is the key to it.
          Attached Files

          Comment


            #6
            Hello Brillo,

            Thanks for your note.

            That is correct. The Open[0], High[0], Low[0], Close[0], Volume[0] that the hosted indicator uses are only for that series.

            I understand your script is working as you expect. In your script I see that you are specifying the BarsArray index for the ADX indicators and using a BarsInProgress check. This would be correct.
            By supplying the BarsArray index as the input, the indicator will calculate based on that series input supplied to the indicator.

            Let us know if we may assist further.
            Brandon H.NinjaTrader Customer Service

            Comment


              #7
              Brandon,
              Thank you for your response in post#4. I think we crossed paths because I posted a second time before your response in post#5 above.
              In the attached strategy I found that it's ONLY necessary to initialize the indicators with the barsarray associated with the bars index that you want the indy to run on:

              Code:
              else if (State == State.DataLoaded)
              {
              // won't work if using this method of construction
              //hostedIndy0 = ATR(14);
              //hostedIndy1 = ATR(14);
              
              // must pass in the input
              hostedIndy0 = ATR(BarsArray[0], 14);
              hostedIndy1 = ATR(BarsArray[1], 14);
              // I'm guessing that in addition to supplying the Input series that
              // hosted indicators are also "subscribing" to the bar update of
              // the series passed in and consequently are only OnBarUpdate called
              // for the BIP for that series.
              // And the Open[0], High[0], Low[0], Close[0], Volume[0]
              // that the hosted indy uses are only for that series.
              }
              Calling them the way you did in post #4 also seems to work.

              In the OnBarUpate method of the hosting strategy the hostedIndy0 and hostedIndy1 can used when BIP is 0 or 1... it makes no difference. What I infer from this behavior is that the overloaded static constructor for the indicator ( the ATR(BarsArray[1],14) call ) is not only associating a specific price series, it also ensures that the hosted indicator ( the ATR in this example ) is only called with the BIP index for the price series passed in. In other words, the ATR(BarsArray[1],14) instance will only be running on BIP 1 and consequently the Open, High, Low, Close, Volume properties that it can use will all point to the prices for the secondary series added with AddDataSeries.

              Based on what I see happening I'd just like to confirm that's how it works. And if there's any place in the Ninja documentation that explains it this way I'd like to know.

              The only hanging thing is that overload to the Update() method. What's that for? There's an Update that takes 2 int parameters named idx and bip.
              Last edited by Brillo; 08-24-2021, 11:58 AM.

              Comment


                #8
                Hello Brillo,

                Thanks for your note.

                What I infer from this behavior is that the overloaded static constructor for the indicator ( the ATR(BarsArray[1],14) call ) is not only associating a specific price series, it also ensures that the hosted indicator ( the ATR in this example ) is only called with the BIP index for the price series passed in. In other words, the ATR(BarsArray[1],14) instance will only be running on BIP 1 and consequently the Open, High, Low, Close, Volume properties that it can use will all point to the prices for the secondary series added with AddDataSeries.

                That is correct. If you use ATR(BarsArray[1], 14) then the ATR will calculate based on the BarsInProgress index for that secondary series. This means that ATR(BarsArray[1], 14) will run on BarsInProgress 1 and the OHLCV values will all come from the secondary series added with AddDataSeries().

                See the help guide documentation below for more information.
                AddDataSeries: https://ninjatrader.com/support/help...dataseries.htm
                BarsArray: https://ninjatrader.com/support/help.../barsarray.htm

                Update() ensures all series are up-to-date and OnBarUpdate() for all hosted scripts has been run.

                See this help guide page for more information: https://ninjatrader.com/support/helpGuides/nt8/?update.htm

                Let us know if we may assist further.
                Brandon H.NinjaTrader Customer Service

                Comment


                  #9
                  Yep.
                  The doc here: AddDataSeries: https://ninjatrader.com/support/help...dataseries.htm
                  At the very bottom is the example showing the indicator being initialized in State == State.OnDataLoaded.
                  It's very important to do it in OnDataLoaded.
                  If you try to initialize the hosted indicator in State == State.OnConfigure for a secondary series then:
                  1. It will compile and run and therefore seems okay.
                  2. BUT the values will NOT be correct. There's no warning.
                  The consequence of adding the indicator in OnDataLoaded is that you can not also add the same indicator to the chart. You can only call AddChartIndicator from the State == State.OnConfigure. This makes sense as plots for any other data series would not make sense.

                  Thanks for your help Brandon. Again, I'm sorry I didn't run some experiments before I bothered to contact support.
                  Kind Regards,
                  Tim

                  Comment

                  Latest Posts

                  Collapse

                  Topics Statistics Last Post
                  Started by algospoke, Yesterday, 06:40 PM
                  2 responses
                  24 views
                  0 likes
                  Last Post algospoke  
                  Started by ghoul, Today, 06:02 PM
                  3 responses
                  15 views
                  0 likes
                  Last Post NinjaTrader_Manfred  
                  Started by jeronymite, 04-12-2024, 04:26 PM
                  3 responses
                  46 views
                  0 likes
                  Last Post jeronymite  
                  Started by Barry Milan, Yesterday, 10:35 PM
                  7 responses
                  23 views
                  0 likes
                  Last Post NinjaTrader_Manfred  
                  Started by AttiM, 02-14-2024, 05:20 PM
                  10 responses
                  181 views
                  0 likes
                  Last Post jeronymite  
                  Working...
                  X