karlwancl / Trady

Trady is a handy library for computing technical indicators, and it targets to be an automated trading system that provides stock data feeding, indicator computing, strategy building and automatic trading. It is built based on .NET Standard 2.0.
https://lppkarl.github.io/Trady
Apache License 2.0
545 stars 184 forks source link

Bug in backtest #69

Closed irperez closed 6 years ago

irperez commented 6 years ago

@lppkarl I believe I found a bug. NullReferenceException in backtesting. Maybe I'm doing something wrong?

If I am doing something wrong, maybe we need validation. Otherwise I think this is valid scenario, no?

Here is my sample.

     public class MyClass
{
      static int RunBackTest()
        {            
            var importer = new AlphaVantageImporter("[YourAPIKey]", OutputSize.full);
            var fb = importer.ImportAsync("fb", startTime: new DateTime(2018,1,1), period: PeriodOption.PerMinute).Result;

            var importer2 = new AlphaVantageImporter("[YourAPIKey]", OutputSize.full);
            var wba = importer2.ImportAsync("xom", startTime: new DateTime(2018, 1, 1), period: PeriodOption.PerMinute).Result;

            var importer3 = new AlphaVantageImporter("[YourAPIKey]", OutputSize.full);
            var att = importer3.ImportAsync("t", startTime: new DateTime(2018, 1, 1), period: PeriodOption.PerMinute).Result;

            var buyRule = Rule.Create(c => c.IsMacdBullishCross(12, 26, 9))
                            .And(c => c.IsRsiOversold2());

            var sellRule = Rule.Create(c => c.IsMacdBearishCross(12, 26, 9))
                            .And(c => c.IsRsiOverbought2());

            var runner = new Trady.Analysis.Backtest.Builder()
                .Add(att, 33).Add(wba, 33).Add(fb, 33)
                .Buy(buyRule)
                .Sell(sellRule)
                .Build();

            var result = runner.RunAsync(10000, 1, new DateTime(2018,1,1)).Result;

            Console.WriteLine($"Transaction count: {result.Transactions.Count():#.##}, P/L ratio: {result.TotalCorrectedProfitLossRatio * 100}%, Principal: {result.TotalPrincipal}, Total: {result.TotalCorrectedBalance}"); 

            return 0;
        }
}

public static class Signals
    {
        public static bool IsRsiOverbought2(this IIndexedOhlcv ic, int periodCount = 14)
            => ic.Get<RelativeStrengthIndex>(periodCount)[ic.Index].Tick.IsTrue(t => t >= 60);

        public static bool IsRsiOversold2(this IIndexedOhlcv ic, int periodCount = 14)
            => ic.Get<RelativeStrengthIndex>(periodCount)[ic.Index].Tick.IsTrue(t => t <= 40);
    }
karlwancl commented 6 years ago

The null reference happens because a sell signal is discovered before a buy signal, getting the open of the last transaction will throw null reference exception

irperez commented 6 years ago

@lppkarl so did I do something wrong here? How do I prevent this from occurring? Or do we need to do a null check before processing the sell signals?

karlwancl commented 6 years ago

Nope, you're not doing wrong. I've commited code to fix the issue this morning.