From fc6b7a830ef96d5db99ff071e74a47e13a667ad8 Mon Sep 17 00:00:00 2001 From: VibeNL Date: Thu, 19 Oct 2023 13:02:17 +0200 Subject: [PATCH] Add custom symbol --- .../FileImporter/DeGiro/DeGiroParserTests.cs | 10 ++--- .../Generic/GenericParserTests.cs | 4 +- .../FileImporter/Nexo/NexoParserTests.cs | 14 +++---- .../ScalableCapitalParserTests.cs | 12 +++--- .../Trading212/Trading212Tests.cs | 12 +++--- .../FileImporter/DeGiro/DeGiroParser.cs | 2 +- .../FileImporter/FileImporterTask.cs | 2 +- .../FileImporter/Generic/GenericParser.cs | 2 +- .../FileImporter/Nexo/NexoParser.cs | 13 +++---- .../ScalableCaptial/ScalableCapitalParser.cs | 4 +- .../Trading212/Trading212Parser.cs | 2 +- .../Ghostfolio/API/Contract/Asset.cs | 20 +++++----- .../Ghostfolio/API/Contract/SymbolProfile.cs | 20 +++++----- .../Ghostfolio/API/GhostfolioAPI.cs | 29 ++++++++++++-- .../Ghostfolio/API/IGhostfolioAPI.cs | 8 ++-- .../API/Mapper/ContractToModelMapper.cs | 2 + .../MarketDataMaintainerTask.cs | 38 +++++++++++++++++-- GhostfolioSidekick/Model/Asset.cs | 12 +++++- 18 files changed, 139 insertions(+), 67 deletions(-) diff --git a/GhostfolioSidekick.UnitTests/FileImporter/DeGiro/DeGiroParserTests.cs b/GhostfolioSidekick.UnitTests/FileImporter/DeGiro/DeGiroParserTests.cs index 9a501e73..31a53941 100644 --- a/GhostfolioSidekick.UnitTests/FileImporter/DeGiro/DeGiroParserTests.cs +++ b/GhostfolioSidekick.UnitTests/FileImporter/DeGiro/DeGiroParserTests.cs @@ -53,7 +53,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrder_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("IE00B3XXRP09", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example1/TestFileSingleOrder.csv" }); @@ -85,8 +85,8 @@ public async Task ConvertActivitiesForAccount_TestFileMultipleOrders_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09", null)).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("NL0009690239", null)).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("IE00B3XXRP09", null)).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("NL0009690239", null)).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example3/TestFileMultipleOrders.csv" }); @@ -126,7 +126,7 @@ public async Task ConvertActivitiesForAccount_TestFileDividend_WithTax_Converted var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("NL0009690239", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("NL0009690239", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example4/TestFileDividend.csv" }); @@ -156,7 +156,7 @@ public async Task ConvertActivitiesForAccount_TestFileDividend_NoTax_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("NL0009690239", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("NL0009690239", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example5/TestFileDividendNoTax.csv" }); diff --git a/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs b/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs index 25b0b1db..760fd201 100644 --- a/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs +++ b/GhostfolioSidekick.UnitTests/FileImporter/Generic/GenericParserTests.cs @@ -40,7 +40,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrder_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.USD)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US67066G1040", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Generic/Example1/Example1.csv" }); @@ -70,7 +70,7 @@ public async Task ConvertActivitiesForAccount_TestFileLifecycle_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.USD)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US67066G1040", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Generic/Example2/Example2.csv" }); diff --git a/GhostfolioSidekick.UnitTests/FileImporter/Nexo/NexoParserTests.cs b/GhostfolioSidekick.UnitTests/FileImporter/Nexo/NexoParserTests.cs index 26caca7f..284fbf80 100644 --- a/GhostfolioSidekick.UnitTests/FileImporter/Nexo/NexoParserTests.cs +++ b/GhostfolioSidekick.UnitTests/FileImporter/Nexo/NexoParserTests.cs @@ -42,8 +42,8 @@ public async Task ConvertActivitiesForAccount_TestFileMultipleOrders_ReferalPend var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Nexo/Example1/Example1.csv" }); @@ -84,8 +84,8 @@ public async Task ConvertActivitiesForAccount_TestFileMultipleOrders_ReferalAppr var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Nexo/Example2/Example2.csv" }); @@ -135,7 +135,7 @@ public async Task ConvertActivitiesForAccount_TestCashback_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Nexo/Example3/Cashback.csv" }); @@ -178,8 +178,8 @@ public async Task ConvertActivitiesForAccount_TestExchangeCoins_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("USDC", It.IsAny, Asset>>())).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("BTC", It.IsAny, Asset>>())).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Nexo/Example4/ExchangeCoins.csv" }); diff --git a/GhostfolioSidekick.UnitTests/FileImporter/ScalableCapital/ScalableCapitalParserTests.cs b/GhostfolioSidekick.UnitTests/FileImporter/ScalableCapital/ScalableCapitalParserTests.cs index 059dde86..6926fdc8 100644 --- a/GhostfolioSidekick.UnitTests/FileImporter/ScalableCapital/ScalableCapitalParserTests.cs +++ b/GhostfolioSidekick.UnitTests/FileImporter/ScalableCapital/ScalableCapitalParserTests.cs @@ -53,7 +53,7 @@ public async Task ConvertActivitiesForAccount_Example1_OrderOnly() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("IE00077FRP95", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/ScalableCapital/Example1/WUMExample1.csv" }); @@ -82,7 +82,7 @@ public async Task ConvertActivitiesForAccount_Example1_DividendOnly() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US92343V1044", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/ScalableCapital/Example1/RKKExample1.csv" }); @@ -113,8 +113,8 @@ public async Task ConvertActivitiesForAccount_Example1_Both() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("IE00077FRP95", null)).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("US92343V1044", null)).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { @@ -158,8 +158,8 @@ public async Task ConvertActivitiesForAccount_Example2_NotDuplicateFeesAndDivide var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset1); - api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset2); + api.Setup(x => x.FindSymbolByIdentifier("IE00077FRP95", null)).ReturnsAsync(asset1); + api.Setup(x => x.FindSymbolByIdentifier("US92343V1044", null)).ReturnsAsync(asset2); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { diff --git a/GhostfolioSidekick.UnitTests/FileImporter/Trading212/Trading212Tests.cs b/GhostfolioSidekick.UnitTests/FileImporter/Trading212/Trading212Tests.cs index dea05e81..52acf738 100644 --- a/GhostfolioSidekick.UnitTests/FileImporter/Trading212/Trading212Tests.cs +++ b/GhostfolioSidekick.UnitTests/FileImporter/Trading212/Trading212Tests.cs @@ -40,7 +40,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrder_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US67066G1040", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example1/TestFileSingleOrder.csv" }); @@ -80,7 +80,7 @@ public async Task ConvertActivitiesForAccount_TestFileMultipleOrdersUS_Converted var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US67066G1040", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example2/TestFileMultipleOrdersUS.csv" }); @@ -121,7 +121,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrderUK_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("GB0007188757", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("GB0007188757", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example3/TestFileSingleOrderUK.csv" }); @@ -150,7 +150,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleDividend_Converted() var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US0378331005", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US0378331005", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example4/TestFileSingleDividend.csv" }); @@ -180,7 +180,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrderUKNativeCurrenc var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("GB0007188757", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("GB0007188757", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example5/TestFileSingleOrderUKNativeCurrency.csv" }); @@ -209,7 +209,7 @@ public async Task ConvertActivitiesForAccount_TestFileSingleOrderMultipleTimes_C var account = fixture.Build().With(x => x.Balance, Balance.Empty(DefaultCurrency.EUR)).Create(); api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account); - api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset); + api.Setup(x => x.FindSymbolByIdentifier("US67066G1040", null)).ReturnsAsync(asset); // Act account = await parser.ConvertActivitiesForAccount(account.Name, new[] { diff --git a/GhostfolioSidekick/FileImporter/DeGiro/DeGiroParser.cs b/GhostfolioSidekick/FileImporter/DeGiro/DeGiroParser.cs index cd447473..bc8abafe 100644 --- a/GhostfolioSidekick/FileImporter/DeGiro/DeGiroParser.cs +++ b/GhostfolioSidekick/FileImporter/DeGiro/DeGiroParser.cs @@ -22,7 +22,7 @@ public DeGiroParser(IGhostfolioAPI api) : base(api) return Array.Empty(); } - var asset = string.IsNullOrWhiteSpace(record.ISIN) ? null : await api.FindSymbolByISIN(record.ISIN); + var asset = string.IsNullOrWhiteSpace(record.ISIN) ? null : await api.FindSymbolByIdentifier(record.ISIN); var fee = GetFee(record, allRecords); var taxes = GetTaxes(record, allRecords); diff --git a/GhostfolioSidekick/FileImporter/FileImporterTask.cs b/GhostfolioSidekick/FileImporter/FileImporterTask.cs index ef9da709..d2d318e7 100644 --- a/GhostfolioSidekick/FileImporter/FileImporterTask.cs +++ b/GhostfolioSidekick/FileImporter/FileImporterTask.cs @@ -34,7 +34,7 @@ public async Task DoWork() var directories = Directory.GetDirectories(fileLocation); - foreach (var directory in directories.Select(x => new DirectoryInfo(x))) + foreach (var directory in directories.Select(x => new DirectoryInfo(x)).OrderBy(x => x.Name)) { var accountName = directory.Name; logger.LogInformation($"AccountName: {accountName}"); diff --git a/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs b/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs index 08dd032c..1e505ae5 100644 --- a/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs +++ b/GhostfolioSidekick/FileImporter/Generic/GenericParser.cs @@ -12,7 +12,7 @@ public GenericParser(IGhostfolioAPI api) : base(api) protected override async Task> ConvertOrders(GenericRecord record, Model.Account account, IEnumerable allRecords) { - var asset = string.IsNullOrWhiteSpace(record.Symbol) ? null : await api.FindSymbolByISIN(record.Symbol); + var asset = string.IsNullOrWhiteSpace(record.Symbol) ? null : await api.FindSymbolByIdentifier(record.Symbol); if (string.IsNullOrWhiteSpace(record.Id)) { diff --git a/GhostfolioSidekick/FileImporter/Nexo/NexoParser.cs b/GhostfolioSidekick/FileImporter/Nexo/NexoParser.cs index e60a4000..45d34956 100644 --- a/GhostfolioSidekick/FileImporter/Nexo/NexoParser.cs +++ b/GhostfolioSidekick/FileImporter/Nexo/NexoParser.cs @@ -2,17 +2,16 @@ using GhostfolioSidekick.Ghostfolio.API; using GhostfolioSidekick.Model; using System.Globalization; -using System.Xml.Linq; namespace GhostfolioSidekick.FileImporter.Nexo { public class NexoParser : CryptoRecordBaseImporter { private Asset[] fiatCoin = new[] { - new Asset(new Currency("EUR"), "EURX", "EURX", null, null, null), - new Asset(new Currency("USD"), "USDX", "USDX", null, null, null), - new Asset(new Currency("EUR"), "EUR", "EUR", null, null, null), - new Asset(new Currency("USD"), "USD", "USD", null, null, null) + new Asset(new Currency("EUR"), "EURX",null, "EURX", null, null, null), + new Asset(new Currency("USD"), "USDX",null, "USDX", null, null, null), + new Asset(new Currency("EUR"), "EUR", null,"EUR", null, null, null), + new Asset(new Currency("USD"), "USD",null, "USD", null, null, null) }; public NexoParser(IGhostfolioAPI api) : base(api) @@ -66,7 +65,7 @@ protected override async Task> ConvertOrders(NexoRecord re return null; } - return await api.FindSymbolByISIN(assetName, x => + return await api.FindSymbolByIdentifier(assetName, x => ParseFindSymbolByISINResult(assetName, assetName, x)); } } @@ -81,7 +80,7 @@ private IEnumerable HandleRecord(NexoRecord record, Activity inputActi return new[] { SetActivity(outputActivity, ActivityType.Receive) }; case "ExchangeDepositedOn": case "Exchange": - return HandleConversion( inputActivity, outputActivity); + return HandleConversion(inputActivity, outputActivity); case "Interest": case "FixedTermInterest": // return new[] { SetActivity(outputActivity, ActivityType.Interest) }; // Staking rewards are not yet supported diff --git a/GhostfolioSidekick/FileImporter/ScalableCaptial/ScalableCapitalParser.cs b/GhostfolioSidekick/FileImporter/ScalableCaptial/ScalableCapitalParser.cs index 5c8b7405..b83c383a 100644 --- a/GhostfolioSidekick/FileImporter/ScalableCaptial/ScalableCapitalParser.cs +++ b/GhostfolioSidekick/FileImporter/ScalableCaptial/ScalableCapitalParser.cs @@ -119,7 +119,7 @@ static string DetermineKey(BaaderBankRKKRecord x) return null; } - var asset = await api.FindSymbolByISIN(record.Isin.Replace("ISIN ", string.Empty)); + var asset = await api.FindSymbolByIdentifier(record.Isin.Replace("ISIN ", string.Empty)); var quantity = decimal.Parse(record.Quantity.Replace("STK ", string.Empty), GetCultureForParsingNumbers()); var unitPrice = record.UnitPrice.GetValueOrDefault() / quantity; @@ -138,7 +138,7 @@ static string DetermineKey(BaaderBankRKKRecord x) private async Task ConvertToOrder(BaaderBankWUMRecord record, ConcurrentDictionary rkkRecords) { - var asset = await api.FindSymbolByISIN(record.Isin); + var asset = await api.FindSymbolByIdentifier(record.Isin); var fee = FindFeeRecord(rkkRecords, record.Reference); diff --git a/GhostfolioSidekick/FileImporter/Trading212/Trading212Parser.cs b/GhostfolioSidekick/FileImporter/Trading212/Trading212Parser.cs index 6a6afeb4..d4a0a6ff 100644 --- a/GhostfolioSidekick/FileImporter/Trading212/Trading212Parser.cs +++ b/GhostfolioSidekick/FileImporter/Trading212/Trading212Parser.cs @@ -19,7 +19,7 @@ protected override async Task> ConvertOrders(Trading212Rec return Array.Empty(); } - var asset = string.IsNullOrWhiteSpace(record.ISIN) ? null : await api.FindSymbolByISIN(record.ISIN); + var asset = string.IsNullOrWhiteSpace(record.ISIN) ? null : await api.FindSymbolByIdentifier(record.ISIN); if (string.IsNullOrWhiteSpace(record.Id)) { diff --git a/GhostfolioSidekick/Ghostfolio/API/Contract/Asset.cs b/GhostfolioSidekick/Ghostfolio/API/Contract/Asset.cs index 5e1d3957..76574955 100644 --- a/GhostfolioSidekick/Ghostfolio/API/Contract/Asset.cs +++ b/GhostfolioSidekick/Ghostfolio/API/Contract/Asset.cs @@ -1,17 +1,19 @@ namespace GhostfolioSidekick.Ghostfolio.API.Contract { - public class Asset - { - public string Currency { get; set; } + public class Asset + { + public string Currency { get; set; } - public string Symbol { get; set; } + public string Symbol { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public string DataSource { get; set; } + public string DataSource { get; set; } - public string AssetSubClass { get; set; } + public string AssetSubClass { get; set; } - public string AssetClass { get; set; } - } + public string AssetClass { get; set; } + + public string ISIN { get; set; } + } } \ No newline at end of file diff --git a/GhostfolioSidekick/Ghostfolio/API/Contract/SymbolProfile.cs b/GhostfolioSidekick/Ghostfolio/API/Contract/SymbolProfile.cs index 409288c8..0d56b5a8 100644 --- a/GhostfolioSidekick/Ghostfolio/API/Contract/SymbolProfile.cs +++ b/GhostfolioSidekick/Ghostfolio/API/Contract/SymbolProfile.cs @@ -1,17 +1,19 @@ namespace GhostfolioSidekick.Ghostfolio.API.Contract { - public class SymbolProfile - { - public string Currency { get; set; } + public class SymbolProfile + { + public string Currency { get; set; } - public string Symbol { get; set; } + public string Symbol { get; set; } - public string DataSource { get; set; } + public string DataSource { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public string AssetSubClass { get; set; } + public string AssetSubClass { get; set; } - public string AssetClass { get; set; } - } + public string AssetClass { get; set; } + + public string ISIN { get; set; } + } } \ No newline at end of file diff --git a/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs b/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs index de9dbc52..b2202583 100644 --- a/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs +++ b/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs @@ -144,7 +144,7 @@ private decimal GetBalance(Balance balance) return new Money(asset.Currency, marketData.MarketPrice, date); } - public async Task FindSymbolByISIN(string? identifier, Func, Model.Asset?> selector) + public async Task FindSymbolByIdentifier(string? identifier, Func, Model.Asset?> selector) { identifier = mapper.MapSymbol(identifier); @@ -194,9 +194,8 @@ private decimal GetBalance(Balance balance) return market.MarketData.Where(x => !benchmarks.Any(y => y.Symbol == x.Symbol)).Select(x => ContractToModelMapper.MapMarketDataInfo(x)); } - public async Task DeleteMarketData(Model.MarketDataInfo marketData) + public async Task DeleteSymbol(Model.MarketDataInfo marketData) { - // https://ghostfolio.vincentberkien.nl/api/v1/admin/profile-data/MANUAL/385c8b0f-cfba-4c9c-a6e9-468a68536d0f var r = await restCall.DoRestDelete($"api/v1/admin/profile-data/{marketData.DataSource}/{marketData.Symbol}"); if (!r.IsSuccessStatusCode) { @@ -206,6 +205,29 @@ public async Task DeleteMarketData(Model.MarketDataInfo marketData) logger.LogInformation($"Deleted symbol {marketData.Symbol}"); } + public async Task CreateManualSymbol(Model.Asset asset) + { + var o = new JObject + { + ["symbol"] = asset.Symbol, + ["isin"] = asset.ISIN, + ["name"] = asset.Name, + ["assetclass"] = asset.AssetClass, + ["assetsubclass"] = asset.AssetSubClass, + ["currency"] = asset.Currency.Symbol, + ["datasource"] = asset.DataSource + }; + var res = o.ToString(); + + var r = await restCall.DoRestPost($"api/v1/admin/profile-data/{asset.DataSource}/{asset.Symbol}", res); + if (!r.IsSuccessStatusCode) + { + throw new NotSupportedException($"Creation failed {asset.Symbol}"); + } + + logger.LogInformation($"Created symbol {asset.Symbol}"); + } + public async Task GetMarketData(Model.MarketDataInfo marketDataInfo) { var content = await restCall.DoRestGet($"api/v1/admin/market-data/{marketDataInfo.DataSource}/{marketDataInfo.Symbol}", CacheDuration.Short()); @@ -393,5 +415,6 @@ private bool AreEquals(Contract.Activity fo, RawActivity eo) return asset; } + } } diff --git a/GhostfolioSidekick/Ghostfolio/API/IGhostfolioAPI.cs b/GhostfolioSidekick/Ghostfolio/API/IGhostfolioAPI.cs index 273363c0..9e5ea5ce 100644 --- a/GhostfolioSidekick/Ghostfolio/API/IGhostfolioAPI.cs +++ b/GhostfolioSidekick/Ghostfolio/API/IGhostfolioAPI.cs @@ -4,7 +4,7 @@ namespace GhostfolioSidekick.Ghostfolio.API { public interface IGhostfolioAPI { - Task FindSymbolByISIN(string? identifier, Func, Asset?> selector = null); + Task FindSymbolByIdentifier(string? identifier, Func, Asset?> selector = null); Task GetConvertedPrice(Money money, Currency targetCurrency, DateTime date); @@ -16,10 +16,12 @@ public interface IGhostfolioAPI Task> GetMarketDataInfo(); - Task DeleteMarketData(MarketDataInfo marketData); - Task GetMarketData(MarketDataInfo marketDataInfo); Task UpdateMarketData(MarketData marketData); + + Task DeleteSymbol(MarketDataInfo marketData); + + Task CreateManualSymbol(Asset asset); } } diff --git a/GhostfolioSidekick/Ghostfolio/API/Mapper/ContractToModelMapper.cs b/GhostfolioSidekick/Ghostfolio/API/Mapper/ContractToModelMapper.cs index e5dd2f23..3d4b03fe 100644 --- a/GhostfolioSidekick/Ghostfolio/API/Mapper/ContractToModelMapper.cs +++ b/GhostfolioSidekick/Ghostfolio/API/Mapper/ContractToModelMapper.cs @@ -45,6 +45,7 @@ private static Model.Asset ParseSymbolProfile(Contract.SymbolProfile symbolProfi return new Model.Asset( CurrencyHelper.ParseCurrency(symbolProfile.Currency), symbolProfile.Symbol, + symbolProfile.ISIN, symbolProfile.Name, symbolProfile.DataSource, symbolProfile.AssetSubClass, @@ -56,6 +57,7 @@ public static Model.Asset ParseSymbolProfile(Contract.Asset symbolProfile) return new Model.Asset( CurrencyHelper.ParseCurrency(symbolProfile.Currency), symbolProfile.Symbol, + symbolProfile.ISIN, symbolProfile.Name, symbolProfile.DataSource, symbolProfile.AssetSubClass, diff --git a/GhostfolioSidekick/MarketDataMaintainer/MarketDataMaintainerTask.cs b/GhostfolioSidekick/MarketDataMaintainer/MarketDataMaintainerTask.cs index 262b2cf7..7c766cbe 100644 --- a/GhostfolioSidekick/MarketDataMaintainer/MarketDataMaintainerTask.cs +++ b/GhostfolioSidekick/MarketDataMaintainer/MarketDataMaintainerTask.cs @@ -1,6 +1,7 @@ using GhostfolioSidekick.Configuration; using GhostfolioSidekick.FileImporter; using GhostfolioSidekick.Ghostfolio.API; +using GhostfolioSidekick.Model; using Microsoft.Extensions.Logging; namespace GhostfolioSidekick.MarketDataMaintainer @@ -30,7 +31,8 @@ public async Task DoWork() { logger.LogInformation($"{nameof(MarketDataMaintainerTask)} Starting to do work"); - await DeletUnusedSymbols(); + await DeleteUnusedSymbols(); + await AddManualSymbols(); await SetTrackingInsightOnSymbols(); logger.LogInformation($"{nameof(MarketDataMaintainerTask)} Done"); @@ -57,16 +59,46 @@ private async Task SetTrackingInsightOnSymbols() } } - private async Task DeletUnusedSymbols() + private async Task DeleteUnusedSymbols() { var marketDataList = await api.GetMarketDataInfo(); foreach (var marketData in marketDataList) { if (marketData.ActivitiesCount == 0) { - await api.DeleteMarketData(marketData); + await api.DeleteSymbol(marketData); } } } + + private async Task AddManualSymbols() + { + var symbolConfigurations = configurationInstance.Symbols; + foreach (var symbolConfiguration in symbolConfigurations) + { + var manualSymbolConfiguration = symbolConfiguration.ManualSymbolConfiguration; + if (manualSymbolConfiguration == null) + { + continue; + } + + var symbol = await api.FindSymbolByIdentifier(symbolConfiguration.Symbol); + if (symbol == null) + { + await api.CreateManualSymbol(new Asset + { + AssetClass = manualSymbolConfiguration.AssetClass, + AssetSubClass = manualSymbolConfiguration.AssetSubClass, + Currency = CurrencyHelper.ParseCurrency(manualSymbolConfiguration.Currency), + DataSource = "MANUAL", + Name = manualSymbolConfiguration.Name, + Symbol = symbolConfiguration.Symbol, + ISIN = manualSymbolConfiguration.ISIN + }); + } + + // TODO: update on difference??? + } + } } } \ No newline at end of file diff --git a/GhostfolioSidekick/Model/Asset.cs b/GhostfolioSidekick/Model/Asset.cs index 1570a757..d3525f05 100644 --- a/GhostfolioSidekick/Model/Asset.cs +++ b/GhostfolioSidekick/Model/Asset.cs @@ -5,10 +5,18 @@ public class Asset public Asset() { } - public Asset(Currency currency, string symbol, string name, string dataSource, string assetSubClass, string assetClass) + public Asset( + Currency currency, + string symbol, + string isin, + string name, + string dataSource, + string assetSubClass, + string assetClass) { Currency = currency; Symbol = symbol; + ISIN = isin; Name = name; DataSource = dataSource; AssetSubClass = assetSubClass; @@ -26,5 +34,7 @@ public Asset(Currency currency, string symbol, string name, string dataSource, s public string AssetSubClass { get; set; } public string AssetClass { get; set; } + + public string ISIN { get; set; } } } \ No newline at end of file