Skip to content

Commit

Permalink
Add coinbase (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
VibeNL authored Aug 28, 2023
1 parent 7123b59 commit 8e6e37b
Show file tree
Hide file tree
Showing 30 changed files with 9,182 additions and 112 deletions.
10 changes: 6 additions & 4 deletions ConsoleHelper/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using GhostfolioSidekick;
using GhostfolioSidekick.FileImporter;
using GhostfolioSidekick.FileImporter.Coinbase;
using GhostfolioSidekick.FileImporter.DeGiro;
using GhostfolioSidekick.FileImporter.ScalableCaptial;
using GhostfolioSidekick.FileImporter.Trading212;
Expand All @@ -23,14 +24,15 @@ static void Main(string[] args)
}

var cs = new ConfigurationSettings();
MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions { });
GhostfolioAPI api = new GhostfolioAPI(memoryCache, logger);
MemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions { });
GhostfolioAPI api = new GhostfolioAPI(memoryCache, logger);
var t = new FileImporterTask(logger, api, cs, new IFileImporter[] {
new ScalableCapitalParser(api),
new DeGiroParser(api),
new Trading212Parser(api)
new Trading212Parser(api),
new CoinbaseParser(api)
});
t.DoWork().Wait();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using AutoFixture;
using FluentAssertions;
using GhostfolioSidekick.FileImporter.Coinbase;
using GhostfolioSidekick.Ghostfolio.API;
using Moq;

namespace GhostfolioSidekick.UnitTests.FileImporter.Coinbase
{
public class CoinbaseParserTests
{
readonly Mock<IGhostfolioAPI> api;

public CoinbaseParserTests()
{
api = new Mock<IGhostfolioAPI>();
}

[Fact]
public async Task CanConvertOrders_Example1_True()
{
// Arrange
var parser = new CoinbaseParser(api.Object);

// Act
var canParse = await parser.CanConvertOrders(new[] { "./FileImporter/TestFiles/Coinbase/Example1/Example1.csv" });

// Assert
canParse.Should().BeTrue();
}

[Fact]
public async Task ConvertToOrders_Example1_Converted()
{
// Arrange
var parser = new CoinbaseParser(api.Object);
var fixture = new Fixture();

var asset1 = fixture.Build<Asset>().With(x => x.Currency, "EUR").Create();

var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("Bitcoin", It.IsAny<Func<IEnumerable<Asset>, Asset>>())).ReturnsAsync(asset1);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Coinbase/Example1/Example1.csv" });

// Assert
orders.Should().BeEquivalentTo(new[]
{
new Order {
AccountId = account.Id,
Asset = asset1,
Comment = "Transaction Reference: [SELL_BTC_638280626190000000]",
Currency = asset1.Currency,
FeeCurrency = asset1.Currency,
Date = new DateTime(2023,08,19,17,23,39, DateTimeKind.Utc),
Fee = 0,
Quantity = 0.00205323M,
Type = OrderType.SELL,
UnitPrice = 24073.28M,
ReferenceCode = "SELL_BTC_638280626190000000"
}});
}

[Fact]
public async Task ConvertToOrders_Example2_Converted()
{
// Arrange
var parser = new CoinbaseParser(api.Object);
var fixture = new Fixture();

var asset1 = fixture.Build<Asset>().With(x => x.Currency, "EUR").Create();
var asset2 = fixture.Build<Asset>().With(x => x.Currency, "EUR").Create();
var asset3 = fixture.Build<Asset>().With(x => x.Currency, "EUR").Create();

var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("Bitcoin", It.IsAny<Func<IEnumerable<Asset>, Asset>>())).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("Ethereum", It.IsAny<Func<IEnumerable<Asset>, Asset>>())).ReturnsAsync(asset2);
api.Setup(x => x.FindSymbolByISIN("Cosmos", It.IsAny<Func<IEnumerable<Asset>, Asset>>())).ReturnsAsync(asset3);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Coinbase/Example2/Example2.csv" });

