- ago
Hi guys,

Can anyone tell me what I'm doing wrong here?

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Collections.Generic; using GabrielEmilio; namespace WealthScript3 {    public class PairsTradeRSI : UserStrategyBase    {       public PairsTradeRSI() : base()       {          AddParameter("Std. Devs", ParameterType.Double, 2, 1, 3, 5);          AddParameter("BB Period", ParameterType.Int32, 50, 10, 80, 10);          AddParameter("StopLoss", ParameterType.Double, 7, 2, 15, 1);       }       public override void Initialize(BarHistory bars)       {          string _stock1 = bars.Symbol, _stock2 = this.Backtester.Strategy.Benchmark;          stock1 = GetHistory(bars, _stock1);          stock2 = GetHistory(bars, _stock2);          nStop = Parameters.FindName("StopLoss").AsDouble;          nStd_Dev = Parameters.FindName("Std. Devs").AsDouble;          nBB_Period = Parameters.FindName("BB Period").AsInt;          xRSI = new RSI(bars.Close, 8);          PlotTimeSeries(xRSI, "FR", "FR", WLColor.Yellow, PlotStyle.Line, true);          xBBSma = new SMA(xRSI, nBB_Period);          PlotTimeSeries(xBBSma, "SMA_FR", "FR", WLColor.White, PlotStyle.DottedLine, true);          xBBUpper = new BBUpper(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBUpper, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          xBBLower = new BBLower(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBLower, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          StartIndex = 160;       }       public override void Execute(BarHistory bars, int idx)       {          bool autorizaCompra = false;          bool autorizaVenda = false;          if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short))          {             if (xRSI.CrossesOver(xBBLower, idx))             {                autorizaCompra = true;             }             else if (xRSI.CrossesUnder(xBBUpper, idx))             {                autorizaVenda = true;             }             if (autorizaCompra)             {                PlaceTrade(stock2, TransactionType.Short, OrderType.Market, 0, -1);                PlaceTrade(stock1, TransactionType.Buy, OrderType.Market, 0, 1);             }             if (autorizaVenda)             {                PlaceTrade(stock1, TransactionType.Short, OrderType.Market, 0, -1);                PlaceTrade(stock2, TransactionType.Buy, OrderType.Market, 0, 1);             }          }          else          {             P1 = FindOpenPosition(1);             P2 = FindOpenPosition(-1);             if (HasOpenPosition(bars, PositionType.Long))             {                if ((P1.ProfitPctAsOf(idx) + P2.ProfitPctAsOf(idx)) < -nStop)                {                   ClosePosition(P1, OrderType.Market, 0, "Stop");                   ClosePosition(P2, OrderType.Market, 0, "Stop");                }                else if (xRSI[idx] >= xBBSma[idx])                {                   PlaceTrade(stock2, TransactionType.Cover, OrderType.Market, 0, "Cover" + stock2);                   PlaceTrade(stock1, TransactionType.Sell, OrderType.Market, 0, "Sell" + stock1);                }             }             if (HasOpenPosition(bars, PositionType.Short))             {                if ((P1.ProfitPctAsOf(idx) + P2.ProfitPctAsOf(idx)) < -nStop)                {                   ClosePosition(P1, OrderType.Market, 0, "Stop");                   ClosePosition(P2, OrderType.Market, 0, "Stop");                }                else if (xRSI[idx] <= xBBSma[idx])                {                   PlaceTrade(stock1, TransactionType.Cover, OrderType.Market, 0, "Cover" + stock1);                   PlaceTrade(stock2, TransactionType.Sell, OrderType.Market, 0, "Sell" + stock2);                }             }          }       }       BarHistory stock1, stock2;       private double nStd_Dev;       private int nBB_Period;       private Position P1;       private Position P2;       private double nStop;       private double xStop;       private TimeSeries xRSI;       private TimeSeries xBBLower;       private TimeSeries xBBSma;       private TimeSeries xBBUpper;    } }


The idea of the code is:
1. When the indicator crosses its BBLower upwards enter Long on it and short on the backtest symbol;
2. When the indicator crosses its BBUpper downwards enter Short on it and Long on the backtest symbol;
3. Take profit when indicator reaches SMA;
4. Trigger the maximum stop loss if applicable.

Error:



Thanks for your help.
0
325
Solved
21 Answers

Reply

Bookmark

Sort
- ago
#1
At lines 81 and 82 you call FindOpenPosition:

P1 = FindOpenPosition(1);
P2 = FindOpenPosition(-1);

At line 86 (and line 100) you use P1 and P2 without testing that they actually have values. If there is no open position associated with either P1 or P2 then you'll get a null reference exception.

Change both lines 86 and 100 to:

