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

Implemented generic parser #27

Merged
merged 6 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<IGhostfolioAPI> api;

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

[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<Asset>().With(x => x.Currency, "USD").Create();
var account = fixture.Create<Account>();

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"
} });
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OrderType,Symbol,Date,Currency,Quantity,UnitPrice,Fee
BUY,US67066G1040,2023-08-07,USD,0.0267001000,453.33,0.02
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
<None Update="FileImporter\TestFiles\DeGiro\Example1\TestFileSingleOrder.csv">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="FileImporter\TestFiles\Generic\Example1\Example1.csv">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="FileImporter\TestFiles\Nexo\Example1\Example1.csv">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
50 changes: 50 additions & 0 deletions GhostfolioSidekick/FileImporter/Generic/GenericParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CsvHelper.Configuration;
using GhostfolioSidekick.Ghostfolio.API;
using System.Globalization;

namespace GhostfolioSidekick.FileImporter.Generic
{
public class GenericParser : RecordBaseImporter<GenericRecord>
{
public GenericParser(IGhostfolioAPI api) : base(api)
{
}

protected override async Task<IEnumerable<Order>> ConvertOrders(GenericRecord record, Account account, IEnumerable<GenericRecord> 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 = ",",
};
}
}
}
24 changes: 24 additions & 0 deletions GhostfolioSidekick/FileImporter/Generic/GenericRecord.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
2 changes: 2 additions & 0 deletions GhostfolioSidekick/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using GhostfolioSidekick.FileImporter;
using GhostfolioSidekick.FileImporter.Coinbase;
using GhostfolioSidekick.FileImporter.DeGiro;
using GhostfolioSidekick.FileImporter.Generic;
using GhostfolioSidekick.FileImporter.Nexo;
using GhostfolioSidekick.FileImporter.ScalableCaptial;
using GhostfolioSidekick.FileImporter.Trading212;
Expand Down Expand Up @@ -46,6 +47,7 @@ static async Task Main(string[] args)
services.AddScoped<IFileImporter, Trading212Parser>();
services.AddScoped<IFileImporter, CoinbaseParser>();
services.AddScoped<IFileImporter, NexoParser>();
services.AddScoped<IFileImporter, GenericParser>();
});

await hostBuilder.RunConsoleAsync();
Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,41 @@ IDENTIFIER,ATOM-USD,Cosmos USD
### Supported formats
| Platform | Source of the files | Buy | Sell | Dividend |
|--|--|--|--|--|
| Generic importer | See below | X | X | X |
| Trading 212 | Export of transaction history | X | X | X |
| De Giro | Export of transaction history | X | - | - |
| Scalable Capital | The CSV files of the Baader bank. Type WUM and RKK | X | X | X |
| Coinbase (Experimental) | Export of transaction history | X | X | - |
| Nexo (Experimental) | Export of transaction history | X | - | - |

#### Generic import format
Beside the supported exchanges and brokers there is also a generic format. This format is only usefull for stocks at the moment, not for cryptocurrency:

| Field | Value(s) |
| ----- | ----- |
| OrderType | BUY ,SELL or DIVIDEND |
| Symbol | The symbol to search
| Date | The date, yyyy-MM-dd |
| Currency | The currency of the unitprice and fee |
| Quantity | The amount of units |
| UnitPrice | The paid price per unit |
| Fee | The total fee paid for the transaction |

##### Example

OrderType,Symbol,Date,Currency,Quantity,UnitPrice,Fee
BUY,US67066G1040,2023-08-07,USD,0.0267001000,453.33,0.02

## Run in Docker
The docker image is: vibenl/ghostfoliosidekick
The docker image is named: vibenl/ghostfoliosidekick

### Settings
| Envs |Description |
|--|--|
|**GHOSTFOLIO_URL** | The endpoint for your ghostfolio instance. |
|**GHOSTFOLIO_ACCESTOKEN** | The token as used to 'login' in the UI |
|**MAPPINGFILE** | (optional) The path to the mapping file containing mapping for identifiers so it can be mapped automatically [Mapping File]() |
|**FileImporterPath** | The path to the files (see [Import Path]()) |
|**MAPPINGFILE** | (optional) The path to the mapping file containing mapping for identifiers so it can be mapped automatically [Mapping File] |
|**FileImporterPath** | The path to the files (see [Import Path]) |

## Contributing

Expand Down
Loading