portfolio-performance / portfolio

Track and evaluate the performance of your investment portfolio across stocks, cryptocurrencies, and other assets.
http://www.portfolio-performance.info
Eclipse Public License 1.0
2.95k stars 605 forks source link

SelfWealth PDF-Importer #2329

Closed flywire closed 3 years ago

flywire commented 3 years ago

One transaction per file. What more can I say?

PDF author: ''
PDFBox Version: 1.8.16
-----------------------------------------
SelfWealth Limited ABN: 52 154 324 428 AFSL 421789 W: www.selfwealth.com.au E: support@selfwealth.com.au
This trade was executed and cleared by OpenMarkets Australia Ltd ABN 38 090 472 012,
AFSL 246 705, Market Particpant of ASX, CHI­X and NSX.
Buy Confirmation
MR JOHN DOE Account Number: 1234567
JOHN DOE A/C Reference No: T20210701123456­-1
1 LONG ROAD Trade Date: 1 Jul 2021
SYDNEY NSW Settlement Date: 5 Jul 2021
2000, AUS Market: ASX
WE HAVE BOUGHT ON YOUR ACCOUNT
Quantity Security Code Security Description Price +1 Consideration Currency
25 UMAX BETA S&P500 YIELDMAX 12.40 $312.50 AUD
Brokerage* $9.50 AUD
Adviser Fee* $0.00 AUD
Net Value $322.00 AUD
GST included in this invoice is $0.86
The confirmation is a tax invoice ­ please retain for tax purposes. If this confirmation does not correspond with your records please contact us immediately at
support@selfwealth.com.au
Settlement Instructions All consideration and any information or documents required by OpenMarkets must be provided to OpenMarkets by 9am AEST on the Settlement
Date. This transaction will be settled from your linked cash account or in accordance with your instructions on the Settlement Date.
Contract Comments
Ex Dividend
* Inclusive of GST
+1 Standard Financial Rounding Applied (if applicable)
This confirmation is provided to you by each of SelfWealth and OpenMarkets. The Brokerage and Adviser fees set out in this confirmation are charged by SelfWealth.
OpenMarkets has not charged you any fees for the above transaction(s). The above transaction(s) and this confirmation are issued subject to the directions, decisions
and requirements of the operator of the relevant Market, ASIC Market Integrity Rules, the operating rules of the relevant Market, and, where relevant, the Clearing
Rules of the relevant Clearing Facility and the Settlement Rules of the relevant Settlement Facility, the customs and usages of the relevant Market and the correction of
errors and omissions. If this confirmation relates to multiple transactions, those transactions may have been completed on ASX or CHI­X.”
Generated At: 5 Jul 2021 16:30:01 PM Page: 1 of 1  

Line 4: Buy Line 6: JOHN DOE A/C Line 7: Trade Date: 1 Jul 2021 Line 11-12: Fields are space delineated except spaces in Security Description Quantity: 25 Security Code: UMAX Security Description: BETA S&P500 YIELDMAX Price +1: 12.40 Consideration: $312.50 Currency: AUD

flywire commented 3 years ago

@Nirus2000 is there a guide how to make these Importers?

Nirus2000 commented 3 years ago

Hallo @flywire Wenn du die PDF-Importer meinst, gibt keinen direkte Anleitung für Importer. Jedoch versuchen wir einen Standard zu implementieren. Am besten schaust du dir hier die PDF-Importer an und hier die testcase zu den jeweiligen Importern.

Als Orientierung für einen einfachen Importer könntest du den Deutsche Bank anschauen mit den passenden TestCases. Für komplexere Importer den FinTech mit diesen TestCases. Die PDF-Importer und TestCases sollte, so den Standard entsprechen.

Grüße Alex

flywire commented 3 years ago

This is a start.

Detail for later: GGT is tax, a refund for business but not personal investors.

package name.abuchen.portfolio.datatransfer.pdf;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;

import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Block;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.DocumentType;
import name.abuchen.portfolio.datatransfer.pdf.PDFParser.Transaction;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.Money;