if ((P1?.ProfitPctAsOf(idx) + P2?.ProfitPctAsOf(idx)) < -nStop)
2
- ago
#2
Many thanks paul1986, worked fine!
0
- ago
#3
Actually, I have another question.

The way it is written, FindOpenPosition is not acting on the open positions on symbol 2 (backtest symbol).

There the positions are opened but not closed on the second symbol.

Any way to solve this?
0
- ago
#4
QUOTE:
The way it is written, FindOpenPosition is not acting on the open positions on symbol 2 (backtest symbol

Emilio, you may want to explore FindOpenPositionAllSymbols.
1
- ago
#5
Hi Eugene

Thanks for the suggestion. Actually, I found this method in QuickRef, but have not been able to use it properly on my system yet.

The system opens multiple positions, long and short, on both the current symbol and the backtest symbol. I still haven't found a practical way to establish a single int indicator and find it later when it comes time to calculate the profit/loss for each trade pair in order to close the trade.

I'll keep trying to see if I can come up with something. rs
0
- ago
#6
By the way, I just saw something about this FindOpenPositionAllSymbols. In the QuickRef description it only accepts one int parameter.
But when I wrote it in Visual Studio, it shows a second option with Symbol and PositionType parameters.
If this really works, it might be a good alternative in some cases.





This probably do not help my case, but is good to know.
0
- ago
#7
@emilio_gabriel - I believe the following is a solution. In the code, please see my comments about FindOpenPositionAllSymbols. Also, I condensed your code a little bit. Hope this helps...

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Collections.Generic; //using GabrielEmilio; namespace WealthScript1 {    public class PairsTradeRSI : UserStrategyBase    {       public PairsTradeRSI() : base()       {          AddParameter("Std. Devs", ParameterType.Double, 2, 1, 3, 5);          AddParameter("BB Period", ParameterType.Int32, 50, 10, 80, 10);          AddParameter("StopLoss", ParameterType.Double, 7, 2, 15, 1);       }       public override void Initialize(BarHistory bars)       {          string _stock1 = bars.Symbol, _stock2 = this.Backtester.Strategy.Benchmark;          stock1 = GetHistory(bars, _stock1);          stock2 = GetHistory(bars, _stock2);          nStop = Parameters.FindName("StopLoss").AsDouble;          nStd_Dev = Parameters.FindName("Std. Devs").AsDouble;          nBB_Period = Parameters.FindName("BB Period").AsInt;          xRSI = new RSI(bars.Close, 8);          PlotTimeSeries(xRSI, "FR", "FR", WLColor.Yellow, PlotStyle.Line, true);          xBBSma = new SMA(xRSI, nBB_Period);          PlotTimeSeries(xBBSma, "SMA_FR", "FR", WLColor.White, PlotStyle.DottedLine, true);          xBBUpper = new BBUpper(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBUpper, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          xBBLower = new BBLower(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBLower, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          StartIndex = 160;       }       public override void Execute(BarHistory bars, int idx)       {          if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short))          {             // when placing trades, use PositionTag = 1 for stock1 and PositionTag = 2 for stock2             if (xRSI.CrossesOver(xBBLower, idx))             {                PlaceTrade(stock2, TransactionType.Short, OrderType.Market, 0, 2);                PlaceTrade(stock1, TransactionType.Buy, OrderType.Market, 0, 1);             }             else if (xRSI.CrossesUnder(xBBUpper, idx))             {                PlaceTrade(stock1, TransactionType.Short, OrderType.Market, 0, 1);                PlaceTrade(stock2, TransactionType.Buy, OrderType.Market, 0, 2);             }          }          else          {             // Note: using FindOpenPositionAllSymbols did not work properly. Hence...             // When placing trades PositionTag = 1 is used for stock1 and PositionTag = 2 for stock2             // (see PlaceTrade calls above).             //             // posBackTest = FindOpenPosition(1); // this worked properly             // But, sometimes FindOpenPositionAllSymbols(2) would return the backtest position instead of             // the expected benchmark position.             // posBenchmark = FindOpenPositionAllSymbols(2);   // this may be buggy             Position posBackTest = null;             Position posBenchmark = null;             if (OpenPositionsAllSymbols.Count != 2)             {                throw new Exception("Expected exactly 2 positions");             }                          // this seems to work fine...             foreach (var pos in OpenPositionsAllSymbols)             {                if (pos.Symbol == stock1.Symbol)                {                   posBackTest = pos;                }                else if (pos.Symbol == stock2.Symbol)                {                   posBenchmark = pos;                }             }             // if we are stopped-out well, then that's it...             if (posBackTest.ProfitPctAsOf(idx) + posBenchmark.ProfitPctAsOf(idx) < -nStop)             {                ClosePosition(posBackTest, OrderType.Market, 0, "Stop");                ClosePosition(posBenchmark, OrderType.Market, 0, "Stop");                return;             }                          // we can use HasOpenPosition because it will always test against the backtest symbol             if (HasOpenPosition(bars, PositionType.Long))             {                // here, posBackTest is the long position and posBenchmark is short                if (xRSI[idx] >= xBBSma[idx])                {                   ClosePosition(posBenchmark, OrderType.Market, 0, "Cover " + posBenchmark.Symbol);                   ClosePosition(posBackTest, OrderType.Market, 0, "Sell " + posBackTest.Symbol);                }             }             else             {                // here, posBenchmark is the long position and posBackTest is short                if (xRSI[idx] <= xBBSma[idx])                {                   ClosePosition(posBenchmark, OrderType.Market, 0, "Sell " + posBenchmark.Symbol);                   ClosePosition(posBackTest, OrderType.Market, 0, "Cover " + posBackTest.Symbol);                }             }          }       }       BarHistory stock1, stock2;       private double nStd_Dev;       private int nBB_Period;       private double nStop;       private double xStop;       private TimeSeries xRSI;       private TimeSeries xBBLower;       private TimeSeries xBBSma;       private TimeSeries xBBUpper;    } }
1
- ago
#8
Thanks again @paul1986!

But when I tried to execute I get the following error:

0
- ago
#9
What is your back test symbol, benchmark symbol, data scale and data range? What are you using for position sizing?

Edit... I see what is going on. I was using exactly one back test symbol. Just get rid of the if block that throws the exception.
0
- ago
#10
There:

0
- ago
#11
I see what is going on. I was using exactly one back test symbol. Just get rid of the if block that throws the exception.

Edit... oops, that's not working properly, either. Let me see what I can do.
0
- ago
#12
Removed, it is not showing the exception, but the error remains

0
- ago
#13
Yes, that's a trick one rs
0
- ago
#14
Below is a new version that addresses the problem with matching the trading pairs considering that the benchmark symbol can have many simultaneous open positions when using multiple symbol datasets. However, there still remains an issue with index out of range when using a dataset that has "old" expired symbols such as in the Dow 30 - where if you look in the Data Manager the symbols have a period in them. In the strategy settings if you specify a date range that happens to include one of those "old" symbols then the index out of range error occurs. If the strategy settings do not include dates for those symbols then the strategy runs. Briefly, I tried many workarounds and checks for invalid indexes in relation to the data but could not find why that occurs. Hence, use dates that don't include the problematic symbols. Or, create a new dataset that doesn't include the old problematic symbols.

Also, I did a tad bit more code cleanup...

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Collections.Generic; using System.Threading; //using GabrielEmilio; namespace WealthScript1 {    public class PairsTradeRSI : UserStrategyBase    {       private static int Stock2PosTag = 100000;              public PairsTradeRSI() : base()       {          AddParameter("Std. Devs", ParameterType.Double, 2, 1, 3, 5);          AddParameter("BB Period", ParameterType.Int32, 50, 10, 80, 10);          AddParameter("StopLoss", ParameterType.Double, 7, 2, 15, 1);       }       public override void Initialize(BarHistory bars)       {          string _stock2 = this.Backtester.Strategy.Benchmark;          stock2 = GetHistory(bars, _stock2);          nStop = Parameters.FindName("StopLoss").AsDouble;          nStd_Dev = Parameters.FindName("Std. Devs").AsDouble;          nBB_Period = Parameters.FindName("BB Period").AsInt;          xRSI = new RSI(bars.Close, 8);          PlotTimeSeries(xRSI, "FR", "FR", WLColor.Yellow, PlotStyle.Line, true);          xBBSma = new SMA(xRSI, nBB_Period);          PlotTimeSeries(xBBSma, "SMA_FR", "FR", WLColor.White, PlotStyle.DottedLine, true);          xBBUpper = new BBUpper(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBUpper, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          xBBLower = new BBLower(xRSI, nBB_Period, nStd_Dev);          PlotTimeSeries(xBBLower, "BB_Lower", "FR", WLColor.Gray, PlotStyle.DottedLine, true);          StartIndex = Math.Max(Math.Max(Math.Max(xRSI.FirstValidIndex, xBBSma.FirstValidIndex), xBBUpper.FirstValidIndex), xBBLower.FirstValidIndex) + 1;       }       public override void Execute(BarHistory bars, int idx)       {          if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short))          {             // When placing trades create a unique PositionTag and record it in the transaction Tag             // for stock1 (back test symbol) and use that unique PositionTag as the PositionTag for stock2             // (benchmark symbol).             // This allows use to find stock2's specific position that is associated with the             // particular back test position. We need to do this because the benchmark symbol             // can end up with many positions.             if (xRSI.CrossesOver(xBBLower, idx))             {                var tx = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 1);                var tag = Interlocked.Increment(ref Stock2PosTag);                tx.Tag = tag;                PlaceTrade(stock2, TransactionType.Short, OrderType.Market, 0, tag);             }             else if (xRSI.CrossesUnder(xBBUpper, idx))             {                var tx = PlaceTrade(bars, TransactionType.Short, OrderType.Market, 0, 1);                var tag = Interlocked.Increment(ref Stock2PosTag);                tx.Tag = tag;                PlaceTrade(stock2, TransactionType.Buy, OrderType.Market, 0, tag);             }          }          else          {             // fetch the back test position             Position posBackTest = FindOpenPosition(1);             var pt = (int)posBackTest.Tag; // the Tag is used to find the associated benchmark position             Position posBenchmark = null;             // find the benchmark position based on the benchmark symbol and the associated position tag...             if (OpenPositionsAllSymbols != null)             {                foreach (var pos in OpenPositionsAllSymbols)                {                   if (pos.Symbol == stock2.Symbol && pos.PositionTag == pt)                   {                      posBenchmark = pos;                      break;                   }                }             }             // if we are stopped-out well, then that's it...             if (posBackTest.ProfitPctAsOf(idx) + posBenchmark?.ProfitPctAsOf(idx) < -nStop)             {                ClosePosition(posBackTest, OrderType.Market, 0, "Stop");                ClosePosition(posBenchmark, OrderType.Market, 0, "Stop");                return;             }                          // we can use HasOpenPosition because it will always test against the backtest symbol             if (HasOpenPosition(bars, PositionType.Long))             {                // here, posBackTest is the long position and posBenchmark is short                if (xRSI[idx] >= xBBSma[idx])                {                   ClosePosition(posBenchmark, OrderType.Market, 0, "Cover " + posBenchmark.Symbol);                   ClosePosition(posBackTest, OrderType.Market, 0, "Sell " + posBackTest.Symbol);                }             }             else             {                // here, posBenchmark is the long position and posBackTest is short                if (xRSI[idx] <= xBBSma[idx])                {                   ClosePosition(posBenchmark, OrderType.Market, 0, "Sell " + posBenchmark.Symbol);                   ClosePosition(posBackTest, OrderType.Market, 0, "Cover " + posBackTest.Symbol);                }             }          }       }       BarHistory stock1, stock2;       private double nStd_Dev;       private int nBB_Period;       private double nStop;       private double xStop;       private TimeSeries xRSI;       private TimeSeries xBBLower;       private TimeSeries xBBSma;       private TimeSeries xBBUpper;    } }
2
Best Answer
- ago
#15
Wow, nice code, a lot to learn about it.

I select just last year of a stable dataset (yahoo Brazil - Bovespa)



The symbols in this errors are in the Bovespa index since always, so probably there's something that I did not understand.

I tried also with a ASCII dataset to be sure, but the same problem occurs
0
- ago
#16
I'm laying here on the couch and being almost useless and suggest checking the StartIndex. Another problem may be the position sizing you have in the strategy settings. If you're over-allocating then maybe one of the trades of the trade pair isn't going through and then that causes issues further on. But, that's just a guess.
0
- ago
#17
I just ran the code in Post #14 above on the Bovespa DataSet, and it didn't produce any errors. What Benchmark symbol do you have specified, and do you have the Yahoo Provider enabled (check mark on) in the Data Manager, Historical Providers tab?

0
- ago
#18
Ok I have a guess.

The Yahoo Provider was enabled, but it was outdated.
Even though it might not be that, I noticed that when I ran the backtest with a symbol that is inside the dataset being tested, I got an error. When I used a symbol from outside the tested dataset, it ran without error. Does it make any sense to you?
0
- ago
#19
So, I'm assuming you're saying the benchmark symbol was the same a one of the symbols in the dataset. If that is the case then I can see why that may cause problems because the strategy is attempting to go long and short on the same symbol at the same time. I'm not sure what WL would do with that. Maybe Glitch can provide some info.

I would presume if you're pair trading you would not want to go short and long on the same symbol at the same time.
0
- ago
#20
If you tell me that problem symbol I can try at my end. I can't see why it should be a problem so need more specific info. My benchmark symbol is SPY, which is not in the DataSet, and there were no errors.
0
- ago
#21
Paul, yes, in the first tests I did not pay attention to this fact and was applying a benchmark symbol that was inside the dataset. In this case, the simple solution is easy, create a dataset without the referred symbol.

Glitch, there are two situations using Brazil - Bovespa as an example:

When it worked, I ran the backtest with the symbol BOVA11.SA as the benchmark. Although it is part of the Brazilian stock market and Yahoo can pull the data, this symbol is not in WL's Brazil - Bovespa dataset - so it worked, there was no conflict.

When the execution went wrong, I used some other symbol from within the dataset - for example, PETR4.SA.
0

Reply

Bookmark

Sort