Skip to content

Commit

Permalink
chore: add random quote test rig (#803)
Browse files Browse the repository at this point in the history
Co-authored-by: Dave Skender <[email protected]>
  • Loading branch information
mihakralj and DaveSkender authored May 15, 2022
1 parent a037753 commit f3813d4
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 42 deletions.
9 changes: 8 additions & 1 deletion tests/indicators/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This file is used by Code Analysis to maintain SuppressMessage
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
Expand All @@ -19,3 +19,10 @@
"StyleCop.CSharp.NamingRules",
"SA1311:Static readonly fields should begin with upper-case letter",
Justification = "Acceptable for test project.")]

[assembly: SuppressMessage(
"Security",
"CA5394:Do not use insecure randomness",
Justification = "Okay for internal test use only.",
Scope = "member",
Target = "~M:Internal.Tests.RandomGbm.Price(System.Double,System.Double,System.Double)~System.Double")]
1 change: 1 addition & 0 deletions tests/indicators/_Initialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ public abstract class TestBase
internal static readonly IEnumerable<Quote> mismatchQuotes = TestData.GetMismatch();
internal static readonly IEnumerable<Quote> noquotes = new List<Quote>();
internal static readonly IEnumerable<Quote> onequote = TestData.GetDefault(1);
internal static readonly IEnumerable<Quote> randomQuotes = TestData.GetRandom(1000);
}
56 changes: 17 additions & 39 deletions tests/indicators/_common/Helper.Importer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,138 +73,116 @@ internal class TestData
{
// DEFAULT: S&P 500 ~2 years of daily data
internal static IEnumerable<Quote> GetDefault(int days = 502)
{
return File.ReadAllLines("_common/data/default.csv")
=> File.ReadAllLines("_common/data/default.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// RANDOM: gaussian brownaian motion
internal static IEnumerable<Quote> GetRandom(int days = 502)
=> new RandomGbm(bars: days);

// BAD DATA
internal static IEnumerable<Quote> GetBad(int days = 502)
{
return File.ReadAllLines("_common/data/bad.csv")
=> File.ReadAllLines("_common/data/bad.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// TOO BIG DATA
internal static IEnumerable<Quote> GetTooBig(int days = 1246)
{
return File.ReadAllLines("_common/data/toobig.csv")
=> File.ReadAllLines("_common/data/toobig.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// MAX SIZE DATA
internal static IEnumerable<Quote> GetMax(int days = 502)
{
return File.ReadAllLines("_common/data/toobig.csv")
=> File.ReadAllLines("_common/data/toobig.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// BITCOIN DATA
internal static IEnumerable<Quote> GetBitcoin(int days = 1246)
{
return File.ReadAllLines("_common/data/bitcoin.csv")
=> File.ReadAllLines("_common/data/bitcoin.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// COMPARE DATA ~2 years of TSLA data (matches default time)
internal static IEnumerable<Quote> GetCompare(int days = 502)
{
return File.ReadAllLines("_common/data/compare.csv")
=> File.ReadAllLines("_common/data/compare.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// INTRADAY DATA
internal static IEnumerable<Quote> GetIntraday(int days = 1564)
{
return File.ReadAllLines("_common/data/intraday.csv")
=> File.ReadAllLines("_common/data/intraday.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// LONGISH DATA ~20 years of S&P 500 daily data
internal static IEnumerable<Quote> GetLongish(int days = 5285)
{
return File.ReadAllLines("_common/data/longish.csv")
=> File.ReadAllLines("_common/data/longish.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// LONGEST DATA ~62 years of S&P 500 daily data
internal static IEnumerable<Quote> GetLongest()
{
return File.ReadAllLines("_common/data/longest.csv")
=> File.ReadAllLines("_common/data/longest.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.ToList();
}

// PENNY DATA
internal static IEnumerable<Quote> GetPenny()
{
return File.ReadAllLines("_common/data/penny.csv")
=> File.ReadAllLines("_common/data/penny.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.ToList();
}

// MISMATCH DATA is in incorrect sequence
internal static IEnumerable<Quote> GetMismatch()
{
return File.ReadAllLines("_common/data/mismatch.csv")
=> File.ReadAllLines("_common/data/mismatch.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.ToList();
}

// SPX, 30 years, daily
internal static IEnumerable<Quote> GetSpx(int days = 8111)
{
return File.ReadAllLines("_common/data/spx.csv")
=> File.ReadAllLines("_common/data/spx.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}

// MSFT, 30 years, daily
internal static IEnumerable<Quote> GetMsft(int days = 8111)
{
return File.ReadAllLines("_common/data/msft.csv")
=> File.ReadAllLines("_common/data/msft.csv")
.Skip(1)
.Select(v => Importer.QuoteFromCsv(v))
.OrderByDescending(x => x.Date)
.Take(days)
.ToList();
}
}
80 changes: 80 additions & 0 deletions tests/indicators/_common/Helper.Random.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Skender.Stock.Indicators;

namespace Internal.Tests;
/**
<summary>
Geometric Brownian Motion (GMB) is a random simulator of market movement.
GBM can be used for testing indicators, validation and Monte Carlo simulations of strategies.
Sample usage:
RandomGbm data = new(); // generates 1 year (252) list of bars
RandomGbm data = new(Bars: 1000); // generates 1,000 bars
RandomGbm data = new(Bars: 252, Volatility: 0.05, Drift: 0.0005, Seed: 100.0)
Parameters
Bars: number of bars (quotes) requested
Volatility: how dymamic/volatile the series should be; default is 1
Drift: incremental drift due to annual interest rate; default is 5%
Seed: starting value of the random series; should not be 0.
</summary>
**/
internal class RandomGbm : List<Quote>
{
private readonly double volatility;
private readonly double drift;
private double seed;

public RandomGbm(
int bars = 252,
double volatility = 1.0,
double drift = 0.05,
double seed = 100.0)
{
this.seed = seed;
this.volatility = volatility * 0.01;
this.drift = drift * 0.01;
for (int i = 0; i < bars; i++)
{
DateTime date = DateTime.Today.AddDays(i - bars);
Add(date);
}
}

public void Add(DateTime timestamp)
{
double open = Price(seed, volatility * volatility, drift);
double close = Price(open, volatility, drift);

double ocMax = Math.Max(open, close);
double high = Price(seed, volatility * 0.5, 0);
high = (high < ocMax) ? (2 * ocMax) - high : high;

double ocMin = Math.Min(open, close);
double low = Price(seed, volatility * 0.5, 0);
low = (low > ocMin) ? (2 * ocMin) - low : low;

double volume = Price(seed * 10, volatility * 2, drift: 0);

Quote quote = new()
{
Date = timestamp,
Open = (decimal)open,
High = (decimal)high,
Low = (decimal)low,
Close = (decimal)close,
Volume = (decimal)volume
};

Add(quote);
seed = close;
}

private static double Price(double seed, double volatility, double drift)
{
Random rnd = new((int)DateTime.UtcNow.Ticks);
double u1 = 1.0 - rnd.NextDouble();
double u2 = 1.0 - rnd.NextDouble();
double z = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
return seed * Math.Exp(drift - (volatility * volatility * 0.5) + (volatility * z));
}
}
11 changes: 9 additions & 2 deletions tests/indicators/a-d/Adl/Adl.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,24 @@ public void WithSma()
[TestMethod]
public void BadData()
{
IEnumerable<AdlResult> r = Indicator.GetAdl(badQuotes);
IEnumerable<AdlResult> r = badQuotes.GetAdl();
Assert.AreEqual(502, r.Count());
}

[TestMethod]
public void BigData()
{
IEnumerable<AdlResult> r = Indicator.GetAdl(bigQuotes);
IEnumerable<AdlResult> r = bigQuotes.GetAdl();
Assert.AreEqual(1246, r.Count());
}

[TestMethod]
public void RandomData()
{
IEnumerable<AdlResult> r = randomQuotes.GetAdl();
Assert.AreEqual(1000, r.Count());
}

[TestMethod]
public void NoQuotes()
{
Expand Down

0 comments on commit f3813d4

Please sign in to comment.