@SuppressWarnings("nls")
public class SelfWealthPDFExtractor extends AbstractPDFExtractor
{
    public SelfWealthPDFExtractor(Client client)
    {
        super(client);

        addBankIdentifier("SelfWealth"); //$NON-NLS-1$
        addBankIdentifier("SelfWealth Limited ABN: 52 154 324 428 AFSL 421789 W: www.selfwealth.com.au E: support@selfwealth.com.au"); //$NON-NLS-1$

        addBuySellTransaction();
    }

    @Override
    public String getPDFAuthor()
    {
        return ""; //$NON-NLS-1$
    }

    @Override
    public String getLabel()
    {
        return "SelfWealth Limited ABN: 52 154 324 428 AFSL 421789 W: www.selfwealth.com.au E: support@selfwealth.com.au"; //$NON-NLS-1$
    }

    private void addBuySellTransaction()
    {
        DocumentType type = new DocumentType("(Buy|Sell) Confirmation");
        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("^(Buy|Sell) Confirmation$");
        type.addBlock(firstRelevantLine);
        firstRelevantLine.set(pdfTransaction);

        pdfTransaction
                // Is type --> "Sell" change from BUY to SELL
                .section("type").optional()
                .match("^(?<type>Sell) Confirmation$")
                .assign((t, v) -> {
                    if (v.get("type").equals("Sell"))
                    {
                        t.setType(PortfolioTransaction.Type.SELL);
                    }
                })

                // JOHN DOE A/C Reference No: T20210701123456­-1
                .section("note")
                .match(" Reference No: (?<note>.*)$")
                .assign((t, v) -> {
                    t.setNote(asNote(v.get("note")));
                })

                // 1 LONG ROAD Trade Date: 1 Jul 2021
                .section("date")
                .match(" Settlement Date: (?<date>\\d+ \\D{3} \\d{4})$")
                .assign((t, v) -> {
                    if (v.get("time") != null)
                        t.setDate(asDate(v.get("date"), v.get("time")));
                    else
                        t.setDate(asDate(v.get("date")));
                })

                // 25 UMAX BETA S&P500 YIELDMAX 12.40 $312.50 AUD
                .section("shares", "symbol", "name", "quote", "amount", "currency")
                .match("^(?<shares>[.,\\d]+) (?<symbol>[\\D]+)") "name", "quote" \\$(?<amount>[.,\\d]+) (?<currency>[\\w]{3})$")
                .assign((t, v) -> {
                    t.setShares(asShares(v.get("shares")));
                    t.setSecurity(getOrCreateSecurity(v));
                    t.setAmount(asAmount(v.get("amount")));
                    t.setCurrencyCode(asCurrencyCode(v.get("currency")));
                })

                // Brokerage* $9.50 AUD
                .section("brokerage_fee")
                .match("^Brokerage Fee//* //$(?<brokerage_fee>.*) [\\w]{3}$")
                // Adviser Fee* $0.00 AUD
                .section("adviser_fee")
                .match("^Adviser Fee//* //$(?<adviser_fee>.*)$")

                fees = brokerage_fee + adviser_fee
                .assign((t, v) -> {
                    t.setAmount(asAmount(v.get("amount")));
                    t.setCurrencyCode(asCurrencyCode(v.get("currency")));

                // Net Value $322.00 AUD
                .section("amount")
                .match("^GST included in this invoice is //$(?<amount>.*) [\\w]{3}$")
                .assign((t, v) -> {
                    t.setAmount(asAmount(v.get("amount")));
                    t.setCurrencyCode(asCurrencyCode(v.get("currency")));

                // GST included in this invoice is $0.86
                .section("gst")
                .match("^GST included in this invoice is //$(?<gst>.*)$")
                .assign((t, v) -> {
                    t.setAmount(asAmount(v.get("amount")));
                    t.setCurrencyCode(asCurrencyCode(v.get("currency")));

                .wrap(BuySellEntryItem::new);

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

    private <T extends Transaction<?>> void addTaxesSectionsTransaction(T transaction, DocumentType type)
    {
        transaction
                // Kapitalertragsteuer (KESt)  - 9,88 USD - 8,71 EUR
                .section("tax", "currency").optional()
                .match("^Kapitalertragsteuer \\(KESt\\) ([\\s]+)?- [.,\\d]+ [\\w]{3} - (?<tax>[.,\\d]+) (?<currency>[\\w]{3})$")
                .assign((t, v) -> processTaxEntries(t, v, type))
    }

    private <T extends Transaction<?>> void addFeesSectionsTransaction(T transaction, DocumentType type)
    {
        transaction
                // Provision EUR 7,90
                // Provision EUR -7,90
                .section("currency", "fee").optional()
                .match("^Provision (?<currency>[\\w]{3}) ([-])?(?<fee>[.,\\d]+)$")
                .assign((t, v) -> processFeeEntries(t, v, type))
    }

    private void processTaxEntries(Object t, Map<String, String> v, DocumentType type)
    {
        if (t instanceof name.abuchen.portfolio.model.Transaction)
        {
            Money tax = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("tax")));
            PDFExtractorUtils.checkAndSetTax(tax, (name.abuchen.portfolio.model.Transaction) t, type);
        }
        else
        {
            Money tax = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("tax")));
            PDFExtractorUtils.checkAndSetTax(tax, ((name.abuchen.portfolio.model.BuySellEntry) t).getPortfolioTransaction(), type);
        }
    }

    private void processFeeEntries(Object t, Map<String, String> v, DocumentType type)
    {
        if (t instanceof name.abuchen.portfolio.model.Transaction)
        {
            Money fee = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("fee")));
            PDFExtractorUtils.checkAndSetFee(fee, 
                            (name.abuchen.portfolio.model.Transaction) t, type);
        }
        else
        {
            Money fee = Money.of(asCurrencyCode(v.get("currency")), asAmount(v.get("fee")));
            PDFExtractorUtils.checkAndSetFee(fee,
                            ((name.abuchen.portfolio.model.BuySellEntry) t).getPortfolioTransaction(), type);
        }
    }
}
Nirus2000 commented 3 years ago

Hello @flywire sorry that I have answered you in german. For us it is better if you start a draft-request. Then we look at it together and show you where there are still changes should be made.

Is SelfWealth a bank or securities account? delete the second identifier

addBankIdentifier("SelfWealth"); //$NON-NLS-1$
addBankIdentifier("SelfWealth Limited ABN: 52 154 324 428 AFSL 421789 W: www.selfwealth.com.au E: support@selfwealth.com.au"); //$NON-NLS-1$

Change getLabel() to this...

