Announcement

Collapse
No announcement yet.

Partner 728x90

Collapse

Creating an indicator with a default constructor

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

    Creating an indicator with a default constructor

    I'm creating a custom indicator that has a fair number of properties, most of which I won't want to change. I'm using this indicator in a strategy, which means I have to call the generated factory method and fill all of the properties. Is there any type of factory or something which can instantiate an indicator without any parameters and move through the SetDefaults lifecycle hook so that I don't need to set any of the properties? When you add an indicator to the chart manually, the UI is somehow able to call OnStateChange to fill the default values (and it's not a static method, so somehow it's instantiating it) - so how is the UI doing it?
    Last edited by pshift; 08-29-2023, 06:21 AM.

    #2
    NinjaTrader Support recommends the partial class approach.

    I recommend you investigate the use of an abstract base class.

    Good reading here and here.

    Comment


      #3
      I could create a factory method for the class, but I see it as a maintenance cost (if any of the properties change I'd need to keep updating it), that's why I was hoping to leverage what already exists.

      When you add a custom indicator to a chart manually, the UI is able to fill all of the properties with defaults set from the OnStateChange hook. That method is not static, so somehow NT is instantiating the indicator without passing any values, but there's not a generated default constructor. Are they just using reflection to read the constructor signature and passing in e.g. default(double) for each param? Is whatever mechanism the UI uses available for instantiating indicators within a strategy?

      Comment


        #4
        Hello pshift,

        The UI instantiates an instance of the indicator class and runs OnStateChange with State set to Set.SetDefaults.
        This is what is setting all of the default values shown in the UI.

        Calling this indicator from a host script would do the same thing.

        I'm not quite understanding, can you clarify why supported approach of setting these default values in State.SetDefaults is insufficient?
        Chelsea B.NinjaTrader Customer Service

        Comment


          #5
          The auto-generated factory method converts all properties to required parameters. The only way that I could instantiate it without parameters is by either creating my own factory that fills the parameters with defaults, or by simply instantiating it with 'new' in the script - but that seems problematic. When I try to simply 'new' my indicator, it never hits the DataLoaded state, presumably because it has no associated input, but how do I assign the input to it (I can't see how the base classes are doing this)? Are there any ramifications to bypassing the other auto-generated code, like the caching mechanism?

          Comment


            #6
            Hello pshift,

            Calling indicator methods internally calls the new keyword behind the scenes by NinjaTrader to generate the instance of the indicator class.

            Calling the new keyword yourself when calling an indicator will not work as it is not the class itself, its a method that when run will generate the indicator class by NinjaTrader behind the scenes.

            Setting the default values in State.SetDefaults would be the correct way to set default values.
            Chelsea B.NinjaTrader Customer Service

            Comment


              #7
              Sorry I don't think we're on the same page. I understand the difference between a class and a method. I am able to instantiate the class with 'new' just fine, and yes it does set the defaults, but by calling new directly, I can't figure out how to associate the input series. The 'Input' property is read-only, and this seems to normally be occurring within the 'CachedIndicator' class. Additionally, I'm wondering about the potential ramifications of bypassing the caching mechanism. For reference, here's the auto-generated factory method for one of my indicators:

              Code:
                      public AM.OpenRange OpenRange(ISeries<double> input, DateTime startTime, int calculationBars, int breakoutBars, int stopOffset, double targetRatio)
                      {
                          if (cacheOpenRange != null)
                              for (int idx = 0; idx < cacheOpenRange.Length; idx++)
                                  if (cacheOpenRange[idx] != null && cacheOpenRange[idx].StartTime == startTime && cacheOpenRange[idx].CalculationBars == calculationBars && cacheOpenRange[idx].BreakoutBars == breakoutBars && cacheOpenRange[idx].StopOffset == stopOffset && cacheOpenRange[idx].TargetRatio == targetRatio && cacheOpenRange[idx].EqualsInput(input))
                                      return cacheOpenRange[idx];
                          return CacheIndicator<AM.OpenRange>(new AM.OpenRange(){ StartTime = startTime, CalculationBars = calculationBars, BreakoutBars = breakoutBars, StopOffset = stopOffset, TargetRatio = targetRatio }, input, ref cacheOpenRange);
                      }​
              As we see, it is either pulling an instance from cache if all of the parameters match, or creating a new cached instance. The series input is the second parameter of the 'CachedIndicator' constructor, so without being able to see that code, I'm not able to discern how the indicator becomes associated with the input. Without input association, the DataLoaded lifecycle never hits and the indicator doesn't work.

              That aside, by using new directly, it feels like I'm circumventing intentional optimizations and potentially other hooks. So if you want to step back to my original question - how does one instantiate an indicator without having to pass values for the properties? E.g. I just want to call 'indicator = MyIndicator()' or maybe 'indicator = MyIndicator(Closes[1])' for MTFA. The auto-generated factory method (shown above) requires values for each property. I don't want to pass any values because I want the defaults. The UI is able to do this, but it's obviously not using the auto-generated factory method, since that method has required parameters.

              Comment


                #8
                Originally posted by pshift View Post
                So if you want to step back to my original question - how does one instantiate an indicator without having to pass values for the properties? E.g. I just want to call 'indicator = MyIndicator()' or maybe 'indicator = MyIndicator(Closes[1])' for MTFA. The auto-generated factory method (shown above) requires values for each property. I don't want to pass any values because I want the defaults. The UI is able to do this, but it's obviously not using the auto-generated factory method, since that method has required parameters.
                I think you'll just need to bite the bullet and create your own
                'convenience constructors' that call the auto-generated ones.

                That is, create a middle-man constructor,

                Code:
                #region NinjaScript Legacy/Convenience Constructors
                namespace NinjaTrader.NinjaScript.Indicators
                {
                    public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
                    {
                        public BltIndicators.BltBetterZones BltBetterZones()
                        {
                            BltIndicators.BltBetterZones x = new BltIndicators.BltBetterZones();
                            x.SetState(State.SetDefaults);
                            BltIndicators.BltBetterZones y = BltBetterZones(x.SwingStrength, x.TickOffset, x.BarsLookback, x.BarsExtended, x.ShowDiamonds);
                            x.SetState(State.Terminated);
                            return y;
                        }
                    }
                }
                #endregion
                ​
                I just now added this code to one of my indicators.

                This compiles ok, but not completely sure this works -- you'll have
                to glean the idea and follow a similar approach for your own indicator.

                However ...
                A much better approach IMHO is to hard-code known defaults, because
                asking the indicator for the defaults means you're creating an entire throw
                away instance just to gather those values, just to create another real instance
                (using the method from the auto-generated code) that you then return.

                Seems kinda pointless when the known default values could be used as
                arguments directly -- I mean, it's your indicator, right? You don't need to
                ask your indicator to run SetDefaults. You're the author, you know the
                defaults already -- they're right there in your code -- sooo, devise a way
                to share them with your 'convenience constructor', or just hard code
                them as constant values.

                [EDIT: For ex, try using a static class, call it 'Defaults', and make both
                your State.SetDefaults code and your convenience constructor use the
                constant values defined in this static class -- this is a lot of extra work,
                so to me, just hard code the arguments using the same constant values
                that you used in your State.SetDefaults handling code]

                Code:
                #region NinjaScript Legacy/Convenience Constructors
                namespace NinjaTrader.NinjaScript.Indicators
                {
                    public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
                    {
                        public BltIndicators.BltBetterZones BltBetterZones()
                        {
                            // use same values as seen in State.SetDefaults
                            return BltBetterZones(5, 3, 50, 50, true);
                        }
                    }
                }
                #endregion
                ​
                ​Perhaps the code snippets above will help you see a way forward.

                Last edited by bltdavid; 08-29-2023, 07:11 AM. Reason: Fix coding error, typos

                Comment


                  #9
                  Appreciate the response. That was my initial thought was simply to create my own factory that would call the auto-generated one with defaults for the parameters. The defaults can just be constants so sharing between the SetDefaults hook and the factory is no issue. There's a small maintenance cost if I add/remove properties, but that should be rare once an indicator is working. I guess the main reason for creating this post is simply that I know the UI is somehow achieving this, so I was hoping to leverage a system that clearly already exists. It's not a big deal though. Cheers.

                  Comment


                    #10
                    Hi PShift,

                    we successfully use the following implementation for some of our solutions at ScaleInTrading.com (#ShamelessPlug). It allows you to instantiate an indicator with default values. We don't add them to the chart, but only refer to the calculated values for calculations, hence adding to chart would still need to be accomplished...

                    Code:
                    var atr = IndicatorMgr.GetIndiAtr(Close); //Note - no period provided here.
                    IndicatorMgr.AddIndicatorToBase(this, atr);
                    ...and the implementation of IndicatorMgr class is:

                    Code:
                    namespace NinjaTrader.Custom.AddOns.ScaleInTrading
                    {
                        internal class IndicatorMgr
                        {      
                            internal static void AddIndicatorToBase(NinjaScriptBase n, Indicator indi)
                            {
                                try
                                {
                                    indi.SetState(State.Configure);
                                }
                                catch (Exception exp)
                                {
                                    Cbi.Log.Process(typeof(Resource), "CbiUnableToCreateInstance2", new object[] { indi.Name, exp.InnerException != null ? exp.InnerException.ToString() : exp.ToString() }, Cbi.LogLevel.Error, Cbi.LogCategories.Default);
                                    indi.SetState(State.Finalized);
                                }
                    
                                indi.Parent = n;
                                //pas.SetInput(series);
                    
                                lock (n.NinjaScripts)
                                    n.NinjaScripts.Add(indi);
                    
                                try
                                {
                                    indi.SetState(n.State);
                                }
                                catch (Exception exp)
                                {
                                    Cbi.Log.Process(typeof(Resource), "CbiUnableToCreateInstance2", new object[] { indi.Name, exp.InnerException != null ? exp.InnerException.ToString() : exp.ToString() }, Cbi.LogLevel.Error, Cbi.LogCategories.Default);
                                    indi.SetState(State.Finalized);
                                }
                            }​
                    
                            internal static ATR GetIndiAtr(ISeries<double> series, int lookbackPeriod = 5)
                            {
                                var atr = new ATR();
                                atr.Period = lookbackPeriod; //Expect this to change, so part of the method signature to pass in.
                                //atr.PropertyA = somePresetValue;
                                //atr.PropertyB = somePresetValue;
                                atr.SetInput(series);
                            
                                return atr;
                            }
                        }
                    }
                    ​


                    ...at which point you can do things like:

                    Code:
                    protected override void OnBarUpdate()
                    {​
                        Print("ATR at last bar is: " + atr[0].ToString());
                    }
                    Hope this helps.

                    Best regards
                    Frank

                    Comment


                      #11
                      what about adding one property called string NTCacheId {get;set;} that is exposed by the attributes [Browsable(false)] [NinjaScriptProperty] and setting the rest to [Browsable(true)] sans the [NinjaScriptProperty] if they are to be exposed in the UI. then you could just create your instance: var myInd1 = MyIndicator("myInd1"); var myInd2 = MyIndicator("myInd2"); which will bypass NT cache and allow unique instances where the defaults would be set in the State.SetDefaults. Its not a true default parameterless constructor, but it works easily.

                      i hope i understood your problem and that this may help, cheers!
                      Dennis
                      Last edited by love2code2trade; 07-07-2024, 11:44 AM.

                      Comment

                      Latest Posts

                      Collapse

                      Topics Statistics Last Post
                      Started by Geovanny Suaza, 02-11-2026, 06:32 PM
                      0 responses
                      563 views
                      0 likes
                      Last Post Geovanny Suaza  
                      Started by Geovanny Suaza, 02-11-2026, 05:51 PM
                      0 responses
                      329 views
                      1 like
                      Last Post Geovanny Suaza  
                      Started by Mindset, 02-09-2026, 11:44 AM
                      0 responses
                      101 views
                      0 likes
                      Last Post Mindset
                      by Mindset
                       
                      Started by Geovanny Suaza, 02-02-2026, 12:30 PM
                      0 responses
                      547 views
                      1 like
                      Last Post Geovanny Suaza  
                      Started by RFrosty, 01-28-2026, 06:49 PM
                      0 responses
                      548 views
                      1 like
                      Last Post RFrosty
                      by RFrosty
                       
                      Working...
                      X