From 9763697946c97245db2f4d81c1e4e3b9386d2a5b Mon Sep 17 00:00:00 2001 From: Vincent Berkien Date: Tue, 29 Aug 2023 08:59:48 +0200 Subject: [PATCH] Implemented generic parser --- .../Generic/GenericParserTests.cs | 63 +++++++++++++++++++ .../TestFiles/Generic/Example1/Example1.csv | 2 + .../GhostfolioSidekick.UnitTests.csproj | 3 + .../FileImporter/Generic/GenericParser.cs | 50 +++++++++++++++ .../FileImporter/Generic/GenericRecord.cs | 24 +++++++ 5 files changed, 142 insertions(+) create mode 100644 GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs create mode 100644 GhostfolioSidekick.UnitTests/FileImporter/TestFiles/Generic/Example1/Example1.csv create mode 100644 GhostfolioSidekick/FileImporter/Generic/GenericParser.cs create mode 100644 GhostfolioSidekick/FileImporter/Generic/GenericRecord.cs diff --git a/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs b/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs new file mode 100644 index 00000000..e7ca6427 --- /dev/null +++ b/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs @@ -0,0 +1,63 @@ +using AutoFixture; +using FluentAssertions; +using GhostfolioSidekick.FileImporter.Generic; +using GhostfolioSidekick.Ghostfolio.API; +using Moq; + +namespace GhostfolioSidekick.UnitTests.FileImporter.Generic +{ + public class GenericParserTests + { + readonly Mock api; + + public GenericParserTests() + { + api = new Mock(); + } + + [Fact] + public async Task CanConvertOrders_TestFileSingleOrder_True() + { + // Arrange + var parser = new GenericParser(api.Object); + + // Act + var canParse = await parser.CanConvertOrders(new[] { "./FileImporter/TestFiles/Generic/Example1/Example1.csv" }); + + // Assert + canParse.Should().BeTrue(); + } + + [Fact] + public async Task ConvertToOrders_TestFileSingleOrder_Converted() + { + // Arrange + var parser = new GenericParser(api.Object); + var fixture = new Fixture(); + + var asset = fixture.Build().With(x => x.Currency, "USD").Create(); + var account = fixture.Create(); + + api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); + api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + + // Act + var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Generic/Example1/Example1.csv" }); + + // Assert + orders.Should().BeEquivalentTo(new[] { new Order { + AccountId = account.Id, + Asset = asset, + Comment = "Transaction Reference: [BUY_US67066G1040_2023-08-07]", + Currency = asset.Currency, + FeeCurrency = "USD", + Date = new DateTime(2023,08,7, 0,0,0, DateTimeKind.Utc), + Fee = 0.02M, + Quantity = 0.0267001M, + Type = OrderType.BUY, + UnitPrice = 453.33M, + ReferenceCode = "BUY_US67066G1040_2023-08-07" + } }); + } + } +} \ No newline at end of file diff --git a/GhostfolioSidekick.UnitTests/FileImporter/TestFiles/Generic/Example1/Example1.csv b/GhostfolioSidekick.UnitTests/FileImporter/TestFiles/Generic/Example1/Example1.csv new file mode 100644 index 00000000..235d7d35 --- /dev/null +++ b/GhostfolioSidekick.UnitTests/FileImporter/TestFiles/Generic/Example1/Example1.csv @@ -0,0 +1,2 @@ +OrderType,Symbol,Date,Currency,Quantity,UnitPrice,Fee +BUY,US67066G1040,2023-08-07,USD,0.0267001000,453.33,0.02 \ No newline at end of file diff --git a/GhostfolioSidekick.UnitTests/GhostfolioSidekick.UnitTests.csproj b/GhostfolioSidekick.UnitTests/GhostfolioSidekick.UnitTests.csproj index e80078e4..b10acff8 100644 --- a/GhostfolioSidekick.UnitTests/GhostfolioSidekick.UnitTests.csproj +++ b/GhostfolioSidekick.UnitTests/GhostfolioSidekick.UnitTests.csproj @@ -44,6 +44,9 @@ Always + + Always + Always diff --git a/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs b/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs new file mode 100644 index 00000000..bc74fd10 --- /dev/null +++ b/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs @@ -0,0 +1,50 @@ +using CsvHelper.Configuration; +using GhostfolioSidekick.Ghostfolio.API; +using System.Globalization; + +namespace GhostfolioSidekick.FileImporter.Generic +{ + public class GenericParser : RecordBaseImporter + { + public GenericParser(IGhostfolioAPI api) : base(api) + { + } + + protected override async Task> ConvertOrders(GenericRecord record, Account account, IEnumerable allRecords) + { + var asset = await api.FindSymbolByISIN(record.Symbol); + + if (string.IsNullOrWhiteSpace(record.Id)) + { + record.Id = $"{record.OrderType}_{record.Symbol}_{record.Date.ToString("yyyy-MM-dd")}"; + } + + var order = new Order + { + AccountId = account.Id, + Asset = asset, + Currency = record.Currency, + Date = record.Date, + Comment = $"Transaction Reference: [{record.Id}]", + Fee = record.Fee ?? 0, + FeeCurrency = record.Currency, + Quantity = record.Quantity, + Type = record.OrderType, + UnitPrice = record.UnitPrice, + ReferenceCode = record.Id, + }; + + return new[] { order }; + } + + protected override CsvConfiguration GetConfig() + { + return new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = true, + CacheFields = true, + Delimiter = ",", + }; + } + } +} diff --git a/GhostfolioSidekick/FileImporter/Generic/GenericRecord.cs b/GhostfolioSidekick/FileImporter/Generic/GenericRecord.cs new file mode 100644 index 00000000..cede5961 --- /dev/null +++ b/GhostfolioSidekick/FileImporter/Generic/GenericRecord.cs @@ -0,0 +1,24 @@ +using GhostfolioSidekick.Ghostfolio.API; + +namespace GhostfolioSidekick.FileImporter.Generic +{ + public class GenericRecord + { + public OrderType OrderType { get; set; } + + public string? Symbol { get; set; } + + public DateTime Date { get; set; } + + public string Currency { get; set; } + + public decimal Quantity { get; set; } + + public decimal UnitPrice { get; set; } + + public decimal? Fee { get; set; } + + [CsvHelper.Configuration.Attributes.Optional] + public string? Id { get; set; } + } +}