Skip to content

Commit

Permalink
Add Directa Converter (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo-deluca authored Jan 6, 2025
1 parent 511689e commit 75ee53d
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 4 deletions.
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.21.2
next-version: 0.22.0
assembly-informational-format: "{NuGetVersion}"
mode: ContinuousDeployment
branches:
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This tool allows you to convert a multiple transaction exports (CSV) to an impor
- [BUX](https://bux.com)
- [DEGIRO](https://degiro.com)
- [Delta](https://delta.app)
- [Directa](https://directatrading.com)
- [eToro](https://www.etoro.com/)
- [Finpension](https://finpension.ch)
- [Freetrade](https://freetrade.io)
Expand Down Expand Up @@ -55,6 +56,16 @@ Login to your DEGIRO account and create an export file (via Inbox > Account Over

Open the Delta app. Open the menu, then click "Settings". Go to "Devices & Data", then "Export data". Select the portfolio to export, then click the "Download" button to get the CSV file.


### Directa

Open Directa App, select "Libera" mode and go on Transactions ("Movimenti").
![Export instructions for Directa, Transactions](./assets/directa-transactions.png)

Choose date range on the right and click on "Excel" icon, in the modal select "File separato da virgole (csv)" and "Estrai"
![Export instructions for Directa, Export](./assets/directa-export.png)


### eToro

Login to your eToro account and navigate to "Portfolio". Then select "History" in the top menu. Next, click on the icon on the far right and select "Account statement". Choose the dates of interest and click "Create". On the next page, click on the Excel icon on the top right to download the file. After downloading, open the file in Excel and delete all the tabs except the "Account Activity" tab. Then use Excel to convert the file to CSV.
Expand Down Expand Up @@ -220,6 +231,7 @@ You can now run `npm run start [exporttype]`. See the table with run commands be
| BUX | `run start bux` |
| DEGIRO | `run start degiro` |
| Delta | `run start delta` |
| Directa | `run start directa` |
| eToro | `run start etoro` |
| Finpension | `run start finpension` (or `fp`) |
| Freetrade | `run start freetrade` (or `ft`) |
Expand Down
Binary file added assets/directa-export.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/directa-transactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.21.2",
"version": "0.22.0",
"type": "module",
"description": "Convert multiple broker exports to Ghostfolio import",
"scripts": {
Expand Down
35 changes: 35 additions & 0 deletions samples/directa-export.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Conto : CONTO COGNOME NOME ,,,,,,,,,,,
Data estrazione : 2-1-2025 11:58:30,,,,,,,,,,,
,,,,,,,,,,,
Tutti i movimenti ordinati per Data Operazione,,,,,,,,,,,
Dal : 01-01-2022,,,,,,,,,,,
al : 01-01-2025,,,,,,,,,,,
,,,,,,,,,,,
Il file include i primi 3000 movimenti,,,,,,,,,,,
,,,,,,,,,,,
Data operazione,Data valuta,Tipo operazione,Ticker,Isin,Protocollo,Descrizione,Quantità,Importo euro,Importo Divisa,Divisa,Riferimento ordine
30-12-2024,30-12-2024,Conferimento con bonifico,,,YYYYYYY,,0,1200,0,EUR,
27-12-2024,27-12-2024,Provento etf,IEMB,IE00B2NPKV68,YYYYYYY,ISHARES JPMORGAN s EMERGING MA,0,20.95,0,EUR,
27-12-2024,27-12-2024,Rit.provento etf,IEMB,IE00B2NPKV68,YYYYYYY,ISHARES JPMORGAN s EMERGING MA,0,-3.57,0,EUR,
27-12-2024,27-12-2024,Provento etf,CNYB,IE00BYPC1H27,YYYYYYY,ISHARES CHINA CNY BOND UCITS E,0,10.94,0,EUR,
27-12-2024,27-12-2024,Rit.provento etf,CNYB,IE00BYPC1H27,YYYYYYY,ISHARES CHINA CNY BOND UCITS E,0,-2.15,0,EUR,
27-12-2024,27-12-2024,Ritenuta su plusvalenza,,,YYYYYYY,,0,-2.86,0,EUR,
27-12-2024,27-12-2024,Coupon certif.,CXF549,IT0006755497,YYYYYYY,MAR SeP 500 EP 240327,0,11,0,EUR,
13-12-2024,13-12-2024,Conferimento con bonifico,,,YYYYYYY,,0,1200,0,EUR,
06-12-2024,05-12-2024,Cedola obb.,M.511185,IT0005595373,YYYYYYY,ALPER FX JUN29 EUR,0,71.25,0,EUR,
06-12-2024,05-12-2024,Rit.cedola obb.,M.511185,IT0005595373,YYYYYYY,ALPER FX JUN29 EUR,0,-18.53,0,EUR,
02-12-2024,05-12-2024,Cedola obb.,M.510999,IT0005583478,YYYYYYY,BTP VALORE SC MZ30 CUM BONUS,0,24.38,0,EUR,
02-12-2024,05-12-2024,Rit.cedola obb.,M.510999,IT0005583478,YYYYYYY,BTP VALORE SC MZ30 CUM BONUS,0,-3.05,0,EUR,
02-12-2024,04-12-2024,Acquisto,ICOV,IE00B3B8Q275,,ETF COVERED BOND ISH,3,-431.04,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,EHYA,IE00BJK55C48,,ISHARES E HIGH YIELD CORP BOND,36,-197.82,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,SMEA,IE00B4K48X80,,ISHARES MSCI EUROPE UCITS ETF,2,-158.58,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,IEMB,IE00B2NPKV68,,ISHARES JPMORGAN s EMERGING MA,2,-171.06,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,EIMI,IE00BKM4GZ66,,ISHARES CORE MSCI EMER MKT IMI,4,-132.18,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,CNYB,IE00BYPC1H27,,ISHARES CHINA CNY BOND UCITS E,39,-196.33,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,SWDA,IE00B4L5Y983,,ISHARES MSCI WORLD (ACC),1,-105.71,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,IWVL,IE00BP3QZB59,,ISHARES MSCI WORLD VALUE FACT,3,-126.53,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,IEVL,IE00BQN1K901,,ISHARES MSCI EUR VALUE FACTOR,22,-192.24,0,EUR,XXXXXXXXX
02-12-2024,04-12-2024,Acquisto,IWDP,IE00B1FZS350,,ETF EPRA DEVMKTS ISH,6,-139.68,0,EUR,XXXXXXXXX
29-05-2024,31-05-2024,Vendita,AV,IT0005366601,,ANTARES VISION,200,666,0,EUR,Q8610242012248
29-05-2024,33-09-2024,Pippo,AV,IT0005366601,,ANTARES VISION,200,666,0,EUR,Q8610242012248
29-05-2024,31-05-2024,,,,,,,,0,,
5 changes: 5 additions & 0 deletions src/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SchwabConverter } from "./converters/schwabConverter";
import { SwissquoteConverter } from "./converters/swissquoteConverter";
import { Trading212Converter } from "./converters/trading212Converter";
import { XtbConverter } from "./converters/xtbConverter";
import { DirectaConverter } from "./converters/directaConverter";

import packageInfo from "../package.json";

Expand Down Expand Up @@ -158,6 +159,10 @@ async function createConverter(converterType: string): Promise<AbstractConverter
console.log("[i] Processing file using XTB converter");
converter = new XtbConverter(securityService);
break;
case "directa":
console.log("[i] Processing file using Directa converter, this is an experimental converter!");
converter = new DirectaConverter(securityService);
break;
default:
throw new Error(`Unknown converter '${converterType}' provided`);
}
Expand Down
130 changes: 130 additions & 0 deletions src/converters/directaConverter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { DirectaConverter } from "./directaConverter";
import { SecurityService } from "../securityService";
import { GhostfolioExport } from "../models/ghostfolioExport";
import YahooFinanceServiceMock from "../testing/yahooFinanceServiceMock";

describe("directaConverter", () => {

beforeEach(() => {
jest.spyOn(console, "log").mockImplementation(jest.fn());
});

afterEach(() => {
jest.clearAllMocks();
});

it("should construct", () => {

// Act
const sut = new DirectaConverter(new SecurityService(new YahooFinanceServiceMock()));

// Assert
expect(sut).toBeTruthy();
});

it("should process sample CSV file", (done) => {

// Arrange
const sut = new DirectaConverter(new SecurityService(new YahooFinanceServiceMock()));
const inputFile = "samples/directa-export.csv";

// Act
sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => {

// Assert
expect(actualExport).toBeTruthy();
expect(actualExport.activities.length).toBeGreaterThan(0);
expect(actualExport.activities.length).toBe(17);

done();
}, () => { done.fail("Should not have an error!"); });
});

describe("should throw an error if", () => {
it("the input file does not exist", (done) => {

// Arrange
const sut = new DirectaConverter(new SecurityService(new YahooFinanceServiceMock()));

let tempFileName = "tmp/testinput/directa-filedoesnotexist.csv";

// Act
sut.readAndProcessFile(tempFileName, () => { done.fail("Should not succeed!"); }, (err: Error) => {

// Assert
expect(err).toBeTruthy();

done();
});
});

it("the input file is empty", (done) => {

// Arrange
const sut = new DirectaConverter(new SecurityService(new YahooFinanceServiceMock()));

let tempFileContent = "";
tempFileContent += "Data operazione,Data valuta,Tipo operazione,Ticker,Isin,Protocollo,Descrizione,Quantità,Importo euro,Importo Divisa,Divisa,Riferimento ordine\n";

// Act
sut.processFileContents(tempFileContent, () => { done.fail("Should not succeed!"); }, (err: Error) => {

// Assert
expect(err).toBeTruthy();
expect(err.message).toContain("An error ocurred while parsing");

done();
});
});

it("the header and row column count doesn't match", (done) => {

// Arrange
const sut = new DirectaConverter(new SecurityService(new YahooFinanceServiceMock()));

let tempFileContent = "";
tempFileContent += "Data operazione,Data valuta,Tipo operazione,Ticker,Isin,Protocollo,Descrizione,Quantità,Importo euro,Importo Divisa,Divisa,Riferimento ordine\n";
tempFileContent += "02-12-2024,04-12-2024,Acquisto,ICOV,IE00B3B8Q275,,ETF COVERED BOND ISH,3,-431.04,0,EUR,XXXXXXXXX,\n";

// Act
sut.processFileContents(tempFileContent, () => { done.fail("Should not succeed!"); }, (err: Error) => {

// Assert
expect(err).toBeTruthy();
expect(err.message).toBe("An error ocurred while parsing!");

done();
});
});
});

it("should log when Yahoo Finance returns no symbol", (done) => {

// Arrange
let tempFileContent = "";

// add fake 10 rows
for (let i = 0; i < 9; i++) {
tempFileContent += "\n";
}

tempFileContent += "Data operazione,Data valuta,Tipo operazione,Ticker,Isin,Protocollo,Descrizione,Quantità,Importo euro,Importo Divisa,Divisa,Riferimento ordine\n";
tempFileContent += "02-12-2024,04-12-2024,Acquisto,ICOV,XXXXXXXX,,ETF COVERED BOND ISH,3,-431.04,0,EUR,T3717285639899"

// Mock Yahoo Finance service to return no quotes.
const yahooFinanceServiceMock = new YahooFinanceServiceMock();
jest.spyOn(yahooFinanceServiceMock, "search").mockImplementation(() => { return Promise.resolve({ quotes: [] }) });
const sut = new DirectaConverter(new SecurityService(yahooFinanceServiceMock));

// Bit hacky, but it works.
const consoleSpy = jest.spyOn((sut as any).progress, "log");

// Act
sut.processFileContents(tempFileContent, () => {

expect(consoleSpy).toHaveBeenCalledWith("[i] No result found for buy action for XXXXXXXX with currency EUR! Please add this manually..\n");

done();
}, () => done.fail("Should not have an error!"));
});
});
Loading

0 comments on commit 75ee53d

Please sign in to comment.