// Assert
orders.Should().BeEquivalentTo(new[]
{
new Order {
AccountId = account.Id,
Asset = asset1, // BTC
Comment = "Transaction Reference: [SELL_BTC_638280626190000000]",
Currency = asset1.Currency,
FeeCurrency = asset1.Currency,
Date = new DateTime(2023,08,19,17,23,39, DateTimeKind.Utc),
Fee = 0,
Quantity = 0.00205323M,
Type = OrderType.SELL,
UnitPrice = 24073.28M,
ReferenceCode = "SELL_BTC_638280626190000000"
},
new Order {
AccountId = account.Id,
Asset = asset2, // ETH
Comment = "Transaction Reference: [BUY_ETH_638175603400000000]",
Currency = asset2.Currency,
FeeCurrency = asset2.Currency,
Date = new DateTime(2023,04,20,4,5,40, DateTimeKind.Utc),
Fee = 0.990000M,
Quantity = 0.00213232M,
Type = OrderType.BUY,
UnitPrice =1810.23M,
ReferenceCode = "BUY_ETH_638175603400000000"
},
new Order {
AccountId = account.Id,
Asset = asset2, // ETH
Comment = "Transaction Reference: [BUY_ETH_638177414840000000]",
Currency = asset2.Currency,
FeeCurrency = asset2.Currency,
Date = new DateTime(2023,4,22,6,24,44, DateTimeKind.Utc),
Fee = 0,
Quantity = 1.0e-08M,
Type = OrderType.BUY,
UnitPrice =1689.10M,
ReferenceCode = "BUY_ETH_638177414840000000"
},
new Order {
AccountId = account.Id,
Asset = asset2, // ETH -> ATOM
Comment = "Transaction Reference: [SELL_ETH_638181135820000000]",
Currency = asset2.Currency,
FeeCurrency = asset2.Currency,
Date = new DateTime(2023,04,26,13,46,22, DateTimeKind.Utc),
Fee = 0.020000M,
Quantity = 0.00052203M,
Type = OrderType.SELL,
UnitPrice = 1762.35M,
ReferenceCode = "SELL_ETH_638181135820000000"
},
new Order {
AccountId = account.Id,
Asset = asset3, // ETH -> ATOM
Comment = "Transaction Reference: [BUY_ATOM_638181135820000000]",
Currency = asset3.Currency,
FeeCurrency = asset3.Currency,
Date = new DateTime(2023,04,26,13,46,22, DateTimeKind.Utc),
Fee = 0,
Quantity = 0.087842M,
Type = OrderType.BUY,
UnitPrice = 10.473344988729764804990778898M,
ReferenceCode = "BUY_ATOM_638181135820000000"
}});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task ConvertToOrders_TestFileSingleOrder_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example1/TestFileSingleOrder.csv" });
Expand Down Expand Up @@ -86,8 +86,8 @@ public async Task ConvertToOrders_TestFileMuitpleOrders_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09")).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("NL0009690239")).ReturnsAsync(asset2);
api.Setup(x => x.FindSymbolByISIN("IE00B3XXRP09", null)).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("NL0009690239", null)).ReturnsAsync(asset2);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/DeGiro/Example3/TestFileMultipleOrders.csv" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public async Task LoadAccounts()
await task.DoWork();

// Assert
testImporter.Verify(x => x.ConvertToOrders("Coinbase", It.Is<IEnumerable<string>>(y => y.Count() == 2)), Times.Once);
testImporter.Verify(x => x.ConvertToOrders("DeGiro", It.Is<IEnumerable<string>>(y => y.Count() == 3)), Times.Once);
testImporter.Verify(x => x.ConvertToOrders("ScalableCapital", It.Is<IEnumerable<string>>(y => y.Count() == 5)), Times.Once);
testImporter.Verify(x => x.ConvertToOrders("Trading212", It.Is<IEnumerable<string>>(y => y.Count() == 8)), Times.Once);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task ConvertToOrders_Example1_OrderOnly()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/ScalableCapital/Example1/WUMExample1.csv" });
Expand All @@ -70,7 +70,7 @@ public async Task ConvertToOrders_Example1_OrderOnly()
Type = OrderType.BUY,
UnitPrice = 8.685M,
ReferenceCode = "SCALQbWiZnN9DtQ"
} });
} });
}

[Fact]
Expand All @@ -84,7 +84,7 @@ public async Task ConvertToOrders_Example1_DividendOnly()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("US92343V1044")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/ScalableCapital/Example1/RKKExample1.csv" });
Expand All @@ -102,7 +102,7 @@ public async Task ConvertToOrders_Example1_DividendOnly()
Type = OrderType.DIVIDEND,
UnitPrice = 0.5057142857142857142857142857M,
ReferenceCode = "WWEK 16100100"
} });
} });
}

[Fact]
Expand All @@ -117,8 +117,8 @@ public async Task ConvertToOrders_Example1_Both()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95")).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("US92343V1044")).ReturnsAsync(asset2);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset2);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] {
Expand All @@ -138,8 +138,8 @@ public async Task ConvertToOrders_Example1_Both()
Quantity = 5,
Type = OrderType.BUY,
UnitPrice = 8.685M,
ReferenceCode = "SCALQbWiZnN9DtQ"
},
ReferenceCode = "SCALQbWiZnN9DtQ"
},
new Order {
AccountId = account.Id,
Asset = asset2,
Expand All @@ -151,8 +151,8 @@ public async Task ConvertToOrders_Example1_Both()
Quantity = 14,
Type = OrderType.DIVIDEND,
UnitPrice = 0.5057142857142857142857142857M,
ReferenceCode = "WWEK 16100100"
} });
ReferenceCode = "WWEK 16100100"
} });
}