 public String getLabel()
 {
     return "SelfWealth"; //$NON-NLS-1$
 }

For the structur in addBuySellTransaction or other transactions....

  1. security with security currency
  2. date
  3. amount and currency
  4. sometimes forex calculation if security not in amount currency
  5. notes (...)

There is many other small thinks... so start in draft-request.

C ya Alex

Nirus2000 commented 3 years ago

Hello and welcome, Therefore, log in to the forum at https://forum.portfolio-performance.info/ and create a thread with the name "PDF-Importer SelfWealth"

For a PDF-Importer we need examples of buy, sell, dividend and so on.... post it there. You can see how this works in the video tutorial.

Video tutorial: Extract PDF documents for debugging

flywire commented 3 years ago

I don't think the words translate exactly so I'm misunderstanding something:

https://forum.portfolio-performance.info/t/buchungen-aus-pdf-dateien-importieren/38#tutorial-extrahieren

Neuen Github Issue Create with the text or post it here in the forum.

I assume translates to: create a new Github Issue or Post it in the forum.

https://github.com/buchen/portfolio/issues/2329#issuecomment-881902163

so start in draft-request

I assume means: new GitHub Pull Request

https://github.com/buchen/portfolio/pull/2340 has draft code, test files and tests.


While no PDF-Importer users can enter manually or follow https://forum.portfolio-performance.info/t/import-csv-file/17123