- ago
I want to add a minimum price and volume filter to this strategy. However, there is a bug so it is not working. Can you please take a look.

Thanks,
Larry

Here are the parms for Price and Volume on Strategy settings tab showing minimum price is $50 and minimum volume is 1,000 shares.



After running the strategy, notice that the minimum price is showing much lower than $50 per share:



I have added the filters on line # 127

CODE:
// Larry added filter for minimum close and minimum volume                               if (close[idx] < Parameters[2].AsDouble)                   return;                if (volume[idx] < Parameters[3].AsDouble)                   return;   


Here is the code:

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript9 {    public class Rotation : UserStrategyBase    {       public Rotation()       {          //Daily bar settings              AddParameter("Number of Buy Candidates", ParameterType.Int32, 6, 1, 10, 1); // Default =6, Parm 0          AddParameter("Stop Loss Pct", ParameterType.Double, 10, 2, 80, 2); // Default = 10, // Parm 1          AddParameter("price", ParameterType.Double, 50, 0, 20, 1); // Default = 15 Parm 2          AddParameter("volume", ParameterType.Double, 1000, 0, 500000, 1000); // Default = 100,000, Parm 3          AddParameter("ROC Period", ParameterType.Int32, 150, 30, 300, 10); // Default = 150, Parm 4          AddParameter("SMA Period", ParameterType.Int32, 200, 30, 300, 10); // Default = 200, Parm 5       }       ROC _avgROC; //declared as an indicator, not a TimeSeries       SMA _smaBenchmark;       BarHistory _bmBars;       string _seriesKey = "Average ROC";       string _bmSymbol = "^GSPC";       //the list of symbols that we should buy each bar       private static List<BarHistory> buys = new List<BarHistory>();       //create the weight indicator and stash it into the BarHistory object for reference in PreExecute       public override void Initialize(BarHistory bars)       {          close = bars.Close;          volume = bars.Volume; // needed for volume inidcator          // needed for volume and stock price minimum          foreach (IndicatorBase ib in startIndexList)             if (ib.FirstValidIndex > StartIndex)                StartIndex = ib.FirstValidIndex;          StartIndex = Math.Max(Parameters[4].AsInt, Parameters[5].AsInt);          _avgROC = ROC.Series(bars.Close, Parameters[4].AsInt);          bars.Cache[_seriesKey] = _avgROC; // "boxed" as ROC, not TimeSeries          _bmBars = GetHistory(bars, _bmSymbol, "Major U.S. and world indices");          _smaBenchmark = SMA.Series(_bmBars.Close, Parameters[5].AsInt);          weight = new RSI(bars.Close, 5);       }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          //store the symbols' _avgROC value in their BarHistory instances          foreach (BarHistory bh in participants)          {             //   TimeSeries symbolRoc = (TimeSeries)bh.Cache[_seriesKey]; // VERY DANGEROUS             ROC symbolRoc = (ROC)bh.Cache[_seriesKey]; // correct             int idx = GetCurrentIndex(bh);             double rocVal = symbolRoc[idx];             if (idx <= symbolRoc.FirstValidIndex)                rocVal = -1.0e6;                          bh.UserData = rocVal; //save the current _avgROC value along with the BarHistory instance          }          //sort the participants by _avgROC value (highest to lowest)          participants.Sort((a, b) => b.UserDataAsDouble.CompareTo(a.UserDataAsDouble));          //participants.Reverse(); //flip the list so the highest ROC are on top          //keep the top symbols based on numParticipants - all open positions          buys.Clear();          //market filter          int currentBar = GetCurrentIndex(_bmBars);          if (_bmBars.Close[currentBar] > _smaBenchmark[currentBar])          {             for (int n = 0; n < Parameters[0].AsInt; n++)             {                if (n >= participants.Count)                   break;                buys.Add(participants[n]);                int idx = GetCurrentIndex(participants[n]);                DateTime dte = participants[n].DateTimes[idx];             }          }          foreach (Position pos in OpenPositionsAllSymbols)          {             if (!buys.Contains(pos.Bars))             {                // find the relevant bar for the given current date                int idx = pos.Bars.IndexOf(dt, true);                if (idx >= 0 && pos.Bars.DateTimes[idx].IsLastTradingDayOfMonth(pos.Bars))                {                   //ClosePosition(pos, OrderType.Market);                   ClosePosition(pos, OrderType.Market, 0, "Sell Faber v2");                }                else                {                   //Backtester.CancelationCode = 220;                   //value = 1.0 - (Parameters[1].AsDouble / 100.0);                   //ClosePosition(pos, OrderType.Stop, pos.EntryPrice * value, "Faber Stop loss");                }             }             Backtester.CancelationCode = 220;             value = 1.0 - (Parameters[1].AsDouble / 100.0);             ClosePosition(pos, OrderType.Stop, pos.EntryPrice * value, "Stop loss Faber v2");          }          foreach (BarHistory bh in buys)          {             if (!HasOpenPosition(bh, PositionType.Long))             {                int idx = GetCurrentIndex(bh);                // no entry if closing price to low                                 // Larry added filter for minumum close and minimum volume                               if (close[idx] < Parameters[2].AsDouble)                   return;                if (volume[idx] < Parameters[3].AsDouble)                   return;                   bool isFirstTradingDayOfMonth = idx > 0 ? bh.DateTimes[idx - 1].IsLastTradingDayOfMonth(bh) : false;                if (isFirstTradingDayOfMonth) // this code makes buy/sell signals only on 1st day of the month, so I don't get daily trade signals repeating during the month                   {                         //Transaction t = PlaceTrade(bh, TransactionType.Buy, OrderType.Market);                         _transaction = PlaceTrade(bh, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Faber v2");                         _transaction.Weight = weight[idx] * 1;                                  }                }          }       }       public override void Execute(BarHistory bars, int idx)       {          //weight = new RSI(bars.Close,5);       }       public override void NewWFOInterval(BarHistory bars)       {          close = bars.Close;          volume = bars.Volume;       }       //private variables       private double value;       private bool trailing;       private IndicatorBase weight;       private Transaction _transaction;       private TimeSeries close;       private TimeSeries volume;       private List<IndicatorBase> startIndexList = new List<IndicatorBase>();    } }





0
655
Solved
11 Replies

Reply

Bookmark

Sort
Cone8
 ( 4.98% )
- ago
#1
3 errors I see...

CODE:
AddParameter("price", ParameterType.Double, 50, 0, 20, 1);
The default value is 50, which is not even in the parameter range for 0 to 20 by 1. You can run it like that, but an optimization will go from 0 to 20. (Zero is probably too low of a price)

CODE:
               if (close[idx] < Parameters[2].AsDouble)                   return;

This statement is ending ALL the buy processing if the close of one of the symbols in the buys list is less than [50]. Instead of return, it should be continue. (Same thing with the volume filter.)

Finally, instead of bars.Close, for a backtest you should use the SplitRev indicator to account for future splits when testing a price filter level.
0
- ago
#2
I have an independent question about the same posted code in the original post. Is it even possible to perform trades (e.g. PlaceTrade, HasOpenPosition) in PreExecute{} in the first place? Doesn't that break the WL8 paradigm of cycling through trading where the bar is the fastest moving variable and the symbol is the next fastest variable?

Why isn't that being done here? Oh, I see; this is a rotational strategy. Well, are you still allowed to trade in PreExecute?
0
- ago
#3
Cone,

As per your suggestion, I changed price parm to:

CODE:
   AddParameter("price", ParameterType.Double, 50, 0, 50, 1);


However, the following snippet is not filtering out lower priced stocks.

In this case I want processing to not execute the "buy". I think the [idx] is referencing the day of the month and in this case I need to locate the price.

CODE:
foreach (BarHistory bh in buys)          {             if (!HasOpenPosition(bh, PositionType.Long))             {                int idx = GetCurrentIndex(bh);                // no entry if closing price to low                                 // Larry added filter for minumum close and minimum volume                               if (close[idx] < Parameters[2].AsDouble)                   return;                if (volume[idx] < Parameters[3].AsDouble)                   return;                   bool isFirstTradingDayOfMonth = idx > 0 ? bh.DateTimes[idx - 1].IsLastTradingDayOfMonth(bh) : false;                if (isFirstTradingDayOfMonth) // this code makes buy/sell signals only on 1st day of the month, so I don't get daily trade signals repeating during the month                   {                         //Transaction t = PlaceTrade(bh, TransactionType.Buy, OrderType.Market);                         _transaction = PlaceTrade(bh, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Faber v2");                         _transaction.Weight = weight[idx] * 1;                                  }                }          }


I will have to think about changing the "SplitRev".

Thank you,
Larry




0
- ago
#4
You need to change ...
CODE:
// Larry added filter for minimum close and minimum volume if (close[idx] < Parameters[2].AsDouble) return; if (volume[idx] < Parameters[3].AsDouble) return;
to
CODE:
if (bh.Close[idx] < Parameters[2].AsDouble || bh.Volume[idx] < Parameters[3].AsDouble) continue;
UPDATE: The above solution has been corrected.

And why are you using the highest value as the default setting? Wouldn't something like this make more sense?
CODE:
AddParameter("price", ParameterType.Double, 20.0, 3.0, 50.0, 1.0); AddParameter("volume", ParameterType.Double, 100000.0, 1000.0, 500000.0, 1000.0); // Default = 100000, Parm 3
0
- ago
#5
What I wanted was to take this snippet and just filter out lower priced stocks prices on the close.

CODE:
         foreach (BarHistory bh in buys)          {             if (!HasOpenPosition(bh, PositionType.Long))             {                int idx = GetCurrentIndex(bh);                 // I need to not execute the buy code below if stock price < 50.                                bool isFirstTradingDayOfMonth = idx > 0 ? bh.DateTimes[idx - 1].IsLastTradingDayOfMonth(bh) : false;                   if (isFirstTradingDayOfMonth) // this code makes buy/sell signals only on 1st day of the month, so I don't get daily trade signals repeating during the month                   {                      //Transaction t = PlaceTrade(bh, TransactionType.Buy, OrderType.Market);                      _transaction = PlaceTrade(bh, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Faber v2");                      _transaction.Weight = weight[idx] * 1;                   }             }


Can you please run the code below which does not have the price and volume filter being executed.

It returns buy and sell signals as expected. It's just including lower priced stocks in the dataset. So that's why I want to add the price and volume filter.

Anything I add to filter out lower priced assets is being ignored. Could you take a look and add a filter for closing price has to be over $50.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript9 {    public class Rotation : UserStrategyBase    {       public Rotation()       {          //Daily bar settings              AddParameter("Number of Buy Candidates", ParameterType.Int32, 6, 1, 10, 1); // Default =6, Parm 0          AddParameter("Stop Loss Pct", ParameterType.Double, 10, 2, 80, 2); // Default = 10, // Parm 1          AddParameter("price", ParameterType.Double, 50, 0, 50, 1); // Default = 15 Parm 2          AddParameter("volume", ParameterType.Double, 1000, 0, 500000, 1000); // Default = 100,000, Parm 3          AddParameter("ROC Period", ParameterType.Int32, 150, 30, 300, 10); // Default = 150, Parm 4          AddParameter("SMA Period", ParameterType.Int32, 200, 30, 300, 10); // Default = 200, Parm 5       }       ROC _avgROC; //declared as an indicator, not a TimeSeries       SMA _smaBenchmark;       BarHistory _bmBars;       string _seriesKey = "Average ROC";       string _bmSymbol = "^GSPC";       //the list of symbols that we should buy each bar       private static List<BarHistory> buys = new List<BarHistory>();       //create the weight indicator and stash it into the BarHistory object for reference in PreExecute       public override void Initialize(BarHistory bars)       {          close = bars.Close;          volume = bars.Volume; // needed for volume inidcator          // needed for volume and stock price minimum          foreach (IndicatorBase ib in startIndexList)             if (ib.FirstValidIndex > StartIndex)                StartIndex = ib.FirstValidIndex;          StartIndex = Math.Max(Parameters[4].AsInt, Parameters[5].AsInt);          _avgROC = ROC.Series(bars.Close, Parameters[4].AsInt);          bars.Cache[_seriesKey] = _avgROC; // "boxed" as ROC, not TimeSeries          _bmBars = GetHistory(bars, _bmSymbol, "Major U.S. and world indices");          _smaBenchmark = SMA.Series(_bmBars.Close, Parameters[5].AsInt);          weight = new RSI(bars.Close, 5);       }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          //store the symbols' _avgROC value in their BarHistory instances          foreach (BarHistory bh in participants)          {             //   TimeSeries symbolRoc = (TimeSeries)bh.Cache[_seriesKey]; // VERY DANGEROUS             ROC symbolRoc = (ROC)bh.Cache[_seriesKey]; // correct             int idx = GetCurrentIndex(bh);             double rocVal = symbolRoc[idx];             if (idx <= symbolRoc.FirstValidIndex)                rocVal = -1.0e6;                          bh.UserData = rocVal; //save the current _avgROC value along with the BarHistory instance          }          //sort the participants by _avgROC value (highest to lowest)          participants.Sort((a, b) => b.UserDataAsDouble.CompareTo(a.UserDataAsDouble));          //participants.Reverse(); //flip the list so the highest ROC are on top          //keep the top symbols based on numParticipants - all open positions          buys.Clear();          //market filter          int currentBar = GetCurrentIndex(_bmBars);          if (_bmBars.Close[currentBar] > _smaBenchmark[currentBar])          {             for (int n = 0; n < Parameters[0].AsInt; n++)             {                if (n >= participants.Count)                   break;                             buys.Add(participants[n]);                int idx = GetCurrentIndex(participants[n]);                DateTime dte = participants[n].DateTimes[idx];             }          }          foreach (Position pos in OpenPositionsAllSymbols)          {             if (!buys.Contains(pos.Bars))             {                // find the relevant bar for the given current date                int idx = pos.Bars.IndexOf(dt, true);                if (idx >= 0 && pos.Bars.DateTimes[idx].IsLastTradingDayOfMonth(pos.Bars))                {                   //ClosePosition(pos, OrderType.Market);                   ClosePosition(pos, OrderType.Market, 0, "Sell Faber v2");                }                else                {                   //Backtester.CancelationCode = 220;                   //value = 1.0 - (Parameters[1].AsDouble / 100.0);                   //ClosePosition(pos, OrderType.Stop, pos.EntryPrice * value, "Faber Stop loss");                }             }             Backtester.CancelationCode = 220;             value = 1.0 - (Parameters[1].AsDouble / 100.0);             ClosePosition(pos, OrderType.Stop, pos.EntryPrice * value, "Stop loss Faber v2");          }          foreach (BarHistory bh in buys)          {             if (!HasOpenPosition(bh, PositionType.Long))             {                int idx = GetCurrentIndex(bh);                 // I need to not execute the buy code below if stock price < 50.                                bool isFirstTradingDayOfMonth = idx > 0 ? bh.DateTimes[idx - 1].IsLastTradingDayOfMonth(bh) : false;                   if (isFirstTradingDayOfMonth) // this code makes buy/sell signals only on 1st day of the month, so I don't get daily trade signals repeating during the month                   {                      //Transaction t = PlaceTrade(bh, TransactionType.Buy, OrderType.Market);                      _transaction = PlaceTrade(bh, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Faber v2");                      _transaction.Weight = weight[idx] * 1;                   }             }          }       }       public override void Execute(BarHistory bars, int idx)       {          //weight = new RSI(bars.Close,5);       }       public override void NewWFOInterval(BarHistory bars)       {          close = bars.Close;          volume = bars.Volume;       }       //private variables       private double value;       private bool trailing;       private IndicatorBase weight;       private Transaction _transaction;       private TimeSeries close;       private TimeSeries volume;       private List<IndicatorBase> startIndexList = new List<IndicatorBase>();    } }

0
- ago
#6
So add a guard (i.e. if statement {...}) to only execute the buy block if the Close > 50.0.
CODE:
   foreach (BarHistory bh in buys)    {       if (!HasOpenPosition(bh, PositionType.Long))       {          int idx = GetCurrentIndex(bh);          if (bh.Close[idx] > 50.0) // execute buy only if stock price > 50.0          {             bool isFirstTradingDayOfMonth = idx > 0 ? bh.DateTimes[idx-1].IsLastTradingDayOfMonth(bh) : false;             if (isFirstTradingDayOfMonth) // this code makes buy/sell signals only on 1st day of the month, so I don't get daily trade signals repeating during the month             {                //Transaction t = PlaceTrade(bh, TransactionType.Buy, OrderType.Market);                _transaction = PlaceTrade(bh, TransactionType.Buy, OrderType.Market, 0, 0, "Buy Faber v2");                _transaction.Weight = weight[idx];             }          }       }    }
My solution in Reply# 4 has been corrected. Forgot the "bh" in the foreach loop needs to be included for the Close property: bh.Close.
0
Best Answer
- ago
#7
Superticker & Cone,

That did the trick.

Much appreciated.

Thank you,

Larry
1
- ago
#8
Larry, Reply# 6 had an error in it. The int idx = GetCurrentIndex(bh); statement needs to come before the if statement so idx is defined as a local variable for that following if statement. Reply# 6 has now been fixed.

Honestly, both Replies #4 and #6 are exactly the same solution expressed in a different way. Happy computing to you.
0
- ago
#9
Superticker,

Got it.

Thank you,
Larry
1
- ago
#10
I am looking to add a volume filter for stocks trading at least 500,000 average for the last 200 days.


Originally, I had this snippet. However, this only tests for the current day. I need code for testing the average over 200 days.


CODE:
    AddParameter("volume", ParameterType.Double, 500000, 100000, 900000, 100000); if (volume[idx] < Parameters[0].AsDouble)                return;   


Maybe something similar to:

CODE:
   avgROC = (ROC.Series(bars.Close, 60) + ROC.Series(bars.Close, 120) + ROC.Series(bars.Close, 200)) / 3;          bars.Cache[seriesKey] = avgROC;


Thank you,
Larry
0
- ago
#11
I'm confused. If you want average Volume, then why is your solution above averaging Closing prices? Use SMA (Simple Moving Average) to average the Volume.
CODE:
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript2 {    public class MyStrategy : UserStrategyBase    {       Parameter avgVolMinimum;       SMA avgVol;       public MyStrategy()       {          avgVolMinimum = AddParameter("volume", ParameterType.Double, 500000, 100000, 900000, 100000);       }              public override void Initialize(BarHistory bars)       {          avgVol = new SMA(bars.Volume, 200);          PlotIndicator(avgVol);          StartIndex = avgVol.FirstValidIndex;       }       public override void Execute(BarHistory bars, int idx)       {          if (HasOpenPosition(bars, PositionType.Long))          {             //code your sell conditions here          }          else          {             //code your buy conditions here             if (avgVol[idx] > avgVolMinimum.AsDouble)             {                . . .             }          }       }    } }
The plot of avgVol is a black line on the Volume pane.

If you average over 200 days, that's going to force your StartIndex to 200 because you need that many days to establish the first average value before attempting to trade. Are you sure you want to do that?

BTW, there's nothing wrong with your original AddParameter statement. It's simply my personal preference to assign a meaningful name to it on the AddParameter line, but you don't have to. However, if you do, then you need the Parameter declaration in the MyStrategy block as shown so the Parameter is "visible" throughout the entire MyStrategy block (including the nested Execute block).
0

Reply

Bookmark

Sort