From 4b411397b6c72924a02bddf5ac7c10612a32c122 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Tue, 9 Jan 2024 21:20:08 +0100 Subject: [PATCH 01/26] Add local file cache to store Yahoo Finance data between runs --- package.json | 2 ++ src/yahooFinanceService.ts | 58 ++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f2d3d1d6..67a96acb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "license": "Apache-2.0", "devDependencies": { + "@types/cacache": "^17.0.2", "@types/node": "^20.10.4", "nodemon": "^3.0.2", "ts-node": "^10.9.1", @@ -19,6 +20,7 @@ }, "dependencies": { "@types/cli-progress": "^3.11.5", + "cacache": "^18.0.2", "cli-progress": "^3.12.0", "cross-fetch": "^4.0.0", "csv-parse": "^5.5.2", diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 0730b9ef..4b155a91 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -1,6 +1,9 @@ +import * as cacache from "cacache"; import yahooFinance from 'yahoo-finance2'; import { YahooFinanceRecord } from './models/yahooFinanceRecord'; +const cachePath = "tmp/e2g-cache"; + export class YahooFinanceService { // Local cache of earlier retrieved symbols. @@ -26,6 +29,9 @@ export class YahooFinanceService { // Retrieve prefered exchange postfix if set in .env this.preferedExchangePostfix = process.env.DEGIRO_PREFERED_EXCHANGE_POSTFIX; + + // Preload the cache from disk. + this.preloadCache().then(() => console.log("[i] Restored symbols from cache..")) } /** @@ -99,13 +105,15 @@ export class YahooFinanceService { this.logDebug(`getSecurity(): Match found for ${isin ?? symbol ?? name}`, progress); - // If there was an isin given, place it in the isin-symbol mapping cache. - if (isin) { - this.isinSymbolCache[isin] = symbolMatch.symbol; + // If there was an isin given, place it in the isin-symbol mapping cache (if it wasn't there before). + if (isin && !this.isinSymbolCache.has(isin)) { + await this.saveInCache(isin, null, symbolMatch.symbol); } - // Store the record in cache by symbol. - this.symbolCache[symbolMatch.symbol] = symbolMatch; + // Store the record in cache by symbol (if it wasn't there before). + if (!this.symbolCache.has(symbolMatch.symbol)) { + await this.saveInCache(null, symbolMatch.symbol, symbolMatch); + } return symbolMatch; } @@ -216,6 +224,46 @@ export class YahooFinanceService { return symbolMatch; } + private async preloadCache() { + + // Verify if there is data in the ISIN-Symbol cache. If so, restore to the local variable. + const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); + if (isinSymbolCacheExist) { + const cache = await cacache.get(cachePath, "isinSymbolCache"); + const cacheAsJson = JSON.parse(Buffer.from(cache.data).toString()); + + for (let key in cacheAsJson) { + this.isinSymbolCache.set(key, cacheAsJson[key]); + } + } + + // Verify if there is data in the Symbol cache. If so, restore to the local variable. + const symbolCacheExists = await cacache.get.info(cachePath, "symbolCache"); + if (symbolCacheExists) { + const cache = await cacache.get(cachePath, "symbolCache"); + const cacheAsJson = JSON.parse(Buffer.from(cache.data).toString()); + + for (let key in cacheAsJson) { + this.symbolCache.set(key, cacheAsJson[key]); + } + } + } + + private async saveInCache(isin?: string, symbol?: string, value?: any) { + + // Save ISIN-value combination to cache if given. + if (isin && value) { + this.isinSymbolCache.set(isin, value); + await cacache.put(cachePath, "isinSymbolCache", JSON.stringify(this.isinSymbolCache)); + } + + // Save symbol-value combination to cache if given. + if (symbol && value) { + this.symbolCache.set(symbol, value); + await cacache.put(cachePath, "symbolCache", JSON.stringify(this.symbolCache)); + } + } + private logDebug(message, progress?, additionalTabs?: boolean) { const messageToLog = (additionalTabs ? '\t' : '') + `\t${message}` From 97bb6d9161a7c20cb62a011ccd2a839105e114a7 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Tue, 9 Jan 2024 21:22:22 +0100 Subject: [PATCH 02/26] Add readme part about cache --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 19126767..024e8ee8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Login to your Finpension account. Select your portfolio from the landing page. T #### Swissquote -Login to your Swissquote account. From the bar menu click on “Transactions”. Select the desired time period as well as types and then select the “export CSV” button to the right.#### Swissquote +Login to your Swissquote account. From the bar menu click on “Transactions”. Select the desired time period as well as types and then select the “export CSV” button to the right. ### Use the tool @@ -63,6 +63,8 @@ You can now run `npm run start [exporttype]`. See the table with run commands be 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. +The tool uses `cacache` to store data retrieved from Yahoo Finance on your disk. The data is stored in `tmp/e2g-cache`. If you feel you need to invalidate your cache, feel free to remove the folder and the tool will recreate the cache when it runs the next time. + ![image](https://user-images.githubusercontent.com/5620002/203356387-1f42ca31-7cff-44a5-8f6c-84045cf7101e.png) ------- From 069e7375226f726dd493dbfe9e449dd0285e8155 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Tue, 9 Jan 2024 21:35:35 +0100 Subject: [PATCH 03/26] Console logging format --- src/yahooFinanceService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 4b155a91..8f6bfe9a 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -31,7 +31,7 @@ export class YahooFinanceService { this.preferedExchangePostfix = process.env.DEGIRO_PREFERED_EXCHANGE_POSTFIX; // Preload the cache from disk. - this.preloadCache().then(() => console.log("[i] Restored symbols from cache..")) + this.preloadCache().then(() => console.log("\n[i] Restored symbols from cache..")) } /** From c20984618c4fa912576719d6c1fcbb674a36b1bb Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Thu, 11 Jan 2024 14:43:23 +0100 Subject: [PATCH 04/26] Fix cache set and parse --- src/yahooFinanceService.ts | 46 ++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 8f6bfe9a..2799ecd4 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -44,8 +44,10 @@ export class YahooFinanceService { */ public async getSecurity(isin?, symbol?, name?, expectedCurrency?, progress?): Promise { + console.log(this.isinSymbolCache) // When isin was given, check wether there is a symbol conversion cached. Then change map. if (isin && this.isinSymbolCache.has(isin)) { + console.log("has isin", isin) symbol = this.isinSymbolCache[isin]; } // Second, check if the requested security is known by symbol (if given). @@ -129,6 +131,8 @@ export class YahooFinanceService { */ private async getSymbolsByQuery(query: string, progress?: any): Promise { + + console.log("ping yahoo finance!", query) // First get quotes for the query. let queryResult = await yahooFinance.search(query, { @@ -227,25 +231,19 @@ export class YahooFinanceService { private async preloadCache() { // Verify if there is data in the ISIN-Symbol cache. If so, restore to the local variable. - const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); + const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); if (isinSymbolCacheExist) { const cache = await cacache.get(cachePath, "isinSymbolCache"); - const cacheAsJson = JSON.parse(Buffer.from(cache.data).toString()); - - for (let key in cacheAsJson) { - this.isinSymbolCache.set(key, cacheAsJson[key]); - } + const cacheAsJson = JSON.parse(cache.data.toString(), this.reviver); + this.isinSymbolCache = cacheAsJson; } // Verify if there is data in the Symbol cache. If so, restore to the local variable. const symbolCacheExists = await cacache.get.info(cachePath, "symbolCache"); if (symbolCacheExists) { const cache = await cacache.get(cachePath, "symbolCache"); - const cacheAsJson = JSON.parse(Buffer.from(cache.data).toString()); - - for (let key in cacheAsJson) { - this.symbolCache.set(key, cacheAsJson[key]); - } + const cacheAsJson = JSON.parse(cache.data.toString(), this.reviver); + this.symbolCache = cacheAsJson; } } @@ -253,14 +251,14 @@ export class YahooFinanceService { // Save ISIN-value combination to cache if given. if (isin && value) { - this.isinSymbolCache.set(isin, value); - await cacache.put(cachePath, "isinSymbolCache", JSON.stringify(this.isinSymbolCache)); + this.isinSymbolCache.set(isin, value); + await cacache.put(cachePath, "isinSymbolCache", JSON.stringify(this.isinSymbolCache, this.replacer)); } // Save symbol-value combination to cache if given. if (symbol && value) { this.symbolCache.set(symbol, value); - await cacache.put(cachePath, "symbolCache", JSON.stringify(this.symbolCache)); + await cacache.put(cachePath, "symbolCache", JSON.stringify(this.symbolCache, this.replacer)); } } @@ -279,4 +277,24 @@ export class YahooFinanceService { } private sink() { } + + private replacer(key, value) { + if(value instanceof Map) { + return { + dataType: 'Map', + value: Array.from(value.entries()), // or with spread: value: [...value] + }; + } else { + return value; + } + } + + private reviver(key, value) { + if(typeof value === 'object' && value !== null) { + if (value.dataType === 'Map') { + return new Map(value.value); + } + } + return value; + } } From bf85ff5f4bbd2fb81522f6a177b3724aea5581f4 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Thu, 11 Jan 2024 14:57:13 +0100 Subject: [PATCH 05/26] Small renames --- src/yahooFinanceService.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 2799ecd4..647d4b95 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -44,12 +44,12 @@ export class YahooFinanceService { */ public async getSecurity(isin?, symbol?, name?, expectedCurrency?, progress?): Promise { - console.log(this.isinSymbolCache) // When isin was given, check wether there is a symbol conversion cached. Then change map. if (isin && this.isinSymbolCache.has(isin)) { console.log("has isin", isin) symbol = this.isinSymbolCache[isin]; } + // Second, check if the requested security is known by symbol (if given). if (symbol) { @@ -109,7 +109,7 @@ export class YahooFinanceService { // If there was an isin given, place it in the isin-symbol mapping cache (if it wasn't there before). if (isin && !this.isinSymbolCache.has(isin)) { - await this.saveInCache(isin, null, symbolMatch.symbol); + await this.saveInCache(isin, null, symbolMatch.symbol); } // Store the record in cache by symbol (if it wasn't there before). @@ -234,7 +234,7 @@ export class YahooFinanceService { const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); if (isinSymbolCacheExist) { const cache = await cacache.get(cachePath, "isinSymbolCache"); - const cacheAsJson = JSON.parse(cache.data.toString(), this.reviver); + const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); this.isinSymbolCache = cacheAsJson; } @@ -242,7 +242,7 @@ export class YahooFinanceService { const symbolCacheExists = await cacache.get.info(cachePath, "symbolCache"); if (symbolCacheExists) { const cache = await cacache.get(cachePath, "symbolCache"); - const cacheAsJson = JSON.parse(cache.data.toString(), this.reviver); + const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); this.symbolCache = cacheAsJson; } } @@ -252,13 +252,13 @@ export class YahooFinanceService { // Save ISIN-value combination to cache if given. if (isin && value) { this.isinSymbolCache.set(isin, value); - await cacache.put(cachePath, "isinSymbolCache", JSON.stringify(this.isinSymbolCache, this.replacer)); + await cacache.put(cachePath, "isinSymbolCache", JSON.stringify(this.isinSymbolCache, this.mapReplacer)); } // Save symbol-value combination to cache if given. if (symbol && value) { this.symbolCache.set(symbol, value); - await cacache.put(cachePath, "symbolCache", JSON.stringify(this.symbolCache, this.replacer)); + await cacache.put(cachePath, "symbolCache", JSON.stringify(this.symbolCache, this.mapReplacer)); } } @@ -278,8 +278,8 @@ export class YahooFinanceService { private sink() { } - private replacer(key, value) { - if(value instanceof Map) { + private mapReplacer(_, value) { + if (value instanceof Map) { return { dataType: 'Map', value: Array.from(value.entries()), // or with spread: value: [...value] @@ -289,12 +289,13 @@ export class YahooFinanceService { } } - private reviver(key, value) { - if(typeof value === 'object' && value !== null) { + private mapReviver(_, value) { + if (typeof value === 'object' && value !== null) { if (value.dataType === 'Map') { return new Map(value.value); } } + return value; } } From 7872413173a2d996bf0f6af7a923d933a9c6bc06 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Thu, 11 Jan 2024 14:58:33 +0100 Subject: [PATCH 06/26] Remove debug logging --- src/yahooFinanceService.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 647d4b95..1f2c82f2 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -46,7 +46,6 @@ export class YahooFinanceService { // When isin was given, check wether there is a symbol conversion cached. Then change map. if (isin && this.isinSymbolCache.has(isin)) { - console.log("has isin", isin) symbol = this.isinSymbolCache[isin]; } @@ -130,9 +129,7 @@ export class YahooFinanceService { * @returns The symbols that are retrieved from Yahoo Finance, if any. */ private async getSymbolsByQuery(query: string, progress?: any): Promise { - - - console.log("ping yahoo finance!", query) + // First get quotes for the query. let queryResult = await yahooFinance.search(query, { From 4841194e6e51226729a2c2942eb846c3a1a47f61 Mon Sep 17 00:00:00 2001 From: Dick Wolff <5620002+dickwolff@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:55:22 +0100 Subject: [PATCH 07/26] Update caching strategy description --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c6f89bb..d4431a0b 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ You can now run `npm run start [exporttype]`. See the table with run commands be 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. -The tool uses `cacache` to store data retrieved from Yahoo Finance on your disk. The data is stored in `tmp/e2g-cache`. If you feel you need to invalidate your cache, feel free to remove the folder and the tool will recreate the cache when it runs the next time. - ![image](https://user-images.githubusercontent.com/5620002/203356387-1f42ca31-7cff-44a5-8f6c-84045cf7101e.png) + +### 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. From 91c9ce7d4b984b5f636423a8af31a0faebc82de2 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 10:44:00 +0100 Subject: [PATCH 08/26] Return cache size on load --- .env.sample | 2 +- src/yahooFinanceService.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index a7efbedc..36f92f40 100644 --- a/.env.sample +++ b/.env.sample @@ -5,7 +5,7 @@ INPUT_FILE = "sample-trading212-export.csv" GHOSTFOLIO_ACCOUNT_ID="2918fee8-180a-470c-9ac6-8138837d5fc7" # Debug logging flag. -DEBUG_LOGGING = false +DEBUG_LOGGING = true # Set prefered exchange if you have securities that are traded at multiple exchanges, like Vanguard FTSE All-World UCITS ETF (VWRL.AS, VWCE.DE, VRRA.L) # Leave commented if you don't want to use this. diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 0b026dde..0b316f15 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -31,7 +31,7 @@ export class YahooFinanceService { this.preferedExchangePostfix = process.env.DEGIRO_PREFERED_EXCHANGE_POSTFIX; // Preload the cache from disk. - this.preloadCache().then(() => console.log("\n[i] Restored symbols from cache..")) + this.preloadCache().then((cacheSize) => console.log(`\n[i] Restored ${cacheSize[0]} ISIN-symbol pairs and ${cacheSize[1]} symbols from cache..`)); } /** @@ -53,7 +53,7 @@ export class YahooFinanceService { if (symbol) { const symbolMatch = this.symbolCache.has(symbol); - + // If a match was found, return the security. if (symbolMatch) { this.logDebug(`Retrieved symbol ${symbol} from cache!`, progress); @@ -237,7 +237,7 @@ export class YahooFinanceService { return symbolMatch; } - private async preloadCache() { + private async preloadCache(): Promise<[number, number]> { // Verify if there is data in the ISIN-Symbol cache. If so, restore to the local variable. const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); @@ -254,6 +254,9 @@ export class YahooFinanceService { const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); this.symbolCache = cacheAsJson; } + + // Return cache sizes. + return [this.isinSymbolCache.size, this.symbolCache.size]; } private async saveInCache(isin?: string, symbol?: string, value?: any) { From e22bb111cabef9e05174b864d551574b5b75f7d4 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 10:50:04 +0100 Subject: [PATCH 09/26] Preload cache on converter create --- src/converter.ts | 10 ++++--- src/yahooFinanceService.ts | 54 ++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/converter.ts b/src/converter.ts index 428a77a7..c05aa13e 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -11,8 +11,7 @@ import { SwissquoteConverter } from "./converters/swissquoteConverter"; import { FinpensionConverter } from "./converters/finpensionConverter"; import { YahooFinanceService } from "./yahooFinanceService"; - -export function createAndRunConverter(converterType: string, inputFilePath: string, outputFilePath: string, completionCallback: CallableFunction, errorCallback: CallableFunction) { +export async function createAndRunConverter(converterType: string, inputFilePath: string, outputFilePath: string, completionCallback: CallableFunction, errorCallback: CallableFunction) { // Verify if Ghostolio account ID is set (because without it there can be no valid output). if (!process.env.GHOSTFOLIO_ACCOUNT_ID) { @@ -22,7 +21,7 @@ export function createAndRunConverter(converterType: string, inputFilePath: stri const converterTypeLc = converterType.toLocaleLowerCase(); // Determine convertor type. - const converter = createConverter(converterTypeLc); + const converter = await createConverter(converterTypeLc); // Map the file to a Ghostfolio import. converter.readAndProcessFile(inputFilePath, (result: GhostfolioExport) => { @@ -41,10 +40,13 @@ export function createAndRunConverter(converterType: string, inputFilePath: stri }, (error) => errorCallback(error)); } -function createConverter(converterType: string): AbstractConverter { +async function createConverter(converterType: string): Promise { const yahooFinanceService = new YahooFinanceService(); + const cacheSize = await yahooFinanceService.loadCache(); + console.log(`[i] Restored ${cacheSize[0]} ISIN-symbol pairs and ${cacheSize[1]} symbols from cache..`); + let converter: AbstractConverter; switch (converterType) { diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 0b316f15..78828c5c 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -29,9 +29,6 @@ export class YahooFinanceService { // Retrieve prefered exchange postfix if set in .env this.preferedExchangePostfix = process.env.DEGIRO_PREFERED_EXCHANGE_POSTFIX; - - // Preload the cache from disk. - this.preloadCache().then((cacheSize) => console.log(`\n[i] Restored ${cacheSize[0]} ISIN-symbol pairs and ${cacheSize[1]} symbols from cache..`)); } /** @@ -53,7 +50,7 @@ export class YahooFinanceService { if (symbol) { const symbolMatch = this.symbolCache.has(symbol); - + // If a match was found, return the security. if (symbolMatch) { this.logDebug(`Retrieved symbol ${symbol} from cache!`, progress); @@ -134,6 +131,33 @@ export class YahooFinanceService { return null; } + /** + * Load the cache with ISIN and symbols. + * + * @returns The size of the loaded cache + */ + public async loadCache(): Promise<[number, number]> { + + // Verify if there is data in the ISIN-Symbol cache. If so, restore to the local variable. + const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); + if (isinSymbolCacheExist) { + const cache = await cacache.get(cachePath, "isinSymbolCache"); + const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); + this.isinSymbolCache = cacheAsJson; + } + + // Verify if there is data in the Symbol cache. If so, restore to the local variable. + const symbolCacheExists = await cacache.get.info(cachePath, "symbolCache"); + if (symbolCacheExists) { + const cache = await cacache.get(cachePath, "symbolCache"); + const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); + this.symbolCache = cacheAsJson; + } + + // Return cache sizes. + return [this.isinSymbolCache.size, this.symbolCache.size]; + } + /** * Get symbols for a security by a given key. * @@ -237,28 +261,6 @@ export class YahooFinanceService { return symbolMatch; } - private async preloadCache(): Promise<[number, number]> { - - // Verify if there is data in the ISIN-Symbol cache. If so, restore to the local variable. - const isinSymbolCacheExist = await cacache.get.info(cachePath, "isinSymbolCache"); - if (isinSymbolCacheExist) { - const cache = await cacache.get(cachePath, "isinSymbolCache"); - const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); - this.isinSymbolCache = cacheAsJson; - } - - // Verify if there is data in the Symbol cache. If so, restore to the local variable. - const symbolCacheExists = await cacache.get.info(cachePath, "symbolCache"); - if (symbolCacheExists) { - const cache = await cacache.get(cachePath, "symbolCache"); - const cacheAsJson = JSON.parse(cache.data.toString(), this.mapReviver); - this.symbolCache = cacheAsJson; - } - - // Return cache sizes. - return [this.isinSymbolCache.size, this.symbolCache.size]; - } - private async saveInCache(isin?: string, symbol?: string, value?: any) { // Save ISIN-value combination to cache if given. From a666468f03115098492dafcebf2dc37cbfc6b716 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 11:32:31 +0100 Subject: [PATCH 10/26] Fix cache retrieval --- src/yahooFinanceService.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index 78828c5c..e46c2fd2 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -43,19 +43,14 @@ export class YahooFinanceService { // When isin was given, check wether there is a symbol conversion cached. Then change map. if (isin && this.isinSymbolCache.has(isin)) { - symbol = this.isinSymbolCache[isin]; + symbol = this.isinSymbolCache.get(isin); } // Second, check if the requested security is known by symbol (if given). - if (symbol) { - - const symbolMatch = this.symbolCache.has(symbol); - - // If a match was found, return the security. - if (symbolMatch) { - this.logDebug(`Retrieved symbol ${symbol} from cache!`, progress); - return symbolMatch[1]; - } + // If a match was found, return the security. + if (symbol && this.symbolCache.has(symbol)) { + this.logDebug(`Retrieved symbol ${symbol} from cache!`, progress); + return this.symbolCache.get(symbol); } // The security is not known. Try to find is. @@ -280,9 +275,9 @@ export class YahooFinanceService { const messageToLog = (additionalTabs ? '\t' : '') + `\t${message}` - if (process.env.DEBUG_LOGGING == "true") { + if (Boolean(process.env.DEBUG_LOGGING) == true) { if (!progress) { - console.log(`[i] ${messageToLog}`); + console.log(`[d] ${messageToLog}`); } else { progress.log(`[d] ${messageToLog}\n`); From 0f25f1249aa16f802a7b284fa46535e2da0fe8f2 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 11:34:32 +0100 Subject: [PATCH 11/26] Bump gitversion --- GitVersion.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitVersion.yml b/GitVersion.yml index 14cb296f..b25bf3af 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 0.4.0 +next-version: 0.5.0 assembly-informational-format: "{NuGetVersion}" mode: ContinuousDeployment branches: From bd4a3f3cd8f73341ed300df6b38aeb85e563a1d6 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 11:50:09 +0100 Subject: [PATCH 12/26] Add some more files to coverage ignore --- jest.config.ts | 5 +++++ src/testUtils.ts | 11 ----------- 2 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 src/testUtils.ts diff --git a/jest.config.ts b/jest.config.ts index 1d9a50fa..ca8d04d2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -9,6 +9,11 @@ const config: Config.InitialOptions = { }, coverageDirectory: 'coverage', collectCoverageFrom: ['src/**/*.ts'], + coveragePathIgnorePatterns: [ + '/src/models', + '/src/manual.ts', + '/src/watcher.ts', + '/src/converter.ts'], coverageReporters: ['text', 'cobertura', 'html'] }; diff --git a/src/testUtils.ts b/src/testUtils.ts deleted file mode 100644 index 18d02558..00000000 --- a/src/testUtils.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* istanbul ignore */ - -import * as fs from "fs"; -import { GhostfolioExport } from "./models/ghostfolioExport"; - -export function getResultFile(fileName: string): GhostfolioExport { - - const contents = fs.readFileSync(fileName, "utf-8"); - - return JSON.parse(contents); -} From 538b716d2dd95539de8c6b215586c3ef109d986b Mon Sep 17 00:00:00 2001 From: Dick Wolff <5620002+dickwolff@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:51:33 +0100 Subject: [PATCH 13/26] Don't run test converters workflow on all pushes --- .github/workflows/frameworkTesting.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/frameworkTesting.yml b/.github/workflows/frameworkTesting.yml index 83d3f9e9..e5d303d9 100644 --- a/.github/workflows/frameworkTesting.yml +++ b/.github/workflows/frameworkTesting.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - feature/* paths: - "src/**" pull_request: From 739da43318ec42949b7dc22f64ac0f838f03dd5d Mon Sep 17 00:00:00 2001 From: Dick Wolff <5620002+dickwolff@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:45:55 +0100 Subject: [PATCH 14/26] Reverse .env.sample change --- .env.sample | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.sample b/.env.sample index 36f92f40..f561b2bd 100644 --- a/.env.sample +++ b/.env.sample @@ -5,8 +5,8 @@ INPUT_FILE = "sample-trading212-export.csv" GHOSTFOLIO_ACCOUNT_ID="2918fee8-180a-470c-9ac6-8138837d5fc7" # Debug logging flag. -DEBUG_LOGGING = true +DEBUG_LOGGING = false # Set prefered exchange if you have securities that are traded at multiple exchanges, like Vanguard FTSE All-World UCITS ETF (VWRL.AS, VWCE.DE, VRRA.L) # Leave commented if you don't want to use this. -#DEGIRO_PREFERED_EXCHANGE_POSTFIX = ".AS" \ No newline at end of file +#DEGIRO_PREFERED_EXCHANGE_POSTFIX = ".AS" From ec378fbbdade3c2f9560605f2bc469e3f11f4845 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Fri, 9 Feb 2024 12:58:23 +0100 Subject: [PATCH 15/26] Remove obsolete symbol check --- src/yahooFinanceService.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/yahooFinanceService.ts b/src/yahooFinanceService.ts index e46c2fd2..6c09f376 100644 --- a/src/yahooFinanceService.ts +++ b/src/yahooFinanceService.ts @@ -53,17 +53,14 @@ export class YahooFinanceService { return this.symbolCache.get(symbol); } - // The security is not known. Try to find is. - - // First try by ISIN. - let symbols = await this.getSymbolsByQuery(isin, progress); - this.logDebug(`getSecurity(): Found ${symbols.length} match${symbols.length === 1 ? "" : "es"} by ISIN ${isin}`, progress); + // The security is not known. Try to find it + let symbols: YahooFinanceRecord[] = []; // First try by ISIN. // If no ISIN was given as a parameter, just skip this part. if (isin) { symbols = await this.getSymbolsByQuery(isin, progress); - this.logDebug(`getSecurity(): Found ${symbols.length} matches by ISIN ${isin}`, progress); + this.logDebug(`getSecurity(): Found ${symbols.length} match${symbols.length === 1 ? "" : "es"} by ISIN ${isin}`, progress); // If no result found by ISIN, try by symbol. if (symbols.length == 0 && symbol) { From beb2d6d1a41dd0ecdeb7dbfaefb12caaa00ec1d8 Mon Sep 17 00:00:00 2001 From: Dick Wolff <5620002+dickwolff@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:20:41 +0100 Subject: [PATCH 16/26] Fix unitPrice calculation for etoro --- src/converters/etoroConverter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/converters/etoroConverter.ts b/src/converters/etoroConverter.ts index ec1f6bf0..08b02835 100644 --- a/src/converters/etoroConverter.ts +++ b/src/converters/etoroConverter.ts @@ -139,6 +139,8 @@ export class EtoroConverter extends AbstractConverter { continue; } + const unitPrice = parseFloat(record.amount / record.units).toFixed(6); + // Add record to export. result.activities.push({ accountId: process.env.GHOSTFOLIO_ACCOUNT_ID, @@ -146,7 +148,7 @@ export class EtoroConverter extends AbstractConverter { fee: 0, quantity: record.units, type: GhostfolioOrderType[record.type], - unitPrice: record.amount, + unitPrice: unitPrice, currency: currency, dataSource: "YAHOO", date: date.format("YYYY-MM-DDTHH:mm:ssZ"), From 4907212229f26555d78e7fe7e0dd90b7b5d925d9 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 09:02:30 +0100 Subject: [PATCH 17/26] Fix unit price calculation --- src/converters/etoroConverter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/converters/etoroConverter.ts b/src/converters/etoroConverter.ts index 08b02835..16983a68 100644 --- a/src/converters/etoroConverter.ts +++ b/src/converters/etoroConverter.ts @@ -139,7 +139,7 @@ export class EtoroConverter extends AbstractConverter { continue; } - const unitPrice = parseFloat(record.amount / record.units).toFixed(6); + const unitPrice = parseFloat((record.amount / record.units).toFixed(6)); // Add record to export. result.activities.push({ From 5d476f90cd1c13335da46116e03e1ccda2d56398 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 09:45:52 +0100 Subject: [PATCH 18/26] Add more validations in existing tests --- jest.config.ts | 2 +- src/converters/degiroConverter.test.ts | 2 +- src/converters/degiroConverterV2.test.ts | 2 +- src/converters/etoroConverter.test.ts | 11 +++++++++-- src/converters/swissquoteConverter.test.ts | 10 +++++++--- src/converters/swissquoteConverter.ts | 2 +- src/converters/trading212Converter.test.ts | 11 +++++++++-- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index ca8d04d2..462e1d16 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,7 +3,7 @@ import type { Config } from '@jest/types'; // Sync object const config: Config.InitialOptions = { verbose: true, - testTimeout: 30000, + testTimeout: 15000, transform: { '^.+\\.tsx?$': 'ts-jest' }, diff --git a/src/converters/degiroConverter.test.ts b/src/converters/degiroConverter.test.ts index 870a686d..589bf454 100644 --- a/src/converters/degiroConverter.test.ts +++ b/src/converters/degiroConverter.test.ts @@ -8,7 +8,7 @@ describe("degiroConverter", () => { // Act const sut = new DeGiroConverter(new YahooFinanceService()); - // Asssert + // Assert expect(sut).toBeTruthy(); }); }); diff --git a/src/converters/degiroConverterV2.test.ts b/src/converters/degiroConverterV2.test.ts index c844d654..532be580 100644 --- a/src/converters/degiroConverterV2.test.ts +++ b/src/converters/degiroConverterV2.test.ts @@ -8,7 +8,7 @@ describe("degiroConverterV2", () => { // Act const sut = new DeGiroConverterV2(new YahooFinanceService()); - // Asssert + // Assert expect(sut).toBeTruthy(); }); }); diff --git a/src/converters/etoroConverter.test.ts b/src/converters/etoroConverter.test.ts index eeea48d9..ea995ac5 100644 --- a/src/converters/etoroConverter.test.ts +++ b/src/converters/etoroConverter.test.ts @@ -28,6 +28,9 @@ describe("etoroConverter", () => { // Assert expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(18); + done(); }, () => { done.fail("Should not have an error!"); }); }); @@ -45,6 +48,7 @@ describe("etoroConverter", () => { // Assert expect(err).toBeTruthy(); + done(); }); }); @@ -62,7 +66,8 @@ describe("etoroConverter", () => { // Assert expect(err).toBeTruthy(); - expect(err.message).toContain("An error ocurred while parsing") + expect(err.message).toContain("An error ocurred while parsing"); + done(); }); }); @@ -85,7 +90,8 @@ describe("etoroConverter", () => { // Assert expect(err).toBeTruthy(); - expect(err.message).toContain("Unit test error") + expect(err.message).toContain("Unit test error"); + done(); }); }); @@ -111,6 +117,7 @@ describe("etoroConverter", () => { sut.processFileContents(tempFileContent, () => { expect(consoleSpy).toHaveBeenCalledWith("[i] No result found for dividend action for NKE/USD! Please add this manually..\n"); + done(); }, () => done.fail("Should not have an error!")); }); diff --git a/src/converters/swissquoteConverter.test.ts b/src/converters/swissquoteConverter.test.ts index 7483fae1..255a54ed 100644 --- a/src/converters/swissquoteConverter.test.ts +++ b/src/converters/swissquoteConverter.test.ts @@ -9,7 +9,7 @@ describe("swissquoteConverter", () => { // Act const sut = new SwissquoteConverter(new YahooFinanceService()); - // Asssert + // Assert expect(sut).toBeTruthy(); }); @@ -24,8 +24,9 @@ describe("swissquoteConverter", () => { // Assert expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(14); - // Finish the test done(); }, () => { fail("Should not have an error!"); }); }); @@ -43,6 +44,7 @@ describe("swissquoteConverter", () => { // Assert expect(err).toBeTruthy(); + done(); }); }); @@ -61,7 +63,8 @@ describe("swissquoteConverter", () => { // Assert expect(err).toBeTruthy(); - expect(err.message).toContain("An error ocurred while parsing") + expect(err.message).toContain("An error ocurred while parsing"); + done(); }); }); @@ -81,6 +84,7 @@ describe("swissquoteConverter", () => { // Assert expect(err).toBeTruthy(); + done(); }); }); diff --git a/src/converters/swissquoteConverter.ts b/src/converters/swissquoteConverter.ts index 67988ca0..ca6efe05 100644 --- a/src/converters/swissquoteConverter.ts +++ b/src/converters/swissquoteConverter.ts @@ -131,7 +131,7 @@ export class SwissquoteConverter extends AbstractConverter { // Log whenever there was no match found. if (!security) { - this.progress.log(`[i]\tNo result found for ${record.transaction} action for ${record.isin || record.symbol || record.name} with currency ${record.currency}! Please add this manually..\n`); + this.progress.log(`[i] No result found for ${record.transaction} action for ${record.isin || record.symbol || record.name} with currency ${record.currency}! Please add this manually..\n`); bar1.increment(); continue; } diff --git a/src/converters/trading212Converter.test.ts b/src/converters/trading212Converter.test.ts index 2cd562b1..7241f69e 100644 --- a/src/converters/trading212Converter.test.ts +++ b/src/converters/trading212Converter.test.ts @@ -28,6 +28,9 @@ describe("trading212Converter", () => { // Assert expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(7); + done(); }, () => { done.fail("Should not have an error!"); }); }); @@ -45,6 +48,7 @@ describe("trading212Converter", () => { // Assert expect(err).toBeTruthy(); + done(); }); }); @@ -62,7 +66,8 @@ describe("trading212Converter", () => { // Assert expect(err).toBeTruthy(); - expect(err.message).toContain("An error ocurred while parsing") + expect(err.message).toContain("An error ocurred while parsing"); + done(); }); }); @@ -85,7 +90,8 @@ describe("trading212Converter", () => { // Assert expect(err).toBeTruthy(); - expect(err.message).toContain("Unit test error") + expect(err.message).toContain("Unit test error"); + done(); }); }); @@ -111,6 +117,7 @@ describe("trading212Converter", () => { 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.fail("Should not have an error!")); }); From aee2d44276df05fcf66a4525dfdb5a65e488a8a8 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 09:53:39 +0100 Subject: [PATCH 19/26] Add tests for finpension and schwab --- sample-schwab-export.csv | 1 + src/converters/finpensionConverter.test.ts | 118 +++++++++++++++++++- src/converters/finpensionConverter.ts | 12 +-- src/converters/schwabConverter.test.ts | 120 ++++++++++++++++++++- src/converters/schwabConverter.ts | 10 +- 5 files changed, 238 insertions(+), 23 deletions(-) diff --git a/sample-schwab-export.csv b/sample-schwab-export.csv index 96411802..62976754 100644 --- a/sample-schwab-export.csv +++ b/sample-schwab-export.csv @@ -98,4 +98,5 @@ Date,Action,Symbol,Description,Quantity,Price,Fees & Comm,Amount 01/03/2023,Reinvest Shares,NMFC,NEW MOUNTAIN FIN CO,10.9694,$12.45,,-$136.56 01/03/2023,Reinvest Shares,TCPC,BLACKROCK TCP CAPITAL CO,10.0443,$13.17,,-$132.25 "09/08/2023","Sell","SNAXX","SCHWAB VALUE ADVANTAGE MONEY ULTRA","500,135","$1.00","","$500135.00" +"10/18/2023","Wire Sent","","WIRED FUNDS DISBURSED","","","","-$100000.00" Transactions Total,,,,,,,"-$26,582.91" \ No newline at end of file diff --git a/src/converters/finpensionConverter.test.ts b/src/converters/finpensionConverter.test.ts index 5e496200..5460cc12 100644 --- a/src/converters/finpensionConverter.test.ts +++ b/src/converters/finpensionConverter.test.ts @@ -1,14 +1,124 @@ -import { YahooFinanceService } from "../yahooFinanceService"; import { FinpensionConverter } from "./finpensionConverter"; +import { YahooFinanceService } from "../yahooFinanceService"; +import { GhostfolioExport } from "../models/ghostfolioExport"; describe("finpensionConverter", () => { - it("should construct", () => { + afterEach(() => { + jest.clearAllMocks(); + }) + + it("should construct", () => { + + // Act + const sut = new FinpensionConverter(new YahooFinanceService()); + + // Assert + expect(sut).toBeTruthy(); + }); + + it("should process sample CSV file", (done) => { + + // Arange + const sut = new FinpensionConverter(new YahooFinanceService()); + const inputFile = "sample-finpension-export.csv"; + + // Act + sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => { + + // Assert + expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(24); + + 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 FinpensionConverter(new YahooFinanceService()); + + let tempFileName = "tmp/testinput/finpension-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 FinpensionConverter(new YahooFinanceService()); - // Asssert - expect(sut).toBeTruthy(); + let tempFileContent = ""; + tempFileContent += `Date;Category;"Asset Name";ISIN;"Number of Shares";"Asset Currency";"Currency Rate";"Asset Price in CHF";"Cash Flow";Balance\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("Yahoo Finance throws an error", (done) => { + + // Arrange + + let tempFileContent = ""; + tempFileContent += `Date;Category;"Asset Name";ISIN;"Number of Shares";"Asset Currency";"Currency Rate";"Asset Price in CHF";"Cash Flow";Balance\n`; + tempFileContent += `2023-07-11;Buy;"CSIF (CH) Bond Corporate Global ex CHF Blue ZBH";CH0189956813;0.001000;CHF;1.000000;821.800000;-0.821800;16.484551`; + + // 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 FinpensionConverter(yahooFinanceService); + + // Act + sut.processFileContents(tempFileContent, () => { done.fail("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 symbol", (done) => { + + // Arrange + + let tempFileContent = ""; + tempFileContent += `Date;Category;"Asset Name";ISIN;"Number of Shares";"Asset Currency";"Currency Rate";"Asset Price in CHF";"Cash Flow";Balance\n`; + tempFileContent += `2023-07-11;Buy;"CSIF (CH) Bond Corporate Global ex CHF Blue ZBH";CH0189956813;0.001000;CHF;1.000000;821.800000;-0.821800;16.484551`; + + // Mock Yahoo Finance service to return null. + const yahooFinanceService = new YahooFinanceService(); + jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { return null }); + const sut = new FinpensionConverter(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 CH0189956813 with currency CHF! Please add this manually..\n"); + + done(); + }, () => done.fail("Should not have an error!")); + }); }); diff --git a/src/converters/finpensionConverter.ts b/src/converters/finpensionConverter.ts index 740aa1af..ad313536 100644 --- a/src/converters/finpensionConverter.ts +++ b/src/converters/finpensionConverter.ts @@ -19,7 +19,7 @@ export class FinpensionConverter extends AbstractConverter { public processFileContents(input: string, successCallback: any, errorCallback: any): void { // Parse the CSV and convert to Ghostfolio import format. - const parser = parse(input, { + parse(input, { delimiter: ";", fromLine: 2, columns: this.processHeaders(input, ";"), @@ -57,7 +57,7 @@ export class FinpensionConverter extends AbstractConverter { }, async (_, records: FinpensionRecord[]) => { // If records is empty, parsing failed.. - if (records === undefined) { + if (records === undefined || records.length === 0) { return errorCallback(new Error("An error ocurred while parsing!")); } @@ -121,7 +121,7 @@ export class FinpensionConverter extends AbstractConverter { // Log whenever there was no match found. if (!security) { - this.progress.log(`[i]\tNo result found for ${record.category} action for ${record.isin || record.assetName} with currency ${record.assetCurrency}! Please add this manually..\n`); + this.progress.log(`[i] No result found for ${record.category} action for ${record.isin || record.assetName} with currency ${record.assetCurrency}! Please add this manually..\n`); bar1.increment(); continue; } @@ -157,12 +157,6 @@ export class FinpensionConverter extends AbstractConverter { successCallback(result); }); - - // Catch any error. - parser.on('error', function (err) { - console.log("[i] An error ocurred while processing the input file! See error below:") - console.error("[e]", err.message); - }); } /** diff --git a/src/converters/schwabConverter.test.ts b/src/converters/schwabConverter.test.ts index 275f2733..417aaaab 100644 --- a/src/converters/schwabConverter.test.ts +++ b/src/converters/schwabConverter.test.ts @@ -1,14 +1,124 @@ -import { YahooFinanceService } from "../yahooFinanceService"; import { SchwabConverter } from "./schwabConverter"; +import { YahooFinanceService } from "../yahooFinanceService"; +import { GhostfolioExport } from "../models/ghostfolioExport"; + +describe("schwabConverter", () => { + + afterEach(() => { + jest.clearAllMocks(); + }) + + it("should construct", () => { + + // Act + const sut = new SchwabConverter(new YahooFinanceService()); + + // Assert + expect(sut).toBeTruthy(); + }); + + it("should process sample CSV file", (done) => { + + // Arange + const sut = new SchwabConverter(new YahooFinanceService()); + const inputFile = "sample-schwab-export.csv"; + + // Act + sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => { -describe("SchwabConverter", () => { + // Assert + expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(98); - it("should construct", () => { + 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 SchwabConverter(new YahooFinanceService()); + + let tempFileName = "tmp/testinput/schwab-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 SchwabConverter(new YahooFinanceService()); - // Asssert - expect(sut).toBeTruthy(); + let tempFileContent = ""; + tempFileContent += `Date,Action,Symbol,Description,Quantity,Price,Fees & Comm,Amount\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("Yahoo Finance throws an error", (done) => { + + // Arrange + let tempFileContent = ""; + tempFileContent += `Date,Action,Symbol,Description,Quantity,Price,Fees & Comm,Amount\n`; + tempFileContent += `08/22/2023,Sell,FIHBX,FEDERATED HERMES INSTL HIGH YIELD BD IS,592.199,$8.46,$10.00,"$5,000.00"\n`; + tempFileContent += `Transactions Total,,,,,,,"-$26,582.91"`; + + // 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 SchwabConverter(yahooFinanceService); + + // Act + sut.processFileContents(tempFileContent, (e) => { done.fail("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 symbol", (done) => { + + // Arrange + let tempFileContent = ""; + tempFileContent += `Date,Action,Symbol,Description,Quantity,Price,Fees & Comm,Amount\n`; + tempFileContent += `08/22/2023,Sell,FIHBX,FEDERATED HERMES INSTL HIGH YIELD BD IS,592.199,$8.46,$10.00,"$5,000.00"\n`; + tempFileContent += `Transactions Total,,,,,,,"-$26,582.91"`; + + // Mock Yahoo Finance service to return null. + const yahooFinanceService = new YahooFinanceService(); + jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { return null }); + const sut = new SchwabConverter(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 sell action for FIHBX with currency USD! Please add this manually..\n"); + + done(); + }, () => done.fail("Should not have an error!")); + }); }); diff --git a/src/converters/schwabConverter.ts b/src/converters/schwabConverter.ts index 652836f9..aa4b2ce3 100644 --- a/src/converters/schwabConverter.ts +++ b/src/converters/schwabConverter.ts @@ -87,15 +87,15 @@ export class SchwabConverter extends AbstractConverter { }, activities: [] } - + // Populate the progress bar. const bar1 = this.progress.create(records.length - 1, 0); - // Skip last line of export ( stats). + // Skip last line of export (stats). for (let idx = 0; idx < records.length - 1; idx++) { const record = records[idx]; - - // Skip administrative fee/deposit/withdraw transactions. + + // Skip administrative deposit/withdraw transactions. if (this.isIgnoredRecord(record)) { bar1.increment(); continue; @@ -142,7 +142,7 @@ export class SchwabConverter extends AbstractConverter { // Log whenever there was no match found. if (!security) { - this.progress.log(`[i]\tNo result found for ${record.action} action for ${record.symbol || record.description} with currency USD! Please add this manually..\n`); + this.progress.log(`[i] No result found for ${record.action} action for ${record.symbol || record.description} with currency USD! Please add this manually..\n`); bar1.increment(); continue; } From 9effd2850575505b6cd7e801cba1ed445153bad0 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 09:59:15 +0100 Subject: [PATCH 20/26] Test cleanup --- src/converters/degiroConverter.test.ts | 4 +++- src/converters/etoroConverter.test.ts | 2 -- src/converters/finpensionConverter.test.ts | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/converters/degiroConverter.test.ts b/src/converters/degiroConverter.test.ts index 589bf454..3549a61f 100644 --- a/src/converters/degiroConverter.test.ts +++ b/src/converters/degiroConverter.test.ts @@ -1,5 +1,5 @@ -import { YahooFinanceService } from "../yahooFinanceService"; import { DeGiroConverter } from "./degiroConverter"; +import { YahooFinanceService } from "../yahooFinanceService"; describe("degiroConverter", () => { @@ -11,4 +11,6 @@ describe("degiroConverter", () => { // Assert expect(sut).toBeTruthy(); }); + + // This converter is replaced by V2, so no sense in unit testing this any further. }); diff --git a/src/converters/etoroConverter.test.ts b/src/converters/etoroConverter.test.ts index ea995ac5..99292686 100644 --- a/src/converters/etoroConverter.test.ts +++ b/src/converters/etoroConverter.test.ts @@ -75,7 +75,6 @@ describe("etoroConverter", () => { it("Yahoo Finance throws an error", (done) => { // Arrange - let tempFileContent = ""; tempFileContent += "Date,Type,Details,Amount,Units,Realized Equity Change,Realized Equity,Balance,Position ID,Asset type,NWA\n"; tempFileContent += `02/01/2024 00:10:33,Dividend,NKE/USD,0.17,-,0.17,"4,581.91",99.60,2272508626,Stocks,0.00`; @@ -100,7 +99,6 @@ describe("etoroConverter", () => { it("should log when Yahoo Finance returns no symbol", (done) => { // Arrange - let tempFileContent = ""; tempFileContent += "Date,Type,Details,Amount,Units,Realized Equity Change,Realized Equity,Balance,Position ID,Asset type,NWA\n"; tempFileContent += `02/01/2024 00:10:33,Dividend,NKE/USD,0.17,-,0.17,"4,581.91",99.60,2272508626,Stocks,0.00`; diff --git a/src/converters/finpensionConverter.test.ts b/src/converters/finpensionConverter.test.ts index 5460cc12..c91d0084 100644 --- a/src/converters/finpensionConverter.test.ts +++ b/src/converters/finpensionConverter.test.ts @@ -75,7 +75,6 @@ describe("finpensionConverter", () => { it("Yahoo Finance throws an error", (done) => { // Arrange - let tempFileContent = ""; tempFileContent += `Date;Category;"Asset Name";ISIN;"Number of Shares";"Asset Currency";"Currency Rate";"Asset Price in CHF";"Cash Flow";Balance\n`; tempFileContent += `2023-07-11;Buy;"CSIF (CH) Bond Corporate Global ex CHF Blue ZBH";CH0189956813;0.001000;CHF;1.000000;821.800000;-0.821800;16.484551`; @@ -100,7 +99,6 @@ describe("finpensionConverter", () => { it("should log when Yahoo Finance returns no symbol", (done) => { // Arrange - let tempFileContent = ""; tempFileContent += `Date;Category;"Asset Name";ISIN;"Number of Shares";"Asset Currency";"Currency Rate";"Asset Price in CHF";"Cash Flow";Balance\n`; tempFileContent += `2023-07-11;Buy;"CSIF (CH) Bond Corporate Global ex CHF Blue ZBH";CH0189956813;0.001000;CHF;1.000000;821.800000;-0.821800;16.484551`; From 4d2324c78a776289dc97af9bd1be1624dfad3527 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 10:00:24 +0100 Subject: [PATCH 21/26] Test cleanup --- src/converters/degiroConverter.test.ts | 14 ++++----- src/converters/etoroConverter.test.ts | 2 +- src/converters/swissquoteConverter.test.ts | 36 +++++++++++----------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/converters/degiroConverter.test.ts b/src/converters/degiroConverter.test.ts index 3549a61f..cf09eb6e 100644 --- a/src/converters/degiroConverter.test.ts +++ b/src/converters/degiroConverter.test.ts @@ -3,14 +3,14 @@ import { YahooFinanceService } from "../yahooFinanceService"; describe("degiroConverter", () => { - it("should construct", () => { + it("should construct", () => { - // Act - const sut = new DeGiroConverter(new YahooFinanceService()); + // Act + const sut = new DeGiroConverter(new YahooFinanceService()); - // Assert - expect(sut).toBeTruthy(); - }); + // Assert + expect(sut).toBeTruthy(); + }); - // This converter is replaced by V2, so no sense in unit testing this any further. + // This converter is replaced by V2, so no sense in unit testing this any further. }); diff --git a/src/converters/etoroConverter.test.ts b/src/converters/etoroConverter.test.ts index 99292686..08fc3fa7 100644 --- a/src/converters/etoroConverter.test.ts +++ b/src/converters/etoroConverter.test.ts @@ -115,7 +115,7 @@ describe("etoroConverter", () => { sut.processFileContents(tempFileContent, () => { expect(consoleSpy).toHaveBeenCalledWith("[i] No result found for dividend action for NKE/USD! Please add this manually..\n"); - + done(); }, () => done.fail("Should not have an error!")); }); diff --git a/src/converters/swissquoteConverter.test.ts b/src/converters/swissquoteConverter.test.ts index 255a54ed..cfce4fbb 100644 --- a/src/converters/swissquoteConverter.test.ts +++ b/src/converters/swissquoteConverter.test.ts @@ -3,7 +3,7 @@ import { GhostfolioExport } from "../models/ghostfolioExport"; import { YahooFinanceService } from "../yahooFinanceService"; describe("swissquoteConverter", () => { - + it("should construct", () => { // Act @@ -12,7 +12,7 @@ describe("swissquoteConverter", () => { // Assert expect(sut).toBeTruthy(); }); - + it("should process sample CSV file", (done) => { // Act @@ -20,15 +20,15 @@ describe("swissquoteConverter", () => { const inputFile = "sample-swissquote-export.csv"; // Act - sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => { + sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => { // Assert expect(actualExport).toBeTruthy(); expect(actualExport.activities.length).toBeGreaterThan(0); expect(actualExport.activities.length).toBe(14); - + done(); - }, () => { fail("Should not have an error!"); }); + }, () => { fail("Should not have an error!"); }); }); describe("should throw an error if", () => { @@ -38,15 +38,15 @@ describe("swissquoteConverter", () => { const sut = new SwissquoteConverter(new YahooFinanceService()); let tempFileName = "tmp/testinput/swissquote-filedoesnotexist.csv"; - + // Act - sut.readAndProcessFile(tempFileName, () => { fail("Should not succeed!"); }, (err: Error) => { + sut.readAndProcessFile(tempFileName, () => { fail("Should not succeed!"); }, (err: Error) => { // Assert expect(err).toBeTruthy(); - + done(); - }); + }); }); it("the input file is empty", (done) => { @@ -56,17 +56,17 @@ describe("swissquoteConverter", () => { // Create temp file. let tempFileContent = ""; - tempFileContent += "Date;Order #;Transaction;Symbol;Name;ISIN;Quantity;Unit price;Costs;Accrued Interest;Net Amount;Balance;Currency\n"; - + tempFileContent += "Date;Order #;Transaction;Symbol;Name;ISIN;Quantity;Unit price;Costs;Accrued Interest;Net Amount;Balance;Currency\n"; + // Act - sut.processFileContents(tempFileContent, () => { fail("Should not succeed!"); }, (err: Error) => { + sut.processFileContents(tempFileContent, () => { fail("Should not succeed!"); }, (err: Error) => { // Assert expect(err).toBeTruthy(); expect(err.message).toContain("An error ocurred while parsing"); - + done(); - }); + }); }); it("Yahoo Finance got empty input for query", (done) => { @@ -76,16 +76,16 @@ describe("swissquoteConverter", () => { // Create temp file. let tempFileContent = ""; - tempFileContent += "Date;Order #;Transaction;Symbol;Name;ISIN;Quantity;Unit price;Costs;Accrued Interest;Net Amount;Balance;Currency\n"; + tempFileContent += "Date;Order #;Transaction;Symbol;Name;ISIN;Quantity;Unit price;Costs;Accrued Interest;Net Amount;Balance;Currency\n"; tempFileContent += "10-08-2022 15:30:02;113947121;Buy;;;;200.0;19.85;5.96;0.00;-3975.96;168660.08;USD"; - + // Act - sut.processFileContents(tempFileContent, () => { fail("Should not succeed!"); }, (err) => { + sut.processFileContents(tempFileContent, () => { fail("Should not succeed!"); }, (err) => { // Assert expect(err).toBeTruthy(); - done(); + done(); }); }); }); From 6d2ecf82462f010a951b7abd3091e4675910724e Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 10:08:31 +0100 Subject: [PATCH 22/26] Add tests for DEGIRO v2 --- sample-degiro-export.csv | 6 +- src/converters/degiroConverterV2.test.ts | 116 ++++++++++++++++++++++- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/sample-degiro-export.csv b/sample-degiro-export.csv index 4f650151..1a5ae62a 100644 --- a/sample-degiro-export.csv +++ b/sample-degiro-export.csv @@ -40,4 +40,8 @@ Datum,Tijd,Valutadatum,Product,ISIN,Omschrijving,FX,Mutatie,,Saldo,,Order Id 25-07-2023,14:31,25-07-2023,ISHARES KOREA,IE00B0M63391,"Koop 1 @ 42,53 EUR",,EUR,-42.53,EUR,45.92,d4f45240-d763-4b95-8590-2ba158d38207 15-05-2019,09:05,15-05-2019,ISHARES MSCI WOR A,IE00B4L5Y983,"Compra 6 ISHARES MSCI WOR A@49,785 EUR (IE00B4L5Y983)",,EUR,-298.71,EUR,0.64,a47e2746-bfbd-4654-bd6c-5e58e470d32f 02-01-2024,14:42,02-01-2024,ISHARES MSCI WOR A,IE00B4L5Y983,Comissões de transação DEGIRO e/ou taxas de terceiros,,EUR,-1.00,EUR,2.54,7b377a93-5695-4131-8954-5c78996fbed4 -02-01-2024,14:42,02-01-2024,ISHARES MSCI WOR A,IE00B4L5Y983,"Compra 1 ISHARES MSCI WOR A@82,055 EUR (IE00B4L5Y983)",,EUR,-82.06,EUR,3.54,7b377a93-5695-4131-8954-5c78996fbed4 \ No newline at end of file +02-01-2024,14:42,02-01-2024,ISHARES MSCI WOR A,IE00B4L5Y983,"Compra 1 ISHARES MSCI WOR A@82,055 EUR (IE00B4L5Y983)",,EUR,-82.06,EUR,3.54,7b377a93-5695-4131-8954-5c78996fbed4 +05-02-2024,07:16,31-01-2024,,,DEGIRO Aansluitingskosten 2024 (Nasdaq - NDQ),,EUR,-2.50,EUR,-6.28, +05-02-2024,07:16,31-01-2024,,,DEGIRO Aansluitingskosten 2024 (Euronext Milan - MIL),,EUR,-2.50,EUR,-3.78, +05-02-2024,07:16,31-01-2024,,,DEGIRO Aansluitingskosten 2024 (Xetra - XET),,EUR,-2.50,EUR,-1.28, +02-02-2024,08:00,01-02-2024,AT&T INC.,US00206R1023,Dividend,,USD,1.39,USD,1.39, \ No newline at end of file diff --git a/src/converters/degiroConverterV2.test.ts b/src/converters/degiroConverterV2.test.ts index 532be580..37cbe775 100644 --- a/src/converters/degiroConverterV2.test.ts +++ b/src/converters/degiroConverterV2.test.ts @@ -1,14 +1,120 @@ -import { YahooFinanceService } from "../yahooFinanceService"; import { DeGiroConverterV2 } from "./degiroConverterV2"; +import { YahooFinanceService } from "../yahooFinanceService"; +import { GhostfolioExport } from "../models/ghostfolioExport"; + +fdescribe("degiroConverterV2", () => { + + it("should construct", () => { + + // Act + const sut = new DeGiroConverterV2(new YahooFinanceService()); -describe("degiroConverterV2", () => { + // Assert + expect(sut).toBeTruthy(); + }); + + it("should process sample CSV file", (done) => { + + // Arange + const sut = new DeGiroConverterV2(new YahooFinanceService()); + const inputFile = "sample-degiro-export.csv"; + + // Act + sut.readAndProcessFile(inputFile, (actualExport: GhostfolioExport) => { + + // Assert + expect(actualExport).toBeTruthy(); + expect(actualExport.activities.length).toBeGreaterThan(0); + expect(actualExport.activities.length).toBe(14); - it("should construct", () => { + 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 DeGiroConverterV2(new YahooFinanceService()); + + let tempFileName = "tmp/testinput/degiro-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 DeGiroConverterV2(new YahooFinanceService()); - // Assert - expect(sut).toBeTruthy(); + let tempFileContent = ""; + tempFileContent += "Datum,Tijd,Valutadatum,Product,ISIN,Omschrijving,FX,Mutatie,,Saldo,,Order Id\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("Yahoo Finance throws an error", (done) => { + + // Arrange + let tempFileContent = ""; + tempFileContent += "Datum,Tijd,Valutadatum,Product,ISIN,Omschrijving,FX,Mutatie,,Saldo,,Order Id\n"; + tempFileContent += `15-12-2022,16:55,15-12-2022,VICI PROPERTIES INC. C,US9256521090,DEGIRO Transactiekosten en/of kosten van derden,,EUR,"-1,00",EUR,"31,98",5925d76b-eb36-46e3-b017-a61a6d03c3e7\n`; + tempFileContent += `15-12-2022,16:55,15-12-2022,VICI PROPERTIES INC. C,US9256521090,"Koop 1 @ 33,9 USD",,USD,"-33,90",USD,"-33,90",5925d76b-eb36-46e3-b017-a61a6d03c3e7`; + + // 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 DeGiroConverterV2(yahooFinanceService); + + // Act + sut.processFileContents(tempFileContent, () => { done.fail("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 symbol", (done) => { + + // Arrange + let tempFileContent = ""; + tempFileContent += "Datum,Tijd,Valutadatum,Product,ISIN,Omschrijving,FX,Mutatie,,Saldo,,Order Id\n"; + tempFileContent += `15-12-2022,16:55,15-12-2022,VICI PROPERTIES INC. C,US9256521090,DEGIRO Transactiekosten en/of kosten van derden,,EUR,"-1,00",EUR,"31,98",5925d76b-eb36-46e3-b017-a61a6d03c3e7\n`; + tempFileContent += `15-12-2022,16:55,15-12-2022,VICI PROPERTIES INC. C,US9256521090,"Koop 1 @ 33,9 USD",,USD,"-33,90",USD,"-33,90",5925d76b-eb36-46e3-b017-a61a6d03c3e7`; + + // Mock Yahoo Finance service to return null. + const yahooFinanceService = new YahooFinanceService(); + jest.spyOn(yahooFinanceService, "getSecurity").mockImplementation(() => { return null }); + const sut = new DeGiroConverterV2(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 US9256521090 with currency EUR! Please add this manually..\n"); + + done(); + }, () => done.fail("Should not have an error!")); + }); }); From a3b03e85c3475adf5ed554be7bf74836670be7fb Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 10:10:11 +0100 Subject: [PATCH 23/26] Fix final degiro test --- src/converters/degiroConverterV2.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/converters/degiroConverterV2.test.ts b/src/converters/degiroConverterV2.test.ts index 37cbe775..b1b4e84a 100644 --- a/src/converters/degiroConverterV2.test.ts +++ b/src/converters/degiroConverterV2.test.ts @@ -25,7 +25,7 @@ fdescribe("degiroConverterV2", () => { // Assert expect(actualExport).toBeTruthy(); expect(actualExport.activities.length).toBeGreaterThan(0); - expect(actualExport.activities.length).toBe(14); + expect(actualExport.activities.length).toBe(15); done(); }, () => { done.fail("Should not have an error!"); }); From ce50f591aca45cb67849c62fad2c0a7170cdb093 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 10:14:46 +0100 Subject: [PATCH 24/26] Move caching to run local section --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f297d9d..d38d4ab0 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,10 @@ You can now run `npm run start [exporttype]`. See the table with run commands be | 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. + ## Import to Ghostfolio @@ -135,7 +139,3 @@ You can now run `npm run start [exporttype]`. See the table with run commands be 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) - -### 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. From 2187cf737c6efc95c9b7c361b7a9fc6e81e332c3 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 10:22:53 +0100 Subject: [PATCH 25/26] Add cache purging support to Docker container --- README.md | 5 +++++ src/watcher.ts | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index d38d4ab0..ff193909 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,16 @@ The following parameters can be given to the Docker run command. | `--env USE_POLLING=true` | Y | When set to true, the container will continously look for new files to process and the container will not stop. | | `--env DEBUG_LOGGING=true` | Y | When set to true, the container will show logs in more detail, useful for error tracing. | | `--env FORCE_DEGIRO_V2=true` | Y | When set to true, the converter will use the DEGIRO V2 converter (currently in beta) when a DEGIRO file was found. | +| `--env PURGE_CACHE=true` | Y | When set to true, the file cache will be purged on start. | 1: You can retrieve your Ghostfolio account ID by going to Accounts > select your account and copying the ID from the URL. ![image](https://user-images.githubusercontent.com/5620002/203353840-f5db7323-fb2f-4f4f-befc-e4e340466a74.png) +### Caching + +The tool uses `cacache` to store data retrieved from Yahoo Finance inside the container. This way the load on Yahoo Finance is reduced and the tool should run faster. The cached data is stored inside the container in `tmp/e2g-cache`. If you feel you need to invalidate your cache, you can do so by adding `--env PURGE_CACHE=true` to your run command. This will clear the cache on container start, and the tool will recreate the cache the next time it has to retrieve data from Yahoo Finance. + ## Run locally diff --git a/src/watcher.ts b/src/watcher.ts index a678602e..baef91d0 100644 --- a/src/watcher.ts +++ b/src/watcher.ts @@ -1,9 +1,20 @@ import path from "path"; import * as fs from "fs"; import chokidar from "chokidar"; +import * as cacache from "cacache"; import * as matcher from "closest-match"; import { createAndRunConverter } from "./converter"; +// Check if the cache should be purged. +if (Boolean(process.env.PURGE_CACHE)) { + + console.log("[i] Purging cache (PURGE_CACHE set to true).."); + Promise.all([ + cacache.rm("tmp/e2g-cache", "isinSymbolCache"), + cacache.rm("tmp/e2g-cache", "symbolCache") + ]).then(() => console.log("[i] Cache purged!")); +} + // Define input and output. const inputFolder = process.env.E2G_INPUT_FOLDER || "/var/e2g-input"; const outputFolder = process.env.E2G_OUTPUT_FOLDER || "/var/e2g-output"; From 1730d303220d8ba6928453024b0bd3baa71d90c4 Mon Sep 17 00:00:00 2001 From: Dick Wolff Date: Sat, 10 Feb 2024 13:15:57 +0100 Subject: [PATCH 26/26] Add percentage to coverage badge --- .github/workflows/frameworkTesting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frameworkTesting.yml b/.github/workflows/frameworkTesting.yml index af4c5dea..a080c462 100644 --- a/.github/workflows/frameworkTesting.yml +++ b/.github/workflows/frameworkTesting.yml @@ -71,7 +71,7 @@ jobs: { "schemaVersion": 1, "label": "Code Coverage", - "message": "$coverage", + "message": "$coverage%", "style": "for-the-badge", "color": "$color" }