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

Modify JustTrade PDF-Importer to support cryptocurrency transaction #3523

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,37 @@
PDFBox Version: 1.8.17
Portfolio Performance Version: 0.61.0
-----------------------------------------
Herr justTRADE
Felix Mustermann Ein Service der Sutor Bank
Strasse 19 JT Technologies GmbH
36000 Stadt Kaiserhofstraße 1660313 Frankfurt am Main
Email: service@justtrade.com
Kundennummer: 87156 Web: www.justtrade.com
ABRECHNUNG KRYPTOHANDEL
Sehr geehrter Herr Felix Mustermann,
dieses Dokument ist eine Bestätigung Ihrer Kryptotransaktion.
Bei Fragen oder im Falle einer fehlerhaften Abrechnung wenden Sie sich bitte an den justTRADE - Kundenservice. Sie
finden die Kontaktdetails im Briefkopf dieser Abrechnung.
Der Gegenwert der Transaktion wird per Valutatag gebucht.
Dieses Dokument wurde maschinell erstellt und wird nicht unterschrieben.
Produktbezeichnung - Stellar
Kennung: XLM
Währung: EUR
Handels- / Ausführungsplatz: Sutor Bank / B4C Markets
Handelsreferenz: 61ccf74741c06f001232ead6
Orderausführung Datum / Zeit: 30. Dezember 2021 01:03:19
Valutatag Datum: 31. Dezember 2021
Transaktionsart: Kauf
Kurs: €0,2352
Stück / Nominale: 1.200
Kurswert: €282,23
Ausmachender Betrag: €282,23
Bitte beachten Sie, dass Gewinne aus Geschäften mit Kryptowährungen im Gegensatz zu Gewinnen aus
Wertpapiergeschäften nicht der Abgeltungssteuer unterliegen und nach einem Jahr Haltedauer steuerfrei sind. Die
bei einer kürzeren Haltedauer anfallenden Steuern sind von Ihnen im Rahmen der Einkommenssteuererklärung zu
deklarieren und an das zuständige Finanzamt abzuführen. Sprechen Sie hierzu ggf. mit Ihrem Steuerberater.
MAX HEINR. SUTOR OHG TELEFON: 040-8090685-0 GESCHÄFTSLEITUNG FINANZAMT HAMBURG AMTSGERICHT HAMBURG
HERMANNSTRASSE 46 TELEFAX: 040-8090685-810 ROBERT FREITAG STNR: 2761000074 HRA 25379
20095 HAMBURG THOMAS MEIER UST-IDNR: DE155617009
POSTFACH 11 33 37 INFO@SUTORBANK.DE BANKLEITZAHL 202 308 00
20433 HAMBURG WWW.SUTORBANK.DE BIC CODE: MHSBDEHBXXX
@@ -0,0 +1,37 @@
PDF Autor: ''
PDFBox Version: 1.8.16
-----------------------------------------
Herr justTRADE
Max Mustermann Ein Service der Sutor Bank
Musterstraße 999 JT Technologies GmbH
99999 Musterstadt Kaiserhofstraße 1660313 Frankfurt am Main
Email: service@justtrade.com
Kundennummer: 547190 Web: www.justtrade.com
ABRECHNUNG KRYPTOHANDEL
Sehr geehrter Herr Max Mustermann,
dieses Dokument ist eine Bestätigung Ihrer Kryptotransaktion.
Bei Fragen oder im Falle einer fehlerhaften Abrechnung wenden Sie sich bitte an den justTRADE - Kundenservice. Sie
finden die Kontaktdetails im Briefkopf dieser Abrechnung.
Der Gegenwert der Transaktion wird per Valutatag gebucht.
Dieses Dokument wurde maschinell erstellt und wird nicht unterschrieben.
Produktbezeichnung - Bitcoin
Kennung: BTC
Währung: EUR
Handels- / Ausführungsplatz: Sutor Bank
Handelsreferenz: 602745d566400f001a9eea76
Orderausführung Datum / Zeit: 13. Februar 2021 04:21:59
Valutatag Datum: 15. Februar 2021
Transaktionsart: Verkauf
Kurs: €39.383,4900
Stück / Nominale: 0,031
Kurswert: €1.220,89
Ausmachender Betrag: €1.220,89
Bitte beachten Sie, dass Gewinne aus Geschäften mit Kryptowährungen im Gegensatz zu Gewinnen aus
Wertpapiergeschäften nicht der Abgeltungssteuer unterliegen und nach einem Jahr Haltedauer steuerfrei sind. Die
bei einer kürzeren Haltedauer anfallenden Steuern sind von Ihnen im Rahmen der Einkommenssteuererklärung zu
deklarieren und an das zuständige Finanzamt abzuführen. Sprechen Sie hierzu ggf. mit Ihrem Steuerberater.
MAX HEINR. SUTOR OHG TELEFON: 040-8090685-0 GESCHÄFTSLEITUNG FINANZAMT HAMBURG AMTSGERICHT HAMBURG
HERMANNSTRASSE 46 TELEFAX: 040-8090685-810 ROBERT FREITAG STNR: 2761000074 HRA 25379
20095 HAMBURG THOMAS MEIER UST-IDNR: DE155617009
POSTFACH 11 33 37 INFO@SUTORBANK.DE BANKLEITZAHL 202 308 00
20433 HAMBURG WWW.SUTORBANK.DE BIC CODE: MHSBDEHBXXX
Expand Up @@ -12,6 +12,8 @@
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasAmount;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasCurrencyCode;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasDate;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFeed;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFeedProperty;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFees;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasGrossValue;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasIsin;
Expand All @@ -26,6 +28,7 @@
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.sale;
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -49,10 +52,31 @@
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.online.impl.CoinGeckoQuoteFeed;

