- ago
I have a strategy that runs fine on a backtest, but just returns all parater values as a 0% return when I try to optimze it. Any thoughts?

The code looks like this

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections; using System.Collections.Generic; namespace WealthScript2 {    public class MyStrategy : UserStrategyBase    {       Parameter _atrLookBackPeriod;       Parameter _highLookBackPeriod;       Parameter _lowLookBackPeriod;       Parameter _atrMult;       Parameter _volMult;       Parameter _buyLimitMult;       Parameter _priorityLimit;       Parameter _stopMargin;       Parameter _lookbackMargin;       Parameter _gapUp;       // New consolidation parameters       Parameter _consolidationATRPeriod;       Parameter _consolidationATRThreshold;       public MyStrategy()       {          _highLookBackPeriod = AddParameter("HighPeriod", ParameterType.Int32, 60, 50, 80, 5);          _lowLookBackPeriod = AddParameter("LowPeriod", ParameterType.Int32, 7, 5, 16, 1);          _atrLookBackPeriod = AddParameter("ATRLookbackPeriod", ParameterType.Int32, 70, 50, 80, 5);          _atrMult = AddParameter("ATRMult", ParameterType.Double, 4.0, 2.0, 10.0, 0.5);          _volMult = AddParameter("VolMult", ParameterType.Double, 1.65, 1.4, 1.75, 0.05);          _buyLimitMult = AddParameter("buyLimitMult", ParameterType.Double, 0.5, 0.5, 2.0, 0.1);          _priorityLimit = AddParameter("PriorityLimit", ParameterType.Double, 130, 50, 280, 5);          _stopMargin = AddParameter("StopMargin", ParameterType.Double, 0.99, 0.95, 1.05, 0.01);          _lookbackMargin = AddParameter("LookbackMargin", ParameterType.Double, 0.99, 0.97, 1.03, 0.01);          _gapUp = AddParameter("gapUp", ParameterType.Double, 1.11, 0.95, 1.3, 0.02);          // Consolidation filter parameters          _consolidationATRPeriod = AddParameter("ConsATRPeriod", ParameterType.Int32, 10, 2, 20, 2);          _consolidationATRThreshold = AddParameter("ConsATRThreshold", ParameterType.Double, 1.3, 0.5, 3.0, 0.1);       }       public override void Initialize(BarHistory bars)       {          // one-time initialization stuff (like creating indicators and plotting) goes in this section                   PlotStopsAndLimits(3);          // init indicators and set up for WFO          NewWFOInterval(bars);          // plot          PlotTimeSeriesLine(_HighChannel >> 1, "HighChannel >> 1", "Price", WLColor.BlueViolet, 1);          PlotTimeSeriesLine(_LowChannel >> 1, "LowChannel >> 1", "Price", WLColor.Violet, 1);          PlotTimeSeriesLine(_myEMA10 >> 1, "EMA10", "Price", WLColor.AquaMarine, 1);          PlotTimeSeriesLine(_myEMA50 >> 1, "EMA50", "Price", WLColor.Cyan, 1);          PlotTimeSeriesLine(_myEMA20 >> 1, "EMA20", "Price", WLColor.Gray, 1);          PlotTimeSeriesLine(_VolEMA >> 1, "VolEMA", "Volume", WLColor.Blue, 2);          PlotTimeSeriesLine((bars.Close - _ATRChannel) >> 1, "ATRChannel", "Price", WLColor.Green, 1);          // Plot consolidation indicator          PlotIndicatorLine(_consolidation);          //EMA is an "unstable indicator" that needs a lot of seed bars to stabilize.          //StartIndex sets the bar number where to start the backtest: 3 x the longest EMA period (the original starts on bar 30, which is too soon for all indicators to be valid)          StartIndex = 3 * Math.Max(50, _highLookBackPeriod.AsInt);       }       IList<IChartPoint> GetPointList(int x1, double y1, int x2, double y2, int x3, double y3, int x4, double y4)       {          //WriteToDebugLog($"[{x1},{y1:N2}]; [{x2},{y2:N2}]; [{x3},{y3:N2}]; [{x4},{y4:N2}]");          _points.Clear();          _points.Add(new HistoricalDataPoint(x1, y1));          _points.Add(new HistoricalDataPoint(x2, y2));          _points.Add(new HistoricalDataPoint(x3, y3));          _points.Add(new HistoricalDataPoint(x4, y4));          return _points;       }       // this is called once for each bar       public override void Execute(BarHistory bars, int bar)       {          double _myOpenCloseMax = 0.0;          if (OpenPositions.Count > 0)          {             if (mySellPrice < bars.Close[bar] - _atrMult.AsDouble * _ATRChannel[bar])             {                mySellPrice = bars.Close[bar] - _atrMult.AsDouble * _ATRChannel[bar];             }             if (mySellPrice < bars.Close[bar] * 0.5)             {                mySellPrice = bars.Close[bar] * 0.5;             }             // Tracks where our stops are rather than just the low             DrawDot(bar, mySellPrice, WLColor.Green, 3, "Price", true);             //process the stop for all open positions             for (int _pos = OpenPositions.Count - 1; _pos >= 0; _pos--)             {                if (bars.Close[bar] < mySellPrice)                {                   myStopPrice = Math.Round((bars.Close[bar] * _stopMargin.AsDouble), 2);                   ClosePosition(OpenPositions[_pos], OrderType.Stop, myStopPrice, $"Long Trend End {myStopPrice}");                   DrawDot(bar, myStopPrice, WLColor.Crimson, 2, "Price", true);                }             }          }          else          {             mySellPrice = 0.0;          }          _myOpenCloseMax = bars.Close[bar - 1];          if (_myOpenCloseMax < bars.Open[bar - 1])          {             _myOpenCloseMax = bars.Open[bar - 1];          }          // Check for consolidation - look at previous bar to avoid look-ahead bias          bool isConsolidating = _consolidation[bar - 1] > 0.5;           myPriority = 1000 * _ATRChannel[bar] / bars.Close[bar];          if (bars.Low[bar] > bars.High[bar - 1])             myPriority += 10;          if          (             bars.Close[bar] > (_gapUp.AsDouble * bars.Close[bar - 1])             && bars.Volume[bar] > _volMult.AsDouble * _VolEMA[bar]             && bars.Close[bar] > bars.Open[bar]             && myPriority < (_priorityLimit.AsDouble)             && isConsolidating // NEW: Only buy if consolidating          )          {             double myBuyPrice = Math.Round(bars.Close[bar] - (_buyLimitMult.AsDouble * _ATRChannel[bar]), 2);             if (mySellPrice < bars.Close[bar] - _atrMult.AsDouble * _ATRChannel[bar])             {                mySellPrice = (bars.Close[bar] - (_atrMult.AsDouble * _ATRChannel[bar]));             }             if (                 myBuyPrice < bars.High[bar]                && myBuyPrice > bars.Low[bar]             )                       {                DrawDot(bar, (bars.Close[bar] * 0.8), WLColor.Fuchsia, 3, "Price", true);                Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, myBuyPrice, myPriority.ToString("0.00"));                if (t != null)                {                   t.Weight = myPriority;                   myNewSellPrice = bars.Close[bar] - _atrMult.AsDouble * _ATRChannel[bar];                   if (mySellPrice < myNewSellPrice)                      mySellPrice = myNewSellPrice;                   if (mySellPrice < _LowChannel[bar])                      mySellPrice = _LowChannel[bar];                }             }          }       }       public override void NewWFOInterval(BarHistory bars)       {          _HighChannel = Highest.Series(bars.Close, _highLookBackPeriod.AsInt);          _LowChannel = Lowest.Series((bars.Low * _lookbackMargin.AsDouble), _lowLookBackPeriod.AsInt);          _myEMA10 = EMA.Series(bars.Close, 10);          _myEMA50 = EMA.Series(bars.Close, 50);          _myEMA20 = EMA.Series(bars.Close, 20);          _VolEMA = EMA.Series(bars.Volume, 30);          _myADX = ADX.Series(bars, _adxPeriod);          _ATRChannel = ATR.Series(bars, _atrLookBackPeriod.AsInt);          // Initialize consolidation indicator          _consolidation = PriceConsolidation.Series(             bars,             atrPeriod: _consolidationATRPeriod.AsInt,             atrThreshold: _consolidationATRThreshold.AsDouble,             rangeMultiplier: 2.0,             rsiPeriod: 14,             useRsi: true,             rsiLowerBound: 40.0,             rsiUpperBound: 60.0,             bbPeriod: 20,             bbWidth: 2.0,             useBollinger: true,             bbAtrThreshold: 0.5,             bbPriceComponent: PriceComponent.Close          );       }       double mySellPrice;       double myNewSellPrice;       double myStopPrice;       double myPriority;       IndicatorBase _HighChannel;       IndicatorBase _LowChannel;       IndicatorBase _myEMA10;       IndicatorBase _myEMA50;       IndicatorBase _myEMA20;       IndicatorBase _VolEMA;       IndicatorBase _myADX;       TimeSeries _ATRChannel;       PriceConsolidation _consolidation; // NEW: Consolidation indicator       List<IChartPoint> _points = new();       int _adxPeriod = 14;    }    // PriceConsolidation Indicator embedded in the strategy    public class PriceConsolidation : IndicatorBase    {       private const int BarsIndex = 0;       private const int AtrPeriodIndex = 1;       private const int AtrThresholdIndex = 2;       private const int RangeMultiplierIndex = 3;       private const int RsiPeriodIndex = 4;       private const int UseRsiIndex = 5;       private const int RsiLowerBoundIndex = 6;       private const int RsiUpperBoundIndex = 7;       private const int BbPeriodIndex = 8;       private const int BbWidthIndex = 9;       private const int UseBollingerIndex = 10;       private const int BbAtrThresholdIndex = 11;       private const int BbPriceComponentIndex = 12;       public PriceConsolidation(          BarHistory bars,          int atrPeriod = 14,          double atrThreshold = 1.0,          double rangeMultiplier = 2.0,          int rsiPeriod = 14,          bool useRsi = true,          double rsiLowerBound = 40.0,          double rsiUpperBound = 60.0,          int bbPeriod = 20,          double bbWidth = 2.0,          bool useBollinger = true,          double bbAtrThreshold = 0.5,          PriceComponent bbPriceComponent = PriceComponent.Close)       {          Parameters[BarsIndex].Value = bars;          Parameters[AtrPeriodIndex].Value = atrPeriod;          Parameters[AtrThresholdIndex].Value = atrThreshold;          Parameters[RangeMultiplierIndex].Value = rangeMultiplier;          Parameters[RsiPeriodIndex].Value = rsiPeriod;          Parameters[UseRsiIndex].Value = useRsi;          Parameters[RsiLowerBoundIndex].Value = rsiLowerBound;          Parameters[RsiUpperBoundIndex].Value = rsiUpperBound;          Parameters[BbPeriodIndex].Value = bbPeriod;          Parameters[BbWidthIndex].Value = bbWidth;          Parameters[UseBollingerIndex].Value = useBollinger;          Parameters[BbAtrThresholdIndex].Value = bbAtrThreshold;          Parameters[BbPriceComponentIndex].Value = bbPriceComponent;          Populate();       }       public override string Name => "Price Consolidation";       public override string Abbreviation => "PriceConsolidation";       public override string HelpDescription => "Detects stock price consolidation using ATR, price range, RSI, and Bollinger Bands analysis.";       public override string PaneTag => "PriceConsolidation";       public override PlotStyle DefaultPlotStyle => PlotStyle.Histogram;       public override void Populate()       {          var bars = Parameters[BarsIndex].AsBarHistory;          var atrPeriod = Parameters[AtrPeriodIndex].AsInt;          var atrThreshold = Parameters[AtrThresholdIndex].AsDouble;          var rangeMultiplier = Parameters[RangeMultiplierIndex].AsDouble;          var rsiPeriod = Parameters[RsiPeriodIndex].AsInt;          var useRsi = Parameters[UseRsiIndex].AsBoolean;          var rsiLowerBound = Parameters[RsiLowerBoundIndex].AsDouble;          var rsiUpperBound = Parameters[RsiUpperBoundIndex].AsDouble;          var bbPeriod = Parameters[BbPeriodIndex].AsInt;          var bbWidth = Parameters[BbWidthIndex].AsDouble;          var useBollinger = Parameters[UseBollingerIndex].AsBoolean;          var bbAtrThreshold = Parameters[BbAtrThresholdIndex].AsDouble;          var bbPriceComponent = Parameters[BbPriceComponentIndex].AsPriceComponent;          DateTimes = bars.DateTimes;          if (bars.Count < atrPeriod || (useRsi && bars.Count < rsiPeriod) || (useBollinger && bars.Count < bbPeriod))          {             return;          }          var atr = ATR.Series(bars, atrPeriod);          var highest = Highest.Series(bars.Close, atrPeriod);          var lowest = Lowest.Series(bars.Close, atrPeriod);          var rsi = useRsi ? RSI.Series(bars.Close, rsiPeriod) : null;          var priceSeries = bbPriceComponent switch          {             PriceComponent.Open => bars.Open,             PriceComponent.High => bars.High,             PriceComponent.Low => bars.Low,             PriceComponent.Close => bars.Close,             PriceComponent.Volume => bars.Volume,             PriceComponent.AveragePriceOHLC => bars.AveragePriceOHLC,             PriceComponent.AveragePriceHLC => bars.AveragePriceHLC,             PriceComponent.AveragePriceHL => bars.AveragePriceHL,             PriceComponent.AveragePriceOC => bars.AveragePriceOC,             PriceComponent.AveragePriceHLCC => bars.AveragePriceHLCC,             _ => bars.Close          };          var bbUpper = useBollinger ? BBUpper.Series(priceSeries, bbPeriod, bbWidth) : null;          var bbLower = useBollinger ? BBLower.Series(priceSeries, bbPeriod, bbWidth) : null;          var maxPeriod = Math.Max(atrPeriod, Math.Max(rsiPeriod, bbPeriod));          for (var i = maxPeriod - 1; i < bars.Count; i++)          {             var priceRange = highest[i] - lowest[i];             var isAtrConsolidation = atr[i] <= atrThreshold && priceRange <= atr[i] * rangeMultiplier;             var isRsiNeutral = !useRsi || (rsi[i] >= rsiLowerBound && rsi[i] <= rsiUpperBound);             var bbWidthInAtr = useBollinger ? (bbUpper[i] - bbLower[i]) / atr[i] : double.MaxValue;             var isBbSqueeze = useBollinger && bbWidthInAtr <= bbAtrThreshold;             Values[i] = (isAtrConsolidation || isBbSqueeze) && isRsiNeutral ? 1.0 : 0.0;          }       }       protected override void GenerateParameters()       {          AddParameter("Bars", ParameterType.BarHistory, null);          AddParameter("ATR Period", ParameterType.Int32, 14);          AddParameter("ATR Threshold", ParameterType.Double, 1.0);          AddParameter("Range Multiplier", ParameterType.Double, 2.0);          AddParameter("RSI Period", ParameterType.Int32, 14);          AddParameter("Use RSI", ParameterType.Boolean, true);          AddParameter("RSI Lower Bound", ParameterType.Double, 40.0);          AddParameter("RSI Upper Bound", ParameterType.Double, 60.0);          AddParameter("BB Period", ParameterType.Int32, 20);          AddParameter("BB Width", ParameterType.Double, 2.0);          AddParameter("Use Bollinger Bands", ParameterType.Boolean, true);          AddParameter("BB ATR Threshold", ParameterType.Double, 0.5);          AddParameter("BB Price Component", ParameterType.PriceComponent, PriceComponent.Close);       }       public static PriceConsolidation Series(          BarHistory bars,          int atrPeriod = 14,          double atrThreshold = 1.0,          double rangeMultiplier = 2.0,          int rsiPeriod = 14,          bool useRsi = true,          double rsiLowerBound = 40.0,          double rsiUpperBound = 60.0,          int bbPeriod = 20,          double bbWidth = 2.0,          bool useBollinger = true,          double bbAtrThreshold = 0.5,          PriceComponent bbPriceComponent = PriceComponent.Close)       {          var key = CacheKey("PriceConsolidation", atrPeriod, atrThreshold, rangeMultiplier, rsiPeriod, useRsi,             rsiLowerBound, rsiUpperBound, bbPeriod, bbWidth, useBollinger, bbAtrThreshold, bbPriceComponent);          if (bars.Cache.TryGetValue(key, out var obj))          {             return (PriceConsolidation)obj;          }          var result = new PriceConsolidation(bars, atrPeriod, atrThreshold, rangeMultiplier, rsiPeriod, useRsi,             rsiLowerBound, rsiUpperBound, bbPeriod, bbWidth, useBollinger, bbAtrThreshold, bbPriceComponent);          bars.Cache[key] = result;          return result;       }    } }
0
378
5 Replies

Reply

Bookmark

Sort
Cone8
 ( 21.51% )
- ago
#1
No trades.
The condition to execute the buy logic is too restrictive and is never true (probably).
0
- ago
#2
It runs fine in a backtest over the same dataset with the default parameters then these same parameters return zero percent in the optimization run
0
- ago
#3
You'll want to make one code correction. IList<> should be List<> so it agrees with your declaration in MyStrategy. This method isn't being called, so this error shouldn't affect your results.
CODE:
List<ChartPoint> GetPointList(int x1, double y1, int x2, double y2, int x3, double y3, int x4, double y4) //IList<IChartPoint> GetPointList(int x1, double y1, int x2, double y2, int x3, double y3, int x4, double y4) { //WriteToDebugLog($"[{x1},{y1:N2}]; [{x2},{y2:N2}]; [{x3},{y3:N2}]; [{x4},{y4:N2}]"); _points.Clear();
My other comment is that a consolidation is a "changing of the guards" such that you're getting the same number of investors selling as are buying so there's very little price change associated with very high volume. Your consolidation indicator isn't even looking at volume, so it's not really measuring consolidation in the strictest sense. I would suggest substituting a traditional consolidation indicator and running your strategy with it instead.

I think your current consolidation indicator is placing so many trading restrictions that you're not getting any trading activity, which is not ideal. And the parameter optimizer cannot find a way around all the restrictions that are in place to make any trades. Optimizers have limitations.
0
- ago
#4
I will look at the issue you pointed out - this code has been worked on over a long period, so there is some dead stuff in there that I shold have deleted!

I appreciate your comment on the consolidation, but the idea is that when there is a sudden change in volume, you want to get on it.

As I said in previous replies, when I run backtests it is getting trades, but the optimization, over the same timescale with the same date sets and the parameters used in the backtest within the range, it gives me zero percent

So it cannot be the lack of trading oportunities, something is going wrong within the code to stop the optimizer working or the optimizor itself is has a glitch
0
- ago
#5
QUOTE:
optimizer itself is has a glitch

Most of these optimizers take one parameter at a time, initially, for stability. However, if you give such an optimizer limited choices, it's likely to get stuck. That's my point.

I guess you could trying writing your own optimizer to handle such restrictions. But I've used several WL optimizers with very good success. However, my strategies are more malleable than yours ... so we are comparing apples to oranges.

Bottom line, I would make strategies malleable so the optimizer is empowered to converge on the best possible solution. And for stability, I wouldn't be optimizing more than 6 parameters in the final production version. (One of my best strategies has only 4 parameters, but it also employs adaptive indicators.)

QUOTE:
when there is a sudden change in volume, you want to get on it.

Agreed; high volume means more opportunity. Volume is important in correctly identifying real consolidation. You need to add more volume dependency in your consolidation indicator.

Happy trading to you.
0

Reply

Bookmark

Sort