Announcement
Collapse
No announcement yet.
Partner 728x90
Collapse
NinjaTrader
Creating an indicator with a default constructor
Collapse
X
-
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.Tags: None
-
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
-
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
-
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
-
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
-
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:
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.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); }
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.
- Likes 1
Comment
-
I think you'll just need to bite the bullet and create your ownOriginally posted by pshift View PostSo 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.
'convenience constructors' that call the auto-generated ones.
That is, create a middle-man constructor,
I just now added this code to one of my indicators.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
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]
Perhaps the code snippets above will help you see a way forward.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
Comment
-
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
-
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...
...and the implementation of IndicatorMgr class is:Code:var atr = IndicatorMgr.GetIndiAtr(Close); //Note - no period provided here. IndicatorMgr.AddIndicatorToBase(this, atr);
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:
Hope this helps.Code:protected override void OnBarUpdate() { Print("ATR at last bar is: " + atr[0].ToString()); }
Best regards
Frank
- Likes 1
Comment
-
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!
DennisLast 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
558 views
0 likes
|
Last Post
|
||
|
Started by Geovanny Suaza, 02-11-2026, 05:51 PM
|
0 responses
324 views
1 like
|
Last Post
|
||
|
Started by Mindset, 02-09-2026, 11:44 AM
|
0 responses
101 views
0 likes
|
Last Post
by Mindset
02-09-2026, 11:44 AM
|
||
|
Started by Geovanny Suaza, 02-02-2026, 12:30 PM
|
0 responses
545 views
1 like
|
Last Post
|
||
|
Started by RFrosty, 01-28-2026, 06:49 PM
|
0 responses
547 views
1 like
|
Last Post
by RFrosty
01-28-2026, 06:49 PM
|

Comment