@SuppressWarnings("nls")
public class JustTradePDFExtractorTest
{
JustTradePDFExtractor extractor = new JustTradePDFExtractor(new Client())
{
@Override
protected CoinGeckoQuoteFeed lookupFeed()
{
// mock the list of coins to avoid remote call
return new CoinGeckoQuoteFeed()
{
@Override
public synchronized List<Coin> getCoins() throws IOException
{
return List.of( //
new Coin("bitcoin", "BTC", "Bitcoin"), //
new Coin("ethereum", "ETH", "Ethereum"), //
new Coin("stellar", "XLM", "Stellar"));
}
};
}
};

@Test
public void testWertpapierKauf01()
{
Expand Down Expand Up @@ -4186,4 +4210,62 @@ public void testDepotauszug05()
assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE),
is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00))));
}

@Test
public void testCryptoKauf01()
{
List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "CryptoKauf01.txt"), errors);

assertThat(countSecurities(results), is(1L));
assertThat(countBuySell(results), is(1L));
assertThat(countAccountTransactions(results), is(0L));
assertThat(results.size(), is(2));
new AssertImportActions().check(results, CurrencyUnit.EUR);

// check security
assertThat(results, hasItem(security( //
hasIsin(null), hasWkn(null), hasTicker("XLM"), //
hasName("Stellar"), //
hasCurrencyCode("EUR"), //
hasFeed(CoinGeckoQuoteFeed.ID), //
hasFeedProperty(CoinGeckoQuoteFeed.COINGECKO_COIN_ID, "stellar"))));

