Skip to content

Commit

Permalink
#55 Add support for Freetrade
Browse files Browse the repository at this point in the history
  • Loading branch information
dickwolff authored May 1, 2024
2 parents d86f1d9 + 7de2f39 commit d6bde75
Show file tree
Hide file tree
Showing 6 changed files with 399 additions and 4 deletions.
4 changes: 2 additions & 2 deletions GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
next-version: 0.8.4
next-version: 0.9.0
assembly-informational-format: "{NuGetVersion}"
mode: ContinuousDeployment
branches:
master:
regex: main
mode: ContinuousDelivery
tag: ""
increment: Patch
increment: Patch
feature:
regex: ^feature?[/-]
mode: ContinuousDelivery
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This tool allows you to convert a multiple transaction exports (CSV) to an impor
- [DEGIRO](https://degiro.com)
- [eToro](https://www.etoro.com/)
- [Finpension](https://finpension.ch)
- [Freetrade](https://freetrade.io)
- [Interactive Brokers (IBKR)](https://www.interactivebrokers.com)
- [Rabobank](https://rabobank.nl)
- [Schwab](https://www.schwab.com)
Expand Down Expand Up @@ -40,6 +41,10 @@ Login to your eToro account and navigate to "Portfolio". Then select "History" i

Login to your Finpension account. Select your portfolio from the landing page. Then to the right of the screen select “Transactions”, on the following page to the right notice “transaction report (CSV-file)” and click to email or click to download locally.

### Freetrade

Open the Freetrade app. Select your portfolio from the option in the top-left under the "Portfolio" heading. Select "Activity" from the list of icons along the bottom of the screen. Select the share icon in the top-right corner and then follow the on-screen instructions.

### Interactive Brokers (IBKR)

Login to your Interactive Brokers account. Navigate to Account Management and click "Reporting" in the sidebar. Next, click on the "Flex Queries"-tab in the "Reporting" section. From the Flex "Queries section", Click the plus (+) icon on the right side to create a new Flex Query. Create a new Flex Query for Trades, and another one for Dividends. Set the export format to "CSV". See the required columns below the image.
Expand Down Expand Up @@ -157,9 +162,10 @@ You can now run `npm run start [exporttype]`. See the table with run commands be
| Trading 212 | `run start trading212` (or `t212`) |
| DEGIRO | `run start degiro` |
| Finpension | `run start finpension` (or `fp`) |
| Freetrade | `run start freetrade` (or `ft`) |
| Swissquote | `run start swissquote` (or `sq`) |
| Schwab | `run start schwab` |

### Caching

The tool uses `cacache` to store data retrieved from Yahoo Finance on disk. This way the load on Yahoo Finance is reduced and the tool should run faster. The cached data is stored in `tmp/e2g-cache`. If you feel you need to invalidate your cache, you can do so by removing the folder and the tool will recreate the cache when you run it the next time.
Expand All @@ -170,4 +176,4 @@ The tool uses `cacache` to store data retrieved from Yahoo Finance on disk. This

The export file can now be imported in Ghostfolio by going to Portfolio > Activities and pressing the 3 dots at the top right of the table. Since Ghostfolio 1.221.0, you can now preview the import and validate the data has been converted correctly. When it is to your satisfaction, press import to add the activities to your portfolio.

![image](https://user-images.githubusercontent.com/5620002/203356387-1f42ca31-7cff-44a5-8f6c-84045cf7101e.png)
![image](https://user-images.githubusercontent.com/5620002/203356387-1f42ca31-7cff-44a5-8f6c-84045cf7101e.png)
12 changes: 12 additions & 0 deletions sample-freetrade-export.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Title,Type,Timestamp,Account Currency,Total Amount,Buy / Sell,Ticker,ISIN,Price per Share in Account Currency,Stamp Duty,Quantity,Venue,Order ID,Order Type,Instrument Currency,Total Shares Amount,Price per Share,FX Rate,Base FX Rate,FX Fee (BPS),FX Fee Amount,Dividend Ex Date,Dividend Pay Date,Dividend Eligible Quantity,Dividend Amount Per Share,Dividend Gross Distribution Amount,Dividend Net Distribution Amount,Dividend Withheld Tax Percentage,Dividend Withheld Tax Amount
Withdrawal,WITHDRAWAL,2024-04-18T18:09:12.259Z,GBP,2412.19,,,,,,,,,,,,,,,,,,,,,,,,
Top up,TOP_UP,2024-04-01T00:35:22.634Z,GBP,1000.00,,,,,,,,,,,,,,,,,,,,,,,,
Alliance Trust,DIVIDEND,2024-03-28T18:45:00.000Z,GBP,26.69,,ATST,GB00B11V7W98,,,421.00000000,,,,GBP,,,,,,,2024-02-29,2024-03-28,421.00000000,0.06340000,,,0,0.00
Nvidia,DIVIDEND,2024-03-27T06:26:00.000Z,GBP,0.31,,NVDA,US67066G1040,,,11.53629370,,,,USD,,,,0.79146484,0,0.00,2024-03-05,2024-03-27,11.53629370,0.04000000,0.46,0.39,15,0.07
Interest,INTEREST_FROM_CASH,2024-03-15T00:00:00.000Z,GBP,0.21,,,,,,,,,,,,,,,,,,,,,,,,
FTSE All World,ORDER,2024-03-04T11:01:19.356Z,GBP,992.50,BUY,VWRL,IE00B3RBWM25,99.25000000,0.00,10.00000000,London Stock Exchange,DJXPPWUNIUCR,BASIC,GBP,992.50,99.25000000,,,0,,,,,,,,,
February Statement,MONTHLY_STATEMENT,2024-03-01T00:00:00.000Z,,,,,,,,,,,,,,,,,,,,,,,,,,
Alliance Trust,ORDER,2024-02-23T15:34:06.099Z,GBP,5068.25,BUY,ATST,GB00B11V7W98,11.97869359,25.22,421.00000000,London Stock Exchange,4AQXSI7ZSABW,MARKET,GBP,5043.03,11.97869359,,,0,,,,,,,,,
Apple,DIVIDEND,2024-02-15T17:39:00.000Z,GBP,6.78,,AAPL,US0378331005,,,41.83076059,,,,USD,,,,0.79485569,0,0.00,2024-02-09,2024-02-15,41.83076059,0.24000000,10.04,8.53,15,1.51
Nvidia,ORDER,2024-01-24T15:16:15.350Z,GBP,1996.75,BUY,NVDA,US67066G1040,484.45622073,0.00,4.10561350,Multiple,4RNV24HTRXMX,MARKET,USD,2534.97,617.43931800,1.26952945,1.27450000,39,7.76,,,,,,,,
Tax Certificate 2022-23,MONTHLY_STATEMENT,2024-01-21T23:52:04.204Z,,,,,,,,,,,,,,,,,,,,,,,,,,
156 changes: 156 additions & 0 deletions src/converters/freetradeConverter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { FreetradeConverter } from "./freetradeConverter";
import { YahooFinanceService } from "../yahooFinanceService";
import { GhostfolioExport } from "../models/ghostfolioExport";

describe("freetradeConverter", () => {

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

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

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

// Act
const sut = new FreetradeConverter(new YahooFinanceService());

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

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

// Arange
const sut = new FreetradeConverter(new YahooFinanceService());
const inputFile = "sample-freetrade-export.csv";

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

// Assert
expect(err).toBeFalsy();
expect(actualExport).toBeTruthy();
expect(actualExport.activities.length).toBeGreaterThan(0);
expect(actualExport.activities.length).toBe(5);

done();
}, (err: any) => { done(err); });
});

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

// Arrange
const sut = new FreetradeConverter(new YahooFinanceService());

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

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

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

done();
});
});

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

// Arrange
const sut = new FreetradeConverter(new YahooFinanceService());

let tempFileContent = "";
tempFileContent += "Title,Type,Timestamp,Account Currency,Total Amount,Buy / Sell,Ticker,ISIN,Price per Share in Account Currency,Stamp Duty,Quantity,Venue,Order ID,Order Type,Instrument Currency,Total Shares Amount,Price per Share,FX Rate,Base FX Rate,FX Fee (BPS),FX Fee Amount,Dividend Ex Date,Dividend Pay Date,Dividend Eligible Quantity,Dividend Amount Per Share,Dividend Gross Distribution Amount,Dividend Net Distribution Amount,Dividend Withheld Tax Percentage,Dividend Withheld Tax Amount\n";

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

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

done();
});
});

