Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes candles timestamps validations #109

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions Trady.Analysis/Trady.Analysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.2" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
Expand Down
26 changes: 18 additions & 8 deletions Trady.Benchmark/Trady.Benchmarks.Version31/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
namespace Trady.Benchmarks.Version31
{
[Config(typeof(Config))]
[CoreJob]
[SimpleJob()]
public class Benchmark
{
private const int _n = 10000;
private const int _n = 100000;
private readonly IOhlcv[] _data;
private readonly ITickTrade[] _tradeData;

Expand All @@ -38,10 +38,10 @@ public Benchmark()

_tradeData = new ITickTrade[_n];
var d = DateTimeOffset.Now;
for(int i = 0; i < _n; i++)
{
_tradeData[i] = new Trade(d.AddSeconds(i), 1, 1);
}
for (int i = 0; i < _n; i++)
{
_tradeData[i] = new Trade(d.AddSeconds(i), 1, 1, i % 2 == 0);
}

}

Expand All @@ -51,8 +51,8 @@ public Config()
{
Add(StatisticColumn.P90);
}
}
}

[Benchmark]
public IReadOnlyList<IOhlcv> TransformToMonthly() => _data.Transform<PerMinute, Monthly>();

Expand Down Expand Up @@ -233,6 +233,16 @@ public Config()
public IReadOnlyList<IOhlcv> TransformFromTradesToDaily() => _tradeData.TransformToCandles<Daily>();
[Benchmark]
public IReadOnlyList<IOhlcv> TransformFromTradesToWeekly() => _tradeData.TransformToCandles<Weekly>();
[Benchmark]
public IReadOnlyList<IOhlcbsv> TransformFromTradesToMinuteBuySell() => _tradeData.TransformToCandles<PerMinute>();
[Benchmark]
public IReadOnlyList<IOhlcbsv> TransformFromTradesToHourlyBuySell() => _tradeData.TransformToCandles<Hourly>();
[Benchmark]
public IReadOnlyList<IOhlcbsv> TransformFromTradesToBeHourlyBuySell() => _tradeData.TransformToCandles<BiHourly>();
[Benchmark]
public IReadOnlyList<IOhlcbsv> TransformFromTradesToDailyBuySell() => _tradeData.TransformToCandles<Daily>();
[Benchmark]
public IReadOnlyList<IOhlcbsv> TransformFromTradesToWeeklyBuySell() => _tradeData.TransformToCandles<Weekly>();
}

public class Program
Expand Down
25 changes: 14 additions & 11 deletions Trady.Core/CandleExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static class IOhlcvDataExtension

#region candle list transformation