// check buy sell transaction
assertThat(results, hasItem(purchase( //
hasDate("2021-12-30T01:03:19"), hasShares(1200), //
hasSource("CryptoKauf01.txt"), hasNote("Handelsreferenz: 61ccf74741c06f001232ead6"), //
hasAmount("EUR", 282.23), hasGrossValue("EUR", 282.23), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}

@Test
public void testCryptoVerkauf01()
{
List<Exception> errors = new ArrayList<>();

List<Item> results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "CryptoVerkauf01.txt"), errors);

assertThat(countSecurities(results), is(1L));
assertThat(countBuySell(results), is(1L));
assertThat(countAccountTransactions(results), is(0L));
assertThat(results.size(), is(2));
new AssertImportActions().check(results, CurrencyUnit.EUR);

// check security
assertThat(results, hasItem(security( //
hasIsin(null), hasWkn(null), hasTicker("BTC"), //
hasName("Bitcoin"), //
hasCurrencyCode("EUR"), //
hasFeed(CoinGeckoQuoteFeed.ID), //
hasFeedProperty(CoinGeckoQuoteFeed.COINGECKO_COIN_ID, "bitcoin"))));

// check buy sell transaction
assertThat(results, hasItem(sale( //
hasDate("2021-02-13T04:21:59"), hasShares(0.031), //
hasSource("CryptoVerkauf01.txt"), hasNote("Handelsreferenz: 602745d566400f001a9eea76"), //
hasAmount("EUR", 1220.89), hasGrossValue("EUR", 1220.89), //
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00))));
}
}
Expand Up @@ -216,7 +216,7 @@ private Optional<Coin> lookupCoin(Map<String, String> values)
{
try
{
String tickerSymbol = values.get("tickerSymbol"); //$NON-NLS-1$
String tickerSymbol = values.get("tickerSymbol").trim(); //$NON-NLS-1$

var coins = lookupFeed().getCoins();
return coins.stream().filter(c -> c.getSymbol().equalsIgnoreCase(tickerSymbol)).findAny();
Expand Down
Expand Up @@ -3,6 +3,7 @@
import static name.abuchen.portfolio.datatransfer.ExtractorUtils.checkAndSetFee;

import static name.abuchen.portfolio.util.TextUtil.stripBlanks;
import static name.abuchen.portfolio.util.TextUtil.trim;

import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Block;
Expand Down Expand Up @@ -32,6 +33,7 @@ public JustTradePDFExtractor(Client client)
addBankIdentifier("SUTOR BANK");

addBuySellTransaction();
addBuySellCryptoTransaction();
addDividendeTransaction();
addAccountStatementTransaction();
}
Expand All @@ -44,7 +46,7 @@ public String getLabel()

private void addBuySellTransaction()
{
DocumentType type = new DocumentType("((Transaktionsart:|Wertpapier Abrechnung) (Kauf|Verkauf)|F.lligkeit\\/Verfall)");
DocumentType type = new DocumentType("((Transaktionsart:|Wertpapier Abrechnung) (Kauf|Verkauf)|F.lligkeit\\/Verfall)", "ABRECHNUNG KRYPTOHANDEL");
this.addDocumentTyp(type);

Transaction<BuySellEntry> pdfTransaction = new Transaction<>();
Expand Down Expand Up @@ -222,6 +224,97 @@ private void addBuySellTransaction()
addFeesSectionsTransaction(pdfTransaction, type);
}

private void addBuySellCryptoTransaction()
{
DocumentType type = new DocumentType("ABRECHNUNG KRYPTOHANDEL");
this.addDocumentTyp(type);

Transaction<BuySellEntry> pdfTransaction = new Transaction<>();
pdfTransaction.subject(() -> {
BuySellEntry entry = new BuySellEntry();
entry.setType(PortfolioTransaction.Type.BUY);
return entry;
});

Block firstRelevantLine = new Block("^ABRECHNUNG KRYPTOHANDEL$");
type.addBlock(firstRelevantLine);
firstRelevantLine.set(pdfTransaction);

pdfTransaction
// If type is "Verkauf" change from BUY to SELL
.section("type").optional()
.match("^Transaktionsart: (?<type>(Kauf|Verkauf))$")
.assign((t, v) -> {
if ("Verkauf".equals(v.get("type")))
t.setType(PortfolioTransaction.Type.SELL);
})

// @formatter:off
// Produktbezeichnung - Bitcoin
// Kennung: BTC
// Währung: EUR
// @formatter:on
.section("name", "tickerSymbol", "currency")
.match("^Produktbezeichnung \\- (?<name>.*)$")
.match("^Kennung: (?<tickerSymbol>[A-Z]*)$")
.match("^W.hrung: (?<currency>[\\w]{3})$")
.assign((t, v) -> t.setSecurity(getOrCreateCryptoCurrency(v)))

// @formatter:off
// Stück / Nominale: 0,031
// @formatter:on
.section("shares")
.match("^St.ck \\/ Nominale: (?<shares>[\\.,\\d]+)$")
.assign((t, v) -> t.setShares(asShares(v.get("shares"))))

// @formatter:off
// Orderausführung Datum / Zeit: 13. Februar 2021 04:21:59
// @formatter:on
.section("time").optional()
.match("^.* (?<time>[\\d]{2}:[\\d]{2}:[\\d]{2}).*$")
.assign((t, v) -> type.getCurrentContext().put("time", v.get("time")))

.oneOf(
// @formatter:off
// Orderausführung Datum / Zeit: 13. Februar 2021 04:21:59
// @formatter:on
section -> section
.attributes("date")
.match("^Orderausf.hrung Datum \\/ Zeit: (?<date>[\\d]{1,2}\\. .* [\\d]{4}).*$")
.assign((t, v) -> {
if (type.getCurrentContext().get("time") != null)
t.setDate(asDate(v.get("date").replace("Mrz", "Mär"), type.getCurrentContext().get("time")));
else
t.setDate(asDate(v.get("date").replace("Mrz", "Mär")));
})
)

.oneOf(
// @formatter:off
// Ausmachender Betrag: €1.220,89
// @formatter:on
section -> section
.attributes("currency", "amount")
.match("^Ausmachender Betrag: (?<currency>\\p{Sc})(?<amount>[\\.,\\d]+)$")
.assign((t, v) -> {
t.setCurrencyCode(asCurrencyCode(v.get("currency")));
t.setAmount(asAmount(v.get("amount")));
})
)

// @formatter:off
// Handelsreferenz: 61ccf74741c06f001232ead6
// @formatter:on
.section("note").optional()
.match("^(?<note>Handelsreferenz: .*)$")
.assign((t, v) -> t.setNote(trim(v.get("note"))))

.wrap(BuySellEntryItem::new);

addTaxesSectionsTransaction(pdfTransaction, type);
addFeesSectionsTransaction(pdfTransaction, type);
}

private void addDividendeTransaction()
{
DocumentType type = new DocumentType("Dividende");
Expand Down