Consider a strategy that can be run on either a Daily or a Weekly scale. Obviously, parameter values that are appropriate for one scale are not for the other. So I was experimenting with trying to change them automatically with change of scale, like so:
But compiling this throws an exception:
0. Object reference not set to an instance of an object.
I guess the offending object is CurrentBars.
Is there a way to make this work?
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.Data; namespace WealthScript2 { public class TEST : UserStrategyBase { private Parameter _prdATR; private TimeSeries atr; public TEST() { if (CurrentBars.Scale == HistoryScale.Daily) { _prdATR = AddParameter("ATR Period", ParameterType.Int32, 20, 5, 63, 1); } if (CurrentBars.Scale == HistoryScale.Weekly) { _prdATR = AddParameter("ATR Period", ParameterType.Int32, 10, 4, 20, 1); } } public override void Initialize(BarHistory bars) { atr = ATR.Series(bars, _prdATR.AsInt); } public override void Execute(BarHistory bars, int idx) { } } }
But compiling this throws an exception:
0. Object reference not set to an instance of an object.
I guess the offending object is CurrentBars.
Is there a way to make this work?
Rename
The place to call AddParameter() is called constructor. In C# a constructor is called when a class or struct is created, and fields and objects are to be initialized, hence CurrentBars isn't accessible in ctor.
Short of creating 2 strategies - one for Daily and the other for Weekly scale - is there a way to create scale-specific parameters inside *one* strategy?
You could change the values of parameters based on scale, but no there's no way to create different sets of parameters or start/stop/step values based on scale.
QUOTE:
You could change the values of parameters based on scale
I tested that with this code:
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.Data; namespace WealthScript5 { public class TEST : UserStrategyBase { private Parameter _prdATR; private TimeSeries atr; private int param_default, param_min, param_max; public TEST() { _prdATR = AddParameter("ATR Period", ParameterType.Int32, param_default, param_min, param_max, 1); } public override void Initialize(BarHistory bars) { if (bars.Scale == HistoryScale.Daily) { param_default = 20; param_min = 5; param_max = 63; } if (bars.Scale == HistoryScale.Weekly) { param_default = 10; param_min = 4; param_max = 20; } atr = ATR.Series(bars, _prdATR.AsInt); } public override void Execute(BarHistory bars, int idx) { } } }
While I no longer get an exception, the parameter values do NOT carry over to the Parameter constructor, it simply uses the values assigned to the variable by default (namely, 0). Did I misunderstand? What am I doing wrong??
QUOTE:
What am I doing wrong??
You're trying to modify parameter values in the constructor - the first area of code executed ever - from Initialize() which is subsequent to it. It's like having a time machine so that why it's not going to work.
Like this -
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.Data; namespace WealthScript2 { public class TEST : UserStrategyBase { private Parameter _prdATR; private int _atrPeriod; private IndicatorBase _atr; public TEST() { _prdATR = AddParameter("ATR Period", ParameterType.Int32, 20, 5, 63, 1); //10, 4, 20, 1); } public override void Initialize(BarHistory bars) { //if weekly use integer division to get the value for weekly _atrPeriod = bars.Scale == HistoryScale.Weekly ? _prdATR.AsInt / 3 : _prdATR.AsInt; _atr = ATR.Series(bars, _atrPeriod); PlotIndicator(_atr); } public override void Execute(BarHistory bars, int idx) { } } }
I like the solution in Post #6 best, but the solution below may underscore the following points better:
1) bars.Scale is not define in the MyStrategy (Test) constructor. MyStrategy needs to begin execution before bars.Scale is defined.
2) Parameter constants used in the constructor cannot be changed. If you want different versions of a variable in the constructor, you need to declare both in the constructor and not change anything afterwards.
3) I wouldn't use a switch statement for this. It's only used here for documentation purposes.
I don't understand why bars.Scale has to be converted ToString() (via the Description property) before it will work in the switch statement. That shouldn't be necessary. And it's not necessary. The above code has been updated the reflect the method discussed in Post #11. Frequency is an enum datatype, so the Switch statement works as expected.
1) bars.Scale is not define in the MyStrategy (Test) constructor. MyStrategy needs to begin execution before bars.Scale is defined.
2) Parameter constants used in the constructor cannot be changed. If you want different versions of a variable in the constructor, you need to declare both in the constructor and not change anything afterwards.
3) I wouldn't use a switch statement for this. It's only used here for documentation purposes.
CODE:
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript2 { public class Test : UserStrategyBase { private Parameter _prdAtrDaily, _prdAtrWeekly; private TimeSeries atr; public Test() { _prdAtrDaily = AddParameter("ATR daily period", ParameterType.Int32, 20, 5, 63, 1); _prdAtrWeekly = AddParameter("ATR weekly period", ParameterType.Int32, 10, 4, 20, 1); } public override void Initialize(BarHistory bars) { switch (bars.Scale.Frequency) { case Frequency.Daily: atr = ATR.Series(bars, _prdAtrDaily.AsInt); break; case Frequency.Weekly: atr = ATR.Series(bars, _prdAtrWeekly.AsInt); break; } } public override void Execute(BarHistory bars, int idx) { } } }
QUOTE:
I don't understand why bars.Scale has to be converted ToString() (via the Description property) before it will work in the switch statement. That shouldn't be necessary.
bars.Scale refers to an object of type HistoryScale (which is a class), and not an integral type, enum, char, bool nor string. Since bars.Scale is an object then you'd have to use pattern matching against the object. See C# switch statement references for more info.
QUOTE:
bars.Scale refers to an object of type HistoryScale (which is a class), and not an integral type, enum,...
I think that's bad design. I tried to cast bars.Scale to HistoryScales, which is an enum, but the C# compiler can't find the namespace that defines the HistoryScales datatype. That's when I got frustrated. If I could get the HistoryScales cast to work, that would be the preferred solution.
superticker's code was closest to what I was looking for. Also, the Parameter description serves as a reminder as to which scale it's meant for.
I modified it thus:
Working nicely thus far (on very limited testing).
Thanks, all !
I modified it thus:
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using WealthLab.Data; namespace WealthScript10 { public class TEST : UserStrategyBase { private Parameter _prdAtrDaily, _prdAtrWeekly; private TimeSeries atr; private int prdATR; public TEST() { _prdAtrDaily = AddParameter("ATR daily period", ParameterType.Int32, 20, 5, 63, 1); _prdAtrWeekly = AddParameter("ATR weekly period", ParameterType.Int32, 10, 4, 20, 1); } public override void Initialize(BarHistory bars) { if (bars.Scale == HistoryScale.Daily) { prdATR = _prdAtrDaily.AsInt; } if (bars.Scale == HistoryScale.Weekly) { prdATR = _prdAtrWeekly.AsInt; } atr = ATR.Series(bars, prdATR); PlotTimeSeriesLine(atr, atr.Description, "paneATR", WLColor.Red); } public override void Execute(BarHistory bars, int idx) { } } }
Working nicely thus far (on very limited testing).
Thanks, all !
"I think that's bad design"
Why?
HistoryScale is a class that encapsulates everything required to describe a historical scale. Not only the frequency (daily, weekly, minute, etc) but also the interval and other things. If you want to do a switch statement without converting it to a string, use the Frequency property,
I stand by the design and think it's good, not bad.
Why?
HistoryScale is a class that encapsulates everything required to describe a historical scale. Not only the frequency (daily, weekly, minute, etc) but also the interval and other things. If you want to do a switch statement without converting it to a string, use the Frequency property,
CODE:
switch (bars.Scale.Frequency) { case Frequency.Daily: atr = ATR.Series(bars, _prdAtrDaily.AsInt); break; case Frequency.Weekly: atr = ATR.Series(bars, _prdAtrWeekly.AsInt); break; }
I stand by the design and think it's good, not bad.
QUOTE:
"I think that's bad design"
Why?
It creates unnecessary run-time overhead.
QUOTE:
HistoryScale is a class that encapsulates everything required to describe a historical scale. Not only the frequency (daily, weekly, minute, etc) but also the interval and other things.
Hmm. Well, the encapsulation is a plus. The run-time overhead is a minus. It's like creating a virtual datatype that binds on run-time. It's more flexible to delay the datatype decision until run-time, but you loose with longer execution time.
So you're saying the bars.Scale.Frequency conversion is a compile time execution, so there's no run-time overhead as with a virtual datatype binding? I guess you win. The bars.Scale.Frequency solution is the way to go with the Switch statement. I didn't appreciate there was a compile time solution to the Switch statement branching/binding problem.
What does, I’m not following you?
QUOTE:
Hmm. Well, the encapsulation is a plus. The run-time overhead is a minus. It's like create a virtual datatype that binds on run-time. It's more flexible to delay the datatype decision until run-time, but you loose with longer execution time.
Longer execution time? Go decompile HistoryScale and you'll see each of the scale instances are generated in the static constructor. There's like a dozen or so of them. They're created one time for the entire execution of app. That is miniscule.
I’ll gladly accept victory graciously this time ☺️
Your Response
Post
Edit Post
Login is required