public static IReadOnlyList<IOhlcv> Transform<TSourcePeriod, TTargetPeriod>(this IEnumerable<IOhlcv> candles)
public static IReadOnlyList<IOhlcv> Transform<TSourcePeriod, TTargetPeriod>(this IEnumerable<IOhlcv> candles, bool checkCandlesTimestampSequence = false)
where TSourcePeriod : IPeriod
where TTargetPeriod : IPeriod
{
Expand All @@ -32,7 +32,10 @@ public static IReadOnlyList<IOhlcv> Transform<TSourcePeriod, TTargetPeriod>(this
if (typeof(TSourcePeriod).Equals(typeof(TTargetPeriod)))
return candles.ToList();

if (!IsTimeframesValid<TSourcePeriod>(candles, out var err))
// To prevent lazy evaluated when compute
var orderedCandles = candles.OrderBy(c => c.DateTime).ToList();

if (!IsTimeframesValid<TSourcePeriod>(orderedCandles, out var err, checkCandlesTimestampSequence))
throw new InvalidTimeframeException(err.DateTime);

if (!IsTransformationValid<TSourcePeriod, TTargetPeriod>())
Expand All @@ -41,9 +44,6 @@ public static IReadOnlyList<IOhlcv> Transform<TSourcePeriod, TTargetPeriod>(this
var outputCandles = new List<IOhlcv>();
var periodInstance = Activator.CreateInstance<TTargetPeriod>();

// To prevent lazy evaluated when compute
var orderedCandles = candles.OrderBy(c => c.DateTime).ToList();

var periodStartTime = orderedCandles[0].DateTime;
var periodEndTime = periodInstance.NextTimestamp(periodStartTime);

Expand Down Expand Up @@ -76,16 +76,21 @@ private static void AddComputedCandleToOutput(List<IOhlcv> outputCandles, List<I
outputCandles.Add(computedCandle);
}

private static bool IsTimeframesValid<TPeriod>(IEnumerable<IOhlcv> candles, out IOhlcv err)
private static bool IsTimeframesValid<TPeriod>(IEnumerable<IOhlcv> candles, out IOhlcv err, bool checkCandlesTimestampSequence = false)
where TPeriod : IPeriod
{
var periodInstance = Activator.CreateInstance<TPeriod>();
err = default;
var offset = candles.Any() ? candles.First().DateTime.Offset.Hours : 0;
for (int i = 0; i < candles.Count() - 1; i++)
{
var nextTime = periodInstance.NextTimestamp(candles.ElementAt(i).DateTime);
var candleEndTime = new DateTimeOffset(nextTime.Date, TimeSpan.FromHours(offset));
var currentCandle = candles.ElementAt(i);
if (checkCandlesTimestampSequence && !periodInstance.IsTimestamp(currentCandle.DateTime))
{
err = candles.ElementAt(i);
return false;
}
var nextTime = periodInstance.NextTimestamp(currentCandle.DateTime);
var candleEndTime = new DateTimeOffset(nextTime.Date, nextTime.Offset);
if (candleEndTime > candles.ElementAt(i + 1).DateTime)
{
err = candles.ElementAt(i);
Expand Down Expand Up @@ -129,8 +134,6 @@ private static IOhlcv ComputeCandles(IEnumerable<IOhlcv> candles)
var volume = candles.Sum(stick => stick.Volume);
return new Candle(dateTime, open, high, low, close, volume);
}


#endregion candle list transformation
}
}
34 changes: 34 additions & 0 deletions Trady.Core/CandleWithBuysAndSells.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using Trady.Core.Infrastructure;

namespace Trady.Core
{
public class CandleWithBuysAndSells : IOhlcbsv
{
public CandleWithBuysAndSells(DateTimeOffset dateTime, decimal open, decimal high, decimal low, decimal close, decimal sellsVolume, decimal buysVolume)
{
DateTime = dateTime;
Open = open;
High = high;
Low = low;
Close = close;
BuysVolume = buysVolume;
SellsVolume = sellsVolume;
Volume = sellsVolume + buysVolume;
}

public DateTimeOffset DateTime { get; set; }

public decimal Open { get; set; }

public decimal High { get; set; }

public decimal Low { get; set; }

public decimal Close { get; set; }

public decimal Volume { get; set; }
public decimal BuysVolume { get; set; }
public decimal SellsVolume { get; set; }
}
}
15 changes: 15 additions & 0 deletions Trady.Core/Infrastructure/IOhlcbsv.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Trady.Core.Infrastructure
{
/// <summary>
/// The same as IOhlcv extended by sells and buys volumes
/// </summary>
public interface IOhlcbsv:IOhlcv
{
decimal BuysVolume { get; set; }
decimal SellsVolume { get; set; }
}
}
3 changes: 2 additions & 1 deletion Trady.Core/Infrastructure/ITickTrade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Trady.Core.Infrastructure
public interface ITickTrade : ITick
{
decimal Price { get; set; }
decimal Volume { get; set; }
decimal Volume { get; set; }
bool IsSell { get; set; }
}
}
5 changes: 3 additions & 2 deletions Trady.Core/Trade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ namespace Trady.Core
{
public class Trade : ITickTrade
{
public Trade(DateTimeOffset date, decimal price, decimal volume)
public Trade(DateTimeOffset date, decimal price, decimal volume, bool isSell)
{
DateTime = date;
Price = price;
Volume = volume;
IsSell = isSell;
}
public decimal Price { get; set; }
public decimal Volume { get ; set ; }
public DateTimeOffset DateTime { get; set; }

public bool IsSell { get; set; }
}
}
25 changes: 19 additions & 6 deletions Trady.Core/TradeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace Trady.Core
{
public static class TradeExtensions
{
public static IReadOnlyList<IOhlcv> TransformToCandles<TTargetPeriod>(this IEnumerable<ITickTrade> trades)
public static IReadOnlyList<IOhlcbsv> TransformToCandles<TTargetPeriod>(this IEnumerable<ITickTrade> trades)
where TTargetPeriod : IPeriod
{
var outputCandles = new List<IOhlcv>();
var outputCandles = new List<IOhlcbsv>();

if (!trades.Any())
return outputCandles;
Expand Down Expand Up @@ -45,13 +45,13 @@ public static IReadOnlyList<IOhlcv> TransformToCandles<TTargetPeriod>(this IEnum

return outputCandles;
}
private static void AddComputedCandleToOutput(List<IOhlcv> outputCandlesFromTrades, List<ITickTrade> tempTrades)
private static void AddComputedCandleToOutput(List<IOhlcbsv> outputCandlesFromTrades, List<ITickTrade> tempTrades)
{
var computedCandle = ComputeCandles(tempTrades);
if (computedCandle != null)
outputCandlesFromTrades.Add(computedCandle);
}
private static IOhlcv ComputeCandles(IEnumerable<ITickTrade> trades)
private static IOhlcbsv ComputeCandles(IEnumerable<ITickTrade> trades)
{
if (!trades.Any())
return null;
Expand All @@ -61,8 +61,21 @@ private static IOhlcv ComputeCandles(IEnumerable<ITickTrade> trades)
var high = trades.Max(trade => trade.Price);
var low = trades.Min(trade => trade.Price);
var close = trades.Last().Price;
var volume = trades.Sum(stick => stick.Volume);
return new Candle(dateTime, open, high, low, close, volume);
var sells = 0m;
var buys = 0m;

foreach(var trade in trades)
{
if (trade.IsSell)
{
sells += trade.Volume;
}
else
{
buys += trade.Volume;
}
}
return new CandleWithBuysAndSells(dateTime, open, high, low, close, sells,buys);
}
}
}
2 changes: 1 addition & 1 deletion Trady.Core/Trady.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Trady.Importer.AlphaVantage/AlphaVantageImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ protected HttpClient Client
var culture = "en-US";
var cultureInfo = new CultureInfo(culture);
var candles = new List<IOhlcv>();
using(var csvReader = new CsvReader(textReader, new Configuration() { CultureInfo = cultureInfo, Delimiter = ",", HasHeaderRecord = true }))
using(var csvReader = new CsvReader(textReader, new CsvConfiguration(cultureInfo) { Delimiter = ",", HasHeaderRecord = true }))
{
bool isHeaderBypassed = false;
while (csvReader.Read())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" Version="7.1.1" />
<PackageReference Include="CsvHelper" Version="15.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Trady.Importer.Csv/CsvImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public CsvImporter(string path, CsvImportConfiguration configuration): this(path
{
using (var fs = File.OpenRead(_path))
using (var sr = new StreamReader(fs))
using (var csvReader = new CsvReader(sr, new Configuration() { CultureInfo = _culture, Delimiter = string.IsNullOrWhiteSpace(_delimiter) ? "," : _delimiter, HasHeaderRecord = _hasHeader }))
using (var csvReader = new CsvReader(sr, new CsvConfiguration(_culture) { Delimiter = string.IsNullOrWhiteSpace(_delimiter) ? "," : _delimiter, HasHeaderRecord = _hasHeader }))
{
var candles = new List<IOhlcv>();
bool isHeaderBypassed = false;
Expand Down
2 changes: 1 addition & 1 deletion Trady.Importer.Csv/Trady.Importer.Csv.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="7.1.1" />
<PackageReference Include="CsvHelper" Version="15.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Trady.Core\Trady.Core.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion Trady.Importer.Yahoo/Trady.Importer.Yahoo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YahooFinanceApi" Version="2.1.1" />
<PackageReference Include="YahooFinanceApi" Version="2.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Trady.Core\Trady.Core.csproj" />
Expand Down
53 changes: 30 additions & 23 deletions Trady.Test/ImporterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
//using Trady.Importer.Quandl;
using Trady.Importer.Stooq;
using Trady.Importer.Yahoo;
using Trady.Core;
using Trady.Core.Period;
using System.Collections.Generic;
using System.IO;
using CsvHelper;

namespace Trady.Test
{
Expand All @@ -36,6 +41,8 @@ public ImporterTest()
//}

// TODO: test later
/**/

[TestMethod]
public void ImportByQuandlYahoo()
{
Expand Down Expand Up @@ -79,30 +86,30 @@ public void ImportByAlphaVantage_Hourly()
var candle = candles.FirstOrDefault();
Assert.IsNotNull(candle);
}
//Waiting for merge pull request for update CSV helper library
// [TestMethod]
// public void ImportByYahoo()
// {
// var importer = new YahooFinanceImporter();
// var candle = importer.ImportAsync("^GSPC", new DateTime(2017, 1, 3), new DateTime(2017, 1, 4)).Result.First(); // Endtime stock history exclusive
// Assert.AreEqual(candle.Open, 2251.570068m);
// Assert.AreEqual(candle.High, 2263.879883m);
// Assert.AreEqual(candle.Low, 2245.129883m);
// Assert.AreEqual(candle.Close, 2257.830078m);
// Assert.AreEqual(candle.Volume, 3_770_530_000);
// }

[TestMethod]
public void ImportByYahoo()
{
var importer = new YahooFinanceImporter();
var candle = importer.ImportAsync("^GSPC", new DateTime(2017, 1, 3), new DateTime(2017, 1, 4)).Result.First(); // Endtime stock history exclusive
Assert.AreEqual(candle.Open, 2251.570068m);
Assert.AreEqual(candle.High, 2263.879883m);
Assert.AreEqual(candle.Low, 2245.129883m);
Assert.AreEqual(candle.Close, 2257.830078m);
Assert.AreEqual(candle.Volume, 3_770_530_000);
}

[TestMethod]
public void ImportByStooq()
{
var importer = new StooqImporter();
var candle = importer.ImportAsync("^SPX", new DateTime(2017, 1, 3), new DateTime(2017, 1, 3)).Result.First(); // Endtime stock history inclusive
Assert.AreEqual(candle.Open, 2251.57m);
Assert.AreEqual(candle.High, 2263.88m);
Assert.AreEqual(candle.Low, 2245.13m);
Assert.AreEqual(candle.Close, 2257.83m);
Assert.AreEqual(candle.Volume, 644_640_832);
}
//[TestMethod]
// public void ImportByStooq()
// {
// var importer = new StooqImporter();
// var candle = importer.ImportAsync("^SPX", new DateTime(2017, 1, 3), new DateTime(2017, 1, 3)).Result.First(); // Endtime stock history inclusive
// Assert.AreEqual(candle.Open, 2251.57m);
// Assert.AreEqual(candle.High, 2263.88m);
// Assert.AreEqual(candle.Low, 2245.13m);
// Assert.AreEqual(candle.Close, 2257.83m);
// Assert.AreEqual(candle.Volume, 644_640_832);
// }

[TestMethod]
public void ImportFromCsv()
Expand Down
Loading