it("Yahoo Finance throws an error", (done) => {

// Arrange

let tempFileContent = "";
tempFileContent += "Title,Type,Timestamp,Account Currency,Total Amount,Buy / Sell,Ticker,ISIN,Price per Share in Account Currency,Stamp Duty,Quantity,Venue,Order ID,Order Type,Instrument Currency,Total Shares Amount,Price per Share,FX Rate,Base FX Rate,FX Fee (BPS),FX Fee Amount,Dividend Ex Date,Dividend Pay Date,Dividend Eligible Quantity,Dividend Amount Per Share,Dividend Gross Distribution Amount,Dividend Net Distribution Amount,Dividend Withheld Tax Percentage,Dividend Withheld Tax Amount\n";
tempFileContent += `Apple,DIVIDEND,2024-02-15T17:39:00.000Z,GBP,6.78,,AAPL,US0378331005,,,41.83076059,,,,USD,,,,0.79485569,0,0.00,2024-02-09,2024-02-15,41.83076059,0.24000000,10.04,8.53,15,1.51`;

// Mock Yahoo Finance service to throw error.
const yahooFinanceService = new YahooFinanceService();
jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { throw new Error("Unit test error"); });
const sut = new FreetradeConverter(yahooFinanceService);

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

// Assert
expect(err).toBeTruthy();
expect(err.message).toContain("Unit test error");

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

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

// Arrange

let tempFileContent = "";
tempFileContent += "Title,Type,Timestamp,Account Currency,Total Amount,Buy / Sell,Ticker,ISIN,Price per Share in Account Currency,Stamp Duty,Quantity,Venue,Order ID,Order Type,Instrument Currency,Total Shares Amount,Price per Share,FX Rate,Base FX Rate,FX Fee (BPS),FX Fee Amount,Dividend Ex Date,Dividend Pay Date,Dividend Eligible Quantity,Dividend Amount Per Share,Dividend Gross Distribution Amount,Dividend Net Distribution Amount,Dividend Withheld Tax Percentage,Dividend Withheld Tax Amount\n";
tempFileContent += `Cisco,ORDER,2024-03-04T11:01:19.356Z,GBP,992.50,BUY,,US17275R1023,99.25000000,0.00,10.00000000,London Stock Exchange,DJXPPWUNIUCR,BASIC,USD,992.50,99.25000000,,,0,,,,,,,,,`


// Mock Yahoo Finance service to return null.
const yahooFinanceService = new YahooFinanceService();
jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { return null });
const sut = new FreetradeConverter(yahooFinanceService);