[Fact]
Expand All @@ -167,8 +167,8 @@ public async Task ConvertToOrders_Example2_NotDuplicateFeesAndDividend()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95")).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("US92343V1044")).ReturnsAsync(asset2);
api.Setup(x => x.FindSymbolByISIN("IE00077FRP95", null)).ReturnsAsync(asset1);
api.Setup(x => x.FindSymbolByISIN("US92343V1044", null)).ReturnsAsync(asset2);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] {
Expand All @@ -190,7 +190,7 @@ public async Task ConvertToOrders_Example2_NotDuplicateFeesAndDividend()
Type = OrderType.BUY,
UnitPrice = 8.685M,
ReferenceCode = "SCALQbWiZnN9DtQ"
},
},
new Order {
AccountId = account.Id,
Asset = asset2,
Expand All @@ -202,8 +202,8 @@ public async Task ConvertToOrders_Example2_NotDuplicateFeesAndDividend()
Quantity = 14,
Type = OrderType.DIVIDEND,
UnitPrice = 0.5057142857142857142857142857M,
ReferenceCode = "WWEK 16100100"
} });
ReferenceCode = "WWEK 16100100"
} });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"You can use this transaction report to inform your likely tax obligations. For US customers, Sells, Converts, Rewards Income, Learning Rewards, and Donations are taxable events. For final tax obligations, please consult your tax advisor."



Transactions
User,some@email.com,0000000000000000000000

Timestamp,Transaction Type,Asset,Quantity Transacted,Spot Price Currency,Spot Price at Transaction,Subtotal,Total (inclusive of fees and/or spread),Fees and/or Spread,Notes
2023-08-19T17:23:39Z,Sell,BTC,0.00205323,EUR,24073.28,"","","",Sent 0.00205323 BTC to 39ADd6pNpeuwjcweLLS5JiJPFjNPJnf4xY
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"You can use this transaction report to inform your likely tax obligations. For US customers, Sells, Converts, Rewards Income, Learning Rewards, and Donations are taxable events. For final tax obligations, please consult your tax advisor."



Transactions
User,some@email.com,0000000000000000000000

Timestamp,Transaction Type,Asset,Quantity Transacted,Spot Price Currency,Spot Price at Transaction,Subtotal,Total (inclusive of fees and/or spread),Fees and/or Spread,Notes
2023-08-19T17:23:39Z,Send,BTC,0.00205323,EUR,24073.28,"","","",Sent 0.00205323 BTC to 39ADd6pNpeuwjcweLLS5JiJPFjNPJnf4xY
2023-04-20T04:05:40Z,Buy,ETH,0.00213232,EUR,1810.23,3.86,4.85,0.990000,Bought 0.00213232 ETH for €4.85 EUR
2023-04-22T06:24:44Z,Receive,ETH,1.0e-08,EUR,1689.10,"","","",Received 0.00000001 ETH from Vweeter Limited
2023-04-26T13:46:22Z,Convert,ETH,0.00052203,EUR,1762.35,0.900000,0.920000,0.020000,Converted 0.00052203 ETH to 0.087842 ATOM
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public async Task ConvertToOrders_TestFileSingleOrder_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("US67066G1040")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example1/TestFileSingleOrder.csv" });
Expand Down Expand Up @@ -71,7 +71,7 @@ public async Task ConvertToOrders_TestFileMultipleOrdersUS_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("US67066G1040")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example2/TestFileMultipleOrdersUS.csv" });
Expand Down Expand Up @@ -117,7 +117,7 @@ public async Task ConvertToOrders_TestFileSingleOrderUK_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("GB0007188757")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("GB0007188757", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example3/TestFileSingleOrderUK.csv" });
Expand Down Expand Up @@ -149,7 +149,7 @@ public async Task ConvertToOrders_TestFileSingleDividend_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("US0378331005")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("US0378331005", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example4/TestFileSingleDividend.csv" });
Expand Down Expand Up @@ -181,7 +181,7 @@ public async Task ConvertToOrders_TestFileSingleOrderUKNativeCurrency_Converted(
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("GB0007188757")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("GB0007188757", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] { "./FileImporter/TestFiles/Trading212/Example5/TestFileSingleOrderUKNativeCurrency.csv" });
Expand Down Expand Up @@ -213,7 +213,7 @@ public async Task ConvertToOrders_TestFileSingleOrderMultipleTimes_Converted()
var account = fixture.Create<Account>();

api.Setup(x => x.GetAccountByName(account.Name)).ReturnsAsync(account);
api.Setup(x => x.FindSymbolByISIN("US67066G1040")).ReturnsAsync(asset);
api.Setup(x => x.FindSymbolByISIN("US67066G1040", null)).ReturnsAsync(asset);

// Act
var orders = await parser.ConvertToOrders(account.Name, new[] {
Expand Down
Loading

0 comments on commit 8e6e37b

Please sign in to comment.