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

Small fixes on XTB #167

Merged
merged 26 commits into from
Jan 20, 2025
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
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
next-version: 0.23.0
next-version: 0.23.1
assembly-informational-format: "{NuGetVersion}"
mode: ContinuousDeployment
branches:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "export-to-ghostfolio",
"version": "0.23.0",
"version": "0.23.1",
"type": "module",
"description": "Convert multiple broker exports to Ghostfolio import",
"scripts": {
Expand Down
18 changes: 17 additions & 1 deletion samples/xtb-export.csv
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ID;Type;Time;Symbol;Comment;Amount
524522819;Stocks/ETF purchase;02.04.2024 14:21:03;SPYL.DE;OPEN BUY 35 @ 11.7405;-410.92
524303821;Stocks/ETF sale;02.04.2024 11:42:37;ZAL.DE;CLOSE BUY 1 @ 26.280;26.28
524223272;Stocks/ETF purchase;02.04.2024 10:21:47;ZAL.DE;OPEN BUY 1 @ 26.400;-26.4
579214169;Dividend;12.07.2024 11:00:27;SPYL.DE;SPYL.DE USD 0.6600/ SHR;1.21
521693016;Deposit;27.03.2024 09:00:21;;JP_MORGAN deposit, JP_MORGAN provider transaction id=TXN-C-4245251-99568, JP_MORGAN merchant reference id=TXN-C-4245251-99568, id=10821051;1400
516595080;Stocks/ETF purchase;15.03.2024 15:54:27;SPYL.DE;OPEN BUY 26 @ 11.3400;-294.84
516573845;Stocks/ETF purchase;15.03.2024 15:37:59;SPYL.DE;OPEN BUY 26 @ 11.3500;-295.1
Expand All @@ -26,4 +27,19 @@ ID;Type;Time;Symbol;Comment;Amount
279466787;Profit/Loss (FX/CFD);22.07.2022 01:35:43;GBPUSD;Profit of position #685669464;0.13
279465197;Profit/Loss (FX/CFD);22.07.2022 01:22:20;EURUSD;Profit of position #685669313;0.14
279464049;Profit/Loss (FX/CFD);22.07.2022 01:14:25;US30;Profit of position #685669781;-0.15
210658050;Swap;11.01.2022 16:16:01;GOLD;Swap of position #515856023;-0.22
210658050;Swap;11.01.2022 16:16:01;GOLD;Swap of position #515856023;-0.
579262991;Dividend;12.07.2024 13:01:17;XRAY.US;XRAY.US USD 0.1600/ SHR;2.56
579262992;Withholding tax;12.07.2024 13:01:17;XRAY.US;XRAY.US USD WHT 30%;-0.77
579262976;Withholding tax;12.07.2024 13:01:17;XRAY.US;XRAY.US USD WHT 30%;-3.84
579262975;Dividend;12.07.2024 13:01:17;XRAY.US;XRAY.US USD 0.1600/ SHR;12.8
579262887;Withholding tax;12.07.2024 13:01:17;XRAY.US;XRAY.US USD WHT 30%;-1.44
579262886;Dividend;12.07.2024 13:01:17;XRAY.US;XRAY.US USD 0.1600/ SHR;4.8
547615089;Stocks/ETF purchase;14.05.2024 17:21:10;FLOA.UK;OPEN BUY 16/16.7399 @ 5.9140;-94.62
547615042;Stocks/ETF purchase;14.05.2024 17:21:09;CSPX.UK;OPEN BUY 0.1799 @ 550.30;-99
547615041;Stocks/ETF purchase;14.05.2024 17:21:09;FLOA.UK;OPEN BUY 0.7399/16.7399 @ 5.9140;-4.38
543857396;Free funds interests tax;07.05.2024 09:36:50;;Free-funds Interest Tax 2024-04;-2
543604525;Free funds interests;07.05.2024 03:16:39;;Free-funds Interest 2024-04;15.17
597124174;Free funds interests;09.08.2024 19:38:51;;Corr Free-funds Interest 2024-07;0.5
597343445;Free funds interests;010.09.2024 20:49:02;;Corr Free-funds Interest 2024-08;-0.5
244234503;Spin off;13.04.2022 14:50:01;T.US;T.US USD 5.8970/ SHR;21.68
121090131;Dividend;05.11.2020 22:00:01;US30.cash;US30.cash USD 6.5000/ Indices;-0.05
2 changes: 1 addition & 1 deletion src/converters/revolutConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,4 @@ export class RevolutConverter extends AbstractConverter {
return "EUR";
}
}
}
}
2 changes: 1 addition & 1 deletion src/converters/xtbConverter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("xtbConverter", () => {
// Assert
expect(actualExport).toBeTruthy();
expect(actualExport.activities.length).toBeGreaterThan(0);
expect(actualExport.activities.length).toBe(20);
expect(actualExport.activities.length).toBe(33);

done();
}, () => { done.fail("Should not have an error!"); });
Expand Down
99 changes: 66 additions & 33 deletions src/converters/xtbConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class XtbConverter extends AbstractConverter {
delimiter: ";",
fromLine: 2,
skip_empty_lines: true,
columns: this.processHeaders(input, ";"),
columns: this.processHeaders(input),
cast: (columnValue, context) => {

// Custom mapping below.
Expand All @@ -41,13 +41,13 @@ export class XtbConverter extends AbstractConverter {
else if (type.indexOf("stocks/etf sale") > -1 || type.indexOf("ações/etf vende") > -1) {
return "sell";
}
else if (type.indexOf("sec fee") > -1 || type.indexOf("swap") > -1 || type.indexOf("commission") > -1 || type.indexOf("free funds interests tax") > -1) {
return "fee";
}
else if (type.indexOf("free funds interests") > -1) {
return "interest";
}
else if (type.indexOf("sec fee") > -1 || type.indexOf("swap") > -1 || type.indexOf("commission") > -1) {
return "fee";
}
else if (type.indexOf("dividend") > -1) {
else if (type.indexOf("dividend") > -1 || type.indexOf("spin off") > -1) { //verify spinoff
return "dividend";
}
else if (type.indexOf("profit/loss") > -1) {
Expand All @@ -60,11 +60,37 @@ export class XtbConverter extends AbstractConverter {
}

// Parse numbers to floats (from string).
if (context.column === "amount") {
if (context.column === "id" || context.column === "amount") {
return parseFloat(columnValue);
}

return columnValue;
},
on_record: (record: XtbRecord) => {

// Post processing steps.

// If a record is typed as dividend, but is a negative amount, then change type to fee.
if (record.type === "dividend" && record.amount < 0) {
record.type = "fee";
}

// If a record is typed as interest, but is a negative correction, then change type to fee.
if (record.type === "interest" && record.comment.toLocaleLowerCase().startsWith("corr")) {
record.type = record.amount < 0 ? "fee" : "interest";
}

// If the record is a profit/loss, check if it should be a fee or interest.
if (record.type.toLocaleLowerCase() === "profitloss") {
if (record.amount < 0) {
record.type = "fee";
}
else {
record.type = "interest";
}
}

return record;
}
}, async (err, records: XtbRecord[]) => {

Expand Down Expand Up @@ -102,27 +128,17 @@ export class XtbConverter extends AbstractConverter {

const date = dayjs(`${record.time}`, "DD.MM.YYYY HH:mm:ss");

// If the record is a profit/loss, check if it should be a fee or interest.
if (record.type.toLocaleLowerCase() === "profitloss") {
if (record.amount < 0) {
record.type = "fee";
}
else {
record.type = "interest";
}
}

// Interest does not have a security, so add those immediately.
if (record.type.toLocaleLowerCase() === "interest") {

// Add interest record to export.
result.activities.push({
accountId: process.env.GHOSTFOLIO_ACCOUNT_ID,
comment: record.comment,
comment: `XTB ${record.id} - ${record.comment}`,
fee: 0,
quantity: 1,
type: GhostfolioOrderType[record.type],
unitPrice: record.amount,
unitPrice: Math.abs(record.amount),
currency: process.env.XTB_ACCOUNT_CURRENCY || "EUR",
dataSource: "MANUAL",
date: date.format("YYYY-MM-DDTHH:mm:ssZ"),
Expand All @@ -138,7 +154,7 @@ export class XtbConverter extends AbstractConverter {
// Add interest record to export.
result.activities.push({
accountId: process.env.GHOSTFOLIO_ACCOUNT_ID,
comment: record.comment,
comment: `XTB ${record.id} - ${record.comment}`,
fee: Math.abs(record.amount),
quantity: 1,
type: GhostfolioOrderType[record.type],
Expand All @@ -153,9 +169,9 @@ export class XtbConverter extends AbstractConverter {
continue;
}

const match = record.comment.match(/(?:OPEN|CLOSE) BUY ((?:[0-9]*[.])?[0-9]+) @ ((?:[0-9]*[.])?[0-9]+)|((?:[0-9]*[.])?[0-9]+)(?:\/)/)
const match = record.comment.match(/(?:OPEN|CLOSE) BUY ([0-9]+(?:\.[0-9]+)?(?:\/[0-9]+(?:\.[0-9]+)?)?) @ ([0-9]+(?:\.[0-9]+)?)|(?:[A-Z\. ]+) ([0-9]+(?:\.[0-9]+)?)/)

let quantity = parseFloat(match[1]);
let quantity = parseFloat(match[1]?.split("/")[0]);
let unitPrice = parseFloat(match[2]);
const dividendPerShare = parseFloat(match[3]);

Expand Down Expand Up @@ -185,21 +201,21 @@ export class XtbConverter extends AbstractConverter {
// Dividend usually goes with a dividend tax record, so look it up.
if (record.type.toLocaleLowerCase() === "dividend") {

const taxRecord = this.lookupDividendTaxRecord(records, idx);
unitPrice = dividendPerShare;
quantity = parseFloat((record.amount / dividendPerShare).toFixed(2));

const taxRecord = this.lookupDividendTaxRecord(record.id, records, idx);

// If there was a dividend tax record found, check if it matches the dividend record.
if (taxRecord && taxRecord.symbol === record.symbol && taxRecord.time === record.time) {

feeAmount = Math.abs(taxRecord.amount);
quantity = (record.amount / dividendPerShare);
unitPrice = dividendPerShare;
}
}

// Add record to export.
result.activities.push({
accountId: process.env.GHOSTFOLIO_ACCOUNT_ID,
comment: record.comment,
comment: `XTB ${record.id} - ${record.comment}`,
fee: feeAmount,
quantity: quantity,
type: GhostfolioOrderType[record.type],
Expand All @@ -219,6 +235,23 @@ export class XtbConverter extends AbstractConverter {
});
}

/**
* @inheritdoc
*/
protected processHeaders(_: string): string[] {

// Generic header mapping from the XTB CSV export.
const csvHeaders = [
"id",
"type",
"time",
"symbol",
"comment",
"amount"];

return csvHeaders;
}

/**
* @inheritdoc
*/
Expand All @@ -228,23 +261,23 @@ export class XtbConverter extends AbstractConverter {
return ignoredRecordTypes.some(t => record.type.toLocaleLowerCase().indexOf(t) > -1)
}

private lookupDividendTaxRecord(records: XtbRecord[], idx: number): XtbRecord | undefined {
private lookupDividendTaxRecord(currentRecordId: number, records: XtbRecord[], idx: number): XtbRecord | undefined {

let taxRecord;

// Look ahead at the next record (if there are any records left).
if (idx > 0 && records.length - 1 > idx + 1) {
const nextRecord = records[idx + 1];


if (nextRecord.type.toLocaleLowerCase().indexOf("tax") > -1) {
if (nextRecord.type.toLocaleLowerCase().indexOf("tax") > -1 && currentRecordId + 1 === nextRecord.id) {
taxRecord = nextRecord;
}
}
else {
// Look back at the previous record.

// If there is no tax record found, look back at the previous record.
if (!taxRecord) {

const previousRecord = records[idx - 1];
if (previousRecord.type.toLocaleLowerCase().indexOf("tax") > -1) {
if (previousRecord?.type.toLocaleLowerCase().indexOf("tax") > -1 && currentRecordId + 1 === previousRecord?.id) {
taxRecord = previousRecord;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/testing/data/yahooFinanceQuoteSummaryResults.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/testing/data/yahooFinanceSearchResults.json

Large diffs are not rendered by default.

Loading