// 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 US17275R1023 with currency USD! Please add this manually..\n");

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

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

// Arrange

let tempFileContent = "";
tempFileContent += "Title,Type,Timestamp,Account Currency,Total Amount,Buy / Sell,Ticker,ISIN,Price per Share in Account Currency,Stamp Duty,Quantity,Venue,Order ID,Order Type,Instrument Currency,Total Shares Amount,Price per Share,FX Rate,Base FX Rate,FX Fee (BPS),FX Fee Amount,Dividend Ex Date,Dividend Pay Date,Dividend Eligible Quantity,Dividend Amount Per Share,Dividend Gross Distribution Amount,Dividend Net Distribution Amount,Dividend Withheld Tax Percentage,Dividend Withheld Tax Amount\n";
tempFileContent += `Cisco,ORDER,2024-03-04T11:01:19.356Z,GBP,992.50,BUY,CSCO,,99.25000000,0.00,10.00000000,London Stock Exchange,DJXPPWUNIUCR,BASIC,USD,992.50,99.25000000,,,0,,,,,,,,,`


// Mock Yahoo Finance service to return null.
const yahooFinanceService = new YahooFinanceService();
jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { return null });
const sut = new FreetradeConverter(yahooFinanceService);

// 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 CSCO with currency USD! Please add this manually..\n");

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

0 comments on commit d6bde75

Please sign in to comment.