From fda5cecb7e814b5202767d531bc8d0b84921076a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:06:47 +0200 Subject: [PATCH 001/106] Add TransactionTaxReport service type --- workers/loc.api/di/types.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/di/types.js b/workers/loc.api/di/types.js index ab72b5cd5..0f1286839 100644 --- a/workers/loc.api/di/types.js +++ b/workers/loc.api/di/types.js @@ -69,5 +69,6 @@ module.exports = { SyncUserStepData: Symbol.for('SyncUserStepData'), SyncUserStepDataFactory: Symbol.for('SyncUserStepDataFactory'), HTTPRequest: Symbol.for('HTTPRequest'), - SummaryByAsset: Symbol.for('SummaryByAsset') + SummaryByAsset: Symbol.for('SummaryByAsset'), + TransactionTaxReport: Symbol.for('TransactionTaxReport') } From a2623357295afa1cb991da78e1027869f2458762 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:07:34 +0200 Subject: [PATCH 002/106] Add TransactionTaxReport service --- .../sync/transaction.tax.report/index.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/index.js diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js new file mode 100644 index 000000000..bf3cf047d --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -0,0 +1,43 @@ +'use strict' + +const { decorateInjectable } = require('../../di/utils') + +const depsTypes = (TYPES) => [ + TYPES.DAO, + TYPES.Authenticator +] +class TransactionTaxReport { + constructor ( + dao, + authenticator + ) { + this.dao = dao + this.authenticator = authenticator + } + + // TODO: + async getFullTaxReport (args = {}) { + const { auth, params } = args ?? {} + const { + start = 0, + end = Date.now() + } = params ?? {} + const user = await this.authenticator + .verifyRequestUser({ auth }) + + // TODO: + return [{ + symbol: 'BTC', + amount: 0.001, + mtsAcquired: Date.now(), + mtsSold: Date.now(), + proceeds: 2.86, + cost: 26.932, + gainOrLoss: -24.072 + }] + } +} + +decorateInjectable(TransactionTaxReport, depsTypes) + +module.exports = TransactionTaxReport From 013a0c35f895b1655c7939bd930c6dba3abc9782 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:08:11 +0200 Subject: [PATCH 003/106] Add TransactionTaxReport service into di --- workers/loc.api/di/app.deps.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/workers/loc.api/di/app.deps.js b/workers/loc.api/di/app.deps.js index db53474f0..732d19245 100644 --- a/workers/loc.api/di/app.deps.js +++ b/workers/loc.api/di/app.deps.js @@ -97,6 +97,7 @@ const { fullTaxReportCsvWriter } = require('../generate-report-file/csv-writer') const FullTaxReport = require('../sync/full.tax.report') +const TransactionTaxReport = require('../sync/transaction.tax.report') const WeightedAveragesReport = require('../sync/weighted.averages.report') const SqliteDbMigrator = require( '../sync/dao/db-migrations/sqlite.db.migrator' @@ -151,6 +152,7 @@ module.exports = ({ ['_positionsSnapshot', TYPES.PositionsSnapshot], ['_fullSnapshotReport', TYPES.FullSnapshotReport], ['_fullTaxReport', TYPES.FullTaxReport], + ['_transactionTaxReport', TYPES.TransactionTaxReport], ['_tradedVolume', TYPES.TradedVolume], ['_totalFeesReport', TYPES.TotalFeesReport], ['_performingLoan', TYPES.PerformingLoan], @@ -393,6 +395,8 @@ module.exports = ({ ) bind(TYPES.FullTaxReport) .to(FullTaxReport) + bind(TYPES.TransactionTaxReport) + .to(TransactionTaxReport) rebind(TYPES.WeightedAveragesReport) .to(WeightedAveragesReport) rebind(TYPES.ReportFileJobData) From 65d331d1654dbbd268b098a81221dc6e89e19fd5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:11:29 +0200 Subject: [PATCH 004/106] Add data consistency checker for TransactionTaxReport --- .../sync/data.consistency.checker/checker.names.js | 1 + .../sync/data.consistency.checker/checkers.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/workers/loc.api/sync/data.consistency.checker/checker.names.js b/workers/loc.api/sync/data.consistency.checker/checker.names.js index db644c065..f4dff2943 100644 --- a/workers/loc.api/sync/data.consistency.checker/checker.names.js +++ b/workers/loc.api/sync/data.consistency.checker/checker.names.js @@ -7,6 +7,7 @@ module.exports = { POSITIONS_SNAPSHOT: 'getPositionsSnapshot', FULL_SNAPSHOT_REPORT: 'getFullSnapshotReport', FULL_TAX_REPORT: 'getFullTaxReport', + TRANSACTION_TAX_REPORT: 'getTransactionTaxReport', TRADED_VOLUME: 'getTradedVolume', TOTAL_FEES_REPORT: 'getTotalFeesReport', PERFORMING_LOAN: 'getPerformingLoan', diff --git a/workers/loc.api/sync/data.consistency.checker/checkers.js b/workers/loc.api/sync/data.consistency.checker/checkers.js index 041ed2cbf..e777dce08 100644 --- a/workers/loc.api/sync/data.consistency.checker/checkers.js +++ b/workers/loc.api/sync/data.consistency.checker/checkers.js @@ -103,6 +103,20 @@ class Checkers { }) } + // TODO: + [CHECKER_NAMES.TRANSACTION_TAX_REPORT] (auth) { + return this.syncCollsManager + .haveCollsBeenSyncedUpToDate({ + auth, + params: { + schema: [ + this.SYNC_API_METHODS.TRADES, + this.SYNC_API_METHODS.CANDLES + ] + } + }) + } + [CHECKER_NAMES.TRADED_VOLUME] (auth) { return this.syncCollsManager .haveCollsBeenSyncedUpToDate({ From 415e184d1272d8b9a3ea7d8d5a7764f04fe39a39 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:12:39 +0200 Subject: [PATCH 005/106] Add params schema for TransactionTaxReport --- workers/loc.api/helpers/schema.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/workers/loc.api/helpers/schema.js b/workers/loc.api/helpers/schema.js index db81e9197..a3ced9b85 100644 --- a/workers/loc.api/helpers/schema.js +++ b/workers/loc.api/helpers/schema.js @@ -214,6 +214,18 @@ const paramsSchemaForFullTaxReportApi = { } } +const paramsSchemaForTransactionTaxReportApi = { + type: 'object', + properties: { + end: { + type: 'integer' + }, + start: { + type: 'integer' + } + } +} + const paramsSchemaForWinLossApi = { type: 'object', properties: { @@ -412,6 +424,15 @@ const paramsSchemaForFullTaxReportFile = { } } +const paramsSchemaForTransactionTaxReportFile = { + type: 'object', + properties: { + ...cloneDeep(paramsSchemaForTransactionTaxReportApi.properties), + timezone, + dateFormat + } +} + const paramsSchemaForTradedVolumeFile = { type: 'object', properties: { @@ -460,6 +481,7 @@ module.exports = { paramsSchemaForPositionsSnapshotApi, paramsSchemaForFullSnapshotReportApi, paramsSchemaForFullTaxReportApi, + paramsSchemaForTransactionTaxReportApi, paramsSchemaForTradedVolumeApi, paramsSchemaForTotalFeesReportApi, paramsSchemaForPerformingLoanApi, @@ -471,6 +493,7 @@ module.exports = { paramsSchemaForPositionsSnapshotFile, paramsSchemaForFullSnapshotReportFile, paramsSchemaForFullTaxReportFile, + paramsSchemaForTransactionTaxReportFile, paramsSchemaForTradedVolumeFile, paramsSchemaForTotalFeesReportFile, paramsSchemaForPerformingLoanFile, From 084a43b11d7cda051ce42cff06009c4fb6ffc288 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:16:46 +0200 Subject: [PATCH 006/106] Add getTransactionTaxReport entrypoint to main service --- workers/loc.api/service.report.framework.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/workers/loc.api/service.report.framework.js b/workers/loc.api/service.report.framework.js index 057c195a8..b1bd3c017 100644 --- a/workers/loc.api/service.report.framework.js +++ b/workers/loc.api/service.report.framework.js @@ -1409,6 +1409,17 @@ class FrameworkReportService extends ReportService { }, 'getFullTaxReport', args, cb) } + getTransactionTaxReport (space, args, cb) { + return this._privResponder(async () => { + await this._dataConsistencyChecker + .check(this._CHECKER_NAMES.TRANSACTION_TAX_REPORT, args) + + checkParams(args, 'paramsSchemaForTransactionTaxReportApi') + + return this._transactionTaxReport.getTransactionTaxReport(args) + }, 'getTransactionTaxReport', args, cb) + } + getTradedVolume (space, args, cb) { return this._privResponder(async () => { await this._dataConsistencyChecker From b99c94ebfde585b6f01e6e42de58430b63dc4ddf Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:17:45 +0200 Subject: [PATCH 007/106] Add report file job data for TransactionTaxReport --- .../report.file.job.data.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/workers/loc.api/generate-report-file/report.file.job.data.js b/workers/loc.api/generate-report-file/report.file.job.data.js index 430725f1c..15fd43463 100644 --- a/workers/loc.api/generate-report-file/report.file.job.data.js +++ b/workers/loc.api/generate-report-file/report.file.job.data.js @@ -528,6 +528,51 @@ class ReportFileJobData extends BaseReportFileJobData { return jobData } + // TODO: + async getTransactionTaxReportFileJobData ( + args, + uId, + uInfo + ) { + checkParams(args, 'paramsSchemaForTransactionTaxReportFile') + + const { + userId, + userInfo + } = await checkJobAndGetUserData( + this.rService, + uId, + uInfo + ) + + const reportFileArgs = getReportFileArgs(args) + + const jobData = { + userInfo, + userId, + name: 'getTransactionTaxReport', + fileNamesMap: [['getTransactionTaxReport', 'transaction-tax-report']], + args: reportFileArgs, + propNameForPagination: null, + columnsCsv: { + symbol: 'DESCRIPTION OF PROPERTY', + amount: 'amount', + mtsAcquired: 'DATE ACQUIRED', + mtsSold: 'DATE SOLD', + proceeds: 'PROCEEDS', + cost: 'COST', + gainOrLoss: 'GAIN OR LOSS' + }, + formatSettings: { + symbol: 'symbol', + mtsAcquired: 'date', + mtsSold: 'date' + } + } + + return jobData + } + async getTradedVolumeFileJobData ( args, uId, From cf609b05d29eac728b3080712e752af283bf1c72 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 18 Mar 2024 12:18:15 +0200 Subject: [PATCH 008/106] Add getTransactionTaxReportFile entrypoint to main service --- workers/loc.api/service.report.framework.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/workers/loc.api/service.report.framework.js b/workers/loc.api/service.report.framework.js index b1bd3c017..73391e692 100644 --- a/workers/loc.api/service.report.framework.js +++ b/workers/loc.api/service.report.framework.js @@ -1564,6 +1564,20 @@ class FrameworkReportService extends ReportService { }, 'getFullTaxReportFile', args, cb) } + /** + * @deprecated + */ + getTransactionTaxReportCsv (...args) { return this.getTransactionTaxReportFile(...args) } + + getTransactionTaxReportFile (space, args, cb) { + return this._responder(() => { + return this._generateReportFile( + 'getTransactionTaxReportFileJobData', + args + ) + }, 'getTransactionTaxReportFile', args, cb) + } + /** * @deprecated */ From 84b3f8415913983a1d173326ed33dda393ca1543 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 19 Mar 2024 15:31:11 +0200 Subject: [PATCH 009/106] Add ability to get trades for trx tax report --- .../sync/transaction.tax.report/index.js | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index bf3cf047d..512f7c44f 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -4,19 +4,33 @@ const { decorateInjectable } = require('../../di/utils') const depsTypes = (TYPES) => [ TYPES.DAO, - TYPES.Authenticator + TYPES.Authenticator, + TYPES.SyncSchema, + TYPES.ALLOWED_COLLS, + TYPES.SYNC_API_METHODS ] class TransactionTaxReport { constructor ( dao, - authenticator + authenticator, + syncSchema, + ALLOWED_COLLS, + SYNC_API_METHODS ) { this.dao = dao this.authenticator = authenticator + this.syncSchema = syncSchema + this.ALLOWED_COLLS = ALLOWED_COLLS + this.SYNC_API_METHODS = SYNC_API_METHODS + + this.tradesMethodColl = this.syncSchema.getMethodCollMap() + .get(this.SYNC_API_METHODS.TRADES) + this.tradesModel = this.syncSchema.getModelsMap() + .get(this.ALLOWED_COLLS.TRADES) } // TODO: - async getFullTaxReport (args = {}) { + async getTransactionTaxReport (args = {}) { const { auth, params } = args ?? {} const { start = 0, @@ -25,6 +39,12 @@ class TransactionTaxReport { const user = await this.authenticator .verifyRequestUser({ auth }) + const trades = await this.#getTrades({ + user, + start, + end + }) + // TODO: return [{ symbol: 'BTC', @@ -36,6 +56,36 @@ class TransactionTaxReport { gainOrLoss: -24.072 }] } + + async #getTrades ({ + user, + start, + end, + symbol + }) { + const symbFilter = ( + Array.isArray(symbol) && + symbol.length !== 0 + ) + ? { $in: { symbol } } + : {} + + return this.dao.getElemsInCollBy( + this.ALLOWED_COLLS.TRADES, + { + filter: { + user_id: user._id, + $lte: { mtsCreate: end }, + $gte: { mtsCreate: start }, + ...symbFilter + }, + sort: [['mtsCreate', -1]], + projection: this.tradesModel, + exclude: ['user_id'], + isExcludePrivate: true + } + ) + } } decorateInjectable(TransactionTaxReport, depsTypes) From 095cf3ce009da396989714c3ccab199111d8766a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 21 Mar 2024 16:20:28 +0200 Subject: [PATCH 010/106] Implement transaction tax report calc --- .../sync/transaction.tax.report/index.js | 252 ++++++++++++++++-- 1 file changed, 236 insertions(+), 16 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 512f7c44f..c8803e0bc 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,5 +1,14 @@ 'use strict' +const { pick } = require('lib-js-util-base') +const { + splitSymbolPairs +} = require('bfx-report/workers/loc.api/helpers') + +const { + isForexSymb +} = require('../helpers') + const { decorateInjectable } = require('../../di/utils') const depsTypes = (TYPES) => [ @@ -7,7 +16,8 @@ const depsTypes = (TYPES) => [ TYPES.Authenticator, TYPES.SyncSchema, TYPES.ALLOWED_COLLS, - TYPES.SYNC_API_METHODS + TYPES.SYNC_API_METHODS, + TYPES.Trades ] class TransactionTaxReport { constructor ( @@ -15,13 +25,15 @@ class TransactionTaxReport { authenticator, syncSchema, ALLOWED_COLLS, - SYNC_API_METHODS + SYNC_API_METHODS, + trades ) { this.dao = dao this.authenticator = authenticator this.syncSchema = syncSchema this.ALLOWED_COLLS = ALLOWED_COLLS this.SYNC_API_METHODS = SYNC_API_METHODS + this.trades = trades this.tradesMethodColl = this.syncSchema.getMethodCollMap() .get(this.SYNC_API_METHODS.TRADES) @@ -39,24 +51,232 @@ class TransactionTaxReport { const user = await this.authenticator .verifyRequestUser({ auth }) - const trades = await this.#getTrades({ - user, - start, - end + const trades = await this.trades.getTrades({ + auth: user, + params: { + start, + end + } }) - // TODO: - return [{ - symbol: 'BTC', - amount: 0.001, - mtsAcquired: Date.now(), - mtsSold: Date.now(), - proceeds: 2.86, - cost: 26.932, - gainOrLoss: -24.072 - }] + if ( + !Array.isArray(trades) || + trades.length === 0 + ) { + return [] + } + + // TODO: need to remove + // console.log('[trades]:'.bgBlue, trades) + const tradesWithRealizedProfit = [] + + for (const [i, trade] of trades.entries()) { + const { + symbol, + execPrice, + execAmount, + amountUsd // cacled amount: `execAmount * execPrice`, if lastSymb is not USD it converts to USD + } = trade ?? {} + + let isSaleTrx = false + let isSaleTrxHistFilled = false + let saleFilledAmount = 0 + const buyTrxsForRealizedProfit = [] + + if ( + !symbol || + !Number.isFinite(execPrice) || + execPrice === 0 || + !Number.isFinite(execAmount) || + execAmount === 0 + ) { + continue + } + + const [firstSymb, lastSymb] = splitSymbolPairs(symbol) + + /* + * Exapmle of considered trxs as sale: + * - buy ETC:BTC -> amount 5, price 0.5 (here needs to be considered as 2 trxs: buy ETC and sale BTC) + * - sale ETC:BTC -> amount -2, price 0.6 (here needs to be considered as 2 trxs: sale ETC and buy BTC) + * - sale ETC:USD -> amount -3, price 4000 + * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR and converted to USD) + */ + const isDistinctSale = execAmount < 0 + const isSaleBetweenCrypto = ( + execAmount > 0 && + !isForexSymb(lastSymb) + ) + isSaleTrx = isDistinctSale || isSaleBetweenCrypto + + if (isSaleTrx) { + if (!Number.isFinite(amountUsd)) { + // TODO: + throw new Error('ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') + } + if ( + !firstSymb || + !lastSymb + ) { + // TODO: + throw new Error('ERR_CURRENCY PAIR HAS NOT BEEN SEPARATED CORRECTLY') + } + + const saleAmount = execAmount < 0 + ? Math.abs(execAmount) + : Math.abs(execAmount * execPrice) + const salePrice = Math.abs(amountUsd) / saleAmount + const saleAsset = isDistinctSale + ? firstSymb + : lastSymb + + // TODO: need to remove + // if (saleAsset === 'XLM') { + // console.log('[execAmount]:', execAmount) + // console.log('[execPrice]:', execPrice) + // console.log('[salePrice]:', salePrice) + // } + + for (const [j, tradeForLookup] of trades.entries()) { + let { + isBuyTrx = false, + isBuyTrxHistFilled = false, + isRealizedProfitDetected = false, + buyFilledAmount = 0, + proceeds = 0 + } = tradeForLookup ?? {} + const saleTrxsForRealizedProfit = tradeForLookup + ?.saleTrxsForRealizedProfit ?? [] + + if (isSaleTrxHistFilled) { + break + } + if ( + j <= i || + isBuyTrxHistFilled || + !symbol || + !Number.isFinite(tradeForLookup?.execAmount) || + tradeForLookup.execAmount === 0 || + !Number.isFinite(tradeForLookup.execPrice) || + tradeForLookup.execPrice === 0 + ) { + continue + } + + const [ + firstSymbForLookup, + lastSymbForLookup + ] = splitSymbolPairs(tradeForLookup.symbol) + + if (!Number.isFinite(tradeForLookup.amountUsd)) { + // TODO: + throw new Error('ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') + } + if ( + !firstSymbForLookup || + !lastSymbForLookup + ) { + // TODO: + throw new Error('ERR_CURRENCY PAIR HAS NOT BEEN SEPARATED CORRECTLY') + } + + if ( + tradeForLookup.execAmount < 0 && + isForexSymb(lastSymbForLookup) + ) { + continue + } + + const asset = tradeForLookup.execAmount > 0 + ? firstSymbForLookup + : lastSymbForLookup + + if (saleAsset !== asset) { + continue + } + + isBuyTrx = true + saleTrxsForRealizedProfit.push(trade) + buyTrxsForRealizedProfit.push(tradeForLookup) + + const buyAmount = tradeForLookup.execAmount > 0 + ? Math.abs(tradeForLookup.execAmount) + : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) + const buyRestAmount = buyAmount - buyFilledAmount + const saleRestAmount = saleAmount - saleFilledAmount + + if (buyRestAmount < saleRestAmount) { + buyFilledAmount = buyAmount + saleFilledAmount += buyRestAmount + proceeds += buyRestAmount * salePrice + isRealizedProfitDetected = true + isBuyTrxHistFilled = true + } + if (buyRestAmount > saleRestAmount) { + buyFilledAmount += saleRestAmount + saleFilledAmount = saleAmount + proceeds += saleRestAmount * salePrice + isSaleTrxHistFilled = true + } + if (buyRestAmount === saleRestAmount) { + buyFilledAmount = buyAmount + saleFilledAmount = saleAmount + proceeds += buyRestAmount * salePrice + isRealizedProfitDetected = true + isBuyTrxHistFilled = true + isSaleTrxHistFilled = true + } + + tradeForLookup.isBuyTrx = isBuyTrx + tradeForLookup.isBuyTrxHistFilled = isBuyTrxHistFilled + tradeForLookup.isRealizedProfitDetected = isRealizedProfitDetected + tradeForLookup.buyFilledAmount = buyFilledAmount + tradeForLookup.proceeds = proceeds + tradeForLookup.saleTrxsForRealizedProfit = saleTrxsForRealizedProfit + + if (isRealizedProfitDetected) { + tradeForLookup.asset = asset + tradeForLookup.amount = Math.abs(tradeForLookup.execAmount) + tradeForLookup.mtsAcquired = tradeForLookup.mtsCreate + tradeForLookup.mtsSold = trade.mtsCreate + tradeForLookup.cost = Math.abs(tradeForLookup.amountUsd) + tradeForLookup.gainOrLoss = proceeds - tradeForLookup.cost + + tradesWithRealizedProfit.push( + pick(tradeForLookup, [ + 'asset', + 'amount', + 'mtsAcquired', + 'mtsSold', + 'proceeds', + 'cost', + 'gainOrLoss' + ]) + ) + } + } + } + + trade.isSaleTrx = isSaleTrx + trade.isSaleTrxHistFilled = isSaleTrxHistFilled + trade.saleFilledAmount = saleFilledAmount + trade.saleFilledAmount = buyTrxsForRealizedProfit + } + + // TODO: Data structure example + // [{ + // asset: 'BTC', + // amount: 0.001, + // mtsAcquired: Date.now(), + // mtsSold: Date.now(), + // proceeds: 2.86, + // cost: 26.932, + // gainOrLoss: -24.072 + // }] + return tradesWithRealizedProfit } + // TODO: async #getTrades ({ user, start, From 219a03ec3dcc997db4b4ca669d8d5a1dbff16831 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 25 Mar 2024 10:13:11 +0200 Subject: [PATCH 011/106] Consider trades in tax reports that were not closed in prev period --- .../sync/transaction.tax.report/index.js | 106 ++++++++++++++---- 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index c8803e0bc..8ab9bcadb 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -41,7 +41,6 @@ class TransactionTaxReport { .get(this.ALLOWED_COLLS.TRADES) } - // TODO: async getTransactionTaxReport (args = {}) { const { auth, params } = args ?? {} const { @@ -51,7 +50,7 @@ class TransactionTaxReport { const user = await this.authenticator .verifyRequestUser({ auth }) - const trades = await this.trades.getTrades({ + const tradesForCurrPeriod = await this.trades.getTrades({ auth: user, params: { start, @@ -59,6 +58,35 @@ class TransactionTaxReport { } }) + if ( + !Array.isArray(tradesForCurrPeriod) || + tradesForCurrPeriod.length === 0 + ) { + return [] + } + + const tradesForPrevPeriod = start > 0 + ? await this.trades.getTrades({ + auth: user, + params: { + start: 0, + end: start - 1 + } + }) + : [] + + const { tradesWithUnrealizedProfit } = await this.#lookUpTrades( + tradesForPrevPeriod + ) + tradesForCurrPeriod.push(...tradesWithUnrealizedProfit) + const { tradesWithRealizedProfit } = await this.#lookUpTrades( + tradesForCurrPeriod + ) + + return tradesWithRealizedProfit + } + + async #lookUpTrades (trades) { if ( !Array.isArray(trades) || trades.length === 0 @@ -66,9 +94,18 @@ class TransactionTaxReport { return [] } - // TODO: need to remove - // console.log('[trades]:'.bgBlue, trades) const tradesWithRealizedProfit = [] + const tradesWithUnrealizedProfit = [] + + if ( + !Array.isArray(trades) || + trades.length === 0 + ) { + return { + tradesWithRealizedProfit, + tradesWithUnrealizedProfit + } + } for (const [i, trade] of trades.entries()) { const { @@ -100,7 +137,7 @@ class TransactionTaxReport { * - buy ETC:BTC -> amount 5, price 0.5 (here needs to be considered as 2 trxs: buy ETC and sale BTC) * - sale ETC:BTC -> amount -2, price 0.6 (here needs to be considered as 2 trxs: sale ETC and buy BTC) * - sale ETC:USD -> amount -3, price 4000 - * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR and converted to USD) + * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR price and converted to USD) */ const isDistinctSale = execAmount < 0 const isSaleBetweenCrypto = ( @@ -130,13 +167,6 @@ class TransactionTaxReport { ? firstSymb : lastSymb - // TODO: need to remove - // if (saleAsset === 'XLM') { - // console.log('[execAmount]:', execAmount) - // console.log('[execPrice]:', execPrice) - // console.log('[salePrice]:', salePrice) - // } - for (const [j, tradeForLookup] of trades.entries()) { let { isBuyTrx = false, @@ -260,20 +290,48 @@ class TransactionTaxReport { trade.isSaleTrx = isSaleTrx trade.isSaleTrxHistFilled = isSaleTrxHistFilled trade.saleFilledAmount = saleFilledAmount - trade.saleFilledAmount = buyTrxsForRealizedProfit + trade.buyTrxsForRealizedProfit = buyTrxsForRealizedProfit } - // TODO: Data structure example - // [{ - // asset: 'BTC', - // amount: 0.001, - // mtsAcquired: Date.now(), - // mtsSold: Date.now(), - // proceeds: 2.86, - // cost: 26.932, - // gainOrLoss: -24.072 - // }] - return tradesWithRealizedProfit + for (const trade of trades) { + if ( + !trade?.isBuyTrx || + trade?.isRealizedProfitDetected + ) { + continue + } + + tradesWithUnrealizedProfit.push(trade) + } + + /* + * Data structure examples: + * - for tradesWithRealizedProfit: + * [{ + * asset: 'BTC', + * amount: 0.001, + * mtsAcquired: Date.now(), + * mtsSold: Date.now(), + * proceeds: 2.86, + * cost: 26.932, + * gainOrLoss: -24.072 + * }] + * + * - for tradesWithUnrealizedProfit: + * [{ + * ...trade, + * isBuyTrx: true, + * isBuyTrxHistFilled: false, + * isRealizedProfitDetected: false, + * buyFilledAmount: 0.001, + * proceeds: 2.86, + * saleTrxsForRealizedProfit: [] + * }] + */ + return { + tradesWithRealizedProfit, + tradesWithUnrealizedProfit + } } // TODO: From 86dee5ff8fa52517bde13ba0d639ba640d2efd68 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 25 Mar 2024 10:52:37 +0200 Subject: [PATCH 012/106] Add CurrencyConversionError class --- workers/loc.api/errors/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/errors/index.js b/workers/loc.api/errors/index.js index 6cd45c546..a4b2cb4e0 100644 --- a/workers/loc.api/errors/index.js +++ b/workers/loc.api/errors/index.js @@ -248,6 +248,12 @@ class AuthTokenTTLSettingError extends ArgsParamsError { } } +class CurrencyConversionError extends BaseError { + constructor (message = 'ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') { + super(message) + } +} + module.exports = { BaseError, CollSyncPermissionError, @@ -284,5 +290,6 @@ module.exports = { LastSyncedInfoGettingError, SyncInfoUpdatingError, AuthTokenGenerationError, - AuthTokenTTLSettingError + AuthTokenTTLSettingError, + CurrencyConversionError } From fd317bf74b3124985bbd78c2a2323240f3f46990 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 25 Mar 2024 10:53:08 +0200 Subject: [PATCH 013/106] Use CurrencyConversionError class --- workers/loc.api/sync/transaction.tax.report/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 8ab9bcadb..0d054a17c 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -8,6 +8,9 @@ const { const { isForexSymb } = require('../helpers') +const { + CurrencyConversionError +} = require('../../errors') const { decorateInjectable } = require('../../di/utils') @@ -148,8 +151,7 @@ class TransactionTaxReport { if (isSaleTrx) { if (!Number.isFinite(amountUsd)) { - // TODO: - throw new Error('ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') + throw new CurrencyConversionError() } if ( !firstSymb || @@ -199,8 +201,7 @@ class TransactionTaxReport { ] = splitSymbolPairs(tradeForLookup.symbol) if (!Number.isFinite(tradeForLookup.amountUsd)) { - // TODO: - throw new Error('ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') + throw new CurrencyConversionError() } if ( !firstSymbForLookup || From b1205ef7019c954d1e827f513b944c59ceed8edd Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 25 Mar 2024 10:55:28 +0200 Subject: [PATCH 014/106] Add CurrencyPairSeparationError class --- workers/loc.api/errors/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/errors/index.js b/workers/loc.api/errors/index.js index a4b2cb4e0..421c82c30 100644 --- a/workers/loc.api/errors/index.js +++ b/workers/loc.api/errors/index.js @@ -254,6 +254,12 @@ class CurrencyConversionError extends BaseError { } } +class CurrencyPairSeparationError extends BaseError { + constructor (message = 'ERR_CURRENCY_PAIR_HAS_NOT_BEEN_SEPARATED_CORRECTLY') { + super(message) + } +} + module.exports = { BaseError, CollSyncPermissionError, @@ -291,5 +297,6 @@ module.exports = { SyncInfoUpdatingError, AuthTokenGenerationError, AuthTokenTTLSettingError, - CurrencyConversionError + CurrencyConversionError, + CurrencyPairSeparationError } From 4e84ad13894bee14a99ca55a4135f00f03ece8a1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 25 Mar 2024 10:55:41 +0200 Subject: [PATCH 015/106] Use CurrencyPairSeparationError class --- workers/loc.api/sync/transaction.tax.report/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 0d054a17c..59341b5d5 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -9,7 +9,8 @@ const { isForexSymb } = require('../helpers') const { - CurrencyConversionError + CurrencyConversionError, + CurrencyPairSeparationError } = require('../../errors') const { decorateInjectable } = require('../../di/utils') @@ -157,8 +158,7 @@ class TransactionTaxReport { !firstSymb || !lastSymb ) { - // TODO: - throw new Error('ERR_CURRENCY PAIR HAS NOT BEEN SEPARATED CORRECTLY') + throw new CurrencyPairSeparationError() } const saleAmount = execAmount < 0 @@ -207,8 +207,7 @@ class TransactionTaxReport { !firstSymbForLookup || !lastSymbForLookup ) { - // TODO: - throw new Error('ERR_CURRENCY PAIR HAS NOT BEEN SEPARATED CORRECTLY') + throw new CurrencyPairSeparationError() } if ( From a5d240839e8346d0a2631eecb6cc52a88f41c211 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 26 Mar 2024 08:52:14 +0200 Subject: [PATCH 016/106] Speed up back iteration --- workers/loc.api/sync/helpers/get-back-iterable.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/workers/loc.api/sync/helpers/get-back-iterable.js b/workers/loc.api/sync/helpers/get-back-iterable.js index 18eee8180..2e31f278b 100644 --- a/workers/loc.api/sync/helpers/get-back-iterable.js +++ b/workers/loc.api/sync/helpers/get-back-iterable.js @@ -5,15 +5,18 @@ module.exports = (array) => { [Symbol.iterator] (areEntriesReturned) { return { index: array.length, + res: { + done: false, + value: undefined + }, next () { this.index -= 1 + this.res.done = this.index < 0 + this.res.value = areEntriesReturned + ? [this.index, array[this.index]] + : array[this.index] - return { - done: this.index < 0, - value: areEntriesReturned - ? [this.index, array[this.index]] - : array[this.index] - } + return this.res } } }, From e11ab8b6d5dc1d977ebb220cf0af5c57f00fa1a5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 26 Mar 2024 09:35:50 +0200 Subject: [PATCH 017/106] Optimize trx tax report calc performance --- .../sync/transaction.tax.report/index.js | 125 +++++++++--------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 59341b5d5..dcc42120d 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -6,7 +6,8 @@ const { } = require('bfx-report/workers/loc.api/helpers') const { - isForexSymb + isForexSymb, + getBackIterable } = require('../helpers') const { CurrencyConversionError, @@ -111,30 +112,32 @@ class TransactionTaxReport { } } - for (const [i, trade] of trades.entries()) { - const { - symbol, - execPrice, - execAmount, - amountUsd // cacled amount: `execAmount * execPrice`, if lastSymb is not USD it converts to USD - } = trade ?? {} + const tradeIterator = getBackIterable(trades) + for (const [i, trade] of tradeIterator.entries()) { let isSaleTrx = false let isSaleTrxHistFilled = false let saleFilledAmount = 0 const buyTrxsForRealizedProfit = [] if ( - !symbol || - !Number.isFinite(execPrice) || - execPrice === 0 || - !Number.isFinite(execAmount) || - execAmount === 0 + !trade?.symbol || + !Number.isFinite(trade?.execPrice) || + trade.execPrice === 0 || + !Number.isFinite(trade?.execAmount) || + trade.execAmount === 0 ) { continue } - const [firstSymb, lastSymb] = splitSymbolPairs(symbol) + const [firstSymb, lastSymb] = ( + trade?.firstSymb && + trade?.lastSymb + ) + ? [trade?.firstSymb, trade?.lastSymb] + : splitSymbolPairs(trade.symbol) + trade.firstSymb = firstSymb + trade.lastSymb = lastSymb /* * Exapmle of considered trxs as sale: @@ -143,15 +146,15 @@ class TransactionTaxReport { * - sale ETC:USD -> amount -3, price 4000 * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR price and converted to USD) */ - const isDistinctSale = execAmount < 0 + const isDistinctSale = trade.execAmount < 0 const isSaleBetweenCrypto = ( - execAmount > 0 && + trade.execAmount > 0 && !isForexSymb(lastSymb) ) isSaleTrx = isDistinctSale || isSaleBetweenCrypto if (isSaleTrx) { - if (!Number.isFinite(amountUsd)) { + if (!Number.isFinite(trade.amountUsd)) { throw new CurrencyConversionError() } if ( @@ -161,44 +164,51 @@ class TransactionTaxReport { throw new CurrencyPairSeparationError() } - const saleAmount = execAmount < 0 - ? Math.abs(execAmount) - : Math.abs(execAmount * execPrice) - const salePrice = Math.abs(amountUsd) / saleAmount + const saleAmount = trade.execAmount < 0 + ? Math.abs(trade.execAmount) + : Math.abs(trade.execAmount * trade.execPrice) + const salePrice = Math.abs(trade.amountUsd) / saleAmount const saleAsset = isDistinctSale ? firstSymb : lastSymb for (const [j, tradeForLookup] of trades.entries()) { - let { - isBuyTrx = false, - isBuyTrxHistFilled = false, - isRealizedProfitDetected = false, - buyFilledAmount = 0, - proceeds = 0 - } = tradeForLookup ?? {} - const saleTrxsForRealizedProfit = tradeForLookup - ?.saleTrxsForRealizedProfit ?? [] - + if (j <= i) { + continue + } if (isSaleTrxHistFilled) { break } if ( - j <= i || - isBuyTrxHistFilled || - !symbol || + tradeForLookup?.isBuyTrxHistFilled || + !tradeForLookup?.symbol || !Number.isFinite(tradeForLookup?.execAmount) || tradeForLookup.execAmount === 0 || - !Number.isFinite(tradeForLookup.execPrice) || + !Number.isFinite(tradeForLookup?.execPrice) || tradeForLookup.execPrice === 0 ) { continue } - const [ - firstSymbForLookup, - lastSymbForLookup - ] = splitSymbolPairs(tradeForLookup.symbol) + tradeForLookup.isBuyTrx = tradeForLookup.isBuyTrx ?? false + tradeForLookup.isBuyTrxHistFilled = tradeForLookup + .isBuyTrxHistFilled ?? false + tradeForLookup.isRealizedProfitDetected = tradeForLookup + .isRealizedProfitDetected ?? false + tradeForLookup.buyFilledAmount = tradeForLookup + .buyFilledAmount ?? 0 + tradeForLookup.proceeds = tradeForLookup.proceeds ?? 0 + tradeForLookup.saleTrxsForRealizedProfit = tradeForLookup + .saleTrxsForRealizedProfit ?? [] + + const [firstSymbForLookup, lastSymbForLookup] = ( + tradeForLookup?.firstSymb && + tradeForLookup?.lastSymb + ) + ? [tradeForLookup?.firstSymb, tradeForLookup?.lastSymb] + : splitSymbolPairs(tradeForLookup.symbol) + tradeForLookup.firstSymb = firstSymbForLookup + tradeForLookup.lastSymb = lastSymbForLookup if (!Number.isFinite(tradeForLookup.amountUsd)) { throw new CurrencyConversionError() @@ -225,52 +235,45 @@ class TransactionTaxReport { continue } - isBuyTrx = true - saleTrxsForRealizedProfit.push(trade) + tradeForLookup.isBuyTrx = true + tradeForLookup.saleTrxsForRealizedProfit.push(trade) buyTrxsForRealizedProfit.push(tradeForLookup) const buyAmount = tradeForLookup.execAmount > 0 ? Math.abs(tradeForLookup.execAmount) : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) - const buyRestAmount = buyAmount - buyFilledAmount + const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount const saleRestAmount = saleAmount - saleFilledAmount if (buyRestAmount < saleRestAmount) { - buyFilledAmount = buyAmount + tradeForLookup.buyFilledAmount = buyAmount saleFilledAmount += buyRestAmount - proceeds += buyRestAmount * salePrice - isRealizedProfitDetected = true - isBuyTrxHistFilled = true + tradeForLookup.proceeds += buyRestAmount * salePrice + tradeForLookup.isRealizedProfitDetected = true + tradeForLookup.isBuyTrxHistFilled = true } if (buyRestAmount > saleRestAmount) { - buyFilledAmount += saleRestAmount + tradeForLookup.buyFilledAmount += saleRestAmount saleFilledAmount = saleAmount - proceeds += saleRestAmount * salePrice + tradeForLookup.proceeds += saleRestAmount * salePrice isSaleTrxHistFilled = true } if (buyRestAmount === saleRestAmount) { - buyFilledAmount = buyAmount + tradeForLookup.buyFilledAmount = buyAmount saleFilledAmount = saleAmount - proceeds += buyRestAmount * salePrice - isRealizedProfitDetected = true - isBuyTrxHistFilled = true + tradeForLookup.proceeds += buyRestAmount * salePrice + tradeForLookup.isRealizedProfitDetected = true + tradeForLookup.isBuyTrxHistFilled = true isSaleTrxHistFilled = true } - tradeForLookup.isBuyTrx = isBuyTrx - tradeForLookup.isBuyTrxHistFilled = isBuyTrxHistFilled - tradeForLookup.isRealizedProfitDetected = isRealizedProfitDetected - tradeForLookup.buyFilledAmount = buyFilledAmount - tradeForLookup.proceeds = proceeds - tradeForLookup.saleTrxsForRealizedProfit = saleTrxsForRealizedProfit - - if (isRealizedProfitDetected) { + if (tradeForLookup.isRealizedProfitDetected) { tradeForLookup.asset = asset tradeForLookup.amount = Math.abs(tradeForLookup.execAmount) tradeForLookup.mtsAcquired = tradeForLookup.mtsCreate tradeForLookup.mtsSold = trade.mtsCreate tradeForLookup.cost = Math.abs(tradeForLookup.amountUsd) - tradeForLookup.gainOrLoss = proceeds - tradeForLookup.cost + tradeForLookup.gainOrLoss = tradeForLookup.proceeds - tradeForLookup.cost tradesWithRealizedProfit.push( pick(tradeForLookup, [ From 1bb8ec95545d72f7bd6364958e1891c7c92dfe04 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 26 Mar 2024 09:36:59 +0200 Subject: [PATCH 018/106] Increase request timeouts for dev mode --- test/config/default.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/config/default.json b/test/config/default.json index bc2254b82..226849f7c 100644 --- a/test/config/default.json +++ b/test/config/default.json @@ -1,7 +1,9 @@ { "app": { "port": 31339, - "host": "127.0.0.1" + "host": "127.0.0.1", + "httpRpcTimeout": 600000, + "wsRpcTimeout": 3600000 }, "grenacheClient": { "query": "rest:report:api", From 7ad37ec1f31c4e600726ef8bc69028252e1c2b9d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 26 Mar 2024 13:28:18 +0200 Subject: [PATCH 019/106] Prevent event loop locking for trx tax report --- .../sync/transaction.tax.report/index.js | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index dcc42120d..ba292a374 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,5 +1,6 @@ 'use strict' +const { setImmediate } = require('node:timers/promises') const { pick } = require('lib-js-util-base') const { splitSymbolPairs @@ -80,24 +81,22 @@ class TransactionTaxReport { }) : [] + const isBackIterativeLookUp = true const { tradesWithUnrealizedProfit } = await this.#lookUpTrades( - tradesForPrevPeriod + tradesForPrevPeriod, { isBackIterativeLookUp } ) tradesForCurrPeriod.push(...tradesWithUnrealizedProfit) const { tradesWithRealizedProfit } = await this.#lookUpTrades( - tradesForCurrPeriod + tradesForCurrPeriod, { isBackIterativeLookUp } ) return tradesWithRealizedProfit } - async #lookUpTrades (trades) { - if ( - !Array.isArray(trades) || - trades.length === 0 - ) { - return [] - } + async #lookUpTrades (trades, opts) { + const { + isBackIterativeLookUp = false + } = opts ?? {} const tradesWithRealizedProfit = [] const tradesWithUnrealizedProfit = [] @@ -112,9 +111,25 @@ class TransactionTaxReport { } } - const tradeIterator = getBackIterable(trades) + let lastLoopUnlockMts = Date.now() + const tradeIterator = isBackIterativeLookUp + ? getBackIterable(trades) + : trades for (const [i, trade] of tradeIterator.entries()) { + const currentLoopUnlockMts = Date.now() + + /* + * Trx hist restoring is a hard sync operation, + * to prevent EventLoop locking more than 1sec + * it needs to resolve async queue + */ + if ((currentLoopUnlockMts - lastLoopUnlockMts) > 1000) { + await setImmediate() + + lastLoopUnlockMts = currentLoopUnlockMts + } + let isSaleTrx = false let isSaleTrxHistFilled = false let saleFilledAmount = 0 @@ -274,18 +289,6 @@ class TransactionTaxReport { tradeForLookup.mtsSold = trade.mtsCreate tradeForLookup.cost = Math.abs(tradeForLookup.amountUsd) tradeForLookup.gainOrLoss = tradeForLookup.proceeds - tradeForLookup.cost - - tradesWithRealizedProfit.push( - pick(tradeForLookup, [ - 'asset', - 'amount', - 'mtsAcquired', - 'mtsSold', - 'proceeds', - 'cost', - 'gainOrLoss' - ]) - ) } } } @@ -297,14 +300,26 @@ class TransactionTaxReport { } for (const trade of trades) { - if ( - !trade?.isBuyTrx || - trade?.isRealizedProfitDetected - ) { + if (!trade?.isBuyTrx) { continue } + if (!trade?.isRealizedProfitDetected) { + tradesWithUnrealizedProfit.push(trade) - tradesWithUnrealizedProfit.push(trade) + continue + } + + tradesWithRealizedProfit.push( + pick(trade, [ + 'asset', + 'amount', + 'mtsAcquired', + 'mtsSold', + 'proceeds', + 'cost', + 'gainOrLoss' + ]) + ) } /* From 921c0bfe06b3ae870aaef483f7c5d829b0355b01 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 1 Apr 2024 14:53:31 +0200 Subject: [PATCH 020/106] Use sale trx to declare into tax report --- .../sync/transaction.tax.report/index.js | 335 +++++++++--------- 1 file changed, 161 insertions(+), 174 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index ba292a374..e2ddc67b3 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,7 +1,6 @@ 'use strict' const { setImmediate } = require('node:timers/promises') -const { pick } = require('lib-js-util-base') const { splitSymbolPairs } = require('bfx-report/workers/loc.api/helpers') @@ -82,15 +81,15 @@ class TransactionTaxReport { : [] const isBackIterativeLookUp = true - const { tradesWithUnrealizedProfit } = await this.#lookUpTrades( + const { buyTradesWithUnrealizedProfit } = await this.#lookUpTrades( tradesForPrevPeriod, { isBackIterativeLookUp } ) - tradesForCurrPeriod.push(...tradesWithUnrealizedProfit) - const { tradesWithRealizedProfit } = await this.#lookUpTrades( + tradesForCurrPeriod.push(...buyTradesWithUnrealizedProfit) + const { saleTradesWithRealizedProfit } = await this.#lookUpTrades( tradesForCurrPeriod, { isBackIterativeLookUp } ) - return tradesWithRealizedProfit + return saleTradesWithRealizedProfit } async #lookUpTrades (trades, opts) { @@ -98,16 +97,16 @@ class TransactionTaxReport { isBackIterativeLookUp = false } = opts ?? {} - const tradesWithRealizedProfit = [] - const tradesWithUnrealizedProfit = [] + const saleTradesWithRealizedProfit = [] + const buyTradesWithUnrealizedProfit = [] if ( !Array.isArray(trades) || trades.length === 0 ) { return { - tradesWithRealizedProfit, - tradesWithUnrealizedProfit + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit } } @@ -130,10 +129,12 @@ class TransactionTaxReport { lastLoopUnlockMts = currentLoopUnlockMts } - let isSaleTrx = false - let isSaleTrxHistFilled = false - let saleFilledAmount = 0 - const buyTrxsForRealizedProfit = [] + trade.isSaleTrx = trade.isSaleTrx ?? false + trade.isSaleTrxHistFilled = trade.isSaleTrxHistFilled ?? false + trade.saleFilledAmount = trade.saleFilledAmount ?? 0 + trade.costForSaleTrx = trade.costForSaleTrx ?? 0 + trade.buyTrxsForRealizedProfit = trade + .buyTrxsForRealizedProfit ?? [] if ( !trade?.symbol || @@ -166,189 +167,175 @@ class TransactionTaxReport { trade.execAmount > 0 && !isForexSymb(lastSymb) ) - isSaleTrx = isDistinctSale || isSaleBetweenCrypto + trade.isSaleTrx = isDistinctSale || isSaleBetweenCrypto - if (isSaleTrx) { - if (!Number.isFinite(trade.amountUsd)) { + if (!trade.isSaleTrx) { + continue + } + if (!Number.isFinite(trade.amountUsd)) { + throw new CurrencyConversionError() + } + if ( + !firstSymb || + !lastSymb + ) { + throw new CurrencyPairSeparationError() + } + + const saleAmount = trade.execAmount < 0 + ? Math.abs(trade.execAmount) + : Math.abs(trade.execAmount * trade.execPrice) + const salePrice = Math.abs(trade.amountUsd) / saleAmount + const saleAsset = isDistinctSale + ? firstSymb + : lastSymb + + for (const [j, tradeForLookup] of trades.entries()) { + if (j <= i) { + continue + } + if (trade.isSaleTrxHistFilled) { + break + } + if ( + tradeForLookup?.isBuyTrxHistFilled || + !tradeForLookup?.symbol || + !Number.isFinite(tradeForLookup?.execAmount) || + tradeForLookup.execAmount === 0 || + !Number.isFinite(tradeForLookup?.execPrice) || + tradeForLookup.execPrice === 0 + ) { + continue + } + + tradeForLookup.isBuyTrx = tradeForLookup.isBuyTrx ?? false + tradeForLookup.isBuyTrxHistFilled = tradeForLookup + .isBuyTrxHistFilled ?? false + tradeForLookup.buyFilledAmount = tradeForLookup + .buyFilledAmount ?? 0 + tradeForLookup.proceedsForBuyTrx = tradeForLookup.proceedsForBuyTrx ?? 0 + tradeForLookup.saleTrxsForRealizedProfit = tradeForLookup + .saleTrxsForRealizedProfit ?? [] + + const [firstSymbForLookup, lastSymbForLookup] = ( + tradeForLookup?.firstSymb && + tradeForLookup?.lastSymb + ) + ? [tradeForLookup?.firstSymb, tradeForLookup?.lastSymb] + : splitSymbolPairs(tradeForLookup.symbol) + tradeForLookup.firstSymb = firstSymbForLookup + tradeForLookup.lastSymb = lastSymbForLookup + + if (!Number.isFinite(tradeForLookup.amountUsd)) { throw new CurrencyConversionError() } if ( - !firstSymb || - !lastSymb + !firstSymbForLookup || + !lastSymbForLookup ) { throw new CurrencyPairSeparationError() } - const saleAmount = trade.execAmount < 0 - ? Math.abs(trade.execAmount) - : Math.abs(trade.execAmount * trade.execPrice) - const salePrice = Math.abs(trade.amountUsd) / saleAmount - const saleAsset = isDistinctSale - ? firstSymb - : lastSymb - - for (const [j, tradeForLookup] of trades.entries()) { - if (j <= i) { - continue - } - if (isSaleTrxHistFilled) { - break - } - if ( - tradeForLookup?.isBuyTrxHistFilled || - !tradeForLookup?.symbol || - !Number.isFinite(tradeForLookup?.execAmount) || - tradeForLookup.execAmount === 0 || - !Number.isFinite(tradeForLookup?.execPrice) || - tradeForLookup.execPrice === 0 - ) { - continue - } - - tradeForLookup.isBuyTrx = tradeForLookup.isBuyTrx ?? false - tradeForLookup.isBuyTrxHistFilled = tradeForLookup - .isBuyTrxHistFilled ?? false - tradeForLookup.isRealizedProfitDetected = tradeForLookup - .isRealizedProfitDetected ?? false - tradeForLookup.buyFilledAmount = tradeForLookup - .buyFilledAmount ?? 0 - tradeForLookup.proceeds = tradeForLookup.proceeds ?? 0 - tradeForLookup.saleTrxsForRealizedProfit = tradeForLookup - .saleTrxsForRealizedProfit ?? [] - - const [firstSymbForLookup, lastSymbForLookup] = ( - tradeForLookup?.firstSymb && - tradeForLookup?.lastSymb - ) - ? [tradeForLookup?.firstSymb, tradeForLookup?.lastSymb] - : splitSymbolPairs(tradeForLookup.symbol) - tradeForLookup.firstSymb = firstSymbForLookup - tradeForLookup.lastSymb = lastSymbForLookup - - if (!Number.isFinite(tradeForLookup.amountUsd)) { - throw new CurrencyConversionError() - } - if ( - !firstSymbForLookup || - !lastSymbForLookup - ) { - throw new CurrencyPairSeparationError() - } - - if ( - tradeForLookup.execAmount < 0 && - isForexSymb(lastSymbForLookup) - ) { - continue - } - - const asset = tradeForLookup.execAmount > 0 - ? firstSymbForLookup - : lastSymbForLookup - - if (saleAsset !== asset) { - continue - } - - tradeForLookup.isBuyTrx = true - tradeForLookup.saleTrxsForRealizedProfit.push(trade) - buyTrxsForRealizedProfit.push(tradeForLookup) - - const buyAmount = tradeForLookup.execAmount > 0 - ? Math.abs(tradeForLookup.execAmount) - : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) - const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount - const saleRestAmount = saleAmount - saleFilledAmount - - if (buyRestAmount < saleRestAmount) { - tradeForLookup.buyFilledAmount = buyAmount - saleFilledAmount += buyRestAmount - tradeForLookup.proceeds += buyRestAmount * salePrice - tradeForLookup.isRealizedProfitDetected = true - tradeForLookup.isBuyTrxHistFilled = true - } - if (buyRestAmount > saleRestAmount) { - tradeForLookup.buyFilledAmount += saleRestAmount - saleFilledAmount = saleAmount - tradeForLookup.proceeds += saleRestAmount * salePrice - isSaleTrxHistFilled = true - } - if (buyRestAmount === saleRestAmount) { - tradeForLookup.buyFilledAmount = buyAmount - saleFilledAmount = saleAmount - tradeForLookup.proceeds += buyRestAmount * salePrice - tradeForLookup.isRealizedProfitDetected = true - tradeForLookup.isBuyTrxHistFilled = true - isSaleTrxHistFilled = true - } - - if (tradeForLookup.isRealizedProfitDetected) { - tradeForLookup.asset = asset - tradeForLookup.amount = Math.abs(tradeForLookup.execAmount) - tradeForLookup.mtsAcquired = tradeForLookup.mtsCreate - tradeForLookup.mtsSold = trade.mtsCreate - tradeForLookup.cost = Math.abs(tradeForLookup.amountUsd) - tradeForLookup.gainOrLoss = tradeForLookup.proceeds - tradeForLookup.cost - } + if ( + tradeForLookup.execAmount < 0 && + isForexSymb(lastSymbForLookup) + ) { + continue + } + + const buyAsset = tradeForLookup.execAmount > 0 + ? firstSymbForLookup + : lastSymbForLookup + + if (saleAsset !== buyAsset) { + continue + } + + tradeForLookup.isBuyTrx = true + tradeForLookup.saleTrxsForRealizedProfit.push(trade) + trade.buyTrxsForRealizedProfit.push(tradeForLookup) + + const buyAmount = tradeForLookup.execAmount > 0 + ? Math.abs(tradeForLookup.execAmount) + : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) + const buyPrice = Math.abs(tradeForLookup.amountUsd) / buyAmount + const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount + const saleRestAmount = saleAmount - trade.saleFilledAmount + + if (buyRestAmount < saleRestAmount) { + tradeForLookup.buyFilledAmount = buyAmount + trade.saleFilledAmount += buyRestAmount + tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice + trade.costForSaleTrx += buyRestAmount * buyPrice + tradeForLookup.isBuyTrxHistFilled = true + } + if (buyRestAmount > saleRestAmount) { + tradeForLookup.buyFilledAmount += saleRestAmount + trade.saleFilledAmount = saleAmount + tradeForLookup.proceedsForBuyTrx += saleRestAmount * salePrice + trade.costForSaleTrx += saleRestAmount * buyPrice + trade.isSaleTrxHistFilled = true + } + if (buyRestAmount === saleRestAmount) { + tradeForLookup.buyFilledAmount = buyAmount + trade.saleFilledAmount = saleAmount + tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice + trade.costForSaleTrx += buyRestAmount * buyPrice + tradeForLookup.isBuyTrxHistFilled = true + trade.isSaleTrxHistFilled = true + } + + if (tradeForLookup.isBuyTrxHistFilled) { + tradeForLookup.buyAsset = buyAsset + tradeForLookup.buyAmount = buyAmount + tradeForLookup.mtsAcquiredForBuyTrx = tradeForLookup.mtsCreate + tradeForLookup.mtsSoldForBuyTrx = trade.mtsCreate + tradeForLookup.costForBuyTrx = Math.abs(tradeForLookup.amountUsd) + tradeForLookup.gainOrLossForBuyTrx = tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx } } - trade.isSaleTrx = isSaleTrx - trade.isSaleTrxHistFilled = isSaleTrxHistFilled - trade.saleFilledAmount = saleFilledAmount - trade.buyTrxsForRealizedProfit = buyTrxsForRealizedProfit + if (trade.isSaleTrxHistFilled) { + trade.saleAsset = saleAsset + trade.saleAmount = saleAmount + trade.mtsAcquiredForSaleTrx = ( + trade.buyTrxsForRealizedProfit[0]?.mtsCreate > + trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + ) + ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + : trade.buyTrxsForRealizedProfit[0]?.mtsCreate + trade.mtsSoldForSaleTrx = trade.mtsCreate + trade.proceedsForSaleTrx = Math.abs(trade.amountUsd) + trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx + } } for (const trade of trades) { - if (!trade?.isBuyTrx) { - continue + if ( + trade?.isBuyTrx && + !trade?.isBuyTrxHistFilled + ) { + buyTradesWithUnrealizedProfit.push(trade) } - if (!trade?.isRealizedProfitDetected) { - tradesWithUnrealizedProfit.push(trade) + if (!trade?.isSaleTrxHistFilled) { continue } - tradesWithRealizedProfit.push( - pick(trade, [ - 'asset', - 'amount', - 'mtsAcquired', - 'mtsSold', - 'proceeds', - 'cost', - 'gainOrLoss' - ]) - ) + saleTradesWithRealizedProfit.push({ + asset: trade.saleAsset, + amount: trade.saleAmount, + mtsAcquired: trade.mtsAcquiredForSaleTrx, + mtsSold: trade.mtsSoldForSaleTrx, + proceeds: trade.proceedsForSaleTrx, + cost: trade.costForSaleTrx, + gainOrLoss: trade.gainOrLoss + }) } - /* - * Data structure examples: - * - for tradesWithRealizedProfit: - * [{ - * asset: 'BTC', - * amount: 0.001, - * mtsAcquired: Date.now(), - * mtsSold: Date.now(), - * proceeds: 2.86, - * cost: 26.932, - * gainOrLoss: -24.072 - * }] - * - * - for tradesWithUnrealizedProfit: - * [{ - * ...trade, - * isBuyTrx: true, - * isBuyTrxHistFilled: false, - * isRealizedProfitDetected: false, - * buyFilledAmount: 0.001, - * proceeds: 2.86, - * saleTrxsForRealizedProfit: [] - * }] - */ return { - tradesWithRealizedProfit, - tradesWithUnrealizedProfit + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit } } From aaa1bce43c3c1d94cfb2f04305502c5265580eca Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 2 Apr 2024 13:52:58 +0300 Subject: [PATCH 021/106] Improve lookup buy trx with unrealized profit --- .../sync/transaction.tax.report/index.js | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index e2ddc67b3..4ff86271b 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -71,18 +71,21 @@ class TransactionTaxReport { } const tradesForPrevPeriod = start > 0 - ? await this.trades.getTrades({ - auth: user, - params: { - start: 0, - end: start - 1 - } + ? await this.#getTrades({ + user, + start: 0, + end: start - 1 }) : [] const isBackIterativeLookUp = true const { buyTradesWithUnrealizedProfit } = await this.#lookUpTrades( - tradesForPrevPeriod, { isBackIterativeLookUp } + tradesForPrevPeriod, + { + isBackIterativeLookUp, + buyTradesWithUnrealizedProfit: true, + isNotGainOrLossRequired: true + } ) tradesForCurrPeriod.push(...buyTradesWithUnrealizedProfit) const { saleTradesWithRealizedProfit } = await this.#lookUpTrades( @@ -94,7 +97,9 @@ class TransactionTaxReport { async #lookUpTrades (trades, opts) { const { - isBackIterativeLookUp = false + isBackIterativeLookUp = false, + isBuyTradesWithUnrealizedProfitRequired = false, + isNotGainOrLossRequired = false } = opts ?? {} const saleTradesWithRealizedProfit = [] @@ -172,7 +177,10 @@ class TransactionTaxReport { if (!trade.isSaleTrx) { continue } - if (!Number.isFinite(trade.amountUsd)) { + if ( + !isNotGainOrLossRequired && + !Number.isFinite(trade.amountUsd) + ) { throw new CurrencyConversionError() } if ( @@ -185,18 +193,20 @@ class TransactionTaxReport { const saleAmount = trade.execAmount < 0 ? Math.abs(trade.execAmount) : Math.abs(trade.execAmount * trade.execPrice) - const salePrice = Math.abs(trade.amountUsd) / saleAmount + const salePrice = isNotGainOrLossRequired + ? 0 + : Math.abs(trade.amountUsd) / saleAmount const saleAsset = isDistinctSale ? firstSymb : lastSymb - for (const [j, tradeForLookup] of trades.entries()) { - if (j <= i) { - continue - } + for (let j = i + 1; trades.length > j; j += 1) { if (trade.isSaleTrxHistFilled) { break } + + const tradeForLookup = trades[j] + if ( tradeForLookup?.isBuyTrxHistFilled || !tradeForLookup?.symbol || @@ -226,7 +236,10 @@ class TransactionTaxReport { tradeForLookup.firstSymb = firstSymbForLookup tradeForLookup.lastSymb = lastSymbForLookup - if (!Number.isFinite(tradeForLookup.amountUsd)) { + if ( + !isNotGainOrLossRequired && + !Number.isFinite(tradeForLookup.amountUsd) + ) { throw new CurrencyConversionError() } if ( @@ -258,7 +271,9 @@ class TransactionTaxReport { const buyAmount = tradeForLookup.execAmount > 0 ? Math.abs(tradeForLookup.execAmount) : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) - const buyPrice = Math.abs(tradeForLookup.amountUsd) / buyAmount + const buyPrice = isNotGainOrLossRequired + ? 0 + : Math.abs(tradeForLookup.amountUsd) / buyAmount const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount const saleRestAmount = saleAmount - trade.saleFilledAmount @@ -290,8 +305,12 @@ class TransactionTaxReport { tradeForLookup.buyAmount = buyAmount tradeForLookup.mtsAcquiredForBuyTrx = tradeForLookup.mtsCreate tradeForLookup.mtsSoldForBuyTrx = trade.mtsCreate - tradeForLookup.costForBuyTrx = Math.abs(tradeForLookup.amountUsd) - tradeForLookup.gainOrLossForBuyTrx = tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx + tradeForLookup.costForBuyTrx = isNotGainOrLossRequired + ? 0 + : Math.abs(tradeForLookup.amountUsd) + tradeForLookup.gainOrLossForBuyTrx = isNotGainOrLossRequired + ? 0 + : tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx } } @@ -305,20 +324,28 @@ class TransactionTaxReport { ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate : trade.buyTrxsForRealizedProfit[0]?.mtsCreate trade.mtsSoldForSaleTrx = trade.mtsCreate - trade.proceedsForSaleTrx = Math.abs(trade.amountUsd) - trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx + trade.proceedsForSaleTrx = isNotGainOrLossRequired + ? 0 + : Math.abs(trade.amountUsd) + trade.gainOrLoss = isNotGainOrLossRequired + ? 0 + : trade.proceedsForSaleTrx - trade.costForSaleTrx } } for (const trade of trades) { if ( + isBuyTradesWithUnrealizedProfitRequired && trade?.isBuyTrx && !trade?.isBuyTrxHistFilled ) { buyTradesWithUnrealizedProfit.push(trade) } - if (!trade?.isSaleTrxHistFilled) { + if ( + isBuyTradesWithUnrealizedProfitRequired || + !trade?.isSaleTrxHistFilled + ) { continue } @@ -339,7 +366,6 @@ class TransactionTaxReport { } } - // TODO: async #getTrades ({ user, start, From 3e91d517cc24eb41028615e881bd427969b0f68a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 3 Apr 2024 15:29:31 +0300 Subject: [PATCH 022/106] Rework trx tax report to consider deposit-withdrawal --- .../sync/transaction.tax.report/index.js | 247 ++++++++++++++---- 1 file changed, 198 insertions(+), 49 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 4ff86271b..bbeccc00c 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,5 +1,6 @@ 'use strict' +const { orderBy } = require('lodash') const { setImmediate } = require('node:timers/promises') const { splitSymbolPairs @@ -22,7 +23,9 @@ const depsTypes = (TYPES) => [ TYPES.SyncSchema, TYPES.ALLOWED_COLLS, TYPES.SYNC_API_METHODS, - TYPES.Trades + TYPES.Movements, + TYPES.RService, + TYPES.GetDataFromApi ] class TransactionTaxReport { constructor ( @@ -31,17 +34,19 @@ class TransactionTaxReport { syncSchema, ALLOWED_COLLS, SYNC_API_METHODS, - trades + movements, + rService, + getDataFromApi ) { this.dao = dao this.authenticator = authenticator this.syncSchema = syncSchema this.ALLOWED_COLLS = ALLOWED_COLLS this.SYNC_API_METHODS = SYNC_API_METHODS - this.trades = trades + this.movements = movements + this.rService = rService + this.getDataFromApi = getDataFromApi - this.tradesMethodColl = this.syncSchema.getMethodCollMap() - .get(this.SYNC_API_METHODS.TRADES) this.tradesModel = this.syncSchema.getModelsMap() .get(this.ALLOWED_COLLS.TRADES) } @@ -55,23 +60,26 @@ class TransactionTaxReport { const user = await this.authenticator .verifyRequestUser({ auth }) - const tradesForCurrPeriod = await this.trades.getTrades({ - auth: user, - params: { - start, - end - } + const { + trxs: trxsForCurrPeriod, + trxsForConvToUsd + } = await this.#getTrxs({ + user, + start, + end }) if ( - !Array.isArray(tradesForCurrPeriod) || - tradesForCurrPeriod.length === 0 + !Array.isArray(trxsForCurrPeriod) || + trxsForCurrPeriod.length === 0 ) { return [] } - const tradesForPrevPeriod = start > 0 - ? await this.#getTrades({ + const { + trxs: trxsForPrevPeriod + } = start > 0 + ? await this.#getTrxs({ user, start: 0, end: start - 1 @@ -80,16 +88,21 @@ class TransactionTaxReport { const isBackIterativeLookUp = true const { buyTradesWithUnrealizedProfit } = await this.#lookUpTrades( - tradesForPrevPeriod, + trxsForPrevPeriod, { isBackIterativeLookUp, buyTradesWithUnrealizedProfit: true, isNotGainOrLossRequired: true } ) - tradesForCurrPeriod.push(...buyTradesWithUnrealizedProfit) + + trxsForCurrPeriod.push(...buyTradesWithUnrealizedProfit) + trxsForConvToUsd.push(...buyTradesWithUnrealizedProfit + .filter((trx) => trx?.lastSymb !== 'USD')) + await this.#convertCurrencies(trxsForConvToUsd) + const { saleTradesWithRealizedProfit } = await this.#lookUpTrades( - tradesForCurrPeriod, { isBackIterativeLookUp } + trxsForCurrPeriod, { isBackIterativeLookUp } ) return saleTradesWithRealizedProfit @@ -177,12 +190,6 @@ class TransactionTaxReport { if (!trade.isSaleTrx) { continue } - if ( - !isNotGainOrLossRequired && - !Number.isFinite(trade.amountUsd) - ) { - throw new CurrencyConversionError() - } if ( !firstSymb || !lastSymb @@ -193,13 +200,18 @@ class TransactionTaxReport { const saleAmount = trade.execAmount < 0 ? Math.abs(trade.execAmount) : Math.abs(trade.execAmount * trade.execPrice) - const salePrice = isNotGainOrLossRequired - ? 0 - : Math.abs(trade.amountUsd) / saleAmount + const _salePrice = isDistinctSale + ? trade.firstSymbPrise + : trade.lastSymbPrise + const salePrice = isNotGainOrLossRequired ? 0 : _salePrice const saleAsset = isDistinctSale ? firstSymb : lastSymb + if (!Number.isFinite(salePrice)) { + throw new CurrencyConversionError() + } + for (let j = i + 1; trades.length > j; j += 1) { if (trade.isSaleTrxHistFilled) { break @@ -236,12 +248,6 @@ class TransactionTaxReport { tradeForLookup.firstSymb = firstSymbForLookup tradeForLookup.lastSymb = lastSymbForLookup - if ( - !isNotGainOrLossRequired && - !Number.isFinite(tradeForLookup.amountUsd) - ) { - throw new CurrencyConversionError() - } if ( !firstSymbForLookup || !lastSymbForLookup @@ -271,12 +277,17 @@ class TransactionTaxReport { const buyAmount = tradeForLookup.execAmount > 0 ? Math.abs(tradeForLookup.execAmount) : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) - const buyPrice = isNotGainOrLossRequired - ? 0 - : Math.abs(tradeForLookup.amountUsd) / buyAmount + const _buyPrice = tradeForLookup.execAmount > 0 + ? tradeForLookup.firstSymbPrise + : tradeForLookup.lastSymbPrise + const buyPrice = isNotGainOrLossRequired ? 0 : _buyPrice const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount const saleRestAmount = saleAmount - trade.saleFilledAmount + if (!Number.isFinite(buyPrice)) { + throw new CurrencyConversionError() + } + if (buyRestAmount < saleRestAmount) { tradeForLookup.buyFilledAmount = buyAmount trade.saleFilledAmount += buyRestAmount @@ -305,12 +316,8 @@ class TransactionTaxReport { tradeForLookup.buyAmount = buyAmount tradeForLookup.mtsAcquiredForBuyTrx = tradeForLookup.mtsCreate tradeForLookup.mtsSoldForBuyTrx = trade.mtsCreate - tradeForLookup.costForBuyTrx = isNotGainOrLossRequired - ? 0 - : Math.abs(tradeForLookup.amountUsd) - tradeForLookup.gainOrLossForBuyTrx = isNotGainOrLossRequired - ? 0 - : tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx + tradeForLookup.costForBuyTrx = buyAmount * buyPrice + tradeForLookup.gainOrLossForBuyTrx = tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx } } @@ -324,12 +331,8 @@ class TransactionTaxReport { ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate : trade.buyTrxsForRealizedProfit[0]?.mtsCreate trade.mtsSoldForSaleTrx = trade.mtsCreate - trade.proceedsForSaleTrx = isNotGainOrLossRequired - ? 0 - : Math.abs(trade.amountUsd) - trade.gainOrLoss = isNotGainOrLossRequired - ? 0 - : trade.proceedsForSaleTrx - trade.costForSaleTrx + trade.proceedsForSaleTrx = saleAmount * salePrice + trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx } } @@ -344,7 +347,8 @@ class TransactionTaxReport { if ( isBuyTradesWithUnrealizedProfitRequired || - !trade?.isSaleTrxHistFilled + !trade?.isSaleTrxHistFilled || + trade?.isMovements ) { continue } @@ -395,6 +399,151 @@ class TransactionTaxReport { } ) } + + async #getTrxs (params) { + const { + user, + start, + end + } = params ?? {} + + const tradesPromise = this.#getTrades(params) + const movementsPromise = this.movements.getMovements({ + auth: user, + start, + end, + isWithdrawals: true, + isDeposits: true + }) + + const [ + trades, + movements + ] = await Promise.all([ + tradesPromise, + movementsPromise + ]) + + const remapedTrxs = [] + const remapedTrxsForConvToUsd = [] + + for (const trade of trades) { + if ( + !trade?.symbol || + !Number.isFinite(trade?.execAmount) || + trade.execAmount === 0 || + !Number.isFinite(trade?.execPrice) || + trade.execPrice === 0 || + !Number.isFinite(trade?.mtsCreate) + ) { + continue + } + + const [firstSymb, lastSymb] = splitSymbolPairs(trade.symbol) + trade.firstSymb = firstSymb + trade.lastSymb = lastSymb + trade.firstSymbPrise = null + trade.lastSymbPrise = null + + remapedTrxs.push(trade) + + if (lastSymb === 'USD') { + trade.firstSymbPrise = trade.execPrice + trade.lastSymbPrise = 1 + + continue + } + + remapedTrxsForConvToUsd.push(trade) + } + + if (remapedTrxs.length === 0) { + return { + trx: [], + trxForConvToUsd: [] + } + } + + for (const movement of movements) { + if ( + !movement?.currency || + isForexSymb(movement.currency) || + !Number.isFinite(movement?.amount) || + movement.amount === 0 || + !Number.isFinite(movement?.mtsUpdated) + ) { + continue + } + + const firstSymb = movement.currency + const lastSymb = 'USD' + const symbSeparator = firstSymb.length > 3 + ? ':' + : '' + + const remapedMovement = { + isMovements: true, + symbol: `t${firstSymb}${symbSeparator}${lastSymb}`, + mtsCreate: movement.mtsUpdated, + firstSymb, + lastSymb, + firstSymbPrise: null, + lastSymbPrise: 1, + execAmount: movement.amount, + // execPrice = firstSymbPrise and should be set when converting currencies + execPrice: 0 + } + + remapedTrxs.push(remapedMovement) + remapedTrxsForConvToUsd.push(remapedMovement) + } + + const trxs = orderBy( + remapedTrxs, + ['mtsCreate'], + ['desc'] + ) + const trxsForConvToUsd = orderBy( + remapedTrxsForConvToUsd, + ['mtsCreate'], + ['desc'] + ) + + return { + trxs, + trxsForConvToUsd + } + } + + // TODO: + async #convertCurrencies (trxs) {} + + async #getPublicTrades (params) { + const { + symbol, + start = 0, + end = Date.now(), + sort = -1 + } = params ?? {} + const args = { + isNotMoreThanInnerMax: true, + params: { symbol, start, end, sort } + } + + const getDataFn = this.rService[this.SYNC_API_METHODS.PUBLIC_TRADES] + .bind(this.rService) + + const res = await this.getDataFromApi({ + getData: (s, args) => getDataFn(args), + args, + callerName: 'TRANSACTION_TAX_REPORT', + eNetErrorAttemptsTimeframeMin: 10, + eNetErrorAttemptsTimeoutMs: 10000, + shouldNotInterrupt: true + }) + + return res + } } decorateInjectable(TransactionTaxReport, depsTypes) From 3a413225fd00f32bd22f3af6f8d41dfe3079cb20 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 4 Apr 2024 16:53:05 +0300 Subject: [PATCH 023/106] Implement currency conversion for trx tax report --- .../sync/transaction.tax.report/index.js | 196 ++++++++++++++++-- 1 file changed, 175 insertions(+), 21 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index bbeccc00c..dd262c3d5 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,6 +1,5 @@ 'use strict' -const { orderBy } = require('lodash') const { setImmediate } = require('node:timers/promises') const { splitSymbolPairs @@ -84,7 +83,7 @@ class TransactionTaxReport { start: 0, end: start - 1 }) - : [] + : { trxs: [] } const isBackIterativeLookUp = true const { buyTradesWithUnrealizedProfit } = await this.#lookUpTrades( @@ -408,22 +407,30 @@ class TransactionTaxReport { } = params ?? {} const tradesPromise = this.#getTrades(params) - const movementsPromise = this.movements.getMovements({ + const withdrawalsPromise = this.movements.getMovements({ + auth: user, + start, + end, + isWithdrawals: true + }) + const depositsPromise = this.movements.getMovements({ auth: user, start, end, - isWithdrawals: true, isDeposits: true }) const [ trades, - movements + withdrawals, + deposits ] = await Promise.all([ tradesPromise, - movementsPromise + withdrawalsPromise, + depositsPromise ]) + const movements = [...withdrawals, ...deposits] const remapedTrxs = [] const remapedTrxsForConvToUsd = [] @@ -460,7 +467,7 @@ class TransactionTaxReport { if (remapedTrxs.length === 0) { return { trx: [], - trxForConvToUsd: [] + trxsForConvToUsd: [] } } @@ -490,7 +497,7 @@ class TransactionTaxReport { firstSymbPrise: null, lastSymbPrise: 1, execAmount: movement.amount, - // execPrice = firstSymbPrise and should be set when converting currencies + // NOTE: execPrice = firstSymbPrise and should be set when converting currencies execPrice: 0 } @@ -498,16 +505,10 @@ class TransactionTaxReport { remapedTrxsForConvToUsd.push(remapedMovement) } - const trxs = orderBy( - remapedTrxs, - ['mtsCreate'], - ['desc'] - ) - const trxsForConvToUsd = orderBy( - remapedTrxsForConvToUsd, - ['mtsCreate'], - ['desc'] - ) + const trxs = remapedTrxs + .sort((a, b) => b?.mtsCreate - a?.mtsCreate) + const trxsForConvToUsd = remapedTrxsForConvToUsd + .sort((a, b) => b?.mtsCreate - a?.mtsCreate) return { trxs, @@ -516,18 +517,171 @@ class TransactionTaxReport { } // TODO: - async #convertCurrencies (trxs) {} + async #convertCurrencies (trxs) { + const trxMapByCcy = new Map() + + for (const trx of trxs) { + const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) + const isNotLastSymbForex = !isForexSymb(trx.lastSymb) + + if (isNotFirstSymbForex) { + if (!trxMapByCcy.has(trx.firstSymb)) { + trxMapByCcy.set(trx.firstSymb, []) + } + + trxMapByCcy.get(trx.firstSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'firstSymbPrise', + secondPrisePropName: 'lastSymbPrise', + trx + }) + } + if (isNotLastSymbForex) { + if (!trxMapByCcy.has(trx.lastSymb)) { + trxMapByCcy.set(trx.lastSymb, []) + } + + trxMapByCcy.get(trx.lastSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'lastSymbPrise', + secondPrisePropName: 'firstSymbPrise', + trx + }) + } + } + for (const [symbol, trxData] of trxMapByCcy.entries()) { + // TODO: + const start = ( + trxData[trxData.length - 1].trx.mtsCreate + + 1000 * 60 + ) + const end = trxData[0].trx.mtsCreate + + const pubTrades = await this.#getPublicTradesRange({ + symbol, + start, + end + }) + + for (const trxDataItem of trxData) { + let lastIndex = 0 + + for (let i = lastIndex + 1; pubTrades.length > i; i += 1) { + const pubTrade = pubTrades[i] + const isLastPubTrade = (i + 1) === pubTrades.length + + if ( + ( + pubTrade?.mts > trxDataItem.trx.mtsCreate && + !isLastPubTrade + ) || + !Number.isFinite(pubTrade?.price) || + pubTrade.price === 0 + ) { + continue + } + + lastIndex = i + trxDataItem.trx[trxDataItem.mainPrisePropName] = pubTrade.price + + if (trxDataItem.trx.isMovements) { + trxDataItem.trx.execPrice = pubTrade.price + + break + } + if ( + !Number.isFinite(trxDataItem.trx.execPrice) || + trxDataItem.trx.execPrice === 0 + ) { + break + } + if ( + trxDataItem.isNotFirstSymbForex && + !trxDataItem.isNotFirstSymbForex + ) { + trxDataItem.trx[trxDataItem.secondPrisePropName] = pubTrade.price / trxDataItem.trx.execPrice + } + if ( + !trxDataItem.isNotFirstSymbForex && + trxDataItem.isNotFirstSymbForex + ) { + trxDataItem.trx[trxDataItem.secondPrisePropName] = pubTrade.price * trxDataItem.trx.execPrice + } + + break + } + } + } + } + + async #getPublicTradesRange (params) { + const symbol = params?.symbol + const start = params?.start + let end = params?.end + let timeoutMts = Date.now() + const res = [] + + while (true) { + const currMts = Date.now() + const mtsDiff = currMts - timeoutMts + + if (mtsDiff > 1000 * 60 * 60 * 12) { + // TODO: + throw new Error('ERR_TRX_TAX_REPORT_GENERATION_TIMEOUT_ERROR') + } + + timeoutMts = currMts + + const { res: pubTrades } = await this.#getPublicTrades({ + symbol: `t${symbol}USD`, + start, + end + }) + + if ( + !Array.isArray(pubTrades) || + pubTrades.length === 0 || + !Number.isFinite(pubTrades[0]?.mts) || + !Number.isFinite(pubTrades[pubTrades.length - 1]?.mts) || + ( + res.length !== 0 && + pubTrades[0]?.mts >= res[res.length - 1]?.mts + ) || + pubTrades[pubTrades.length - 1]?.mts <= start + ) { + res.push(...pubTrades) + + break + } + + end = pubTrades[pubTrades.length - 1].mts - 1 + res.push(...pubTrades) + } + + return res + } async #getPublicTrades (params) { const { symbol, start = 0, end = Date.now(), - sort = -1 + sort = -1, + limit = 10000 } = params ?? {} const args = { isNotMoreThanInnerMax: true, - params: { symbol, start, end, sort } + params: { + symbol, + start, + end, + sort, + limit, + notCheckNextPage: true, + notThrowError: true + } } const getDataFn = this.rService[this.SYNC_API_METHODS.PUBLIC_TRADES] From f20ba5fa350b0b86a5c87a4247a24f65de6ebe8c Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 5 Apr 2024 09:15:26 +0200 Subject: [PATCH 024/106] Speed up currency conversion for trx tax report --- .../sync/transaction.tax.report/index.js | 167 +++++++++++------- 1 file changed, 106 insertions(+), 61 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index dd262c3d5..9c023feae 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -320,19 +320,17 @@ class TransactionTaxReport { } } - if (trade.isSaleTrxHistFilled) { - trade.saleAsset = saleAsset - trade.saleAmount = saleAmount - trade.mtsAcquiredForSaleTrx = ( - trade.buyTrxsForRealizedProfit[0]?.mtsCreate > - trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate - ) - ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate - : trade.buyTrxsForRealizedProfit[0]?.mtsCreate - trade.mtsSoldForSaleTrx = trade.mtsCreate - trade.proceedsForSaleTrx = saleAmount * salePrice - trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx - } + trade.saleAsset = saleAsset + trade.saleAmount = saleAmount + trade.mtsAcquiredForSaleTrx = ( + trade.buyTrxsForRealizedProfit[0]?.mtsCreate > + trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + ) + ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + : trade.buyTrxsForRealizedProfit[0]?.mtsCreate + trade.mtsSoldForSaleTrx = trade.mtsCreate + trade.proceedsForSaleTrx = saleAmount * salePrice + trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx } for (const trade of trades) { @@ -346,7 +344,7 @@ class TransactionTaxReport { if ( isBuyTradesWithUnrealizedProfitRequired || - !trade?.isSaleTrxHistFilled || + !trade?.isSaleTrx || trade?.isMovements ) { continue @@ -516,54 +514,21 @@ class TransactionTaxReport { } } - // TODO: async #convertCurrencies (trxs) { - const trxMapByCcy = new Map() - - for (const trx of trxs) { - const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) - const isNotLastSymbForex = !isForexSymb(trx.lastSymb) - - if (isNotFirstSymbForex) { - if (!trxMapByCcy.has(trx.firstSymb)) { - trxMapByCcy.set(trx.firstSymb, []) - } + const trxMapByCcy = this.#getTrxMapByCcy(trxs) - trxMapByCcy.get(trx.firstSymb).push({ - isNotFirstSymbForex, - isNotLastSymbForex, - mainPrisePropName: 'firstSymbPrise', - secondPrisePropName: 'lastSymbPrise', - trx - }) - } - if (isNotLastSymbForex) { - if (!trxMapByCcy.has(trx.lastSymb)) { - trxMapByCcy.set(trx.lastSymb, []) - } - - trxMapByCcy.get(trx.lastSymb).push({ - isNotFirstSymbForex, - isNotLastSymbForex, - mainPrisePropName: 'lastSymbPrise', - secondPrisePropName: 'firstSymbPrise', - trx - }) - } - } for (const [symbol, trxData] of trxMapByCcy.entries()) { - // TODO: - const start = ( - trxData[trxData.length - 1].trx.mtsCreate + - 1000 * 60 + const pubTrades = [] + const pubTradeChunkPayloads = this.#getPubTradeChunkPayloads( + symbol, + trxData ) - const end = trxData[0].trx.mtsCreate - const pubTrades = await this.#getPublicTradesRange({ - symbol, - start, - end - }) + for (const chunkPayload of pubTradeChunkPayloads) { + const chunk = await this.#getPublicTradeChunk(chunkPayload) + + pubTrades.push(...chunk) + } for (const trxDataItem of trxData) { let lastIndex = 0 @@ -601,13 +566,17 @@ class TransactionTaxReport { trxDataItem.isNotFirstSymbForex && !trxDataItem.isNotFirstSymbForex ) { - trxDataItem.trx[trxDataItem.secondPrisePropName] = pubTrade.price / trxDataItem.trx.execPrice + trxDataItem.trx[trxDataItem.secondPrisePropName] = ( + pubTrade.price / trxDataItem.trx.execPrice + ) } if ( !trxDataItem.isNotFirstSymbForex && trxDataItem.isNotFirstSymbForex ) { - trxDataItem.trx[trxDataItem.secondPrisePropName] = pubTrade.price * trxDataItem.trx.execPrice + trxDataItem.trx[trxDataItem.secondPrisePropName] = ( + pubTrade.price * trxDataItem.trx.execPrice + ) } break @@ -616,7 +585,82 @@ class TransactionTaxReport { } } - async #getPublicTradesRange (params) { + #getTrxMapByCcy (trxs) { + const trxMapByCcy = new Map() + + for (const trx of trxs) { + const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) + const isNotLastSymbForex = !isForexSymb(trx.lastSymb) + + if (isNotFirstSymbForex) { + if (!trxMapByCcy.has(trx.firstSymb)) { + trxMapByCcy.set(trx.firstSymb, []) + } + + trxMapByCcy.get(trx.firstSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'firstSymbPrise', + secondPrisePropName: 'lastSymbPrise', + trx + }) + } + if (isNotLastSymbForex) { + if (!trxMapByCcy.has(trx.lastSymb)) { + trxMapByCcy.set(trx.lastSymb, []) + } + + trxMapByCcy.get(trx.lastSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'lastSymbPrise', + secondPrisePropName: 'firstSymbPrise', + trx + }) + } + } + + return trxMapByCcy + } + + #getPubTradeChunkPayloads (symbol, trxData) { + const pubTradeChunkPayloads = [] + + for (const { trx } of trxData) { + const lastPayloads = pubTradeChunkPayloads[pubTradeChunkPayloads.length - 1] + const lastMts = lastPayloads?.start ?? lastPayloads?.end + const currMts = trx.mtsCreate + + if (!lastPayloads?.end) { + pubTradeChunkPayloads.push({ + symbol, + end: currMts, + start: null + }) + + continue + } + + const mtsDiff = lastMts - currMts + const maxAllowedTimeframe = 1000 * 60 * 60 * 24 + + if (mtsDiff < maxAllowedTimeframe) { + lastPayloads.start = currMts + + continue + } + + pubTradeChunkPayloads.push({ + symbol, + end: currMts, + start: null + }) + } + + return pubTradeChunkPayloads + } + + async #getPublicTradeChunk (params) { const symbol = params?.symbol const start = params?.start let end = params?.end @@ -636,13 +680,14 @@ class TransactionTaxReport { const { res: pubTrades } = await this.#getPublicTrades({ symbol: `t${symbol}USD`, - start, + start: 0, end }) if ( !Array.isArray(pubTrades) || pubTrades.length === 0 || + !Number.isFinite(start) || !Number.isFinite(pubTrades[0]?.mts) || !Number.isFinite(pubTrades[pubTrades.length - 1]?.mts) || ( From ec92940fad33d07b5990f0237f3df89c3ea8103b Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 5 Apr 2024 09:46:29 +0200 Subject: [PATCH 025/106] Add TrxTaxReportGenerationTimeoutError error class --- workers/loc.api/errors/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/errors/index.js b/workers/loc.api/errors/index.js index 421c82c30..8d33851b0 100644 --- a/workers/loc.api/errors/index.js +++ b/workers/loc.api/errors/index.js @@ -260,6 +260,12 @@ class CurrencyPairSeparationError extends BaseError { } } +class TrxTaxReportGenerationTimeoutError extends BaseError { + constructor (message = 'ERR_TRX_TAX_REPORT_GENERATION_TIMEOUT') { + super(message) + } +} + module.exports = { BaseError, CollSyncPermissionError, @@ -298,5 +304,6 @@ module.exports = { AuthTokenGenerationError, AuthTokenTTLSettingError, CurrencyConversionError, - CurrencyPairSeparationError + CurrencyPairSeparationError, + TrxTaxReportGenerationTimeoutError } From 40c6e8bd5ee18d6b580b8b583565352bb6ecc113 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 5 Apr 2024 09:47:07 +0200 Subject: [PATCH 026/106] Use TrxTaxReportGenerationTimeoutError error class --- workers/loc.api/sync/transaction.tax.report/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 9c023feae..88859e2eb 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -11,7 +11,8 @@ const { } = require('../helpers') const { CurrencyConversionError, - CurrencyPairSeparationError + CurrencyPairSeparationError, + TrxTaxReportGenerationTimeoutError } = require('../../errors') const { decorateInjectable } = require('../../di/utils') @@ -672,8 +673,7 @@ class TransactionTaxReport { const mtsDiff = currMts - timeoutMts if (mtsDiff > 1000 * 60 * 60 * 12) { - // TODO: - throw new Error('ERR_TRX_TAX_REPORT_GENERATION_TIMEOUT_ERROR') + throw new TrxTaxReportGenerationTimeoutError() } timeoutMts = currMts From 13c8eba2f8b897bf1d6a7a720a506cab4b61b6d7 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 09:12:09 +0300 Subject: [PATCH 027/106] Add ability to send ws event when trx tax report generated --- workers/loc.api/ws-transport/ws.event.emitter.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/workers/loc.api/ws-transport/ws.event.emitter.js b/workers/loc.api/ws-transport/ws.event.emitter.js index c1ea2481a..4d0f2350c 100644 --- a/workers/loc.api/ws-transport/ws.event.emitter.js +++ b/workers/loc.api/ws-transport/ws.event.emitter.js @@ -138,6 +138,21 @@ class WSEventEmitter extends AbstractWSEventEmitter { }, 'emitBfxUnamePwdAuthRequired') } + emitTrxTaxReportGenerationInBackgroundToOne ( + handler = () => {}, + auth = {} + ) { + return this.emit(async (user, ...args) => { + if (this.isNotTargetUser(auth, user)) { + return { isNotEmitted: true } + } + + return typeof handler === 'function' + ? await handler(user, ...args) + : handler + }, 'emitTrxTaxReportGenerationInBackgroundToOne') + } + async emitRedirectingRequestsStatusToApi ( handler = () => {} ) { From 7ed653cb4539e0a8a07f5d595cfcc419ca980383 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 09:13:49 +0300 Subject: [PATCH 028/106] Add ability to make trx tax report in background --- .../sync/transaction.tax.report/index.js | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 88859e2eb..f6cb31879 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -25,7 +25,9 @@ const depsTypes = (TYPES) => [ TYPES.SYNC_API_METHODS, TYPES.Movements, TYPES.RService, - TYPES.GetDataFromApi + TYPES.GetDataFromApi, + TYPES.WSEventEmitterFactory, + TYPES.Logger ] class TransactionTaxReport { constructor ( @@ -36,7 +38,9 @@ class TransactionTaxReport { SYNC_API_METHODS, movements, rService, - getDataFromApi + getDataFromApi, + wsEventEmitterFactory, + logger ) { this.dao = dao this.authenticator = authenticator @@ -46,11 +50,30 @@ class TransactionTaxReport { this.movements = movements this.rService = rService this.getDataFromApi = getDataFromApi + this.wsEventEmitterFactory = wsEventEmitterFactory + this.logger = logger this.tradesModel = this.syncSchema.getModelsMap() .get(this.ALLOWED_COLLS.TRADES) } + async makeTrxTaxReportInBackground (args = {}) { + const { auth, params } = args ?? {} + const user = await this.authenticator + .verifyRequestUser({ auth }) + const _args = { auth: user, params } + + this.wsEventEmitterFactory() + .emitTrxTaxReportGenerationInBackgroundToOne(() => { + return this.getTransactionTaxReport(_args) + }, user) + .then(() => {}, (err) => { + this.logger.error(`TRX_TAX_REPORT_GEN_FAILED: ${err.stack || err}`) + }) + + return true + } + async getTransactionTaxReport (args = {}) { const { auth, params } = args ?? {} const { From 2d03cbb32a9dde4ed72c92c583200ef320022a1c Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 09:14:41 +0300 Subject: [PATCH 029/106] Add endpoint to make trx tax report in background --- workers/loc.api/service.report.framework.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/workers/loc.api/service.report.framework.js b/workers/loc.api/service.report.framework.js index 73391e692..2e0ea6bf7 100644 --- a/workers/loc.api/service.report.framework.js +++ b/workers/loc.api/service.report.framework.js @@ -1420,6 +1420,17 @@ class FrameworkReportService extends ReportService { }, 'getTransactionTaxReport', args, cb) } + makeTrxTaxReportInBackground (space, args, cb) { + return this._privResponder(async () => { + await this._dataConsistencyChecker + .check(this._CHECKER_NAMES.TRANSACTION_TAX_REPORT, args) + + checkParams(args, 'paramsSchemaForTransactionTaxReportApi') + + return this._transactionTaxReport.makeTrxTaxReportInBackground(args) + }, 'makeTrxTaxReportInBackground', args, cb) + } + getTradedVolume (space, args, cb) { return this._privResponder(async () => { await this._dataConsistencyChecker From 912d8e1e21c982cfb8c478610aadb1a8a978e34b Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 09:21:10 +0300 Subject: [PATCH 030/106] Improve data consistency checker for trx tax report --- workers/loc.api/sync/data.consistency.checker/checkers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/data.consistency.checker/checkers.js b/workers/loc.api/sync/data.consistency.checker/checkers.js index e777dce08..ff5b666e4 100644 --- a/workers/loc.api/sync/data.consistency.checker/checkers.js +++ b/workers/loc.api/sync/data.consistency.checker/checkers.js @@ -103,7 +103,6 @@ class Checkers { }) } - // TODO: [CHECKER_NAMES.TRANSACTION_TAX_REPORT] (auth) { return this.syncCollsManager .haveCollsBeenSyncedUpToDate({ @@ -111,7 +110,8 @@ class Checkers { params: { schema: [ this.SYNC_API_METHODS.TRADES, - this.SYNC_API_METHODS.CANDLES + this.SYNC_API_METHODS.LEDGERS, + this.SYNC_API_METHODS.MOVEMENTS ] } }) From 3a679d2c19d86dc26364fa6a00d9ceb31e6587c5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 10:03:39 +0300 Subject: [PATCH 031/106] Move trades lookup fn into separate helper --- .../transaction.tax.report/helpers/index.js | 7 + .../helpers/look-up-trades.js | 276 ++++++++++++++++++ .../sync/transaction.tax.report/index.js | 275 +---------------- 3 files changed, 291 insertions(+), 267 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/index.js create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js new file mode 100644 index 000000000..9ff20c7f7 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -0,0 +1,7 @@ +'use strict' + +const lookUpTrades = require('./look-up-trades') + +module.exports = { + lookUpTrades +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js new file mode 100644 index 000000000..d15bd56a8 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -0,0 +1,276 @@ +'use strict' + +const { setImmediate } = require('node:timers/promises') +const { + splitSymbolPairs +} = require('bfx-report/workers/loc.api/helpers') + +const { + isForexSymb, + getBackIterable +} = require('../../helpers') + +const { + CurrencyConversionError, + CurrencyPairSeparationError +} = require('../../../errors') + +module.exports = async (trades, opts) => { + const { + isBackIterativeLookUp = false, + isBuyTradesWithUnrealizedProfitRequired = false, + isNotGainOrLossRequired = false + } = opts ?? {} + + const saleTradesWithRealizedProfit = [] + const buyTradesWithUnrealizedProfit = [] + + if ( + !Array.isArray(trades) || + trades.length === 0 + ) { + return { + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit + } + } + + let lastLoopUnlockMts = Date.now() + const tradeIterator = isBackIterativeLookUp + ? getBackIterable(trades) + : trades + + for (const [i, trade] of tradeIterator.entries()) { + const currentLoopUnlockMts = Date.now() + + /* + * Trx hist restoring is a hard sync operation, + * to prevent EventLoop locking more than 1sec + * it needs to resolve async queue + */ + if ((currentLoopUnlockMts - lastLoopUnlockMts) > 1000) { + await setImmediate() + + lastLoopUnlockMts = currentLoopUnlockMts + } + + trade.isSaleTrx = trade.isSaleTrx ?? false + trade.isSaleTrxHistFilled = trade.isSaleTrxHistFilled ?? false + trade.saleFilledAmount = trade.saleFilledAmount ?? 0 + trade.costForSaleTrx = trade.costForSaleTrx ?? 0 + trade.buyTrxsForRealizedProfit = trade + .buyTrxsForRealizedProfit ?? [] + + if ( + !trade?.symbol || + !Number.isFinite(trade?.execPrice) || + trade.execPrice === 0 || + !Number.isFinite(trade?.execAmount) || + trade.execAmount === 0 + ) { + continue + } + + const [firstSymb, lastSymb] = ( + trade?.firstSymb && + trade?.lastSymb + ) + ? [trade?.firstSymb, trade?.lastSymb] + : splitSymbolPairs(trade.symbol) + trade.firstSymb = firstSymb + trade.lastSymb = lastSymb + + /* + * Exapmle of considered trxs as sale: + * - buy ETC:BTC -> amount 5, price 0.5 (here needs to be considered as 2 trxs: buy ETC and sale BTC) + * - sale ETC:BTC -> amount -2, price 0.6 (here needs to be considered as 2 trxs: sale ETC and buy BTC) + * - sale ETC:USD -> amount -3, price 4000 + * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR price and converted to USD) + */ + const isDistinctSale = trade.execAmount < 0 + const isSaleBetweenCrypto = ( + trade.execAmount > 0 && + !isForexSymb(lastSymb) + ) + trade.isSaleTrx = isDistinctSale || isSaleBetweenCrypto + + if (!trade.isSaleTrx) { + continue + } + if ( + !firstSymb || + !lastSymb + ) { + throw new CurrencyPairSeparationError() + } + + const saleAmount = trade.execAmount < 0 + ? Math.abs(trade.execAmount) + : Math.abs(trade.execAmount * trade.execPrice) + const _salePrice = isDistinctSale + ? trade.firstSymbPrise + : trade.lastSymbPrise + const salePrice = isNotGainOrLossRequired ? 0 : _salePrice + const saleAsset = isDistinctSale + ? firstSymb + : lastSymb + + if (!Number.isFinite(salePrice)) { + throw new CurrencyConversionError() + } + + for (let j = i + 1; trades.length > j; j += 1) { + if (trade.isSaleTrxHistFilled) { + break + } + + const tradeForLookup = trades[j] + + if ( + tradeForLookup?.isBuyTrxHistFilled || + !tradeForLookup?.symbol || + !Number.isFinite(tradeForLookup?.execAmount) || + tradeForLookup.execAmount === 0 || + !Number.isFinite(tradeForLookup?.execPrice) || + tradeForLookup.execPrice === 0 + ) { + continue + } + + tradeForLookup.isBuyTrx = tradeForLookup.isBuyTrx ?? false + tradeForLookup.isBuyTrxHistFilled = tradeForLookup + .isBuyTrxHistFilled ?? false + tradeForLookup.buyFilledAmount = tradeForLookup + .buyFilledAmount ?? 0 + tradeForLookup.proceedsForBuyTrx = tradeForLookup.proceedsForBuyTrx ?? 0 + tradeForLookup.saleTrxsForRealizedProfit = tradeForLookup + .saleTrxsForRealizedProfit ?? [] + + const [firstSymbForLookup, lastSymbForLookup] = ( + tradeForLookup?.firstSymb && + tradeForLookup?.lastSymb + ) + ? [tradeForLookup?.firstSymb, tradeForLookup?.lastSymb] + : splitSymbolPairs(tradeForLookup.symbol) + tradeForLookup.firstSymb = firstSymbForLookup + tradeForLookup.lastSymb = lastSymbForLookup + + if ( + !firstSymbForLookup || + !lastSymbForLookup + ) { + throw new CurrencyPairSeparationError() + } + + if ( + tradeForLookup.execAmount < 0 && + isForexSymb(lastSymbForLookup) + ) { + continue + } + + const buyAsset = tradeForLookup.execAmount > 0 + ? firstSymbForLookup + : lastSymbForLookup + + if (saleAsset !== buyAsset) { + continue + } + + tradeForLookup.isBuyTrx = true + tradeForLookup.saleTrxsForRealizedProfit.push(trade) + trade.buyTrxsForRealizedProfit.push(tradeForLookup) + + const buyAmount = tradeForLookup.execAmount > 0 + ? Math.abs(tradeForLookup.execAmount) + : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) + const _buyPrice = tradeForLookup.execAmount > 0 + ? tradeForLookup.firstSymbPrise + : tradeForLookup.lastSymbPrise + const buyPrice = isNotGainOrLossRequired ? 0 : _buyPrice + const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount + const saleRestAmount = saleAmount - trade.saleFilledAmount + + if (!Number.isFinite(buyPrice)) { + throw new CurrencyConversionError() + } + + if (buyRestAmount < saleRestAmount) { + tradeForLookup.buyFilledAmount = buyAmount + trade.saleFilledAmount += buyRestAmount + tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice + trade.costForSaleTrx += buyRestAmount * buyPrice + tradeForLookup.isBuyTrxHistFilled = true + } + if (buyRestAmount > saleRestAmount) { + tradeForLookup.buyFilledAmount += saleRestAmount + trade.saleFilledAmount = saleAmount + tradeForLookup.proceedsForBuyTrx += saleRestAmount * salePrice + trade.costForSaleTrx += saleRestAmount * buyPrice + trade.isSaleTrxHistFilled = true + } + if (buyRestAmount === saleRestAmount) { + tradeForLookup.buyFilledAmount = buyAmount + trade.saleFilledAmount = saleAmount + tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice + trade.costForSaleTrx += buyRestAmount * buyPrice + tradeForLookup.isBuyTrxHistFilled = true + trade.isSaleTrxHistFilled = true + } + + if (tradeForLookup.isBuyTrxHistFilled) { + tradeForLookup.buyAsset = buyAsset + tradeForLookup.buyAmount = buyAmount + tradeForLookup.mtsAcquiredForBuyTrx = tradeForLookup.mtsCreate + tradeForLookup.mtsSoldForBuyTrx = trade.mtsCreate + tradeForLookup.costForBuyTrx = buyAmount * buyPrice + tradeForLookup.gainOrLossForBuyTrx = tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx + } + } + + trade.saleAsset = saleAsset + trade.saleAmount = saleAmount + trade.mtsAcquiredForSaleTrx = ( + trade.buyTrxsForRealizedProfit[0]?.mtsCreate > + trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + ) + ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate + : trade.buyTrxsForRealizedProfit[0]?.mtsCreate + trade.mtsSoldForSaleTrx = trade.mtsCreate + trade.proceedsForSaleTrx = saleAmount * salePrice + trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx + } + + for (const trade of trades) { + if ( + isBuyTradesWithUnrealizedProfitRequired && + trade?.isBuyTrx && + !trade?.isBuyTrxHistFilled + ) { + buyTradesWithUnrealizedProfit.push(trade) + } + + if ( + isBuyTradesWithUnrealizedProfitRequired || + !trade?.isSaleTrx || + trade?.isMovements + ) { + continue + } + + saleTradesWithRealizedProfit.push({ + asset: trade.saleAsset, + amount: trade.saleAmount, + mtsAcquired: trade.mtsAcquiredForSaleTrx, + mtsSold: trade.mtsSoldForSaleTrx, + proceeds: trade.proceedsForSaleTrx, + cost: trade.costForSaleTrx, + gainOrLoss: trade.gainOrLoss + }) + } + + return { + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit + } +} diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index f6cb31879..dd6fd23d5 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,19 +1,18 @@ 'use strict' -const { setImmediate } = require('node:timers/promises') const { splitSymbolPairs } = require('bfx-report/workers/loc.api/helpers') const { - isForexSymb, - getBackIterable + isForexSymb } = require('../helpers') const { - CurrencyConversionError, - CurrencyPairSeparationError, TrxTaxReportGenerationTimeoutError } = require('../../errors') +const { + lookUpTrades +} = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -110,7 +109,7 @@ class TransactionTaxReport { : { trxs: [] } const isBackIterativeLookUp = true - const { buyTradesWithUnrealizedProfit } = await this.#lookUpTrades( + const { buyTradesWithUnrealizedProfit } = await lookUpTrades( trxsForPrevPeriod, { isBackIterativeLookUp, @@ -124,273 +123,13 @@ class TransactionTaxReport { .filter((trx) => trx?.lastSymb !== 'USD')) await this.#convertCurrencies(trxsForConvToUsd) - const { saleTradesWithRealizedProfit } = await this.#lookUpTrades( + const { saleTradesWithRealizedProfit } = await lookUpTrades( trxsForCurrPeriod, { isBackIterativeLookUp } ) return saleTradesWithRealizedProfit } - async #lookUpTrades (trades, opts) { - const { - isBackIterativeLookUp = false, - isBuyTradesWithUnrealizedProfitRequired = false, - isNotGainOrLossRequired = false - } = opts ?? {} - - const saleTradesWithRealizedProfit = [] - const buyTradesWithUnrealizedProfit = [] - - if ( - !Array.isArray(trades) || - trades.length === 0 - ) { - return { - saleTradesWithRealizedProfit, - buyTradesWithUnrealizedProfit - } - } - - let lastLoopUnlockMts = Date.now() - const tradeIterator = isBackIterativeLookUp - ? getBackIterable(trades) - : trades - - for (const [i, trade] of tradeIterator.entries()) { - const currentLoopUnlockMts = Date.now() - - /* - * Trx hist restoring is a hard sync operation, - * to prevent EventLoop locking more than 1sec - * it needs to resolve async queue - */ - if ((currentLoopUnlockMts - lastLoopUnlockMts) > 1000) { - await setImmediate() - - lastLoopUnlockMts = currentLoopUnlockMts - } - - trade.isSaleTrx = trade.isSaleTrx ?? false - trade.isSaleTrxHistFilled = trade.isSaleTrxHistFilled ?? false - trade.saleFilledAmount = trade.saleFilledAmount ?? 0 - trade.costForSaleTrx = trade.costForSaleTrx ?? 0 - trade.buyTrxsForRealizedProfit = trade - .buyTrxsForRealizedProfit ?? [] - - if ( - !trade?.symbol || - !Number.isFinite(trade?.execPrice) || - trade.execPrice === 0 || - !Number.isFinite(trade?.execAmount) || - trade.execAmount === 0 - ) { - continue - } - - const [firstSymb, lastSymb] = ( - trade?.firstSymb && - trade?.lastSymb - ) - ? [trade?.firstSymb, trade?.lastSymb] - : splitSymbolPairs(trade.symbol) - trade.firstSymb = firstSymb - trade.lastSymb = lastSymb - - /* - * Exapmle of considered trxs as sale: - * - buy ETC:BTC -> amount 5, price 0.5 (here needs to be considered as 2 trxs: buy ETC and sale BTC) - * - sale ETC:BTC -> amount -2, price 0.6 (here needs to be considered as 2 trxs: sale ETC and buy BTC) - * - sale ETC:USD -> amount -3, price 4000 - * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR price and converted to USD) - */ - const isDistinctSale = trade.execAmount < 0 - const isSaleBetweenCrypto = ( - trade.execAmount > 0 && - !isForexSymb(lastSymb) - ) - trade.isSaleTrx = isDistinctSale || isSaleBetweenCrypto - - if (!trade.isSaleTrx) { - continue - } - if ( - !firstSymb || - !lastSymb - ) { - throw new CurrencyPairSeparationError() - } - - const saleAmount = trade.execAmount < 0 - ? Math.abs(trade.execAmount) - : Math.abs(trade.execAmount * trade.execPrice) - const _salePrice = isDistinctSale - ? trade.firstSymbPrise - : trade.lastSymbPrise - const salePrice = isNotGainOrLossRequired ? 0 : _salePrice - const saleAsset = isDistinctSale - ? firstSymb - : lastSymb - - if (!Number.isFinite(salePrice)) { - throw new CurrencyConversionError() - } - - for (let j = i + 1; trades.length > j; j += 1) { - if (trade.isSaleTrxHistFilled) { - break - } - - const tradeForLookup = trades[j] - - if ( - tradeForLookup?.isBuyTrxHistFilled || - !tradeForLookup?.symbol || - !Number.isFinite(tradeForLookup?.execAmount) || - tradeForLookup.execAmount === 0 || - !Number.isFinite(tradeForLookup?.execPrice) || - tradeForLookup.execPrice === 0 - ) { - continue - } - - tradeForLookup.isBuyTrx = tradeForLookup.isBuyTrx ?? false - tradeForLookup.isBuyTrxHistFilled = tradeForLookup - .isBuyTrxHistFilled ?? false - tradeForLookup.buyFilledAmount = tradeForLookup - .buyFilledAmount ?? 0 - tradeForLookup.proceedsForBuyTrx = tradeForLookup.proceedsForBuyTrx ?? 0 - tradeForLookup.saleTrxsForRealizedProfit = tradeForLookup - .saleTrxsForRealizedProfit ?? [] - - const [firstSymbForLookup, lastSymbForLookup] = ( - tradeForLookup?.firstSymb && - tradeForLookup?.lastSymb - ) - ? [tradeForLookup?.firstSymb, tradeForLookup?.lastSymb] - : splitSymbolPairs(tradeForLookup.symbol) - tradeForLookup.firstSymb = firstSymbForLookup - tradeForLookup.lastSymb = lastSymbForLookup - - if ( - !firstSymbForLookup || - !lastSymbForLookup - ) { - throw new CurrencyPairSeparationError() - } - - if ( - tradeForLookup.execAmount < 0 && - isForexSymb(lastSymbForLookup) - ) { - continue - } - - const buyAsset = tradeForLookup.execAmount > 0 - ? firstSymbForLookup - : lastSymbForLookup - - if (saleAsset !== buyAsset) { - continue - } - - tradeForLookup.isBuyTrx = true - tradeForLookup.saleTrxsForRealizedProfit.push(trade) - trade.buyTrxsForRealizedProfit.push(tradeForLookup) - - const buyAmount = tradeForLookup.execAmount > 0 - ? Math.abs(tradeForLookup.execAmount) - : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) - const _buyPrice = tradeForLookup.execAmount > 0 - ? tradeForLookup.firstSymbPrise - : tradeForLookup.lastSymbPrise - const buyPrice = isNotGainOrLossRequired ? 0 : _buyPrice - const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount - const saleRestAmount = saleAmount - trade.saleFilledAmount - - if (!Number.isFinite(buyPrice)) { - throw new CurrencyConversionError() - } - - if (buyRestAmount < saleRestAmount) { - tradeForLookup.buyFilledAmount = buyAmount - trade.saleFilledAmount += buyRestAmount - tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice - trade.costForSaleTrx += buyRestAmount * buyPrice - tradeForLookup.isBuyTrxHistFilled = true - } - if (buyRestAmount > saleRestAmount) { - tradeForLookup.buyFilledAmount += saleRestAmount - trade.saleFilledAmount = saleAmount - tradeForLookup.proceedsForBuyTrx += saleRestAmount * salePrice - trade.costForSaleTrx += saleRestAmount * buyPrice - trade.isSaleTrxHistFilled = true - } - if (buyRestAmount === saleRestAmount) { - tradeForLookup.buyFilledAmount = buyAmount - trade.saleFilledAmount = saleAmount - tradeForLookup.proceedsForBuyTrx += buyRestAmount * salePrice - trade.costForSaleTrx += buyRestAmount * buyPrice - tradeForLookup.isBuyTrxHistFilled = true - trade.isSaleTrxHistFilled = true - } - - if (tradeForLookup.isBuyTrxHistFilled) { - tradeForLookup.buyAsset = buyAsset - tradeForLookup.buyAmount = buyAmount - tradeForLookup.mtsAcquiredForBuyTrx = tradeForLookup.mtsCreate - tradeForLookup.mtsSoldForBuyTrx = trade.mtsCreate - tradeForLookup.costForBuyTrx = buyAmount * buyPrice - tradeForLookup.gainOrLossForBuyTrx = tradeForLookup.proceedsForBuyTrx - tradeForLookup.costForBuyTrx - } - } - - trade.saleAsset = saleAsset - trade.saleAmount = saleAmount - trade.mtsAcquiredForSaleTrx = ( - trade.buyTrxsForRealizedProfit[0]?.mtsCreate > - trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate - ) - ? trade.buyTrxsForRealizedProfit[trade.buyTrxsForRealizedProfit.length - 1]?.mtsCreate - : trade.buyTrxsForRealizedProfit[0]?.mtsCreate - trade.mtsSoldForSaleTrx = trade.mtsCreate - trade.proceedsForSaleTrx = saleAmount * salePrice - trade.gainOrLoss = trade.proceedsForSaleTrx - trade.costForSaleTrx - } - - for (const trade of trades) { - if ( - isBuyTradesWithUnrealizedProfitRequired && - trade?.isBuyTrx && - !trade?.isBuyTrxHistFilled - ) { - buyTradesWithUnrealizedProfit.push(trade) - } - - if ( - isBuyTradesWithUnrealizedProfitRequired || - !trade?.isSaleTrx || - trade?.isMovements - ) { - continue - } - - saleTradesWithRealizedProfit.push({ - asset: trade.saleAsset, - amount: trade.saleAmount, - mtsAcquired: trade.mtsAcquiredForSaleTrx, - mtsSold: trade.mtsSoldForSaleTrx, - proceeds: trade.proceedsForSaleTrx, - cost: trade.costForSaleTrx, - gainOrLoss: trade.gainOrLoss - }) - } - - return { - saleTradesWithRealizedProfit, - buyTradesWithUnrealizedProfit - } - } - async #getTrades ({ user, start, @@ -609,6 +348,7 @@ class TransactionTaxReport { } } + // TODO: #getTrxMapByCcy (trxs) { const trxMapByCcy = new Map() @@ -647,6 +387,7 @@ class TransactionTaxReport { return trxMapByCcy } + // TODO: #getPubTradeChunkPayloads (symbol, trxData) { const pubTradeChunkPayloads = [] From ffc268c264722e6d83cf434289e29c2ece1f6dc8 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 10:10:03 +0300 Subject: [PATCH 032/106] Move get-trx-map-by-ccy fn into separate helper --- .../helpers/get-trx-map-by-ccy.js | 43 ++++++++++++++++++ .../transaction.tax.report/helpers/index.js | 4 +- .../sync/transaction.tax.report/index.js | 44 ++----------------- 3 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js new file mode 100644 index 000000000..0744d0da8 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js @@ -0,0 +1,43 @@ +'use strict' + +const { + isForexSymb +} = require('../../helpers') + +module.exports = async (trxs) => { + const trxMapByCcy = new Map() + + for (const trx of trxs) { + const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) + const isNotLastSymbForex = !isForexSymb(trx.lastSymb) + + if (isNotFirstSymbForex) { + if (!trxMapByCcy.has(trx.firstSymb)) { + trxMapByCcy.set(trx.firstSymb, []) + } + + trxMapByCcy.get(trx.firstSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'firstSymbPrise', + secondPrisePropName: 'lastSymbPrise', + trx + }) + } + if (isNotLastSymbForex) { + if (!trxMapByCcy.has(trx.lastSymb)) { + trxMapByCcy.set(trx.lastSymb, []) + } + + trxMapByCcy.get(trx.lastSymb).push({ + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrisePropName: 'lastSymbPrise', + secondPrisePropName: 'firstSymbPrise', + trx + }) + } + } + + return trxMapByCcy +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index 9ff20c7f7..fc2cd9d1e 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -1,7 +1,9 @@ 'use strict' const lookUpTrades = require('./look-up-trades') +const getTrxMapByCcy = require('./get-trx-map-by-ccy') module.exports = { - lookUpTrades + lookUpTrades, + getTrxMapByCcy } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index dd6fd23d5..5775e572b 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -11,7 +11,8 @@ const { TrxTaxReportGenerationTimeoutError } = require('../../errors') const { - lookUpTrades + lookUpTrades, + getTrxMapByCcy } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -278,7 +279,7 @@ class TransactionTaxReport { } async #convertCurrencies (trxs) { - const trxMapByCcy = this.#getTrxMapByCcy(trxs) + const trxMapByCcy = getTrxMapByCcy(trxs) for (const [symbol, trxData] of trxMapByCcy.entries()) { const pubTrades = [] @@ -348,45 +349,6 @@ class TransactionTaxReport { } } - // TODO: - #getTrxMapByCcy (trxs) { - const trxMapByCcy = new Map() - - for (const trx of trxs) { - const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) - const isNotLastSymbForex = !isForexSymb(trx.lastSymb) - - if (isNotFirstSymbForex) { - if (!trxMapByCcy.has(trx.firstSymb)) { - trxMapByCcy.set(trx.firstSymb, []) - } - - trxMapByCcy.get(trx.firstSymb).push({ - isNotFirstSymbForex, - isNotLastSymbForex, - mainPrisePropName: 'firstSymbPrise', - secondPrisePropName: 'lastSymbPrise', - trx - }) - } - if (isNotLastSymbForex) { - if (!trxMapByCcy.has(trx.lastSymb)) { - trxMapByCcy.set(trx.lastSymb, []) - } - - trxMapByCcy.get(trx.lastSymb).push({ - isNotFirstSymbForex, - isNotLastSymbForex, - mainPrisePropName: 'lastSymbPrise', - secondPrisePropName: 'firstSymbPrise', - trx - }) - } - } - - return trxMapByCcy - } - // TODO: #getPubTradeChunkPayloads (symbol, trxData) { const pubTradeChunkPayloads = [] From 14c902d2c954e06420bfb7c686320789335b061d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 10:21:00 +0300 Subject: [PATCH 033/106] Move get-pub-trade-chunk-payloads fn into separate helper --- .../helpers/get-pub-trade-chunk-payloads.js | 38 ++++++++++++++++ .../helpers/get-trx-map-by-ccy.js | 2 +- .../transaction.tax.report/helpers/index.js | 4 +- .../sync/transaction.tax.report/index.js | 43 ++----------------- 4 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk-payloads.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk-payloads.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk-payloads.js new file mode 100644 index 000000000..2266c239e --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk-payloads.js @@ -0,0 +1,38 @@ +'use strict' + +module.exports = (symbol, trxData) => { + const pubTradeChunkPayloads = [] + + for (const { trx } of trxData) { + const lastPayloads = pubTradeChunkPayloads[pubTradeChunkPayloads.length - 1] + const lastMts = lastPayloads?.start ?? lastPayloads?.end + const currMts = trx.mtsCreate + + if (!lastPayloads?.end) { + pubTradeChunkPayloads.push({ + symbol, + end: currMts, + start: null + }) + + continue + } + + const mtsDiff = lastMts - currMts + const maxAllowedTimeframe = 1000 * 60 * 60 * 24 + + if (mtsDiff < maxAllowedTimeframe) { + lastPayloads.start = currMts + + continue + } + + pubTradeChunkPayloads.push({ + symbol, + end: currMts, + start: null + }) + } + + return pubTradeChunkPayloads +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js index 0744d0da8..c4721e7e7 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js @@ -4,7 +4,7 @@ const { isForexSymb } = require('../../helpers') -module.exports = async (trxs) => { +module.exports = (trxs) => { const trxMapByCcy = new Map() for (const trx of trxs) { diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index fc2cd9d1e..1278c518d 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -2,8 +2,10 @@ const lookUpTrades = require('./look-up-trades') const getTrxMapByCcy = require('./get-trx-map-by-ccy') +const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') module.exports = { lookUpTrades, - getTrxMapByCcy + getTrxMapByCcy, + getPubTradeChunkPayloads } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 5775e572b..476a452e5 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -12,7 +12,8 @@ const { } = require('../../errors') const { lookUpTrades, - getTrxMapByCcy + getTrxMapByCcy, + getPubTradeChunkPayloads } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -283,7 +284,7 @@ class TransactionTaxReport { for (const [symbol, trxData] of trxMapByCcy.entries()) { const pubTrades = [] - const pubTradeChunkPayloads = this.#getPubTradeChunkPayloads( + const pubTradeChunkPayloads = getPubTradeChunkPayloads( symbol, trxData ) @@ -349,44 +350,6 @@ class TransactionTaxReport { } } - // TODO: - #getPubTradeChunkPayloads (symbol, trxData) { - const pubTradeChunkPayloads = [] - - for (const { trx } of trxData) { - const lastPayloads = pubTradeChunkPayloads[pubTradeChunkPayloads.length - 1] - const lastMts = lastPayloads?.start ?? lastPayloads?.end - const currMts = trx.mtsCreate - - if (!lastPayloads?.end) { - pubTradeChunkPayloads.push({ - symbol, - end: currMts, - start: null - }) - - continue - } - - const mtsDiff = lastMts - currMts - const maxAllowedTimeframe = 1000 * 60 * 60 * 24 - - if (mtsDiff < maxAllowedTimeframe) { - lastPayloads.start = currMts - - continue - } - - pubTradeChunkPayloads.push({ - symbol, - end: currMts, - start: null - }) - } - - return pubTradeChunkPayloads - } - async #getPublicTradeChunk (params) { const symbol = params?.symbol const start = params?.start From fab0d2ab52e29a41991679b17434dcd20d15ba28 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 10:30:11 +0300 Subject: [PATCH 034/106] Fix amount column name for trx tax report file --- workers/loc.api/generate-report-file/report.file.job.data.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workers/loc.api/generate-report-file/report.file.job.data.js b/workers/loc.api/generate-report-file/report.file.job.data.js index 15fd43463..f4a254dfa 100644 --- a/workers/loc.api/generate-report-file/report.file.job.data.js +++ b/workers/loc.api/generate-report-file/report.file.job.data.js @@ -528,7 +528,6 @@ class ReportFileJobData extends BaseReportFileJobData { return jobData } - // TODO: async getTransactionTaxReportFileJobData ( args, uId, @@ -556,7 +555,7 @@ class ReportFileJobData extends BaseReportFileJobData { propNameForPagination: null, columnsCsv: { symbol: 'DESCRIPTION OF PROPERTY', - amount: 'amount', + amount: 'AMOUNT', mtsAcquired: 'DATE ACQUIRED', mtsSold: 'DATE SOLD', proceeds: 'PROCEEDS', From bc41c2a321dca6e1ec3374ad9e410a7e02bd1fe9 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 8 Apr 2024 10:21:35 +0200 Subject: [PATCH 035/106] Fix asset column name for trx tax report file --- workers/loc.api/generate-report-file/report.file.job.data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/generate-report-file/report.file.job.data.js b/workers/loc.api/generate-report-file/report.file.job.data.js index f4a254dfa..ad545f3a3 100644 --- a/workers/loc.api/generate-report-file/report.file.job.data.js +++ b/workers/loc.api/generate-report-file/report.file.job.data.js @@ -554,7 +554,7 @@ class ReportFileJobData extends BaseReportFileJobData { args: reportFileArgs, propNameForPagination: null, columnsCsv: { - symbol: 'DESCRIPTION OF PROPERTY', + asset: 'DESCRIPTION OF PROPERTY', amount: 'AMOUNT', mtsAcquired: 'DATE ACQUIRED', mtsSold: 'DATE SOLD', @@ -563,7 +563,7 @@ class ReportFileJobData extends BaseReportFileJobData { gainOrLoss: 'GAIN OR LOSS' }, formatSettings: { - symbol: 'symbol', + asset: 'symbol', mtsAcquired: 'date', mtsSold: 'date' } From 9624094e3481338b15da7a50ab7e5cb1f3e14b86 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 9 Apr 2024 08:02:31 +0300 Subject: [PATCH 036/106] Add helper to check params and set default --- .../helpers/check-params-and-set-default.js | 34 +++++++++++++++++++ .../transaction.tax.report/helpers/index.js | 4 ++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js b/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js new file mode 100644 index 000000000..7b9ab6c73 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js @@ -0,0 +1,34 @@ +'use strict' + +const strategyList = ['isFIFO', 'isLIFO'] + +module.exports = (args = {}) => { + const _args = { ...args } + const params = { ..._args.params } + const start = params.start ?? 0 + const end = params.end ?? Date.now() + + const areAllFalse = strategyList + .every((name) => !params[name]) + + if (areAllFalse) { + params.isLIFO = true + } + + const isOnlyOneTrue = strategyList + .filter((name) => params[name]) + .length === 1 + + if (!isOnlyOneTrue) { + // TODO: + throw new Error('ERR_WRONG_CONDITION_PASSED') + } + + _args.params = { + start, + end, + ...params + } + + return _args +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index 1278c518d..24aae6fbb 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -3,9 +3,11 @@ const lookUpTrades = require('./look-up-trades') const getTrxMapByCcy = require('./get-trx-map-by-ccy') const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') +const checkParamsAndSetDefault = require('./check-params-and-set-default') module.exports = { lookUpTrades, getTrxMapByCcy, - getPubTradeChunkPayloads + getPubTradeChunkPayloads, + checkParamsAndSetDefault } From 8f56d96d0598f57c401ea34bf1919d8f95e6a7ee Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 9 Apr 2024 08:03:20 +0300 Subject: [PATCH 037/106] Add fifo strategy for trx tax report --- .../helpers/look-up-trades.js | 21 +++++++++++++++--- .../sync/transaction.tax.report/index.js | 22 ++++++++++++++----- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index d15bd56a8..f1425a0af 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -17,7 +17,8 @@ const { module.exports = async (trades, opts) => { const { - isBackIterativeLookUp = false, + isBackIterativeSaleLookUp = false, + isBackIterativeBuyLookUp = false, isBuyTradesWithUnrealizedProfitRequired = false, isNotGainOrLossRequired = false } = opts ?? {} @@ -36,7 +37,7 @@ module.exports = async (trades, opts) => { } let lastLoopUnlockMts = Date.now() - const tradeIterator = isBackIterativeLookUp + const tradeIterator = isBackIterativeSaleLookUp ? getBackIterable(trades) : trades @@ -119,7 +120,21 @@ module.exports = async (trades, opts) => { throw new CurrencyConversionError() } - for (let j = i + 1; trades.length > j; j += 1) { + const startPoint = isBackIterativeBuyLookUp + ? trades.length - 1 + : i + 1 + const checkPoint = (j) => ( + isBackIterativeBuyLookUp + ? i < j + : trades.length > j + ) + const shiftPoint = (j) => ( + isBackIterativeBuyLookUp + ? j - 1 + : j + 1 + ) + + for (let j = startPoint; checkPoint(j); j = shiftPoint(j)) { if (trade.isSaleTrxHistFilled) { break } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 476a452e5..ddcca62b0 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -13,7 +13,8 @@ const { const { lookUpTrades, getTrxMapByCcy, - getPubTradeChunkPayloads + getPubTradeChunkPayloads, + checkParamsAndSetDefault } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -76,10 +77,12 @@ class TransactionTaxReport { } async getTransactionTaxReport (args = {}) { - const { auth, params } = args ?? {} + const { auth, params } = checkParamsAndSetDefault(args) const { start = 0, - end = Date.now() + end = Date.now(), + isFIFO, + isLIFO } = params ?? {} const user = await this.authenticator .verifyRequestUser({ auth }) @@ -110,11 +113,14 @@ class TransactionTaxReport { }) : { trxs: [] } - const isBackIterativeLookUp = true + const isBackIterativeSaleLookUp = isFIFO && !isLIFO + const isBackIterativeBuyLookUp = isFIFO && !isLIFO + const { buyTradesWithUnrealizedProfit } = await lookUpTrades( trxsForPrevPeriod, { - isBackIterativeLookUp, + isBackIterativeSaleLookUp, + isBackIterativeBuyLookUp, buyTradesWithUnrealizedProfit: true, isNotGainOrLossRequired: true } @@ -126,7 +132,11 @@ class TransactionTaxReport { await this.#convertCurrencies(trxsForConvToUsd) const { saleTradesWithRealizedProfit } = await lookUpTrades( - trxsForCurrPeriod, { isBackIterativeLookUp } + trxsForCurrPeriod, + { + isBackIterativeSaleLookUp, + isBackIterativeBuyLookUp + } ) return saleTradesWithRealizedProfit From 1c80fb551c7acef032cb8783ca959ae40f7a4d7a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 9 Apr 2024 13:29:01 +0300 Subject: [PATCH 038/106] Add support tax strategy prop as enum field --- workers/loc.api/helpers/schema.js | 7 ++++ .../helpers/check-params-and-set-default.js | 34 ------------------- .../transaction.tax.report/helpers/index.js | 4 +-- .../helpers/trx.tax.strategies.js | 8 +++++ .../sync/transaction.tax.report/index.js | 16 ++++----- 5 files changed, 25 insertions(+), 44 deletions(-) delete mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/trx.tax.strategies.js diff --git a/workers/loc.api/helpers/schema.js b/workers/loc.api/helpers/schema.js index a3ced9b85..0059fd710 100644 --- a/workers/loc.api/helpers/schema.js +++ b/workers/loc.api/helpers/schema.js @@ -222,6 +222,13 @@ const paramsSchemaForTransactionTaxReportApi = { }, start: { type: 'integer' + }, + strategy: { + type: 'string', + enum: [ + 'FIFO', + 'LIFO' + ] } } } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js b/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js deleted file mode 100644 index 7b9ab6c73..000000000 --- a/workers/loc.api/sync/transaction.tax.report/helpers/check-params-and-set-default.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const strategyList = ['isFIFO', 'isLIFO'] - -module.exports = (args = {}) => { - const _args = { ...args } - const params = { ..._args.params } - const start = params.start ?? 0 - const end = params.end ?? Date.now() - - const areAllFalse = strategyList - .every((name) => !params[name]) - - if (areAllFalse) { - params.isLIFO = true - } - - const isOnlyOneTrue = strategyList - .filter((name) => params[name]) - .length === 1 - - if (!isOnlyOneTrue) { - // TODO: - throw new Error('ERR_WRONG_CONDITION_PASSED') - } - - _args.params = { - start, - end, - ...params - } - - return _args -} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index 24aae6fbb..7e684b659 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -3,11 +3,11 @@ const lookUpTrades = require('./look-up-trades') const getTrxMapByCcy = require('./get-trx-map-by-ccy') const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') -const checkParamsAndSetDefault = require('./check-params-and-set-default') +const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') module.exports = { lookUpTrades, getTrxMapByCcy, getPubTradeChunkPayloads, - checkParamsAndSetDefault + TRX_TAX_STRATEGIES } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/trx.tax.strategies.js b/workers/loc.api/sync/transaction.tax.report/helpers/trx.tax.strategies.js new file mode 100644 index 000000000..087b2bbf7 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/trx.tax.strategies.js @@ -0,0 +1,8 @@ +'use strict' + +const TRX_TAX_STRATEGIES = { + FIFO: 'FIFO', + LIFO: 'LIFO' +} + +module.exports = TRX_TAX_STRATEGIES diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index ddcca62b0..09f81e579 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -14,7 +14,7 @@ const { lookUpTrades, getTrxMapByCcy, getPubTradeChunkPayloads, - checkParamsAndSetDefault + TRX_TAX_STRATEGIES } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -77,16 +77,16 @@ class TransactionTaxReport { } async getTransactionTaxReport (args = {}) { - const { auth, params } = checkParamsAndSetDefault(args) - const { - start = 0, - end = Date.now(), - isFIFO, - isLIFO - } = params ?? {} + const { auth, params } = args ?? {} + const start = params.start ?? 0 + const end = params.end ?? Date.now() + const strategy = params.strategy ?? TRX_TAX_STRATEGIES.LIFO const user = await this.authenticator .verifyRequestUser({ auth }) + const isFIFO = strategy === TRX_TAX_STRATEGIES.FIFO + const isLIFO = strategy === TRX_TAX_STRATEGIES.LIFO + const { trxs: trxsForCurrPeriod, trxsForConvToUsd From 304131dd15bab86b9c9b23358db7311e59284c99 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 10 Apr 2024 12:10:45 +0200 Subject: [PATCH 039/106] Fix lookup trades logic --- .../helpers/look-up-trades.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index f1425a0af..f78d8139d 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -65,7 +65,10 @@ module.exports = async (trades, opts) => { if ( !trade?.symbol || !Number.isFinite(trade?.execPrice) || - trade.execPrice === 0 || + ( + !isBuyTradesWithUnrealizedProfitRequired && + trade.execPrice === 0 + ) || !Number.isFinite(trade?.execAmount) || trade.execAmount === 0 ) { @@ -95,7 +98,10 @@ module.exports = async (trades, opts) => { ) trade.isSaleTrx = isDistinctSale || isSaleBetweenCrypto - if (!trade.isSaleTrx) { + if ( + !trade.isSaleTrx || + trade.isBuyTradesWithUnrealizedProfitForPrevPeriod + ) { continue } if ( @@ -147,7 +153,10 @@ module.exports = async (trades, opts) => { !Number.isFinite(tradeForLookup?.execAmount) || tradeForLookup.execAmount === 0 || !Number.isFinite(tradeForLookup?.execPrice) || - tradeForLookup.execPrice === 0 + ( + !isBuyTradesWithUnrealizedProfitRequired && + tradeForLookup.execPrice === 0 + ) ) { continue } @@ -262,11 +271,13 @@ module.exports = async (trades, opts) => { trade?.isBuyTrx && !trade?.isBuyTrxHistFilled ) { + trade.isBuyTradesWithUnrealizedProfitForPrevPeriod = true buyTradesWithUnrealizedProfit.push(trade) } if ( isBuyTradesWithUnrealizedProfitRequired || + trade?.isBuyTradesWithUnrealizedProfitForPrevPeriod || !trade?.isSaleTrx || trade?.isMovements ) { From 522a4783ca27c16301f0855fc27ce6253b4a6913 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 10 Apr 2024 12:11:50 +0200 Subject: [PATCH 040/106] Fix movements consideration into trx tax report --- workers/loc.api/sync/transaction.tax.report/index.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 09f81e579..b8105c135 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -121,14 +121,14 @@ class TransactionTaxReport { { isBackIterativeSaleLookUp, isBackIterativeBuyLookUp, - buyTradesWithUnrealizedProfit: true, + isBuyTradesWithUnrealizedProfitRequired: true, isNotGainOrLossRequired: true } ) trxsForCurrPeriod.push(...buyTradesWithUnrealizedProfit) trxsForConvToUsd.push(...buyTradesWithUnrealizedProfit - .filter((trx) => trx?.lastSymb !== 'USD')) + .filter((trx) => trx?.isMovements || trx?.lastSymb !== 'USD')) await this.#convertCurrencies(trxsForConvToUsd) const { saleTradesWithRealizedProfit } = await lookUpTrades( @@ -237,13 +237,6 @@ class TransactionTaxReport { remapedTrxsForConvToUsd.push(trade) } - if (remapedTrxs.length === 0) { - return { - trx: [], - trxsForConvToUsd: [] - } - } - for (const movement of movements) { if ( !movement?.currency || From 714dcfc34cb1968459b3528f5d25ef2af1b5615f Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 15 Apr 2024 09:11:34 +0300 Subject: [PATCH 041/106] Add mock trades for unit test coverage --- .../helpers/__test__/helpers/mock-trades.js | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js new file mode 100644 index 000000000..8ae2998d5 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js @@ -0,0 +1,113 @@ +'use strict' + +const mockTradesForNextYear = [ + { + symbol: 'tUSTUSD', + mtsCreate: Date.UTC(2024, 3, 27), + execAmount: -200, + execPrice: 0.98 + }, + { + isMovements: true, + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 3, 1), + execAmount: -1, + execPrice: 41_000 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 2, 17), + execAmount: -5, + execPrice: 61_000 + }, + { + isMovements: true, + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 1, 8), + execAmount: -3, + execPrice: 44_000 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 0, 14), + execAmount: 2, + execPrice: 48_000 + } +] +const mockTrades = [ + { + symbol: 'tUSTEUR', + mtsCreate: Date.UTC(2023, 6, 21), + execAmount: -100, + execPrice: 0.9, + firstSymbPrise: 1.05, + lastSymbPrise: 0.95 + }, + { + symbol: 'tETHUST', + mtsCreate: Date.UTC(2023, 5, 11), + execAmount: -1, + execPrice: 2800, + firstSymbPrise: 3_110, + lastSymbPrise: 1.11 + }, + { + symbol: 'tETHBTC', + mtsCreate: Date.UTC(2023, 4, 22), + execAmount: -1, + execPrice: 0.055, + firstSymbPrise: 2_650, + lastSymbPrise: 48_000 + }, + { + symbol: 'tETHUSD', + mtsCreate: Date.UTC(2023, 4, 10), + execAmount: -1, + execPrice: 2_000 + }, + { + symbol: 'tETHUSD', + mtsCreate: Date.UTC(2023, 3, 10), + execAmount: -2, + execPrice: 3_200 + }, + { + isMovements: true, + symbol: 'tETHUSD', + mtsCreate: Date.UTC(2023, 3, 2), + execAmount: -2, + execPrice: 3000 + }, + { + symbol: 'tETHBTC', + mtsCreate: Date.UTC(2023, 2, 23), + execAmount: 10, + execPrice: 0.05, + firstSymbPrise: 2_601, + lastSymbPrise: 50_000 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2023, 2, 3), + execAmount: -2, + execPrice: 33_000 + }, + { + isMovements: true, + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2023, 1, 5), + execAmount: 20, + execPrice: 43_000 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2023, 0, 10), + execAmount: 3, + execPrice: 20_000 + } +] + +module.exports = { + mockTradesForNextYear, + mockTrades +} From 2e954ed176c76b712e4cf15c3e26498e079fff5a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 15 Apr 2024 09:12:57 +0300 Subject: [PATCH 042/106] Add mocked-trades getter for unit test coverage --- .../__test__/helpers/get-mocked-trades.js | 50 +++++++++++++++++++ .../helpers/__test__/helpers/index.js | 13 +++++ 2 files changed, 63 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js new file mode 100644 index 000000000..fcf444e42 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js @@ -0,0 +1,50 @@ +'use strict' + +const splitSymbolPairs = require( + 'bfx-report/workers/loc.api/helpers/split-symbol-pairs' +) + +module.exports = (mockTrades, opts) => { + const missingFields = { + _id: 1, + id: 1, + orderID: 1, + orderType: 'EXCHANGE LIMIT', + orderPrice: null, + maker: 1, + fee: -0.5, + feeCurrency: 'USD', + subUserId: null, + user_id: 1, + + firstSymb: null, + lastSymb: null, + firstSymbPrise: null, + lastSymbPrise: null + } + + return mockTrades.map((trade, i) => { + const isMovements = opts?.isMovements ?? trade?.isMovements + const [firstSymb, lastSymb] = splitSymbolPairs(trade.symbol) + + return { + ...missingFields, + + isMovements, + _id: i + 1, + id: i + 1, + orderID: i + 1, + orderPrice: trade.execPrice, + firstSymb, + lastSymb, + firstSymbPrise: lastSymb === 'USD' ? trade.execPrice : null, + lastSymbPrise: lastSymb === 'USD' ? 1 : null, + + ...trade, + + mtsCreate: opts?.year + ? new Date(trade.mtsCreate).setUTCFullYear(opts?.year) + : trade.mtsCreate + } + }) +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js new file mode 100644 index 000000000..2013b36b1 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js @@ -0,0 +1,13 @@ +'use strict' + +const { + mockTradesForNextYear, + mockTrades +} = require('./mock-trades') +const getMockedTrades = require('./get-mocked-trades') + +module.exports = { + mockTradesForNextYear, + mockTrades, + getMockedTrades +} From b14117a4e18a00547df3fa65290fdabdef8ec194 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 15 Apr 2024 09:15:18 +0300 Subject: [PATCH 043/106] Add main test structure for trx tax lookUpTrades helper --- .../helpers/__test__/look-up-trades.spec.js | 116 ++++++++++++++++++ .../helpers/look-up-trades.js | 6 +- 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js new file mode 100644 index 000000000..18cc489b5 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -0,0 +1,116 @@ +'use strict' + +const { assert } = require('chai') + +const lookUpTrades = require('../look-up-trades') +const { + mockTradesForNextYear, + mockTrades, + getMockedTrades +} = require('./helpers') + +describe('lookUpTrades helper for trx tax report', () => { + it('Lookup buy trx with unrealized profit, LIFO strategy', async function () { + this.timeout(1000) + + const { + buyTradesWithUnrealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: false, + isBackIterativeBuyLookUp: false, + isBuyTradesWithUnrealizedProfitRequired: true, + isNotGainOrLossRequired: true + } + ) + + console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) + }) + + it('Lookup sale trx with realized profit, LIFO strategy', async function () { + this.timeout(1000) + + const { + saleTradesWithRealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: false, + isBackIterativeBuyLookUp: false, + isBuyTradesWithUnrealizedProfitRequired: false, + isNotGainOrLossRequired: false + } + ) + + console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + }) + it('Lookup buy trx with unrealized profit, FIFO strategy', async function () { + this.timeout(1000) + + const { + buyTradesWithUnrealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: true, + isBackIterativeBuyLookUp: true, + isBuyTradesWithUnrealizedProfitRequired: true, + isNotGainOrLossRequired: true + } + ) + + console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) + }) + + it('Lookup sale trx with realized profit, FIFO strategy', async function () { + this.timeout(1000) + + const { + saleTradesWithRealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: true, + isBackIterativeBuyLookUp: true, + isBuyTradesWithUnrealizedProfitRequired: false, + isNotGainOrLossRequired: false + } + ) + + console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + }) + + it('Lookup sale trx with realized profit considering prev year, LIFO strategy', async function () { + this.timeout(1000) + + const { + buyTradesWithUnrealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: false, + isBackIterativeBuyLookUp: false, + isBuyTradesWithUnrealizedProfitRequired: true, + isNotGainOrLossRequired: true + } + ) + console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) + const _mockTradesForNextYear = getMockedTrades(mockTradesForNextYear) + _mockTradesForNextYear.push(...buyTradesWithUnrealizedProfit) + + const { + saleTradesWithRealizedProfit + } = await lookUpTrades( + _mockTradesForNextYear, + { + isBackIterativeSaleLookUp: false, + isBackIterativeBuyLookUp: false, + isBuyTradesWithUnrealizedProfitRequired: false, + isNotGainOrLossRequired: false + } + ) + + console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + }) +}) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index f78d8139d..71701bf97 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -1,9 +1,9 @@ 'use strict' const { setImmediate } = require('node:timers/promises') -const { - splitSymbolPairs -} = require('bfx-report/workers/loc.api/helpers') +const splitSymbolPairs = require( + 'bfx-report/workers/loc.api/helpers/split-symbol-pairs' +) const { isForexSymb, From 53ea82b206a4b63f70f43358c8a48a8c64a7ecac Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 16 Apr 2024 12:28:51 +0300 Subject: [PATCH 044/106] Add test case checker for buy trades with unrealized profit --- .../helpers/__test__/test-cases/index.js | 7 +++ .../test-buy-trades-with-unrealized-profit.js | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js new file mode 100644 index 000000000..41a5a122c --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -0,0 +1,7 @@ +'use strict' + +const testBuyTradesWithUnrealizedProfit = require('./test-buy-trades-with-unrealized-profit') + +module.exports = { + testBuyTradesWithUnrealizedProfit +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js new file mode 100644 index 000000000..6aaa38152 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js @@ -0,0 +1,48 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const trade = arr[index] + const { + isMovements, + mtsCreate, + firstSymb, + lastSymb, + execAmount, + execPrice, + + buyFilledAmount + } = props ?? {} + + assert.isObject(trade) + assert.isBoolean(trade.isBuyTradesWithUnrealizedProfitForPrevPeriod) + assert.isOk(trade.isBuyTradesWithUnrealizedProfitForPrevPeriod) + assert.isBoolean(trade.isBuyTrx) + assert.isOk(trade.isBuyTrx) + assert.isBoolean(trade.isBuyTrxHistFilled) + assert.isNotOk(trade.isBuyTrxHistFilled) + assert.isNumber(trade.proceedsForBuyTrx) + assert.equal(trade.proceedsForBuyTrx, 0) + assert.isNumber(trade.proceedsForBuyTrx) + assert.equal(trade.proceedsForBuyTrx, 0) + assert.isNumber(trade.firstSymbPrise) + assert.isNumber(trade.lastSymbPrise) + assert.isArray(trade.saleTrxsForRealizedProfit) + + assert.isBoolean(trade.isMovements) + assert.equal(trade.isMovements, isMovements) + assert.isNumber(trade.mtsCreate) + assert.equal(trade.mtsCreate, mtsCreate) + assert.isString(trade.firstSymb) + assert.equal(trade.firstSymb, firstSymb) + assert.isString(trade.lastSymb) + assert.equal(trade.lastSymb, lastSymb) + assert.isNumber(trade.execAmount) + assert.equal(trade.execAmount, execAmount) + assert.isNumber(trade.execPrice) + assert.equal(trade.execPrice, execPrice) + + assert.isNumber(trade.buyFilledAmount) + assert.equal(trade.buyFilledAmount, buyFilledAmount) +} From 22cfede232016351690bc72ac56f45da833d8131 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 16 Apr 2024 12:30:39 +0300 Subject: [PATCH 045/106] Add test coverage for lifo lookup buy trx with unrealized profit --- .../helpers/__test__/look-up-trades.spec.js | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index 18cc489b5..627371409 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -8,6 +8,9 @@ const { mockTrades, getMockedTrades } = require('./helpers') +const { + testBuyTradesWithUnrealizedProfit +} = require('./test-cases') describe('lookUpTrades helper for trx tax report', () => { it('Lookup buy trx with unrealized profit, LIFO strategy', async function () { @@ -25,7 +28,54 @@ describe('lookUpTrades helper for trx tax report', () => { } ) - console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) + assert.isArray(buyTradesWithUnrealizedProfit) + assert.equal(buyTradesWithUnrealizedProfit.length, 5) + + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 0, { + isMovements: false, + mtsCreate: Date.UTC(2023, 5, 11), + firstSymb: 'ETH', + lastSymb: 'UST', + execAmount: -1, + execPrice: 2_800, + buyFilledAmount: 100 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 1, { + isMovements: false, + mtsCreate: Date.UTC(2023, 4, 22), + firstSymb: 'ETH', + lastSymb: 'BTC', + execAmount: -1, + execPrice: 0.055, + buyFilledAmount: 0 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 2, { + isMovements: false, + mtsCreate: Date.UTC(2023, 2, 23), + firstSymb: 'ETH', + lastSymb: 'BTC', + execAmount: 10, + execPrice: 0.05, + buyFilledAmount: 7 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 3, { + isMovements: true, + mtsCreate: Date.UTC(2023, 1, 5), + firstSymb: 'BTC', + lastSymb: 'USD', + execAmount: 20, + execPrice: 43_000, + buyFilledAmount: 2.5 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 4, { + isMovements: false, + mtsCreate: Date.UTC(2023, 0, 10), + firstSymb: 'BTC', + lastSymb: 'USD', + execAmount: 3, + execPrice: 20_000, + buyFilledAmount: 0 + }) }) it('Lookup sale trx with realized profit, LIFO strategy', async function () { From bec571fbd69766e9e8b114d53d1b0e0a862fa1f7 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 16 Apr 2024 12:31:20 +0300 Subject: [PATCH 046/106] Fix lookup buy trx with unrealized profit --- .../helpers/look-up-trades.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index 71701bf97..cba990f01 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -55,6 +55,8 @@ module.exports = async (trades, opts) => { lastLoopUnlockMts = currentLoopUnlockMts } + trade.isMovements = trade.isMovements ?? false + trade.isSaleTrx = trade.isSaleTrx ?? false trade.isSaleTrxHistFilled = trade.isSaleTrxHistFilled ?? false trade.saleFilledAmount = trade.saleFilledAmount ?? 0 @@ -62,6 +64,13 @@ module.exports = async (trades, opts) => { trade.buyTrxsForRealizedProfit = trade .buyTrxsForRealizedProfit ?? [] + trade.isBuyTrx = trade.isBuyTrx ?? false + trade.isBuyTrxHistFilled = trade.isBuyTrxHistFilled ?? false + trade.buyFilledAmount = trade.buyFilledAmount ?? 0 + trade.proceedsForBuyTrx = trade.proceedsForBuyTrx ?? 0 + trade.saleTrxsForRealizedProfit = trade + .saleTrxsForRealizedProfit ?? [] + if ( !trade?.symbol || !Number.isFinite(trade?.execPrice) || @@ -91,12 +100,17 @@ module.exports = async (trades, opts) => { * - sale ETC:USD -> amount -3, price 4000 * - sale UST:EUR - > amount -3, price 0.9 (here needs to be considered EUR price and converted to USD) */ + const isLastSymbForex = isForexSymb(lastSymb) const isDistinctSale = trade.execAmount < 0 const isSaleBetweenCrypto = ( trade.execAmount > 0 && - !isForexSymb(lastSymb) + !isLastSymbForex ) trade.isSaleTrx = isDistinctSale || isSaleBetweenCrypto + trade.isBuyTrx = ( + trade.execAmount > 0 || + !isLastSymbForex + ) if ( !trade.isSaleTrx || @@ -193,6 +207,8 @@ module.exports = async (trades, opts) => { continue } + tradeForLookup.isBuyTrx = true + const buyAsset = tradeForLookup.execAmount > 0 ? firstSymbForLookup : lastSymbForLookup @@ -201,7 +217,6 @@ module.exports = async (trades, opts) => { continue } - tradeForLookup.isBuyTrx = true tradeForLookup.saleTrxsForRealizedProfit.push(trade) trade.buyTrxsForRealizedProfit.push(tradeForLookup) From 909ee7d21b1e3fcc062e50db1cf3b114decbb6f5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 16 Apr 2024 13:32:13 +0300 Subject: [PATCH 047/106] Add test coverage for fifo lookup buy trx with unrealized profit --- .../helpers/__test__/look-up-trades.spec.js | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index 627371409..4f96eb92f 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -14,8 +14,6 @@ const { describe('lookUpTrades helper for trx tax report', () => { it('Lookup buy trx with unrealized profit, LIFO strategy', async function () { - this.timeout(1000) - const { buyTradesWithUnrealizedProfit } = await lookUpTrades( @@ -79,8 +77,6 @@ describe('lookUpTrades helper for trx tax report', () => { }) it('Lookup sale trx with realized profit, LIFO strategy', async function () { - this.timeout(1000) - const { saleTradesWithRealizedProfit } = await lookUpTrades( @@ -95,9 +91,8 @@ describe('lookUpTrades helper for trx tax report', () => { console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) }) - it('Lookup buy trx with unrealized profit, FIFO strategy', async function () { - this.timeout(1000) + it('Lookup buy trx with unrealized profit, FIFO strategy', async function () { const { buyTradesWithUnrealizedProfit } = await lookUpTrades( @@ -110,12 +105,57 @@ describe('lookUpTrades helper for trx tax report', () => { } ) - console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) + assert.isArray(buyTradesWithUnrealizedProfit) + assert.equal(buyTradesWithUnrealizedProfit.length, 5) + + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 0, { + isMovements: false, + mtsCreate: Date.UTC(2023, 5, 11), + firstSymb: 'ETH', + lastSymb: 'UST', + execAmount: -1, + execPrice: 2_800, + buyFilledAmount: 100 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 1, { + isMovements: false, + mtsCreate: Date.UTC(2023, 4, 22), + firstSymb: 'ETH', + lastSymb: 'BTC', + execAmount: -1, + execPrice: 0.055, + buyFilledAmount: 0 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 2, { + isMovements: false, + mtsCreate: Date.UTC(2023, 2, 23), + firstSymb: 'ETH', + lastSymb: 'BTC', + execAmount: 10, + execPrice: 0.05, + buyFilledAmount: 7 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 3, { + isMovements: true, + mtsCreate: Date.UTC(2023, 1, 5), + firstSymb: 'BTC', + lastSymb: 'USD', + execAmount: 20, + execPrice: 43_000, + buyFilledAmount: 0 + }) + testBuyTradesWithUnrealizedProfit(buyTradesWithUnrealizedProfit, 4, { + isMovements: false, + mtsCreate: Date.UTC(2023, 0, 10), + firstSymb: 'BTC', + lastSymb: 'USD', + execAmount: 3, + execPrice: 20_000, + buyFilledAmount: 2.5 + }) }) it('Lookup sale trx with realized profit, FIFO strategy', async function () { - this.timeout(1000) - const { saleTradesWithRealizedProfit } = await lookUpTrades( @@ -132,8 +172,6 @@ describe('lookUpTrades helper for trx tax report', () => { }) it('Lookup sale trx with realized profit considering prev year, LIFO strategy', async function () { - this.timeout(1000) - const { buyTradesWithUnrealizedProfit } = await lookUpTrades( From 96932b57833e6d928a5d793f44f75ef4f6f00cc1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 16 Apr 2024 13:44:09 +0300 Subject: [PATCH 048/106] Add test case checker for sale trades with realized profit --- .../helpers/__test__/test-cases/index.js | 4 ++- .../test-sale-trades-with-realized-profit.js | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index 41a5a122c..64e5b20bd 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -1,7 +1,9 @@ 'use strict' const testBuyTradesWithUnrealizedProfit = require('./test-buy-trades-with-unrealized-profit') +const testSaleTradesWithRealizedProfit = require('./test-sale-trades-with-realized-profit') module.exports = { - testBuyTradesWithUnrealizedProfit + testBuyTradesWithUnrealizedProfit, + testSaleTradesWithRealizedProfit } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js new file mode 100644 index 000000000..4ec0a6957 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js @@ -0,0 +1,33 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const trade = arr[index] + const { + asset, + amount, + mtsAcquired, + mtsSold, + proceeds, + cost, + gainOrLoss + } = props ?? {} + + assert.isObject(trade) + + assert.isString(trade.asset) + assert.equal(trade.asset, asset) + assert.isNumber(trade.amount) + assert.equal(trade.amount, amount) + assert.isNumber(trade.mtsAcquired) + assert.equal(trade.mtsAcquired, mtsAcquired) + assert.isNumber(trade.mtsSold) + assert.equal(trade.mtsSold, mtsSold) + assert.isNumber(trade.proceeds) + assert.equal(trade.proceeds, proceeds) + assert.isNumber(trade.cost) + assert.equal(trade.cost, cost) + assert.isNumber(trade.gainOrLoss) + assert.equal(trade.gainOrLoss, gainOrLoss) +} From ac01fbe0d584efbbcb2e77eb884b9991d3945ddc Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 17 Apr 2024 13:17:49 +0300 Subject: [PATCH 049/106] Improve test case checker for sale trades with realized profit --- .../test-sale-trades-with-realized-profit.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js index 4ec0a6957..4fdadffcf 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-sale-trades-with-realized-profit.js @@ -2,6 +2,16 @@ const { assert } = require('chai') +/* + * It's a simple workaround for passing test with + * issue `0.1 + 0.2 = 0.30000000000000004` + */ +const truncFloat = (num, precision) => { + const _precision = precision ?? 13 + + return Math.trunc(num * 10 ** _precision) / 10 ** _precision +} + module.exports = (arr, index, props) => { const trade = arr[index] const { @@ -25,9 +35,9 @@ module.exports = (arr, index, props) => { assert.isNumber(trade.mtsSold) assert.equal(trade.mtsSold, mtsSold) assert.isNumber(trade.proceeds) - assert.equal(trade.proceeds, proceeds) + assert.equal(truncFloat(trade.proceeds), proceeds) assert.isNumber(trade.cost) - assert.equal(trade.cost, cost) + assert.equal(truncFloat(trade.cost), cost) assert.isNumber(trade.gainOrLoss) - assert.equal(trade.gainOrLoss, gainOrLoss) + assert.equal(truncFloat(trade.gainOrLoss), gainOrLoss) } From f56ce5df41c4adaea82b0cde0918f2bb0512e3c9 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 17 Apr 2024 13:52:51 +0300 Subject: [PATCH 050/106] Add test coverage for lifo lookup sale trx with realized profit --- .../helpers/__test__/look-up-trades.spec.js | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index 4f96eb92f..2a90fcfd9 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -9,7 +9,8 @@ const { getMockedTrades } = require('./helpers') const { - testBuyTradesWithUnrealizedProfit + testBuyTradesWithUnrealizedProfit, + testSaleTradesWithRealizedProfit } = require('./test-cases') describe('lookUpTrades helper for trx tax report', () => { @@ -89,7 +90,72 @@ describe('lookUpTrades helper for trx tax report', () => { } ) - console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + assert.isArray(saleTradesWithRealizedProfit) + assert.equal(saleTradesWithRealizedProfit.length, 7) + + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 0, { + asset: 'UST', + amount: 100, + mtsAcquired: Date.UTC(2023, 5, 11), + mtsSold: Date.UTC(2023, 6, 21), + proceeds: 105, + cost: 111, + gainOrLoss: -6 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 1, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 5, 11), + proceeds: 3110, + cost: 2601, + gainOrLoss: 509 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 2, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 4, 22), + proceeds: 2650, + cost: 2601, + gainOrLoss: 49 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 3, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 4, 10), + proceeds: 2000, + cost: 2601, + gainOrLoss: -601 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 4, { + asset: 'ETH', + amount: 2, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 3, 10), + proceeds: 6400, + cost: 5202, + gainOrLoss: 1198 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 5, { + asset: 'BTC', + amount: 0.5, + mtsAcquired: Date.UTC(2023, 1, 5), + mtsSold: Date.UTC(2023, 2, 23), + proceeds: 25000, + cost: 21500, + gainOrLoss: 3500 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 6, { + asset: 'BTC', + amount: 2, + mtsAcquired: Date.UTC(2023, 1, 5), + mtsSold: Date.UTC(2023, 2, 3), + proceeds: 66000, + cost: 86000, + gainOrLoss: -20000 + }) }) it('Lookup buy trx with unrealized profit, FIFO strategy', async function () { From 93cfd685932df0462fd3b195e84efe49135b9826 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 17 Apr 2024 14:13:31 +0300 Subject: [PATCH 051/106] Add test coverage for fifo lookup sale trx with realized profit --- .../helpers/__test__/look-up-trades.spec.js | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index 2a90fcfd9..e9435537c 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -234,7 +234,72 @@ describe('lookUpTrades helper for trx tax report', () => { } ) - console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + assert.isArray(saleTradesWithRealizedProfit) + assert.equal(saleTradesWithRealizedProfit.length, 7) + + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 0, { + asset: 'UST', + amount: 100, + mtsAcquired: Date.UTC(2023, 5, 11), + mtsSold: Date.UTC(2023, 6, 21), + proceeds: 105, + cost: 111, + gainOrLoss: -6 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 1, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 5, 11), + proceeds: 3110, + cost: 2601, + gainOrLoss: 509 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 2, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 4, 22), + proceeds: 2650, + cost: 2601, + gainOrLoss: 49 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 3, { + asset: 'ETH', + amount: 1, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 4, 10), + proceeds: 2000, + cost: 2601, + gainOrLoss: -601 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 4, { + asset: 'ETH', + amount: 2, + mtsAcquired: Date.UTC(2023, 2, 23), + mtsSold: Date.UTC(2023, 3, 10), + proceeds: 6400, + cost: 5202, + gainOrLoss: 1198 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 5, { + asset: 'BTC', + amount: 0.5, + mtsAcquired: Date.UTC(2023, 0, 10), + mtsSold: Date.UTC(2023, 2, 23), + proceeds: 25000, + cost: 10000, + gainOrLoss: 15000 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 6, { + asset: 'BTC', + amount: 2, + mtsAcquired: Date.UTC(2023, 0, 10), + mtsSold: Date.UTC(2023, 2, 3), + proceeds: 66000, + cost: 40000, + gainOrLoss: 26000 + }) }) it('Lookup sale trx with realized profit considering prev year, LIFO strategy', async function () { From 982f9d602a32d16074db1134dedbadb1b5644fe1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 17 Apr 2024 15:12:18 +0300 Subject: [PATCH 052/106] Add test coverage for lifo lookup sale trx with realized profit with prev year --- .../helpers/__test__/look-up-trades.spec.js | 83 ++++++++++++------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index e9435537c..aa388cad0 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -107,8 +107,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 5, 11), - proceeds: 3110, - cost: 2601, + proceeds: 3_110, + cost: 2_601, gainOrLoss: 509 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 2, { @@ -116,8 +116,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 4, 22), - proceeds: 2650, - cost: 2601, + proceeds: 2_650, + cost: 2_601, gainOrLoss: 49 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 3, { @@ -125,8 +125,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 4, 10), - proceeds: 2000, - cost: 2601, + proceeds: 2_000, + cost: 2_601, gainOrLoss: -601 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 4, { @@ -134,27 +134,27 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 2, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 3, 10), - proceeds: 6400, - cost: 5202, - gainOrLoss: 1198 + proceeds: 6_400, + cost: 5_202, + gainOrLoss: 1_198 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 5, { asset: 'BTC', amount: 0.5, mtsAcquired: Date.UTC(2023, 1, 5), mtsSold: Date.UTC(2023, 2, 23), - proceeds: 25000, - cost: 21500, - gainOrLoss: 3500 + proceeds: 25_000, + cost: 21_500, + gainOrLoss: 3_500 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 6, { asset: 'BTC', amount: 2, mtsAcquired: Date.UTC(2023, 1, 5), mtsSold: Date.UTC(2023, 2, 3), - proceeds: 66000, - cost: 86000, - gainOrLoss: -20000 + proceeds: 66_000, + cost: 86_000, + gainOrLoss: -20_000 }) }) @@ -251,8 +251,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 5, 11), - proceeds: 3110, - cost: 2601, + proceeds: 3_110, + cost: 2_601, gainOrLoss: 509 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 2, { @@ -260,8 +260,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 4, 22), - proceeds: 2650, - cost: 2601, + proceeds: 2_650, + cost: 2_601, gainOrLoss: 49 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 3, { @@ -269,8 +269,8 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 1, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 4, 10), - proceeds: 2000, - cost: 2601, + proceeds: 2_000, + cost: 2_601, gainOrLoss: -601 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 4, { @@ -278,27 +278,27 @@ describe('lookUpTrades helper for trx tax report', () => { amount: 2, mtsAcquired: Date.UTC(2023, 2, 23), mtsSold: Date.UTC(2023, 3, 10), - proceeds: 6400, - cost: 5202, - gainOrLoss: 1198 + proceeds: 6_400, + cost: 5_202, + gainOrLoss: 1_198 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 5, { asset: 'BTC', amount: 0.5, mtsAcquired: Date.UTC(2023, 0, 10), mtsSold: Date.UTC(2023, 2, 23), - proceeds: 25000, - cost: 10000, - gainOrLoss: 15000 + proceeds: 25_000, + cost: 10_000, + gainOrLoss: 15_000 }) testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 6, { asset: 'BTC', amount: 2, mtsAcquired: Date.UTC(2023, 0, 10), mtsSold: Date.UTC(2023, 2, 3), - proceeds: 66000, - cost: 40000, - gainOrLoss: 26000 + proceeds: 66_000, + cost: 40_000, + gainOrLoss: 26_000 }) }) @@ -314,7 +314,6 @@ describe('lookUpTrades helper for trx tax report', () => { isNotGainOrLossRequired: true } ) - console.log('[buyTradesWithUnrealizedProfit]:', buyTradesWithUnrealizedProfit) const _mockTradesForNextYear = getMockedTrades(mockTradesForNextYear) _mockTradesForNextYear.push(...buyTradesWithUnrealizedProfit) @@ -330,6 +329,26 @@ describe('lookUpTrades helper for trx tax report', () => { } ) - console.log('[saleTradesWithRealizedProfit]:', saleTradesWithRealizedProfit) + assert.isArray(saleTradesWithRealizedProfit) + assert.equal(saleTradesWithRealizedProfit.length, 2) + + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 0, { + asset: 'UST', + amount: 200, + mtsAcquired: Date.UTC(2023, 5, 11), + mtsSold: Date.UTC(2024, 3, 27), + proceeds: 196, + cost: 222, + gainOrLoss: -26 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 1, { + asset: 'BTC', + amount: 5, + mtsAcquired: Date.UTC(2023, 1, 5), + mtsSold: Date.UTC(2024, 2, 17), + proceeds: 305_000, + cost: 220_275, + gainOrLoss: 84_725 + }) }) }) From ed64143c4e2cfe91af198eba9712d179d853a4db Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 17 Apr 2024 15:16:17 +0300 Subject: [PATCH 053/106] Add test coverage for fifo lookup sale trx with realized profit with prev year --- .../helpers/__test__/look-up-trades.spec.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js index aa388cad0..d2b3e749b 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/look-up-trades.spec.js @@ -351,4 +351,54 @@ describe('lookUpTrades helper for trx tax report', () => { gainOrLoss: 84_725 }) }) + + it('Lookup sale trx with realized profit considering prev year, FIFO strategy', async function () { + const { + buyTradesWithUnrealizedProfit + } = await lookUpTrades( + getMockedTrades(mockTrades), + { + isBackIterativeSaleLookUp: true, + isBackIterativeBuyLookUp: true, + isBuyTradesWithUnrealizedProfitRequired: true, + isNotGainOrLossRequired: true + } + ) + const _mockTradesForNextYear = getMockedTrades(mockTradesForNextYear) + _mockTradesForNextYear.push(...buyTradesWithUnrealizedProfit) + + const { + saleTradesWithRealizedProfit + } = await lookUpTrades( + _mockTradesForNextYear, + { + isBackIterativeSaleLookUp: true, + isBackIterativeBuyLookUp: true, + isBuyTradesWithUnrealizedProfitRequired: false, + isNotGainOrLossRequired: false + } + ) + + assert.isArray(saleTradesWithRealizedProfit) + assert.equal(saleTradesWithRealizedProfit.length, 2) + + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 0, { + asset: 'UST', + amount: 200, + mtsAcquired: Date.UTC(2023, 5, 11), + mtsSold: Date.UTC(2024, 3, 27), + proceeds: 196, + cost: 222, + gainOrLoss: -26 + }) + testSaleTradesWithRealizedProfit(saleTradesWithRealizedProfit, 1, { + asset: 'BTC', + amount: 5, + mtsAcquired: Date.UTC(2023, 1, 5), + mtsSold: Date.UTC(2024, 2, 17), + proceeds: 305_000, + cost: 215_000, + gainOrLoss: 90_000 + }) + }) }) From bdb1ef94f213f7a9e7722381bf89ca2235f3c2b1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Apr 2024 07:41:59 +0300 Subject: [PATCH 054/106] Fix price prop naming for trx tax report --- .../__test__/helpers/get-mocked-trades.js | 8 ++++---- .../helpers/__test__/helpers/mock-trades.js | 16 +++++++-------- .../test-buy-trades-with-unrealized-profit.js | 4 ++-- .../helpers/get-trx-map-by-ccy.js | 8 ++++---- .../helpers/look-up-trades.js | 8 ++++---- .../sync/transaction.tax.report/index.js | 20 +++++++++---------- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js index fcf444e42..888584732 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trades.js @@ -19,8 +19,8 @@ module.exports = (mockTrades, opts) => { firstSymb: null, lastSymb: null, - firstSymbPrise: null, - lastSymbPrise: null + firstSymbPrice: null, + lastSymbPrice: null } return mockTrades.map((trade, i) => { @@ -37,8 +37,8 @@ module.exports = (mockTrades, opts) => { orderPrice: trade.execPrice, firstSymb, lastSymb, - firstSymbPrise: lastSymb === 'USD' ? trade.execPrice : null, - lastSymbPrise: lastSymb === 'USD' ? 1 : null, + firstSymbPrice: lastSymb === 'USD' ? trade.execPrice : null, + lastSymbPrice: lastSymb === 'USD' ? 1 : null, ...trade, diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js index 8ae2998d5..9df101bab 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-trades.js @@ -40,24 +40,24 @@ const mockTrades = [ mtsCreate: Date.UTC(2023, 6, 21), execAmount: -100, execPrice: 0.9, - firstSymbPrise: 1.05, - lastSymbPrise: 0.95 + firstSymbPrice: 1.05, + lastSymbPrice: 0.95 }, { symbol: 'tETHUST', mtsCreate: Date.UTC(2023, 5, 11), execAmount: -1, execPrice: 2800, - firstSymbPrise: 3_110, - lastSymbPrise: 1.11 + firstSymbPrice: 3_110, + lastSymbPrice: 1.11 }, { symbol: 'tETHBTC', mtsCreate: Date.UTC(2023, 4, 22), execAmount: -1, execPrice: 0.055, - firstSymbPrise: 2_650, - lastSymbPrise: 48_000 + firstSymbPrice: 2_650, + lastSymbPrice: 48_000 }, { symbol: 'tETHUSD', @@ -83,8 +83,8 @@ const mockTrades = [ mtsCreate: Date.UTC(2023, 2, 23), execAmount: 10, execPrice: 0.05, - firstSymbPrise: 2_601, - lastSymbPrise: 50_000 + firstSymbPrice: 2_601, + lastSymbPrice: 50_000 }, { symbol: 'tBTCUSD', diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js index 6aaa38152..d1eeb4a85 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-buy-trades-with-unrealized-profit.js @@ -26,8 +26,8 @@ module.exports = (arr, index, props) => { assert.equal(trade.proceedsForBuyTrx, 0) assert.isNumber(trade.proceedsForBuyTrx) assert.equal(trade.proceedsForBuyTrx, 0) - assert.isNumber(trade.firstSymbPrise) - assert.isNumber(trade.lastSymbPrise) + assert.isNumber(trade.firstSymbPrice) + assert.isNumber(trade.lastSymbPrice) assert.isArray(trade.saleTrxsForRealizedProfit) assert.isBoolean(trade.isMovements) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js index c4721e7e7..c3cf51163 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js @@ -19,8 +19,8 @@ module.exports = (trxs) => { trxMapByCcy.get(trx.firstSymb).push({ isNotFirstSymbForex, isNotLastSymbForex, - mainPrisePropName: 'firstSymbPrise', - secondPrisePropName: 'lastSymbPrise', + mainPricePropName: 'firstSymbPrice', + secondPricePropName: 'lastSymbPrice', trx }) } @@ -32,8 +32,8 @@ module.exports = (trxs) => { trxMapByCcy.get(trx.lastSymb).push({ isNotFirstSymbForex, isNotLastSymbForex, - mainPrisePropName: 'lastSymbPrise', - secondPrisePropName: 'firstSymbPrise', + mainPricePropName: 'lastSymbPrice', + secondPricePropName: 'firstSymbPrice', trx }) } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index cba990f01..167e53f3b 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -129,8 +129,8 @@ module.exports = async (trades, opts) => { ? Math.abs(trade.execAmount) : Math.abs(trade.execAmount * trade.execPrice) const _salePrice = isDistinctSale - ? trade.firstSymbPrise - : trade.lastSymbPrise + ? trade.firstSymbPrice + : trade.lastSymbPrice const salePrice = isNotGainOrLossRequired ? 0 : _salePrice const saleAsset = isDistinctSale ? firstSymb @@ -224,8 +224,8 @@ module.exports = async (trades, opts) => { ? Math.abs(tradeForLookup.execAmount) : Math.abs(tradeForLookup.execAmount * tradeForLookup.execPrice) const _buyPrice = tradeForLookup.execAmount > 0 - ? tradeForLookup.firstSymbPrise - : tradeForLookup.lastSymbPrise + ? tradeForLookup.firstSymbPrice + : tradeForLookup.lastSymbPrice const buyPrice = isNotGainOrLossRequired ? 0 : _buyPrice const buyRestAmount = buyAmount - tradeForLookup.buyFilledAmount const saleRestAmount = saleAmount - trade.saleFilledAmount diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index b8105c135..68192a389 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -222,14 +222,14 @@ class TransactionTaxReport { const [firstSymb, lastSymb] = splitSymbolPairs(trade.symbol) trade.firstSymb = firstSymb trade.lastSymb = lastSymb - trade.firstSymbPrise = null - trade.lastSymbPrise = null + trade.firstSymbPrice = null + trade.lastSymbPrice = null remapedTrxs.push(trade) if (lastSymb === 'USD') { - trade.firstSymbPrise = trade.execPrice - trade.lastSymbPrise = 1 + trade.firstSymbPrice = trade.execPrice + trade.lastSymbPrice = 1 continue } @@ -260,10 +260,10 @@ class TransactionTaxReport { mtsCreate: movement.mtsUpdated, firstSymb, lastSymb, - firstSymbPrise: null, - lastSymbPrise: 1, + firstSymbPrice: null, + lastSymbPrice: 1, execAmount: movement.amount, - // NOTE: execPrice = firstSymbPrise and should be set when converting currencies + // NOTE: execPrice = firstSymbPrice and should be set when converting currencies execPrice: 0 } @@ -317,7 +317,7 @@ class TransactionTaxReport { } lastIndex = i - trxDataItem.trx[trxDataItem.mainPrisePropName] = pubTrade.price + trxDataItem.trx[trxDataItem.mainPricePropName] = pubTrade.price if (trxDataItem.trx.isMovements) { trxDataItem.trx.execPrice = pubTrade.price @@ -334,7 +334,7 @@ class TransactionTaxReport { trxDataItem.isNotFirstSymbForex && !trxDataItem.isNotFirstSymbForex ) { - trxDataItem.trx[trxDataItem.secondPrisePropName] = ( + trxDataItem.trx[trxDataItem.secondPricePropName] = ( pubTrade.price / trxDataItem.trx.execPrice ) } @@ -342,7 +342,7 @@ class TransactionTaxReport { !trxDataItem.isNotFirstSymbForex && trxDataItem.isNotFirstSymbForex ) { - trxDataItem.trx[trxDataItem.secondPrisePropName] = ( + trxDataItem.trx[trxDataItem.secondPricePropName] = ( pubTrade.price * trxDataItem.trx.execPrice ) } From b18a1525b377df431768d83550ffae2cb25ff987 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Apr 2024 08:13:29 +0300 Subject: [PATCH 055/106] Move remap trades logic into separate helper --- .../transaction.tax.report/helpers/index.js | 5 ++- .../helpers/re-map-trades.js | 44 +++++++++++++++++++ .../sync/transaction.tax.report/index.js | 40 +++-------------- 3 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index 7e684b659..c7bcba98b 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -4,10 +4,11 @@ const lookUpTrades = require('./look-up-trades') const getTrxMapByCcy = require('./get-trx-map-by-ccy') const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') - +const reMapTrades = require('./re-map-trades') module.exports = { lookUpTrades, getTrxMapByCcy, getPubTradeChunkPayloads, - TRX_TAX_STRATEGIES + TRX_TAX_STRATEGIES, + reMapTrades } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js new file mode 100644 index 000000000..3441b5ea4 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js @@ -0,0 +1,44 @@ +'use strict' + +const { + splitSymbolPairs +} = require('bfx-report/workers/loc.api/helpers') + +module.exports = (trades, params) => { + const { + remapedTrxs, + remapedTrxsForConvToUsd + } = params + + for (const trade of trades) { + if ( + !trade?.symbol || + !Number.isFinite(trade?.execAmount) || + trade.execAmount === 0 || + !Number.isFinite(trade?.execPrice) || + trade.execPrice === 0 || + !Number.isFinite(trade?.mtsCreate) + ) { + continue + } + + const [firstSymb, lastSymb] = splitSymbolPairs(trade.symbol) + trade.firstSymb = firstSymb + trade.lastSymb = lastSymb + trade.firstSymbPrice = null + trade.lastSymbPrice = null + + remapedTrxs.push(trade) + + if (lastSymb === 'USD') { + trade.firstSymbPrice = trade.execPrice + trade.lastSymbPrice = 1 + + continue + } + + remapedTrxsForConvToUsd.push(trade) + } + + return params +} diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 68192a389..6785b25fb 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,9 +1,5 @@ 'use strict' -const { - splitSymbolPairs -} = require('bfx-report/workers/loc.api/helpers') - const { isForexSymb } = require('../helpers') @@ -14,7 +10,8 @@ const { lookUpTrades, getTrxMapByCcy, getPubTradeChunkPayloads, - TRX_TAX_STRATEGIES + TRX_TAX_STRATEGIES, + reMapTrades } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -207,35 +204,10 @@ class TransactionTaxReport { const remapedTrxs = [] const remapedTrxsForConvToUsd = [] - for (const trade of trades) { - if ( - !trade?.symbol || - !Number.isFinite(trade?.execAmount) || - trade.execAmount === 0 || - !Number.isFinite(trade?.execPrice) || - trade.execPrice === 0 || - !Number.isFinite(trade?.mtsCreate) - ) { - continue - } - - const [firstSymb, lastSymb] = splitSymbolPairs(trade.symbol) - trade.firstSymb = firstSymb - trade.lastSymb = lastSymb - trade.firstSymbPrice = null - trade.lastSymbPrice = null - - remapedTrxs.push(trade) - - if (lastSymb === 'USD') { - trade.firstSymbPrice = trade.execPrice - trade.lastSymbPrice = 1 - - continue - } - - remapedTrxsForConvToUsd.push(trade) - } + reMapTrades( + trades, + { remapedTrxs, remapedTrxsForConvToUsd } + ) for (const movement of movements) { if ( From bda5d2d1a5994d29a1fb82602c31afca978957f4 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Apr 2024 09:45:27 +0300 Subject: [PATCH 056/106] Move remap movements logic into separate helper --- .../transaction.tax.report/helpers/index.js | 5 +- .../helpers/re-map-movements.js | 48 +++++++++++++++++++ .../sync/transaction.tax.report/index.js | 44 +++-------------- 3 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index c7bcba98b..6f301929d 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -5,10 +5,13 @@ const getTrxMapByCcy = require('./get-trx-map-by-ccy') const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') const reMapTrades = require('./re-map-trades') +const reMapMovements = require('./re-map-movements') + module.exports = { lookUpTrades, getTrxMapByCcy, getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, - reMapTrades + reMapTrades, + reMapMovements } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js new file mode 100644 index 000000000..9391d82b8 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js @@ -0,0 +1,48 @@ +'use strict' + +const { + isForexSymb +} = require('../../helpers') + +module.exports = (movements, params) => { + const { + remapedTrxs, + remapedTrxsForConvToUsd + } = params + + for (const movement of movements) { + if ( + !movement?.currency || + isForexSymb(movement.currency) || + !Number.isFinite(movement?.amount) || + movement.amount === 0 || + !Number.isFinite(movement?.mtsUpdated) + ) { + continue + } + + const firstSymb = movement.currency + const lastSymb = 'USD' + const symbSeparator = firstSymb.length > 3 + ? ':' + : '' + + const remapedMovement = { + isMovements: true, + symbol: `t${firstSymb}${symbSeparator}${lastSymb}`, + mtsCreate: movement.mtsUpdated, + firstSymb, + lastSymb, + firstSymbPrice: null, + lastSymbPrice: 1, + execAmount: movement.amount, + // NOTE: execPrice = firstSymbPrice and should be set when converting currencies + execPrice: 0 + } + + remapedTrxs.push(remapedMovement) + remapedTrxsForConvToUsd.push(remapedMovement) + } + + return params +} diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 6785b25fb..53a96c775 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,8 +1,5 @@ 'use strict' -const { - isForexSymb -} = require('../helpers') const { TrxTaxReportGenerationTimeoutError } = require('../../errors') @@ -11,7 +8,8 @@ const { getTrxMapByCcy, getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, - reMapTrades + reMapTrades, + reMapMovements } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -208,40 +206,10 @@ class TransactionTaxReport { trades, { remapedTrxs, remapedTrxsForConvToUsd } ) - - for (const movement of movements) { - if ( - !movement?.currency || - isForexSymb(movement.currency) || - !Number.isFinite(movement?.amount) || - movement.amount === 0 || - !Number.isFinite(movement?.mtsUpdated) - ) { - continue - } - - const firstSymb = movement.currency - const lastSymb = 'USD' - const symbSeparator = firstSymb.length > 3 - ? ':' - : '' - - const remapedMovement = { - isMovements: true, - symbol: `t${firstSymb}${symbSeparator}${lastSymb}`, - mtsCreate: movement.mtsUpdated, - firstSymb, - lastSymb, - firstSymbPrice: null, - lastSymbPrice: 1, - execAmount: movement.amount, - // NOTE: execPrice = firstSymbPrice and should be set when converting currencies - execPrice: 0 - } - - remapedTrxs.push(remapedMovement) - remapedTrxsForConvToUsd.push(remapedMovement) - } + reMapMovements( + movements, + { remapedTrxs, remapedTrxsForConvToUsd } + ) const trxs = remapedTrxs .sort((a, b) => b?.mtsCreate - a?.mtsCreate) From 881c392b66071ca056cdaf5eb3c080d61a4a79be Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Apr 2024 12:26:58 +0300 Subject: [PATCH 057/106] Improve currency conversion precision for tax --- workers/loc.api/sync/transaction.tax.report/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 53a96c775..f4aa5cd5b 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -241,7 +241,7 @@ class TransactionTaxReport { for (const trxDataItem of trxData) { let lastIndex = 0 - for (let i = lastIndex + 1; pubTrades.length > i; i += 1) { + for (let i = lastIndex; pubTrades.length > i; i += 1) { const pubTrade = pubTrades[i] const isLastPubTrade = (i + 1) === pubTrades.length From ee011edd250f35dd3d7b17261420ba2ad89408f5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Apr 2024 12:57:28 +0300 Subject: [PATCH 058/106] Move convertCurrencyBySymbol logic into separate helper --- .../helpers/convert-currency-by-symbol.js | 56 +++++++++++++++++++ .../transaction.tax.report/helpers/index.js | 4 +- .../sync/transaction.tax.report/index.js | 56 +------------------ 3 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js b/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js new file mode 100644 index 000000000..256bf5972 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js @@ -0,0 +1,56 @@ +'use strict' + +module.exports = (trxData, pubTrades) => { + for (const trxDataItem of trxData) { + let lastIndex = 0 + + for (let i = lastIndex; pubTrades.length > i; i += 1) { + const pubTrade = pubTrades[i] + const isLastPubTrade = (i + 1) === pubTrades.length + + if ( + ( + pubTrade?.mts > trxDataItem.trx.mtsCreate && + !isLastPubTrade + ) || + !Number.isFinite(pubTrade?.price) || + pubTrade.price === 0 + ) { + continue + } + + lastIndex = i + trxDataItem.trx[trxDataItem.mainPricePropName] = pubTrade.price + + if (trxDataItem.trx.isMovements) { + trxDataItem.trx.execPrice = pubTrade.price + + break + } + if ( + !Number.isFinite(trxDataItem.trx.execPrice) || + trxDataItem.trx.execPrice === 0 + ) { + break + } + if ( + trxDataItem.isNotFirstSymbForex && + !trxDataItem.isNotFirstSymbForex + ) { + trxDataItem.trx[trxDataItem.secondPricePropName] = ( + pubTrade.price / trxDataItem.trx.execPrice + ) + } + if ( + !trxDataItem.isNotFirstSymbForex && + trxDataItem.isNotFirstSymbForex + ) { + trxDataItem.trx[trxDataItem.secondPricePropName] = ( + pubTrade.price * trxDataItem.trx.execPrice + ) + } + + break + } + } +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index 6f301929d..b98bac7f1 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -6,6 +6,7 @@ const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') const reMapTrades = require('./re-map-trades') const reMapMovements = require('./re-map-movements') +const convertCurrencyBySymbol = require('./convert-currency-by-symbol') module.exports = { lookUpTrades, @@ -13,5 +14,6 @@ module.exports = { getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, reMapTrades, - reMapMovements + reMapMovements, + convertCurrencyBySymbol } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index f4aa5cd5b..3339f42de 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -9,7 +9,8 @@ const { getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, reMapTrades, - reMapMovements + reMapMovements, + convertCurrencyBySymbol } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -238,58 +239,7 @@ class TransactionTaxReport { pubTrades.push(...chunk) } - for (const trxDataItem of trxData) { - let lastIndex = 0 - - for (let i = lastIndex; pubTrades.length > i; i += 1) { - const pubTrade = pubTrades[i] - const isLastPubTrade = (i + 1) === pubTrades.length - - if ( - ( - pubTrade?.mts > trxDataItem.trx.mtsCreate && - !isLastPubTrade - ) || - !Number.isFinite(pubTrade?.price) || - pubTrade.price === 0 - ) { - continue - } - - lastIndex = i - trxDataItem.trx[trxDataItem.mainPricePropName] = pubTrade.price - - if (trxDataItem.trx.isMovements) { - trxDataItem.trx.execPrice = pubTrade.price - - break - } - if ( - !Number.isFinite(trxDataItem.trx.execPrice) || - trxDataItem.trx.execPrice === 0 - ) { - break - } - if ( - trxDataItem.isNotFirstSymbForex && - !trxDataItem.isNotFirstSymbForex - ) { - trxDataItem.trx[trxDataItem.secondPricePropName] = ( - pubTrade.price / trxDataItem.trx.execPrice - ) - } - if ( - !trxDataItem.isNotFirstSymbForex && - trxDataItem.isNotFirstSymbForex - ) { - trxDataItem.trx[trxDataItem.secondPricePropName] = ( - pubTrade.price * trxDataItem.trx.execPrice - ) - } - - break - } - } + convertCurrencyBySymbol(trxData, pubTrades) } } From 4f044d178785718d350ab6afe58a9501e011eac2 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Apr 2024 15:49:25 +0300 Subject: [PATCH 059/106] Enhance trade mock data for trx tax report --- test/helpers/mock-data.js | 112 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/test/helpers/mock-data.js b/test/helpers/mock-data.js index dd927b363..5a6553f84 100644 --- a/test/helpers/mock-data.js +++ b/test/helpers/mock-data.js @@ -130,7 +130,7 @@ module.exports = new Map([ 'trades', [ [ - 12345, + 100012345, 'tBTCUSD', _ms, 12345, @@ -143,7 +143,33 @@ module.exports = new Map([ 'USD' ], [ - 10012346, + 110012345, + 'tETHUSD', + _ms, + 12345, + 0.12345, + 12345, + null, + null, + false, + -3.0001, + 'USD' + ], + [ + 120012345, + 'tETHUSD', + _ms, + 12345, + -0.11, + 12355, + null, + null, + false, + -3.0001, + 'USD' + ], + [ + 130012345, 'tBTCEUR', _ms, 12345, @@ -156,7 +182,20 @@ module.exports = new Map([ 'BTC' ], [ - 20012347, + 140012345, + 'tBTCEUR', + _ms, + 12345, + -0.11, + 12355, + null, + null, + false, + -0.0001, + 'BTC' + ], + [ + 150012347, 'tBTCGBP', _ms, 12345, @@ -169,7 +208,20 @@ module.exports = new Map([ 'BTC' ], [ - 30012345, + 160012347, + 'tBTCGBP', + _ms, + 12345, + -0.11, + 12355, + null, + null, + false, + -0.0001, + 'BTC' + ], + [ + 170012345, 'tBTCJPY', _ms, 12345, @@ -180,6 +232,58 @@ module.exports = new Map([ false, -0.0001, 'BTC' + ], + [ + 180012345, + 'tBTCJPY', + _ms, + 12345, + -0.11, + 12355, + null, + null, + false, + -0.0001, + 'BTC' + ], + [ + 190012345, + 'tETHBTC', + _ms, + 12345, + 0.12345, + 12345, + null, + null, + false, + -0.0001, + 'BTC' + ], + [ + 200012345, + 'tETHBTC', + _ms, + 12345, + -0.11, + 12355, + null, + null, + false, + -0.0001, + 'BTC' + ], + [ + 210012345, + 'tBTCUSD', + _ms, + 12345, + 0.12345, + 12345, + null, + null, + false, + -3.0001, + 'USD' ] ] ], From e6dd3de67f3eaac9f0c7da7388ed080c15691644 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Apr 2024 15:51:17 +0300 Subject: [PATCH 060/106] Fix getTrades test case to consider new mock data --- test/test-cases/api-sync-mode-sqlite-test-cases.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-cases/api-sync-mode-sqlite-test-cases.js b/test/test-cases/api-sync-mode-sqlite-test-cases.js index ae03d76f5..644b29f5f 100644 --- a/test/test-cases/api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/api-sync-mode-sqlite-test-cases.js @@ -1808,7 +1808,7 @@ module.exports = ( symbol: ['tBTCUSD', 'tETHUSD'], start: 0, end, - limit: 10 + limit: 2 }, id: 5 }) From f71d156de05856eeac31111abb678d13515b2392 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Apr 2024 15:52:44 +0300 Subject: [PATCH 061/106] Add test case for getTransactionTaxReport method with lifo strategy --- ...itional-api-sync-mode-sqlite-test-cases.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js index 768dc5d2e..6eac0d4ed 100644 --- a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js @@ -413,6 +413,44 @@ module.exports = ( } }) + it('it should be successfully performed by the getTransactionTaxReport method, LIFO strategy', async function () { + this.timeout(60000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'getTransactionTaxReport', + params: { + end, + start: end - (45 * 24 * 60 * 60 * 1000), + strategy: 'LIFO' + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isArray(res.body.result) + assert.isAtLeast(res.body.result.length, 1) + + res.body.result.forEach((item) => { + assert.isObject(item) + assert.containsAllKeys(item, [ + 'asset', + 'amount', + 'mtsAcquired', + 'mtsSold', + 'proceeds', + 'cost', + 'gainOrLoss' + ]) + }) + }) + it('it should be successfully performed by the getTradedVolume method', async function () { this.timeout(60000) From 3c34b099ccdc48150cc3c5aafb0794e2117797f3 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Apr 2024 16:43:17 +0300 Subject: [PATCH 062/106] Add test case for getTransactionTaxReport method with fifo strategy --- test/helpers/mock-data.js | 12 +++--- ...itional-api-sync-mode-sqlite-test-cases.js | 40 ++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/test/helpers/mock-data.js b/test/helpers/mock-data.js index 5a6553f84..8e423591e 100644 --- a/test/helpers/mock-data.js +++ b/test/helpers/mock-data.js @@ -160,7 +160,7 @@ module.exports = new Map([ 'tETHUSD', _ms, 12345, - -0.11, + -0.011, 12355, null, null, @@ -186,7 +186,7 @@ module.exports = new Map([ 'tBTCEUR', _ms, 12345, - -0.11, + -0.011, 12355, null, null, @@ -212,7 +212,7 @@ module.exports = new Map([ 'tBTCGBP', _ms, 12345, - -0.11, + -0.011, 12355, null, null, @@ -238,7 +238,7 @@ module.exports = new Map([ 'tBTCJPY', _ms, 12345, - -0.11, + -0.011, 12355, null, null, @@ -251,7 +251,7 @@ module.exports = new Map([ 'tETHBTC', _ms, 12345, - 0.12345, + 0.01, 12345, null, null, @@ -264,7 +264,7 @@ module.exports = new Map([ 'tETHBTC', _ms, 12345, - -0.11, + -0.01, 12355, null, null, diff --git a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js index 6eac0d4ed..f0dd5a828 100644 --- a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js @@ -424,7 +424,7 @@ module.exports = ( method: 'getTransactionTaxReport', params: { end, - start: end - (45 * 24 * 60 * 60 * 1000), + start: start + (45 * 24 * 60 * 60 * 1000), strategy: 'LIFO' }, id: 5 @@ -451,6 +451,44 @@ module.exports = ( }) }) + it('it should be successfully performed by the getTransactionTaxReport method, FIFO strategy', async function () { + this.timeout(60000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'getTransactionTaxReport', + params: { + end, + start: start + (45 * 24 * 60 * 60 * 1000), + strategy: 'FIFO' + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isArray(res.body.result) + assert.isAtLeast(res.body.result.length, 1) + + res.body.result.forEach((item) => { + assert.isObject(item) + assert.containsAllKeys(item, [ + 'asset', + 'amount', + 'mtsAcquired', + 'mtsSold', + 'proceeds', + 'cost', + 'gainOrLoss' + ]) + }) + }) + it('it should be successfully performed by the getTradedVolume method', async function () { this.timeout(60000) From c731dc4c8efa505c8dc472748217f8825d7a9621 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Apr 2024 17:15:52 +0300 Subject: [PATCH 063/106] Add test case for getTransactionTaxReportFile method --- ...itional-api-sync-mode-sqlite-test-cases.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js index f0dd5a828..55be51e4e 100644 --- a/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/additional-api-sync-mode-sqlite-test-cases.js @@ -1110,6 +1110,33 @@ module.exports = ( ) }) + it('it should be successfully performed by the getTransactionTaxReportFile method', async function () { + this.timeout(60000) + + const procPromise = queueToPromise(params.processorQueue) + const aggrPromise = queueToPromise(params.aggregatorQueue) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'getTransactionTaxReportFile', + params: { + isPDFRequired, + end, + start, + strategy: 'LIFO', + email + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + await testMethodOfGettingReportFile(procPromise, aggrPromise, res) + }) + it('it should be successfully performed by the getTradedVolumeFile method', async function () { this.timeout(60000) From ebee5072739328816339b870d841ad5f9256c255 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 10:55:20 +0300 Subject: [PATCH 064/106] Add mock movements --- .../__test__/helpers/mock-movements.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js new file mode 100644 index 000000000..6ac5a7f4e --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js @@ -0,0 +1,49 @@ +'use strict' + +module.exports = [ + { + currency: 'BTC', + mtsUpdated: Date.UTC(2023, 8, 29), + amount: -0.9 + }, + { + currency: 'USD', + mtsUpdated: Date.UTC(2023, 7, 17), + amount: -123 + }, + { + currency: 'JPY', + mtsUpdated: Date.UTC(2023, 6, 19), + amount: 111.2 + }, + { + currency: 'ETH', + mtsUpdated: Date.UTC(2023, 5, 12), + amount: 19.6 + }, + { + currency: 'GBP', + mtsUpdated: Date.UTC(2023, 4, 3), + amount: 602 + }, + { + currency: 'BTC', + mtsUpdated: Date.UTC(2023, 3, 21), + amount: 2.4 + }, + { + currency: 'EUR', + mtsUpdated: Date.UTC(2023, 2, 17), + amount: 210 + }, + { + currency: 'UST', + mtsUpdated: Date.UTC(2023, 1, 15), + amount: 301 + }, + { + currency: 'USD', + mtsUpdated: Date.UTC(2023, 0, 11), + amount: 200 + } +] From 99b6fae4376c6196f4c8470e0c6801fe11637b44 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 10:56:28 +0300 Subject: [PATCH 065/106] Add helper to get mocked movements --- .../__test__/helpers/get-mocked-movements.js | 35 +++++++++++++++++++ .../helpers/__test__/helpers/index.js | 6 +++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-movements.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-movements.js new file mode 100644 index 000000000..66c402e50 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-movements.js @@ -0,0 +1,35 @@ +'use strict' + +module.exports = (mockMovements, opts) => { + const missingFields = { + _id: 1, + id: 1, + currencyName: 'CCY_NAME_EXAMPLE', + status: 'COMPLETED', + amountUsd: null, // It's not used here + fee: -0.5, + destinationAddress: '12345qwerty54321', + transactionId: 'qwerty12345ytrewq', + note: 'Mocked movements', + subUserId: null, + user_id: 1 + } + + return mockMovements.map((movement, i) => { + const mtsUpdated = opts?.year + ? new Date(movement.mtsUpdated).setUTCFullYear(opts?.year) + : movement.mtsUpdated + + return { + ...missingFields, + + _id: i + 1, + id: i + 1, + + ...movement, + + mtsStarted: mtsUpdated - 60000, + mtsUpdated + } + }) +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js index 2013b36b1..05dac9010 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js @@ -5,9 +5,13 @@ const { mockTrades } = require('./mock-trades') const getMockedTrades = require('./get-mocked-trades') +const mockMovements = require('./mock-movements') +const getMockedMovements = require('./get-mocked-movements') module.exports = { mockTradesForNextYear, mockTrades, - getMockedTrades + getMockedTrades, + mockMovements, + getMockedMovements } From 14009c62d3e6fae1886cb32f3bad79e592d42338 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 11:40:48 +0300 Subject: [PATCH 066/106] Rework remap helper naming --- .../transaction.tax.report/helpers/index.js | 8 ++++---- ...re-map-movements.js => remap-movements.js} | 10 +++++----- .../{re-map-trades.js => remap-trades.js} | 8 ++++---- .../sync/transaction.tax.report/index.js | 20 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) rename workers/loc.api/sync/transaction.tax.report/helpers/{re-map-movements.js => remap-movements.js} (84%) rename workers/loc.api/sync/transaction.tax.report/helpers/{re-map-trades.js => remap-trades.js} (87%) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index b98bac7f1..f171d0d7f 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -4,8 +4,8 @@ const lookUpTrades = require('./look-up-trades') const getTrxMapByCcy = require('./get-trx-map-by-ccy') const getPubTradeChunkPayloads = require('./get-pub-trade-chunk-payloads') const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') -const reMapTrades = require('./re-map-trades') -const reMapMovements = require('./re-map-movements') +const remapTrades = require('./remap-trades') +const remapMovements = require('./remap-movements') const convertCurrencyBySymbol = require('./convert-currency-by-symbol') module.exports = { @@ -13,7 +13,7 @@ module.exports = { getTrxMapByCcy, getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, - reMapTrades, - reMapMovements, + remapTrades, + remapMovements, convertCurrencyBySymbol } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/remap-movements.js similarity index 84% rename from workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js rename to workers/loc.api/sync/transaction.tax.report/helpers/remap-movements.js index 9391d82b8..eda6e5c31 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-movements.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/remap-movements.js @@ -6,8 +6,8 @@ const { module.exports = (movements, params) => { const { - remapedTrxs, - remapedTrxsForConvToUsd + remappedTrxs, + remappedTrxsForConvToUsd } = params for (const movement of movements) { @@ -27,7 +27,7 @@ module.exports = (movements, params) => { ? ':' : '' - const remapedMovement = { + const remappedMovement = { isMovements: true, symbol: `t${firstSymb}${symbSeparator}${lastSymb}`, mtsCreate: movement.mtsUpdated, @@ -40,8 +40,8 @@ module.exports = (movements, params) => { execPrice: 0 } - remapedTrxs.push(remapedMovement) - remapedTrxsForConvToUsd.push(remapedMovement) + remappedTrxs.push(remappedMovement) + remappedTrxsForConvToUsd.push(remappedMovement) } return params diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js similarity index 87% rename from workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js rename to workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js index 3441b5ea4..06630f804 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/re-map-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js @@ -6,8 +6,8 @@ const { module.exports = (trades, params) => { const { - remapedTrxs, - remapedTrxsForConvToUsd + remappedTrxs, + remappedTrxsForConvToUsd } = params for (const trade of trades) { @@ -28,7 +28,7 @@ module.exports = (trades, params) => { trade.firstSymbPrice = null trade.lastSymbPrice = null - remapedTrxs.push(trade) + remappedTrxs.push(trade) if (lastSymb === 'USD') { trade.firstSymbPrice = trade.execPrice @@ -37,7 +37,7 @@ module.exports = (trades, params) => { continue } - remapedTrxsForConvToUsd.push(trade) + remappedTrxsForConvToUsd.push(trade) } return params diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 3339f42de..e1aa72442 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -8,8 +8,8 @@ const { getTrxMapByCcy, getPubTradeChunkPayloads, TRX_TAX_STRATEGIES, - reMapTrades, - reMapMovements, + remapTrades, + remapMovements, convertCurrencyBySymbol } = require('./helpers') @@ -200,21 +200,21 @@ class TransactionTaxReport { ]) const movements = [...withdrawals, ...deposits] - const remapedTrxs = [] - const remapedTrxsForConvToUsd = [] + const remappedTrxs = [] + const remappedTrxsForConvToUsd = [] - reMapTrades( + remapTrades( trades, - { remapedTrxs, remapedTrxsForConvToUsd } + { remappedTrxs, remappedTrxsForConvToUsd } ) - reMapMovements( + remapMovements( movements, - { remapedTrxs, remapedTrxsForConvToUsd } + { remappedTrxs, remappedTrxsForConvToUsd } ) - const trxs = remapedTrxs + const trxs = remappedTrxs .sort((a, b) => b?.mtsCreate - a?.mtsCreate) - const trxsForConvToUsd = remapedTrxsForConvToUsd + const trxsForConvToUsd = remappedTrxsForConvToUsd .sort((a, b) => b?.mtsCreate - a?.mtsCreate) return { From 3a750adeaecc4d6c3b349b8bf5b7ec6803de5f45 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 11:44:58 +0300 Subject: [PATCH 067/106] Add test cases checker for remapped movements --- .../helpers/__test__/test-cases/index.js | 4 ++- .../test-cases/test-remapped-movements.js | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-movements.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index 64e5b20bd..2f706fefc 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -2,8 +2,10 @@ const testBuyTradesWithUnrealizedProfit = require('./test-buy-trades-with-unrealized-profit') const testSaleTradesWithRealizedProfit = require('./test-sale-trades-with-realized-profit') +const testRemappedMovements = require('./test-remapped-movements') module.exports = { testBuyTradesWithUnrealizedProfit, - testSaleTradesWithRealizedProfit + testSaleTradesWithRealizedProfit, + testRemappedMovements } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-movements.js new file mode 100644 index 000000000..163d9702b --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-movements.js @@ -0,0 +1,32 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const trx = arr[index] + const { + mtsCreate, + symbol, + firstSymb, + execAmount + } = props ?? {} + + assert.isObject(trx) + assert.isBoolean(trx.isMovements) + assert.isOk(trx.isMovements) + assert.isNumber(trx.mtsCreate) + assert.equal(trx.mtsCreate, mtsCreate) + assert.isNull(trx.firstSymbPrice) + assert.isNumber(trx.lastSymbPrice) + assert.equal(trx.lastSymbPrice, 1) + assert.isString(trx.symbol) + assert.equal(trx.symbol, symbol) + assert.isString(trx.firstSymb) + assert.equal(trx.firstSymb, firstSymb) + assert.isString(trx.lastSymb) + assert.equal(trx.lastSymb, 'USD') + assert.isNumber(trx.execAmount) + assert.equal(trx.execAmount, execAmount) + assert.isNumber(trx.execPrice) + assert.equal(trx.execPrice, 0) +} From 1384e464461b7f5707f20c39d8ec62590c71091d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 11:46:05 +0300 Subject: [PATCH 068/106] Add test coverage for remap-movements helper --- .../helpers/__test__/remap-movements.spec.js | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js new file mode 100644 index 000000000..2daeb9174 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js @@ -0,0 +1,60 @@ +'use strict' + +const { assert } = require('chai') + +const remapMovements = require('../remap-movements') +const { + mockMovements, + getMockedMovements +} = require('./helpers') +const { + testRemappedMovements +} = require('./test-cases') + +describe('reMapMovements helper for trx tax report', () => { + it('Re-map movements to trade data structure', function () { + const remappedTrxs = [] + const remappedTrxsForConvToUsd = [] + const params = { remappedTrxs, remappedTrxsForConvToUsd } + + const returnedParams = remapMovements( + getMockedMovements(mockMovements), + params + ) + + assert.isObject(returnedParams) + assert.equal(returnedParams, params) + assert.isArray(returnedParams.remappedTrxs) + assert.equal(returnedParams.remappedTrxs, remappedTrxs) + assert.isArray(returnedParams.remappedTrxsForConvToUsd) + assert.equal(returnedParams.remappedTrxsForConvToUsd, remappedTrxsForConvToUsd) + + assert.equal(remappedTrxs.length, 4) + assert.equal(remappedTrxsForConvToUsd.length, remappedTrxs.length) + + testRemappedMovements(remappedTrxs, 0, { + mtsCreate: Date.UTC(2023, 8, 29), + symbol: 'tBTCUSD', + firstSymb: 'BTC', + execAmount: -0.9 + }) + testRemappedMovements(remappedTrxs, 1, { + mtsCreate: Date.UTC(2023, 5, 12), + symbol: 'tETHUSD', + firstSymb: 'ETH', + execAmount: 19.6 + }) + testRemappedMovements(remappedTrxs, 2, { + mtsCreate: Date.UTC(2023, 3, 21), + symbol: 'tBTCUSD', + firstSymb: 'BTC', + execAmount: 2.4 + }) + testRemappedMovements(remappedTrxs, 3, { + mtsCreate: Date.UTC(2023, 1, 15), + symbol: 'tUSTUSD', + firstSymb: 'UST', + execAmount: 301 + }) + }) +}) From f412333c0718d4c4d766c1de372c751672cd9e5d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 13:27:22 +0300 Subject: [PATCH 069/106] Enhance mock movement data --- .../helpers/__test__/helpers/mock-movements.js | 15 +++++++++++++++ .../helpers/__test__/remap-movements.spec.js | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js index 6ac5a7f4e..82552bb2f 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/mock-movements.js @@ -21,6 +21,21 @@ module.exports = [ mtsUpdated: Date.UTC(2023, 5, 12), amount: 19.6 }, + { + currency: '', + mtsUpdated: Date.UTC(2023, 5, 11), + amount: 19.6 + }, + { + currency: 'LTC', + mtsUpdated: Date.UTC(2023, 5, 10), + amount: 0 + }, + { + currency: 'LTC', + mtsUpdated: null, + amount: 10.2 + }, { currency: 'GBP', mtsUpdated: Date.UTC(2023, 4, 3), diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js index 2daeb9174..6533be952 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-movements.spec.js @@ -11,8 +11,8 @@ const { testRemappedMovements } = require('./test-cases') -describe('reMapMovements helper for trx tax report', () => { - it('Re-map movements to trade data structure', function () { +describe('remapMovements helper for trx tax report', () => { + it('Remap movements to trx data structure', function () { const remappedTrxs = [] const remappedTrxsForConvToUsd = [] const params = { remappedTrxs, remappedTrxsForConvToUsd } From e360af82587dc5a5092a0d48a1a3063d80801218 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 13:29:10 +0300 Subject: [PATCH 070/106] Add test cases checker for remapped trades --- .../helpers/__test__/test-cases/index.js | 4 +- .../test-cases/test-remapped-trades.js | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-trades.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index 2f706fefc..e9f51e4c1 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -3,9 +3,11 @@ const testBuyTradesWithUnrealizedProfit = require('./test-buy-trades-with-unrealized-profit') const testSaleTradesWithRealizedProfit = require('./test-sale-trades-with-realized-profit') const testRemappedMovements = require('./test-remapped-movements') +const testRemappedTrades = require('./test-remapped-trades') module.exports = { testBuyTradesWithUnrealizedProfit, testSaleTradesWithRealizedProfit, - testRemappedMovements + testRemappedMovements, + testRemappedTrades } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-trades.js new file mode 100644 index 000000000..3ee09bbb6 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-remapped-trades.js @@ -0,0 +1,38 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const trx = arr[index] + const { + mtsCreate, + symbol, + firstSymb, + lastSymb, + firstSymbPrice, + lastSymbPrice + } = props ?? {} + + assert.isObject(trx) + assert.isNumber(trx.mtsCreate) + assert.equal(trx.mtsCreate, mtsCreate) + assert.isString(trx.symbol) + assert.equal(trx.symbol, symbol) + assert.isString(trx.firstSymb) + assert.equal(trx.firstSymb, firstSymb) + assert.isString(trx.lastSymb) + assert.equal(trx.lastSymb, lastSymb) + + if (firstSymbPrice) { + assert.isNumber(trx.firstSymbPrice) + assert.equal(trx.firstSymbPrice, firstSymbPrice) + } else { + assert.isNull(trx.firstSymbPrice) + } + if (lastSymbPrice) { + assert.isNumber(trx.lastSymbPrice) + assert.equal(trx.lastSymbPrice, lastSymbPrice) + } else { + assert.isNull(trx.lastSymbPrice) + } +} From b01feb83518eedc20dbef9df5555fda559388dbd Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 22 Apr 2024 13:29:57 +0300 Subject: [PATCH 071/106] Add test coverage for remap-trades helper --- .../helpers/__test__/remap-trades.spec.js | 119 ++++++++++++++++++ .../helpers/remap-trades.js | 6 +- 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-trades.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-trades.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-trades.spec.js new file mode 100644 index 000000000..20df884c3 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/remap-trades.spec.js @@ -0,0 +1,119 @@ +'use strict' + +const { assert } = require('chai') + +const remapTrades = require('../remap-trades') + +const { + testRemappedTrades +} = require('./test-cases') + +const mockedTrades = [ + { + symbol: 'tUSTEUR', + mtsCreate: Date.UTC(2024, 5, 19), + execAmount: 303.2, + execPrice: 3_123 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 4, 18), + execAmount: 3.1, + execPrice: 20_000 + }, + { + symbol: 'tBTCUSD', + mtsCreate: Date.UTC(2024, 3, 21), + execAmount: 0, + execPrice: 20_000 + }, + { + symbol: '', + mtsCreate: Date.UTC(2024, 2, 12), + execAmount: 4.6, + execPrice: 20_000 + }, + { + symbol: 'tETHUSD', + mtsCreate: Date.UTC(2024, 1, 2), + execAmount: 4.6, + execPrice: 0 + }, + { + symbol: 'tETHUSD', + mtsCreate: null, + execAmount: 4.6, + execPrice: 0.512 + }, + { + symbol: 'tETHBTC', + mtsCreate: Date.UTC(2024, 0, 8), + execAmount: -12.4, + execPrice: 20_000 + } +] + +describe('remapTrades helper for trx tax report', () => { + it('Remap trades to trx data structure', function () { + const remappedTrxs = [] + const remappedTrxsForConvToUsd = [] + const params = { remappedTrxs, remappedTrxsForConvToUsd } + + const returnedParams = remapTrades( + mockedTrades, + params + ) + + assert.isObject(returnedParams) + assert.equal(returnedParams, params) + assert.isArray(returnedParams.remappedTrxs) + assert.equal(returnedParams.remappedTrxs, remappedTrxs) + assert.isArray(returnedParams.remappedTrxsForConvToUsd) + assert.equal(returnedParams.remappedTrxsForConvToUsd, remappedTrxsForConvToUsd) + + assert.equal(remappedTrxs.length, 3) + assert.equal(remappedTrxsForConvToUsd.length, 2) + + testRemappedTrades(remappedTrxs, 0, { + mtsCreate: Date.UTC(2024, 5, 19), + symbol: 'tUSTEUR', + firstSymb: 'UST', + lastSymb: 'EUR', + firstSymbPrice: null, + lastSymbPrice: null + }) + testRemappedTrades(remappedTrxs, 1, { + mtsCreate: Date.UTC(2024, 4, 18), + symbol: 'tBTCUSD', + firstSymb: 'BTC', + lastSymb: 'USD', + firstSymbPrice: 20_000, + lastSymbPrice: 1 + }) + testRemappedTrades(remappedTrxs, 2, { + mtsCreate: Date.UTC(2024, 0, 8), + symbol: 'tETHBTC', + firstSymb: 'ETH', + lastSymb: 'BTC', + firstSymbPrice: null, + lastSymbPrice: null + }) + + testRemappedTrades(remappedTrxsForConvToUsd, 0, { + mtsCreate: Date.UTC(2024, 5, 19), + symbol: 'tUSTEUR', + firstSymb: 'UST', + lastSymb: 'EUR', + firstSymbPrice: null, + lastSymbPrice: null + }) + testRemappedTrades(remappedTrxsForConvToUsd, 1, { + mtsCreate: Date.UTC(2024, 0, 8), + symbol: 'tETHBTC', + firstSymb: 'ETH', + lastSymb: 'BTC', + firstSymbPrice: null, + lastSymbPrice: null + }) + }) +}) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js index 06630f804..080580db4 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/remap-trades.js @@ -1,8 +1,8 @@ 'use strict' -const { - splitSymbolPairs -} = require('bfx-report/workers/loc.api/helpers') +const splitSymbolPairs = require( + 'bfx-report/workers/loc.api/helpers/split-symbol-pairs' +) module.exports = (trades, params) => { const { From 9361032779c39f4858d711eb5dc752a9e43b2985 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 23 Apr 2024 13:55:37 +0300 Subject: [PATCH 072/106] Add test cases checker for trx map by currency --- .../helpers/__test__/test-cases/index.js | 4 +- .../test-cases/test-trx-map-by-ccy.js | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-trx-map-by-ccy.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index e9f51e4c1..d1c692ab5 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -4,10 +4,12 @@ const testBuyTradesWithUnrealizedProfit = require('./test-buy-trades-with-unreal const testSaleTradesWithRealizedProfit = require('./test-sale-trades-with-realized-profit') const testRemappedMovements = require('./test-remapped-movements') const testRemappedTrades = require('./test-remapped-trades') +const testTrxMapByCcy = require('./test-trx-map-by-ccy') module.exports = { testBuyTradesWithUnrealizedProfit, testSaleTradesWithRealizedProfit, testRemappedMovements, - testRemappedTrades + testRemappedTrades, + testTrxMapByCcy } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-trx-map-by-ccy.js new file mode 100644 index 000000000..f048bc115 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-trx-map-by-ccy.js @@ -0,0 +1,38 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (trxMapByCcy, ccy, dataArray) => { + const array = trxMapByCcy.get(ccy) + + assert.isArray(array) + assert.lengthOf(array, dataArray.length) + + for (const [i, item] of array.entries()) { + assert.isObject(dataArray[i]) + + const { + mtsCreate, + isNotFirstSymbForex, + isNotLastSymbForex, + mainPrice, + secondPrice + } = dataArray[i] + + assert.isObject(item) + assert.isBoolean(item.isNotFirstSymbForex) + assert.equal(item.isNotFirstSymbForex, isNotFirstSymbForex) + assert.isBoolean(item.isNotLastSymbForex) + assert.equal(item.isNotLastSymbForex, isNotLastSymbForex) + assert.isString(item.mainPricePropName) + assert.isString(item.secondPricePropName) + + assert.isObject(item.trx) + assert.isNumber(item.trx.mtsCreate) + assert.equal(item.trx.mtsCreate, mtsCreate) + assert.isNumber(item.trx[item.mainPricePropName]) + assert.equal(item.trx[item.mainPricePropName], mainPrice) + assert.isNumber(item.trx[item.secondPricePropName]) + assert.equal(item.trx[item.secondPricePropName], secondPrice) + } +} From 3aaaff43a06b1e457ef02d663e4e23fb88d9a4b4 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 23 Apr 2024 13:56:41 +0300 Subject: [PATCH 073/106] Add test coverage for get-trx-map-by-ccy helper --- .../__test__/get-trx-map-by-ccy.spec.js | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-trx-map-by-ccy.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-trx-map-by-ccy.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-trx-map-by-ccy.spec.js new file mode 100644 index 000000000..c9b18fdfc --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-trx-map-by-ccy.spec.js @@ -0,0 +1,116 @@ +'use strict' + +const { assert } = require('chai') + +const getTrxMapByCcy = require('../get-trx-map-by-ccy') + +const { + testTrxMapByCcy +} = require('./test-cases') + +const firstSymbPrice = 12345 +const lastSymbPrice = 54321 + +const mockedTrxs = [ + { + mtsCreate: Date.UTC(2024, 5, 19), + firstSymb: 'BTC', + lastSymb: 'USD' + }, + { + mtsCreate: Date.UTC(2024, 4, 18), + firstSymb: 'UST', + lastSymb: 'EUR' + }, + { + mtsCreate: Date.UTC(2024, 3, 21), + firstSymb: 'ETH', + lastSymb: 'GBP' + }, + { + mtsCreate: Date.UTC(2024, 2, 12), + firstSymb: 'LTC', + lastSymb: 'JPY' + }, + { + mtsCreate: Date.UTC(2024, 1, 2), + firstSymb: 'ETH', + lastSymb: 'BTC' + }, + { + mtsCreate: Date.UTC(2024, 0, 8), + firstSymb: 'UST', + lastSymb: 'USD' + } +].map((trx) => ({ + ...trx, + firstSymbPrice, + lastSymbPrice +})) + +describe('getTrxMapByCcy helper for trx tax report', () => { + it('Get trx map by currency for conversion to USD', function () { + const trxMapByCcy = getTrxMapByCcy(mockedTrxs) + + assert.instanceOf(trxMapByCcy, Map) + assert.lengthOf(trxMapByCcy, 4) + + testTrxMapByCcy(trxMapByCcy, 'BTC', [ + { + mtsCreate: Date.UTC(2024, 5, 19), + isNotFirstSymbForex: true, + isNotLastSymbForex: false, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + }, + { + mtsCreate: Date.UTC(2024, 1, 2), + isNotFirstSymbForex: true, + isNotLastSymbForex: true, + mainPrice: lastSymbPrice, + secondPrice: firstSymbPrice + } + ]) + testTrxMapByCcy(trxMapByCcy, 'UST', [ + { + mtsCreate: Date.UTC(2024, 4, 18), + isNotFirstSymbForex: true, + isNotLastSymbForex: false, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + }, + { + mtsCreate: Date.UTC(2024, 0, 8), + isNotFirstSymbForex: true, + isNotLastSymbForex: false, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + } + ]) + testTrxMapByCcy(trxMapByCcy, 'ETH', [ + { + mtsCreate: Date.UTC(2024, 3, 21), + isNotFirstSymbForex: true, + isNotLastSymbForex: false, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + }, + { + mtsCreate: Date.UTC(2024, 1, 2), + isNotFirstSymbForex: true, + isNotLastSymbForex: true, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + } + ]) + testTrxMapByCcy(trxMapByCcy, 'LTC', [ + { + mtsCreate: Date.UTC(2024, 2, 12), + isNotFirstSymbForex: true, + isNotLastSymbForex: false, + mainPrice: firstSymbPrice, + secondPrice: lastSymbPrice + } + ]) + }) +}) From 42049ac8091348a0f5119928a69ee4cbdcb31b98 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 23 Apr 2024 16:13:53 +0300 Subject: [PATCH 074/106] Add test cases checker for pub trade chunk payloads --- .../helpers/__test__/test-cases/index.js | 4 ++- .../test-pub-trade-chunk-payloads.js | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-pub-trade-chunk-payloads.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index d1c692ab5..9e028a965 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -5,11 +5,13 @@ const testSaleTradesWithRealizedProfit = require('./test-sale-trades-with-realiz const testRemappedMovements = require('./test-remapped-movements') const testRemappedTrades = require('./test-remapped-trades') const testTrxMapByCcy = require('./test-trx-map-by-ccy') +const testPubTradeChunkPayloads = require('./test-pub-trade-chunk-payloads') module.exports = { testBuyTradesWithUnrealizedProfit, testSaleTradesWithRealizedProfit, testRemappedMovements, testRemappedTrades, - testTrxMapByCcy + testTrxMapByCcy, + testPubTradeChunkPayloads } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-pub-trade-chunk-payloads.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-pub-trade-chunk-payloads.js new file mode 100644 index 000000000..401159284 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-pub-trade-chunk-payloads.js @@ -0,0 +1,25 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const payload = arr[index] + const { + symbol, + end, + start + } = props ?? {} + + assert.isObject(payload) + assert.isString(payload.symbol) + assert.equal(payload.symbol, symbol) + assert.isNumber(payload.end) + assert.equal(payload.end, end) + + if (start) { + assert.isNumber(payload.start) + assert.equal(payload.start, start) + } else { + assert.isNull(payload.start) + } +} From d47ee8cd46b5522b6cd37ba95fccf4b056b2e556 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 23 Apr 2024 16:15:09 +0300 Subject: [PATCH 075/106] Add test coverage for get-pub-trade-chunk-payloads helper --- .../get-pub-trade-chunk-payloads.spec.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk-payloads.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk-payloads.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk-payloads.spec.js new file mode 100644 index 000000000..9ad8f13cd --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk-payloads.spec.js @@ -0,0 +1,82 @@ +'use strict' + +const { assert } = require('chai') + +const getPubTradeChunkPayloads = require('../get-pub-trade-chunk-payloads') + +const { + testPubTradeChunkPayloads +} = require('./test-cases') + +describe('getPubTradeChunkPayloads helper for trx tax report', () => { + it('should push separate request payload for trxs with mts more than 24h', function () { + const trxData = [ + { mtsCreate: Date.UTC(2024, 10, 11, 12) }, + + { mtsCreate: Date.UTC(2024, 8, 15, 11) }, + { mtsCreate: Date.UTC(2024, 8, 15, 10) }, + + { mtsCreate: Date.UTC(2024, 0, 9, 17) }, + { mtsCreate: Date.UTC(2024, 0, 9, 16, 1) }, + + { mtsCreate: Date.UTC(2024, 0, 8, 16) }, + { mtsCreate: Date.UTC(2024, 0, 8, 15) }, + { mtsCreate: Date.UTC(2024, 0, 8, 14) }, + + { mtsCreate: Date.UTC(2024, 0, 5, 9) } + ].map((trx) => ({ trx })) + const pubTradeChunkPayloads = getPubTradeChunkPayloads('BTC', trxData) + + assert.isArray(pubTradeChunkPayloads) + assert.lengthOf(pubTradeChunkPayloads, 5) + + testPubTradeChunkPayloads(pubTradeChunkPayloads, 0, { + symbol: 'BTC', + end: Date.UTC(2024, 10, 11, 12), + start: null + }) + testPubTradeChunkPayloads(pubTradeChunkPayloads, 1, { + symbol: 'BTC', + end: Date.UTC(2024, 8, 15, 11), + start: Date.UTC(2024, 8, 15, 10) + }) + testPubTradeChunkPayloads(pubTradeChunkPayloads, 2, { + symbol: 'BTC', + end: Date.UTC(2024, 0, 9, 17), + start: Date.UTC(2024, 0, 9, 16, 1) + }) + testPubTradeChunkPayloads(pubTradeChunkPayloads, 3, { + symbol: 'BTC', + end: Date.UTC(2024, 0, 8, 16), + start: Date.UTC(2024, 0, 8, 14) + }) + testPubTradeChunkPayloads(pubTradeChunkPayloads, 4, { + symbol: 'BTC', + end: Date.UTC(2024, 0, 5, 9), + start: null + }) + }) + + it('should not push separate request payload for trxs', function () { + const trxData = [ + { mtsCreate: Date.UTC(2024, 1, 9, 1) }, + { mtsCreate: Date.UTC(2024, 1, 8, 16, 12, 15) }, + { mtsCreate: Date.UTC(2024, 1, 8, 16, 1) }, + { mtsCreate: Date.UTC(2024, 1, 8, 16) }, + { mtsCreate: Date.UTC(2024, 1, 8, 15) }, + { mtsCreate: Date.UTC(2024, 1, 8, 14) }, + { mtsCreate: Date.UTC(2024, 1, 8, 1) }, + { mtsCreate: Date.UTC(2024, 1, 7, 2) } + ].map((trx) => ({ trx })) + const pubTradeChunkPayloads = getPubTradeChunkPayloads('ETH', trxData) + + assert.isArray(pubTradeChunkPayloads) + assert.lengthOf(pubTradeChunkPayloads, 1) + + testPubTradeChunkPayloads(pubTradeChunkPayloads, 0, { + symbol: 'ETH', + end: Date.UTC(2024, 1, 9, 1), + start: Date.UTC(2024, 1, 7, 2) + }) + }) +}) From 871100992d971763d5caf03892ca17e2e118c33e Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Apr 2024 15:48:50 +0300 Subject: [PATCH 076/106] Add helper to get mocked trx map by ccy --- .../__test__/helpers/get-mocked-trx-map-by-ccy.js | 11 +++++++++++ .../helpers/__test__/helpers/index.js | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trx-map-by-ccy.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trx-map-by-ccy.js new file mode 100644 index 000000000..2fc1a51ad --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-trx-map-by-ccy.js @@ -0,0 +1,11 @@ +'use strict' + +const getMockedTrades = require('./get-mocked-trades') +const getTrxMapByCcy = require('../../get-trx-map-by-ccy') + +module.exports = (mockTrades, opts) => { + const mockedTrades = getMockedTrades(mockTrades, opts) + const trxMapByCcy = getTrxMapByCcy(mockedTrades) + + return trxMapByCcy +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js index 05dac9010..27d7248d0 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js @@ -7,11 +7,13 @@ const { const getMockedTrades = require('./get-mocked-trades') const mockMovements = require('./mock-movements') const getMockedMovements = require('./get-mocked-movements') +const getMockedTrxMapByCcy = require('./get-mocked-trx-map-by-ccy') module.exports = { mockTradesForNextYear, mockTrades, getMockedTrades, mockMovements, - getMockedMovements + getMockedMovements, + getMockedTrxMapByCcy } From 3e0257b4ff852204a5c0814e874bfd16203bb2d5 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Apr 2024 15:50:02 +0300 Subject: [PATCH 077/106] Add test cases checker for converted currency by symbol --- .../helpers/__test__/test-cases/index.js | 4 ++- .../test-converted-currency-by-symbol.js | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-converted-currency-by-symbol.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js index 9e028a965..1dda6f08d 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/index.js @@ -6,6 +6,7 @@ const testRemappedMovements = require('./test-remapped-movements') const testRemappedTrades = require('./test-remapped-trades') const testTrxMapByCcy = require('./test-trx-map-by-ccy') const testPubTradeChunkPayloads = require('./test-pub-trade-chunk-payloads') +const testConvertedCurrencyBySymbol = require('./test-converted-currency-by-symbol') module.exports = { testBuyTradesWithUnrealizedProfit, @@ -13,5 +14,6 @@ module.exports = { testRemappedMovements, testRemappedTrades, testTrxMapByCcy, - testPubTradeChunkPayloads + testPubTradeChunkPayloads, + testConvertedCurrencyBySymbol } diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-converted-currency-by-symbol.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-converted-currency-by-symbol.js new file mode 100644 index 000000000..73fafa1dc --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/test-cases/test-converted-currency-by-symbol.js @@ -0,0 +1,35 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = (arr, index, props) => { + const { trx } = arr[index] + const { + mtsCreate, + symbol, + execPrice, + firstSymbPrice, + lastSymbPrice + } = props ?? {} + + assert.isObject(trx) + assert.isNumber(trx.mtsCreate) + assert.equal(trx.mtsCreate, mtsCreate) + assert.isString(trx.symbol) + assert.equal(trx.symbol, symbol) + assert.isNumber(trx.execPrice) + assert.equal(trx.execPrice, execPrice) + + if (firstSymbPrice) { + assert.isNumber(trx.firstSymbPrice) + assert.equal(trx.firstSymbPrice, firstSymbPrice) + } else { + assert.isNull(trx.firstSymbPrice) + } + if (lastSymbPrice) { + assert.isNumber(trx.lastSymbPrice) + assert.equal(trx.lastSymbPrice, lastSymbPrice) + } else { + assert.isNull(trx.lastSymbPrice) + } +} From 12896035cb47129dec9e7d725130c8935774b5e0 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Apr 2024 15:52:20 +0300 Subject: [PATCH 078/106] Add test coverage for convert-currency-by-symbol helper --- .../convert-currency-by-symbol.spec.js | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/convert-currency-by-symbol.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/convert-currency-by-symbol.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/convert-currency-by-symbol.spec.js new file mode 100644 index 000000000..d8ff64949 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/convert-currency-by-symbol.spec.js @@ -0,0 +1,143 @@ +'use strict' + +const { assert } = require('chai') + +const convertCurrencyBySymbol = require('../convert-currency-by-symbol') + +const { + getMockedTrxMapByCcy +} = require('./helpers') +const { + testConvertedCurrencyBySymbol +} = require('./test-cases') + +const mockTrades = [ + { + mtsCreate: Date.UTC(2024, 0, 9, 17), + symbol: 'tBTCJPY', + execPrice: 6_041_453 + }, + { + mtsCreate: Date.UTC(2024, 0, 9, 16, 1), + symbol: 'tBTCGBP', + execPrice: 30_550 + }, + + { + mtsCreate: Date.UTC(2024, 0, 8, 16), + symbol: 'tBTCEUR', + execPrice: 33_000 + }, + { + mtsCreate: Date.UTC(2024, 0, 8, 15), + symbol: 'tBTCUSD', + execPrice: 0, + isMovements: true + }, + { + mtsCreate: Date.UTC(2024, 0, 8, 14), + symbol: 'tETHBTC', + execPrice: 0.512 + }, + { + mtsCreate: Date.UTC(2024, 0, 8, 14), + symbol: 'tETHBTC', + execPrice: 0.511 + }, + + { + mtsCreate: Date.UTC(2024, 0, 5, 9), + symbol: 'tBTCUSD', + execPrice: 0, + isMovements: true + }, + + { + mtsCreate: Date.UTC(2024, 0, 5, 7), + symbol: 'tBTCUST', + execPrice: 20_050 + } +] +const mockedPubTrades = [ + { mts: Date.UTC(2024, 0, 9, 17, 1), price: 39_400 }, + { mts: Date.UTC(2024, 0, 9, 16, 50), price: 39_000 }, + { mts: Date.UTC(2024, 0, 9, 16, 1), price: 38_000 }, + + { mts: Date.UTC(2024, 0, 8, 16), price: 35_000 }, + { mts: Date.UTC(2024, 0, 8, 15, 30), price: 22_700 }, + { mts: Date.UTC(2024, 0, 8, 14, 30), price: 22_500 }, + { mts: Date.UTC(2024, 0, 8, 14), price: 22_000 }, + + { mts: Date.UTC(2024, 0, 5, 10), price: 21_300 }, + { mts: Date.UTC(2024, 0, 5, 9), price: 21_000 }, + { mts: Date.UTC(2024, 0, 5, 8), price: 21_100 } +] + +describe('convertCurrencyBySymbol helper for trx tax report', () => { + it('should convert trx BTC to USD using pub trades', function () { + const mockedTrxMapByCcy = getMockedTrxMapByCcy(mockTrades) + const trxData = mockedTrxMapByCcy.get('BTC') + + convertCurrencyBySymbol(trxData, mockedPubTrades) + + assert.isArray(trxData) + assert.lengthOf(trxData, 8) + + testConvertedCurrencyBySymbol(trxData, 0, { + mtsCreate: Date.UTC(2024, 0, 9, 17), + symbol: 'tBTCJPY', + execPrice: 6_041_453, + firstSymbPrice: 39_000, + lastSymbPrice: 39_000 / 6_041_453 + }) + testConvertedCurrencyBySymbol(trxData, 1, { + mtsCreate: Date.UTC(2024, 0, 9, 16, 1), + symbol: 'tBTCGBP', + execPrice: 30_550, + firstSymbPrice: 38_000, + lastSymbPrice: 38_000 / 30_550 + }) + testConvertedCurrencyBySymbol(trxData, 2, { + mtsCreate: Date.UTC(2024, 0, 8, 16), + symbol: 'tBTCEUR', + execPrice: 33_000, + firstSymbPrice: 35_000, + lastSymbPrice: 35_000 / 33_000 + }) + testConvertedCurrencyBySymbol(trxData, 3, { + mtsCreate: Date.UTC(2024, 0, 8, 15), + symbol: 'tBTCUSD', + execPrice: 22_500, + firstSymbPrice: 22_500, + lastSymbPrice: 1 + }) + testConvertedCurrencyBySymbol(trxData, 4, { + mtsCreate: Date.UTC(2024, 0, 8, 14), + symbol: 'tETHBTC', + execPrice: 0.512, + firstSymbPrice: null, + lastSymbPrice: 22_000 + }) + testConvertedCurrencyBySymbol(trxData, 5, { + mtsCreate: Date.UTC(2024, 0, 8, 14), + symbol: 'tETHBTC', + execPrice: 0.511, + firstSymbPrice: null, + lastSymbPrice: 22_000 + }) + testConvertedCurrencyBySymbol(trxData, 6, { + mtsCreate: Date.UTC(2024, 0, 5, 9), + symbol: 'tBTCUSD', + execPrice: 21_000, + firstSymbPrice: 21_000, + lastSymbPrice: 1 + }) + testConvertedCurrencyBySymbol(trxData, 7, { + mtsCreate: Date.UTC(2024, 0, 5, 7), + symbol: 'tBTCUST', + execPrice: 20_050, + firstSymbPrice: 21_100, + lastSymbPrice: null + }) + }) +}) From 3a9d74e78cfabe17c7990a7d165397e71f2b99d4 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Apr 2024 15:54:03 +0300 Subject: [PATCH 079/106] Fix currency converter by symbol for forex ccy --- .../helpers/convert-currency-by-symbol.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js b/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js index 256bf5972..1cdda13dc 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/convert-currency-by-symbol.js @@ -35,7 +35,7 @@ module.exports = (trxData, pubTrades) => { } if ( trxDataItem.isNotFirstSymbForex && - !trxDataItem.isNotFirstSymbForex + !trxDataItem.isNotLastSymbForex ) { trxDataItem.trx[trxDataItem.secondPricePropName] = ( pubTrade.price / trxDataItem.trx.execPrice @@ -43,7 +43,7 @@ module.exports = (trxData, pubTrades) => { } if ( !trxDataItem.isNotFirstSymbForex && - trxDataItem.isNotFirstSymbForex + trxDataItem.isNotLastSymbForex ) { trxDataItem.trx[trxDataItem.secondPricePropName] = ( pubTrade.price * trxDataItem.trx.execPrice From d5910e534ff7e712eb47609f043bac5cb1be45eb Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 25 Apr 2024 09:27:27 +0300 Subject: [PATCH 080/106] Fix getting pub trade chunk --- workers/loc.api/sync/transaction.tax.report/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index e1aa72442..5183401b9 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -266,8 +266,10 @@ class TransactionTaxReport { end }) + if (!Array.isArray(pubTrades)) { + break + } if ( - !Array.isArray(pubTrades) || pubTrades.length === 0 || !Number.isFinite(start) || !Number.isFinite(pubTrades[0]?.mts) || From 6e86a07939d4314140a8bb8621dcf0a9fae277c0 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 25 Apr 2024 09:49:27 +0300 Subject: [PATCH 081/106] Move pub trade chunk getter into separate module --- .../helpers/get-pub-trade-chunk.js | 54 +++++++++++++++++++ .../transaction.tax.report/helpers/index.js | 4 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk.js new file mode 100644 index 000000000..162253392 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-pub-trade-chunk.js @@ -0,0 +1,54 @@ +'use strict' + +const { + TrxTaxReportGenerationTimeoutError +} = require('../../../errors') + +module.exports = async (params, pubTradeGetter) => { + const symbol = params?.symbol + const start = params?.start + let end = params?.end + let timeoutMts = Date.now() + const res = [] + + while (true) { + const currMts = Date.now() + const mtsDiff = currMts - timeoutMts + + if (mtsDiff > 1000 * 60 * 60 * 12) { + throw new TrxTaxReportGenerationTimeoutError() + } + + timeoutMts = currMts + + const { res: pubTrades } = await pubTradeGetter({ + symbol: `t${symbol}USD`, + start: 0, + end + }) + + if (!Array.isArray(pubTrades)) { + break + } + if ( + pubTrades.length === 0 || + !Number.isFinite(start) || + !Number.isFinite(pubTrades[0]?.mts) || + !Number.isFinite(pubTrades[pubTrades.length - 1]?.mts) || + ( + res.length !== 0 && + pubTrades[0]?.mts >= res[res.length - 1]?.mts + ) || + pubTrades[pubTrades.length - 1]?.mts <= start + ) { + res.push(...pubTrades) + + break + } + + end = pubTrades[pubTrades.length - 1].mts - 1 + res.push(...pubTrades) + } + + return res +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/index.js index f171d0d7f..301113101 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/index.js @@ -7,6 +7,7 @@ const TRX_TAX_STRATEGIES = require('./trx.tax.strategies') const remapTrades = require('./remap-trades') const remapMovements = require('./remap-movements') const convertCurrencyBySymbol = require('./convert-currency-by-symbol') +const getPubTradeChunk = require('./get-pub-trade-chunk') module.exports = { lookUpTrades, @@ -15,5 +16,6 @@ module.exports = { TRX_TAX_STRATEGIES, remapTrades, remapMovements, - convertCurrencyBySymbol + convertCurrencyBySymbol, + getPubTradeChunk } From 6250ec55a1499a5c99bef8942551486044314ace Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 25 Apr 2024 09:52:52 +0300 Subject: [PATCH 082/106] Use pub trade chunk getter for ccy conversion --- .../sync/transaction.tax.report/index.js | 112 +++++------------- 1 file changed, 32 insertions(+), 80 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 5183401b9..d6bd17ef6 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,8 +1,5 @@ 'use strict' -const { - TrxTaxReportGenerationTimeoutError -} = require('../../errors') const { lookUpTrades, getTrxMapByCcy, @@ -10,7 +7,8 @@ const { TRX_TAX_STRATEGIES, remapTrades, remapMovements, - convertCurrencyBySymbol + convertCurrencyBySymbol, + getPubTradeChunk } = require('./helpers') const { decorateInjectable } = require('../../di/utils') @@ -138,36 +136,6 @@ class TransactionTaxReport { return saleTradesWithRealizedProfit } - async #getTrades ({ - user, - start, - end, - symbol - }) { - const symbFilter = ( - Array.isArray(symbol) && - symbol.length !== 0 - ) - ? { $in: { symbol } } - : {} - - return this.dao.getElemsInCollBy( - this.ALLOWED_COLLS.TRADES, - { - filter: { - user_id: user._id, - $lte: { mtsCreate: end }, - $gte: { mtsCreate: start }, - ...symbFilter - }, - sort: [['mtsCreate', -1]], - projection: this.tradesModel, - exclude: ['user_id'], - isExcludePrivate: true - } - ) - } - async #getTrxs (params) { const { user, @@ -234,7 +202,10 @@ class TransactionTaxReport { ) for (const chunkPayload of pubTradeChunkPayloads) { - const chunk = await this.#getPublicTradeChunk(chunkPayload) + const chunk = await getPubTradeChunk( + chunkPayload, + (...args) => this.#getPublicTrades(...args) + ) pubTrades.push(...chunk) } @@ -243,53 +214,34 @@ class TransactionTaxReport { } } - async #getPublicTradeChunk (params) { - const symbol = params?.symbol - const start = params?.start - let end = params?.end - let timeoutMts = Date.now() - const res = [] - - while (true) { - const currMts = Date.now() - const mtsDiff = currMts - timeoutMts - - if (mtsDiff > 1000 * 60 * 60 * 12) { - throw new TrxTaxReportGenerationTimeoutError() - } - - timeoutMts = currMts - - const { res: pubTrades } = await this.#getPublicTrades({ - symbol: `t${symbol}USD`, - start: 0, - end - }) + async #getTrades ({ + user, + start, + end, + symbol + }) { + const symbFilter = ( + Array.isArray(symbol) && + symbol.length !== 0 + ) + ? { $in: { symbol } } + : {} - if (!Array.isArray(pubTrades)) { - break - } - if ( - pubTrades.length === 0 || - !Number.isFinite(start) || - !Number.isFinite(pubTrades[0]?.mts) || - !Number.isFinite(pubTrades[pubTrades.length - 1]?.mts) || - ( - res.length !== 0 && - pubTrades[0]?.mts >= res[res.length - 1]?.mts - ) || - pubTrades[pubTrades.length - 1]?.mts <= start - ) { - res.push(...pubTrades) - - break + return this.dao.getElemsInCollBy( + this.ALLOWED_COLLS.TRADES, + { + filter: { + user_id: user._id, + $lte: { mtsCreate: end }, + $gte: { mtsCreate: start }, + ...symbFilter + }, + sort: [['mtsCreate', -1]], + projection: this.tradesModel, + exclude: ['user_id'], + isExcludePrivate: true } - - end = pubTrades[pubTrades.length - 1].mts - 1 - res.push(...pubTrades) - } - - return res + ) } async #getPublicTrades (params) { From 28fa005068be23123881eb26326fdf3948d1cad3 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 25 Apr 2024 15:13:47 +0300 Subject: [PATCH 083/106] Add helper to get mocked pub trades --- .../__test__/helpers/get-mocked-pub-trades.js | 22 +++++++++++++++++++ .../helpers/__test__/helpers/index.js | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-pub-trades.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-pub-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-pub-trades.js new file mode 100644 index 000000000..7f74c4174 --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/get-mocked-pub-trades.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = (opts) => { + const { + lenght = 10, + start = Date.UTC(2025, 0, 1), + end = Date.UTC(2025, 11, 31), + price = 55_000 + } = opts ?? {} + const diff = end - start + const step = diff / (lenght - 1) + + return new Array(lenght) + .fill(null) + .map((item, i, arr) => { + const mts = i < arr.length - 1 + ? Math.trunc(end - step * i) + : start + + return { mts, price } + }) +} diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js index 27d7248d0..a5692cae2 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/helpers/index.js @@ -8,6 +8,7 @@ const getMockedTrades = require('./get-mocked-trades') const mockMovements = require('./mock-movements') const getMockedMovements = require('./get-mocked-movements') const getMockedTrxMapByCcy = require('./get-mocked-trx-map-by-ccy') +const getMockedPubTrades = require('./get-mocked-pub-trades') module.exports = { mockTradesForNextYear, @@ -15,5 +16,6 @@ module.exports = { getMockedTrades, mockMovements, getMockedMovements, - getMockedTrxMapByCcy + getMockedTrxMapByCcy, + getMockedPubTrades } From 579707b6abad453b647fbd507b4909ac470f84c8 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 25 Apr 2024 15:17:28 +0300 Subject: [PATCH 084/106] Add test coverage for get-pub-trade-chunk helper --- .../__test__/get-pub-trade-chunk.spec.js | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk.spec.js diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk.spec.js b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk.spec.js new file mode 100644 index 000000000..7b45d5eff --- /dev/null +++ b/workers/loc.api/sync/transaction.tax.report/helpers/__test__/get-pub-trade-chunk.spec.js @@ -0,0 +1,117 @@ +'use strict' + +const { assert } = require('chai') + +const getPubTradeChunk = require('../get-pub-trade-chunk') + +const { + getMockedPubTrades +} = require('./helpers') + +describe('getPubTradeChunk helper for trx tax report', () => { + it('should get pub trades for required start and end', async function () { + const params = { + symbol: 'BTC', + start: Date.UTC(2024, 0, 1), + end: Date.UTC(2024, 11, 31) + } + + let nextRequiredEndPoint = params.end + let callAmount = 0 + const requiredCallAmount = 3 + const diff = params.end - params.start + const step = diff / requiredCallAmount + + const pubTrades = await getPubTradeChunk(params, (args) => { + callAmount += 1 + + assert.isString(args.symbol) + assert.equal(args.symbol, 'tBTCUSD') + assert.isNumber(args.start) + assert.equal(args.start, 0) + assert.isNumber(args.end) + assert.equal(args.end, nextRequiredEndPoint) + + const res = getMockedPubTrades({ + lenght: 100, + end: args.end, + start: callAmount < requiredCallAmount + ? Math.trunc(args.end - step) + : params.start, + price: 51_000 + }) + + nextRequiredEndPoint = res[res.length - 1].mts - 1 + + return { res } + }) + + assert.isArray(pubTrades) + assert.lengthOf(pubTrades, 300) + + for (const pubTrade of pubTrades) { + assert.isObject(pubTrade) + assert.isNumber(pubTrade.mts) + assert.isNumber(pubTrade.price) + assert.equal(pubTrade.price, 51_000) + } + + assert.equal(pubTrades[0].mts, params.end) + assert.equal(pubTrades[pubTrades.length - 1].mts, params.start) + }) + + it('should get pub trades for required start and end with empty array in last response', async function () { + const params = { + symbol: 'ETH', + start: Date.UTC(2024, 1, 5), + end: Date.UTC(2024, 10, 25) + } + + let nextRequiredEndPoint = params.end + let callAmount = 0 + const requiredCallAmount = 5 + const diff = params.end - params.start + const step = diff / requiredCallAmount + + const pubTrades = await getPubTradeChunk(params, (args) => { + callAmount += 1 + + assert.isString(args.symbol) + assert.equal(args.symbol, 'tETHUSD') + assert.isNumber(args.start) + assert.equal(args.start, 0) + assert.isNumber(args.end) + assert.equal(args.end, nextRequiredEndPoint) + + if (callAmount === requiredCallAmount) { + return { res: [] } + } + + const res = getMockedPubTrades({ + lenght: 10_000, + end: args.end, + start: callAmount < requiredCallAmount + ? Math.trunc(args.end - step) + : params.start, + price: 33_000 + }) + + nextRequiredEndPoint = res[res.length - 1].mts - 1 + + return { res } + }) + + assert.isArray(pubTrades) + assert.lengthOf(pubTrades, 40_000) + + for (const pubTrade of pubTrades) { + assert.isObject(pubTrade) + assert.isNumber(pubTrade.mts) + assert.isNumber(pubTrade.price) + assert.equal(pubTrade.price, 33_000) + } + + assert.equal(pubTrades[0].mts, params.end) + assert.equal(pubTrades[pubTrades.length - 1].mts, nextRequiredEndPoint + 1) + }) +}) From 43e27565236405a63f21da539effa99226c9fea1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 6 May 2024 14:05:27 +0300 Subject: [PATCH 085/106] Fix pushing large array into array for trx tax report --- workers/loc.api/helpers/index.js | 6 ++++-- workers/loc.api/helpers/utils.js | 9 ++++++++- .../loc.api/sync/transaction.tax.report/index.js | 13 +++++++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/workers/loc.api/helpers/index.js b/workers/loc.api/helpers/index.js index 677615245..528f54f6f 100644 --- a/workers/loc.api/helpers/index.js +++ b/workers/loc.api/helpers/index.js @@ -11,7 +11,8 @@ const { pickLowerObjectsNumbers, sumAllObjectsNumbers, pickAllLowerObjectsNumbers, - sumArrayVolumes + sumArrayVolumes, + pushLargeArr } = require('./utils') const { isSubAccountApiKeys, @@ -33,5 +34,6 @@ module.exports = { pickLowerObjectsNumbers, sumAllObjectsNumbers, pickAllLowerObjectsNumbers, - sumArrayVolumes + sumArrayVolumes, + pushLargeArr } diff --git a/workers/loc.api/helpers/utils.js b/workers/loc.api/helpers/utils.js index 541bb2591..75a8a374e 100644 --- a/workers/loc.api/helpers/utils.js +++ b/workers/loc.api/helpers/utils.js @@ -266,6 +266,12 @@ const sumArrayVolumes = (propName, objects = []) => { }, []) } +const pushLargeArr = (dest, src) => { + for (const item of src) { + dest.push(item) + } +} + module.exports = { checkParamsAuth, tryParseJSON, @@ -276,5 +282,6 @@ module.exports = { pickLowerObjectsNumbers, sumAllObjectsNumbers, pickAllLowerObjectsNumbers, - sumArrayVolumes + sumArrayVolumes, + pushLargeArr } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index d6bd17ef6..225a961c6 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,5 +1,7 @@ 'use strict' +const { pushLargeArr } = require('../../helpers/utils') + const { lookUpTrades, getTrxMapByCcy, @@ -120,9 +122,12 @@ class TransactionTaxReport { } ) - trxsForCurrPeriod.push(...buyTradesWithUnrealizedProfit) - trxsForConvToUsd.push(...buyTradesWithUnrealizedProfit - .filter((trx) => trx?.isMovements || trx?.lastSymb !== 'USD')) + pushLargeArr(trxsForCurrPeriod, buyTradesWithUnrealizedProfit) + pushLargeArr( + trxsForConvToUsd, + buyTradesWithUnrealizedProfit + .filter((trx) => trx?.isMovements || trx?.lastSymb !== 'USD') + ) await this.#convertCurrencies(trxsForConvToUsd) const { saleTradesWithRealizedProfit } = await lookUpTrades( @@ -207,7 +212,7 @@ class TransactionTaxReport { (...args) => this.#getPublicTrades(...args) ) - pubTrades.push(...chunk) + pushLargeArr(pubTrades, chunk) } convertCurrencyBySymbol(trxData, pubTrades) From 8165e032e3f2c222de3a5dbcbcde3981af3943b0 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 7 May 2024 09:29:36 +0300 Subject: [PATCH 086/106] Improve error classes for trx tax report --- workers/loc.api/errors/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workers/loc.api/errors/index.js b/workers/loc.api/errors/index.js index 8d33851b0..36a3a8d04 100644 --- a/workers/loc.api/errors/index.js +++ b/workers/loc.api/errors/index.js @@ -249,14 +249,14 @@ class AuthTokenTTLSettingError extends ArgsParamsError { } class CurrencyConversionError extends BaseError { - constructor (message = 'ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') { - super(message) + constructor (data, message = 'ERR_CURRENCY_HAS_NOT_BEEN_CONVERTED_TO_USD') { + super({ data, message }) } } class CurrencyPairSeparationError extends BaseError { - constructor (message = 'ERR_CURRENCY_PAIR_HAS_NOT_BEEN_SEPARATED_CORRECTLY') { - super(message) + constructor (data, message = 'ERR_CURRENCY_PAIR_HAS_NOT_BEEN_SEPARATED_CORRECTLY') { + super({ data, message }) } } From bffc8c141be12ae3aee77afaa2d28e2d7e227bd6 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 7 May 2024 09:30:50 +0300 Subject: [PATCH 087/106] Improve error logging for trx tax report --- .../helpers/look-up-trades.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index 167e53f3b..6593b8af2 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -122,7 +122,11 @@ module.exports = async (trades, opts) => { !firstSymb || !lastSymb ) { - throw new CurrencyPairSeparationError() + throw new CurrencyPairSeparationError({ + symbol: trade.symbol, + firstSymb, + lastSymb + }) } const saleAmount = trade.execAmount < 0 @@ -137,7 +141,10 @@ module.exports = async (trades, opts) => { : lastSymb if (!Number.isFinite(salePrice)) { - throw new CurrencyConversionError() + throw new CurrencyConversionError({ + symbol: saleAsset, + price: salePrice + }) } const startPoint = isBackIterativeBuyLookUp @@ -197,7 +204,11 @@ module.exports = async (trades, opts) => { !firstSymbForLookup || !lastSymbForLookup ) { - throw new CurrencyPairSeparationError() + throw new CurrencyPairSeparationError({ + symbol: tradeForLookup.symbol, + firstSymb: firstSymbForLookup, + lastSymb: lastSymbForLookup + }) } if ( @@ -231,7 +242,10 @@ module.exports = async (trades, opts) => { const saleRestAmount = saleAmount - trade.saleFilledAmount if (!Number.isFinite(buyPrice)) { - throw new CurrencyConversionError() + throw new CurrencyConversionError({ + symbol: buyAsset, + price: buyPrice + }) } if (buyRestAmount < saleRestAmount) { From 15019ae6b076dc2788ef96b2f3df84c73a5bf71b Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 7 May 2024 09:31:22 +0300 Subject: [PATCH 088/106] Fix converting symbols like tETHF0:USTF0 for trx tax report --- .../helpers/get-trx-map-by-ccy.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js index c3cf51163..662678718 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/get-trx-map-by-ccy.js @@ -4,19 +4,24 @@ const { isForexSymb } = require('../../helpers') +// Handle tETHF0:USTF0 symbols +const symbRegExpNormalizer = /F0$/i + module.exports = (trxs) => { const trxMapByCcy = new Map() for (const trx of trxs) { + const firstSymb = trx.firstSymb.replace(symbRegExpNormalizer, '') + const lastSymb = trx.lastSymb.replace(symbRegExpNormalizer, '') const isNotFirstSymbForex = !isForexSymb(trx.firstSymb) const isNotLastSymbForex = !isForexSymb(trx.lastSymb) if (isNotFirstSymbForex) { - if (!trxMapByCcy.has(trx.firstSymb)) { - trxMapByCcy.set(trx.firstSymb, []) + if (!trxMapByCcy.has(firstSymb)) { + trxMapByCcy.set(firstSymb, []) } - trxMapByCcy.get(trx.firstSymb).push({ + trxMapByCcy.get(firstSymb).push({ isNotFirstSymbForex, isNotLastSymbForex, mainPricePropName: 'firstSymbPrice', @@ -25,11 +30,11 @@ module.exports = (trxs) => { }) } if (isNotLastSymbForex) { - if (!trxMapByCcy.has(trx.lastSymb)) { - trxMapByCcy.set(trx.lastSymb, []) + if (!trxMapByCcy.has(lastSymb)) { + trxMapByCcy.set(lastSymb, []) } - trxMapByCcy.get(trx.lastSymb).push({ + trxMapByCcy.get(lastSymb).push({ isNotFirstSymbForex, isNotLastSymbForex, mainPricePropName: 'lastSymbPrice', From 35fac3ac8084afb1906a8f2cb28c5d8184e17789 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:08:42 +0300 Subject: [PATCH 089/106] Add interrupter factory --- workers/loc.api/di/factories/index.js | 4 ++- .../di/factories/interrupter-factory.js | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 workers/loc.api/di/factories/interrupter-factory.js diff --git a/workers/loc.api/di/factories/index.js b/workers/loc.api/di/factories/index.js index e0c1d5945..d0cde3b95 100644 --- a/workers/loc.api/di/factories/index.js +++ b/workers/loc.api/di/factories/index.js @@ -7,6 +7,7 @@ const syncFactory = require('./sync-factory') const processMessageManagerFactory = require('./process-message-manager-factory') const syncUserStepDataFactory = require('./sync-user-step-data-factory') const wsEventEmitterFactory = require('./ws-event-emitter-factory') +const interrupterFactory = require('./interrupter-factory') module.exports = { migrationsFactory, @@ -15,5 +16,6 @@ module.exports = { syncFactory, processMessageManagerFactory, syncUserStepDataFactory, - wsEventEmitterFactory + wsEventEmitterFactory, + interrupterFactory } diff --git a/workers/loc.api/di/factories/interrupter-factory.js b/workers/loc.api/di/factories/interrupter-factory.js new file mode 100644 index 000000000..52bac04ce --- /dev/null +++ b/workers/loc.api/di/factories/interrupter-factory.js @@ -0,0 +1,29 @@ +'use strict' + +const { + AuthError +} = require('bfx-report/workers/loc.api/errors') + +const TYPES = require('../types') + +module.exports = (ctx) => { + const authenticator = ctx.container.get(TYPES.Authenticator) + + return (params) => { + const { user } = params ?? {} + + if (!user) { + throw new AuthError() + } + + const interrupter = ctx.container.get( + TYPES.Interrupter + ) + + authenticator.setInterrupterToUserSession( + user, interrupter + ) + + return interrupter + } +} From b53b159458b38d5c64746984b36b34cd43144230 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:09:27 +0300 Subject: [PATCH 090/106] Add InterrupterFactory service type --- workers/loc.api/di/types.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/di/types.js b/workers/loc.api/di/types.js index 0f1286839..316ac85fa 100644 --- a/workers/loc.api/di/types.js +++ b/workers/loc.api/di/types.js @@ -70,5 +70,6 @@ module.exports = { SyncUserStepDataFactory: Symbol.for('SyncUserStepDataFactory'), HTTPRequest: Symbol.for('HTTPRequest'), SummaryByAsset: Symbol.for('SummaryByAsset'), - TransactionTaxReport: Symbol.for('TransactionTaxReport') + TransactionTaxReport: Symbol.for('TransactionTaxReport'), + InterrupterFactory: Symbol.for('InterrupterFactory') } From dbc3ee7a08a1fd47caa604c58566f32c73e9c218 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:12:05 +0300 Subject: [PATCH 091/106] Add InterrupterFactory service into di --- workers/loc.api/di/app.deps.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/di/app.deps.js b/workers/loc.api/di/app.deps.js index 732d19245..3e87d7388 100644 --- a/workers/loc.api/di/app.deps.js +++ b/workers/loc.api/di/app.deps.js @@ -112,7 +112,8 @@ const { syncFactory, processMessageManagerFactory, syncUserStepDataFactory, - wsEventEmitterFactory + wsEventEmitterFactory, + interrupterFactory } = require('./factories') const Crypto = require('../sync/crypto') const Authenticator = require('../sync/authenticator') @@ -343,6 +344,8 @@ module.exports = ({ bind(TYPES.SyncInterrupter) .to(SyncInterrupter) .inSingletonScope() + bind(TYPES.InterrupterFactory) + .toFactory(interrupterFactory) bind(TYPES.Movements) .to(Movements) bind(TYPES.WinLossVSAccountBalance) From 4dc785f3de6e5256cd87509753e9c781cda05603 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:29:27 +0300 Subject: [PATCH 092/106] Add ability to process interrupters on sign-out --- workers/loc.api/sync/authenticator/index.js | 58 +++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/workers/loc.api/sync/authenticator/index.js b/workers/loc.api/sync/authenticator/index.js index 79f98a06d..ca700a6a2 100644 --- a/workers/loc.api/sync/authenticator/index.js +++ b/workers/loc.api/sync/authenticator/index.js @@ -10,6 +10,9 @@ const { isENetError, isAuthError } = require('bfx-report/workers/loc.api/helpers') +const Interrupter = require( + 'bfx-report/workers/loc.api/interrupter' +) const { serializeVal } = require('../dao/helpers') const { @@ -501,6 +504,7 @@ class Authenticator { throw new AuthError() } + await this._processInterrupters(user) this.removeUserSession({ email, isSubAccount, @@ -1105,6 +1109,34 @@ class Authenticator { return true } + setInterrupterToUserSession (user, interrupter) { + const token = this.getUserSessionByEmail(user)?.token + + if (!token) { + throw new AuthError() + } + + const userSession = this.userSessions.get(token) + + if (!userSession) { + throw new AuthError() + } + + userSession.interrupters = userSession.interrupters instanceof Set + ? userSession.interrupters + : new Set() + + if (!(interrupter instanceof Interrupter)) { + return + } + + interrupter.onceInterrupted(() => { + userSession.interrupters.delete(interrupter) + }) + + userSession.interrupters.add(interrupter) + } + setUserSession (user) { const { token, @@ -1518,6 +1550,32 @@ class Authenticator { ) ) } + + async _processInterrupters (user) { + const token = this.getUserSessionByEmail(user)?.token + const userSession = this.userSessions.get(token) + + if ( + !(userSession?.interrupters instanceof Set) || + userSession.interrupters.size === 0 + ) { + return + } + + const promises = [] + + for (const interrupter of userSession.interrupters.values()) { + userSession.interrupters.delete(interrupter) + + if (!(interrupter instanceof Interrupter)) { + continue + } + + promises.push(interrupter.interrupt()) + } + + return await Promise.all(promises) + } } decorateInjectable(Authenticator, depsTypes) From 186c8d0e7b76e742f60b5d502daf4e8449e4236a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:54:55 +0300 Subject: [PATCH 093/106] Add interrupter into lookUpTrades helper --- .../helpers/look-up-trades.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js index 6593b8af2..035eb795f 100644 --- a/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js +++ b/workers/loc.api/sync/transaction.tax.report/helpers/look-up-trades.js @@ -20,7 +20,8 @@ module.exports = async (trades, opts) => { isBackIterativeSaleLookUp = false, isBackIterativeBuyLookUp = false, isBuyTradesWithUnrealizedProfitRequired = false, - isNotGainOrLossRequired = false + isNotGainOrLossRequired = false, + interrupter } = opts ?? {} const saleTradesWithRealizedProfit = [] @@ -42,6 +43,13 @@ module.exports = async (trades, opts) => { : trades for (const [i, trade] of tradeIterator.entries()) { + if (interrupter?.hasInterrupted()) { + return { + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit + } + } + const currentLoopUnlockMts = Date.now() /* @@ -162,6 +170,12 @@ module.exports = async (trades, opts) => { ) for (let j = startPoint; checkPoint(j); j = shiftPoint(j)) { + if (interrupter?.hasInterrupted()) { + return { + saleTradesWithRealizedProfit, + buyTradesWithUnrealizedProfit + } + } if (trade.isSaleTrxHistFilled) { break } From 8f41e02bc72e999de9d33dacb2223439dc238707 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 08:55:25 +0300 Subject: [PATCH 094/106] Add ability to interrupt trx tax report --- .../sync/transaction.tax.report/index.js | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 225a961c6..2cbf9442b 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -25,7 +25,8 @@ const depsTypes = (TYPES) => [ TYPES.RService, TYPES.GetDataFromApi, TYPES.WSEventEmitterFactory, - TYPES.Logger + TYPES.Logger, + TYPES.InterrupterFactory ] class TransactionTaxReport { constructor ( @@ -38,7 +39,8 @@ class TransactionTaxReport { rService, getDataFromApi, wsEventEmitterFactory, - logger + logger, + interrupterFactory ) { this.dao = dao this.authenticator = authenticator @@ -50,6 +52,7 @@ class TransactionTaxReport { this.getDataFromApi = getDataFromApi this.wsEventEmitterFactory = wsEventEmitterFactory this.logger = logger + this.interrupterFactory = interrupterFactory this.tradesModel = this.syncSchema.getModelsMap() .get(this.ALLOWED_COLLS.TRADES) @@ -79,6 +82,7 @@ class TransactionTaxReport { const strategy = params.strategy ?? TRX_TAX_STRATEGIES.LIFO const user = await this.authenticator .verifyRequestUser({ auth }) + const interrupter = this.interrupterFactory({ user }) const isFIFO = strategy === TRX_TAX_STRATEGIES.FIFO const isLIFO = strategy === TRX_TAX_STRATEGIES.LIFO @@ -96,6 +100,8 @@ class TransactionTaxReport { !Array.isArray(trxsForCurrPeriod) || trxsForCurrPeriod.length === 0 ) { + interrupter.emitInterrupted() + return [] } @@ -118,7 +124,8 @@ class TransactionTaxReport { isBackIterativeSaleLookUp, isBackIterativeBuyLookUp, isBuyTradesWithUnrealizedProfitRequired: true, - isNotGainOrLossRequired: true + isNotGainOrLossRequired: true, + interrupter } ) @@ -128,16 +135,23 @@ class TransactionTaxReport { buyTradesWithUnrealizedProfit .filter((trx) => trx?.isMovements || trx?.lastSymb !== 'USD') ) - await this.#convertCurrencies(trxsForConvToUsd) + await this.#convertCurrencies(trxsForConvToUsd, { interrupter }) const { saleTradesWithRealizedProfit } = await lookUpTrades( trxsForCurrPeriod, { isBackIterativeSaleLookUp, - isBackIterativeBuyLookUp + isBackIterativeBuyLookUp, + interrupter } ) + interrupter.emitInterrupted() + + if (interrupter.hasInterrupted()) { + return [] + } + return saleTradesWithRealizedProfit } @@ -196,10 +210,15 @@ class TransactionTaxReport { } } - async #convertCurrencies (trxs) { + async #convertCurrencies (trxs, opts) { + const { interrupter } = opts const trxMapByCcy = getTrxMapByCcy(trxs) for (const [symbol, trxData] of trxMapByCcy.entries()) { + if (interrupter.hasInterrupted()) { + return + } + const pubTrades = [] const pubTradeChunkPayloads = getPubTradeChunkPayloads( symbol, @@ -207,9 +226,13 @@ class TransactionTaxReport { ) for (const chunkPayload of pubTradeChunkPayloads) { + if (interrupter.hasInterrupted()) { + return + } + const chunk = await getPubTradeChunk( chunkPayload, - (...args) => this.#getPublicTrades(...args) + (...args) => this.#getPublicTrades(...args, opts) ) pushLargeArr(pubTrades, chunk) @@ -249,7 +272,7 @@ class TransactionTaxReport { ) } - async #getPublicTrades (params) { + async #getPublicTrades (params, opts) { const { symbol, start = 0, @@ -257,6 +280,7 @@ class TransactionTaxReport { sort = -1, limit = 10000 } = params ?? {} + const { interrupter } = opts const args = { isNotMoreThanInnerMax: true, params: { @@ -279,7 +303,7 @@ class TransactionTaxReport { callerName: 'TRANSACTION_TAX_REPORT', eNetErrorAttemptsTimeframeMin: 10, eNetErrorAttemptsTimeoutMs: 10000, - shouldNotInterrupt: true + interrupter }) return res From 9e1efedc4fbb66b444d4ca506e2f632856231651 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 11:41:56 +0300 Subject: [PATCH 095/106] Simplify orig session obj ref return --- workers/loc.api/sync/authenticator/index.js | 41 +++++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/workers/loc.api/sync/authenticator/index.js b/workers/loc.api/sync/authenticator/index.js index ca700a6a2..9263edfc6 100644 --- a/workers/loc.api/sync/authenticator/index.js +++ b/workers/loc.api/sync/authenticator/index.js @@ -754,7 +754,7 @@ class Authenticator { ) { const session = this.getUserSessionByToken( token, - isReturnedPassword + { isReturnedPassword } ) const { authToken, apiKey, apiSecret } = session ?? {} @@ -1110,13 +1110,10 @@ class Authenticator { } setInterrupterToUserSession (user, interrupter) { - const token = this.getUserSessionByEmail(user)?.token - - if (!token) { - throw new AuthError() - } - - const userSession = this.userSessions.get(token) + const userSession = this.getUserSessionByEmail( + user, + { shouldOrigObjRefBeReturned: true } + ) if (!userSession) { throw new AuthError() @@ -1167,17 +1164,35 @@ class Authenticator { this.userTokenMapByEmail.set(tokenKey, token) } - getUserSessionByToken (token, isReturnedPassword) { + getUserSessionByToken (token, opts) { + const { + isReturnedPassword, + shouldOrigObjRefBeReturned + } = opts ?? {} + const session = this.userSessions.get(token) + if (shouldOrigObjRefBeReturned) { + return session + } + return pickSessionProps(session, isReturnedPassword) } - getUserSessionByEmail (user, isReturnedPassword) { + getUserSessionByEmail (user, opts) { + const { + isReturnedPassword, + shouldOrigObjRefBeReturned + } = opts ?? {} + const tokenKey = this._getTokenKeyByEmailField(user) const token = this.userTokenMapByEmail.get(tokenKey) const session = this.userSessions.get(token) + if (shouldOrigObjRefBeReturned) { + return session + } + return pickSessionProps(session, isReturnedPassword) } @@ -1552,8 +1567,10 @@ class Authenticator { } async _processInterrupters (user) { - const token = this.getUserSessionByEmail(user)?.token - const userSession = this.userSessions.get(token) + const userSession = this.getUserSessionByEmail( + user, + { shouldOrigObjRefBeReturned: true } + ) if ( !(userSession?.interrupters instanceof Set) || From a63e6017e60a98cf22c221561bc47d6e864bab6e Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 9 May 2024 11:42:19 +0300 Subject: [PATCH 096/106] Fix pub trades args for trx tax report --- workers/loc.api/sync/transaction.tax.report/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 2cbf9442b..ca519070b 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -232,7 +232,7 @@ class TransactionTaxReport { const chunk = await getPubTradeChunk( chunkPayload, - (...args) => this.#getPublicTrades(...args, opts) + (params) => this.#getPublicTrades(params, opts) ) pushLargeArr(pubTrades, chunk) From d5ba950c90de883336b917f0f74d0536705ab112 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:42:53 +0300 Subject: [PATCH 097/106] Set name into sync interrupter --- workers/loc.api/sync/sync.interrupter/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/workers/loc.api/sync/sync.interrupter/index.js b/workers/loc.api/sync/sync.interrupter/index.js index 5f28d4b52..865c0dd60 100644 --- a/workers/loc.api/sync/sync.interrupter/index.js +++ b/workers/loc.api/sync/sync.interrupter/index.js @@ -3,6 +3,9 @@ const Interrupter = require( 'bfx-report/workers/loc.api/interrupter' ) +const INTERRUPTER_NAMES = require( + 'bfx-report/workers/loc.api/interrupter/interrupter.names' +) const SYNC_PROGRESS_STATES = require('../progress/sync.progress.states') @@ -20,6 +23,7 @@ class SyncInterrupter extends Interrupter { this.INITIAL_PROGRESS = 'SYNCHRONIZATION_HAS_NOT_BEEN_STARTED_TO_INTERRUPT' this.INTERRUPTED_PROGRESS = SYNC_PROGRESS_STATES.INTERRUPTED_PROGRESS + this.setName(INTERRUPTER_NAMES.SYNC_INTERRUPTER) this._init() } From 7a945715fae6dfc1360d3a6daa0a72a6483da32a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:44:43 +0300 Subject: [PATCH 098/106] Add ability to set name into interrupter using factory --- workers/loc.api/di/factories/interrupter-factory.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/di/factories/interrupter-factory.js b/workers/loc.api/di/factories/interrupter-factory.js index 52bac04ce..866644474 100644 --- a/workers/loc.api/di/factories/interrupter-factory.js +++ b/workers/loc.api/di/factories/interrupter-factory.js @@ -10,7 +10,7 @@ module.exports = (ctx) => { const authenticator = ctx.container.get(TYPES.Authenticator) return (params) => { - const { user } = params ?? {} + const { user, name } = params ?? {} if (!user) { throw new AuthError() @@ -18,7 +18,7 @@ module.exports = (ctx) => { const interrupter = ctx.container.get( TYPES.Interrupter - ) + ).setName(name) authenticator.setInterrupterToUserSession( user, interrupter From 521138a3bc1073beb45118fe0042f20460e39815 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:46:14 +0300 Subject: [PATCH 099/106] Set name into trx tax report interrupter --- workers/loc.api/sync/transaction.tax.report/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index ca519070b..6056bbbdb 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -1,5 +1,9 @@ 'use strict' +const INTERRUPTER_NAMES = require( + 'bfx-report/workers/loc.api/interrupter/interrupter.names' +) + const { pushLargeArr } = require('../../helpers/utils') const { @@ -82,7 +86,10 @@ class TransactionTaxReport { const strategy = params.strategy ?? TRX_TAX_STRATEGIES.LIFO const user = await this.authenticator .verifyRequestUser({ auth }) - const interrupter = this.interrupterFactory({ user }) + const interrupter = this.interrupterFactory({ + user, + name: INTERRUPTER_NAMES.TRX_REPORT_INTERRUPTER + }) const isFIFO = strategy === TRX_TAX_STRATEGIES.FIFO const isLIFO = strategy === TRX_TAX_STRATEGIES.LIFO From 28e742d69e4821d54fb181481c3b5e642712b7e7 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:47:31 +0300 Subject: [PATCH 100/106] Add ability to interrupt user operation --- workers/loc.api/sync/authenticator/index.js | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/authenticator/index.js b/workers/loc.api/sync/authenticator/index.js index 9263edfc6..31ededdaf 100644 --- a/workers/loc.api/sync/authenticator/index.js +++ b/workers/loc.api/sync/authenticator/index.js @@ -1134,6 +1134,15 @@ class Authenticator { userSession.interrupters.add(interrupter) } + async interruptOperations (args) { + await this._processInterrupters( + args?.auth, + args?.params?.names + ) + + return true + } + setUserSession (user) { const { token, @@ -1566,7 +1575,14 @@ class Authenticator { ) } - async _processInterrupters (user) { + async _processInterrupters (user, names) { + const _names = Array.isArray(names) + ? names + : [names] + const filteredName = _names.filter((name) => ( + name && + typeof name === 'string' + )) const userSession = this.getUserSessionByEmail( user, { shouldOrigObjRefBeReturned: true } @@ -1576,12 +1592,17 @@ class Authenticator { !(userSession?.interrupters instanceof Set) || userSession.interrupters.size === 0 ) { - return + return [] } const promises = [] + const interrupters = [...userSession.interrupters] + .filter((int) => ( + filteredName.length === 0 || + filteredName.some((name) => name === int.name) + )) - for (const interrupter of userSession.interrupters.values()) { + for (const interrupter of interrupters) { userSession.interrupters.delete(interrupter) if (!(interrupter instanceof Interrupter)) { From 2524513c1d91dcf40345d6cbbb90b07aa8ecd06f Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:48:27 +0300 Subject: [PATCH 101/106] Add interruptOperations endpoint --- workers/loc.api/service.report.framework.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workers/loc.api/service.report.framework.js b/workers/loc.api/service.report.framework.js index 2e0ea6bf7..a335306ab 100644 --- a/workers/loc.api/service.report.framework.js +++ b/workers/loc.api/service.report.framework.js @@ -463,6 +463,14 @@ class FrameworkReportService extends ReportService { }, 'stopSyncNow', args, cb) } + interruptOperations (space, args = {}, cb) { + return this._privResponder(() => { + checkParams(args, 'paramsSchemaForInterruptOperations', ['names']) + + return this._authenticator.interruptOperations(args) + }, 'interruptOperations', args, cb) + } + getPublicTradesConf (space, args = {}, cb) { return this._privResponder(() => { return this._publicCollsConfAccessors From a3e88d1af48c50d82df2510395033016fd627840 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 10 May 2024 13:49:48 +0300 Subject: [PATCH 102/106] Add params schema for interruptOperations endpoint --- workers/loc.api/helpers/schema.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/workers/loc.api/helpers/schema.js b/workers/loc.api/helpers/schema.js index 0059fd710..572cb2fdd 100644 --- a/workers/loc.api/helpers/schema.js +++ b/workers/loc.api/helpers/schema.js @@ -49,6 +49,22 @@ const paramsSchemaForUpdateSubAccount = { } } +const paramsSchemaForInterruptOperations = { + type: 'object', + properties: { + names: { + type: 'array', + minItems: 1, + items: { + type: 'string', + enum: [ + 'TRX_REPORT_INTERRUPTER' + ] + } + } + } +} + const paramsSchemaForCandlesApi = { ...cloneDeep(baseParamsSchemaForCandlesApi), properties: { @@ -482,6 +498,7 @@ module.exports = { paramsSchemaForEditCandlesConf, paramsSchemaForCreateSubAccount, paramsSchemaForUpdateSubAccount, + paramsSchemaForInterruptOperations, paramsSchemaForBalanceHistoryApi, paramsSchemaForWinLossApi, paramsSchemaForWinLossVSAccountBalanceApi, From 43fc721bc53036cefeca1f48391802504143ece6 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 13 May 2024 12:10:07 +0300 Subject: [PATCH 103/106] Add TransactionTaxReport service into di singleton scope --- workers/loc.api/di/app.deps.js | 1 + 1 file changed, 1 insertion(+) diff --git a/workers/loc.api/di/app.deps.js b/workers/loc.api/di/app.deps.js index 3e87d7388..ccf75527f 100644 --- a/workers/loc.api/di/app.deps.js +++ b/workers/loc.api/di/app.deps.js @@ -400,6 +400,7 @@ module.exports = ({ .to(FullTaxReport) bind(TYPES.TransactionTaxReport) .to(TransactionTaxReport) + .inSingletonScope() rebind(TYPES.WeightedAveragesReport) .to(WeightedAveragesReport) rebind(TYPES.ReportFileJobData) From dd66db3865fe7bbe023ffd68a84f3b94c4d6b188 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 13 May 2024 12:13:26 +0300 Subject: [PATCH 104/106] Add test coverage for transaction tax report interruption --- test/7-interrupt-operations.spec.js | 242 ++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 test/7-interrupt-operations.spec.js diff --git a/test/7-interrupt-operations.spec.js b/test/7-interrupt-operations.spec.js new file mode 100644 index 000000000..8b497536d --- /dev/null +++ b/test/7-interrupt-operations.spec.js @@ -0,0 +1,242 @@ +'use strict' + +const { + setTimeout +} = require('node:timers/promises') +const path = require('node:path') +const request = require('supertest') +const { assert } = require('chai') + +const { + stopEnvironment +} = require('bfx-report/test/helpers/helpers.boot') +const { + rmDB, + rmAllFiles +} = require('bfx-report/test/helpers/helpers.core') + +const { + startEnvironment +} = require('./helpers/helpers.boot') +const { + getRServiceProxy, + emptyDB, + rmRf +} = require('./helpers/helpers.core') +const { + createMockRESTv2SrvWithDate +} = require('./helpers/helpers.mock-rest-v2') + +process.env.NODE_CONFIG_DIR = path.join(__dirname, 'config') +const { app } = require('bfx-report-express') +const agent = request.agent(app) + +const { + signUpTestCase, + getSyncProgressTestCase +} = require('./test-cases') + +let wrkReportServiceApi = null +let mockRESTv2Srv = null + +const basePath = '/api' +const tempDirPath = path.join(__dirname, '..', 'workers/loc.api/queue/temp') +const dbDirPath = path.join(__dirname, '..', 'db') +const reportFolderPath = path.join(__dirname, '..', 'report-files') +const date = new Date() +const end = date.getTime() +const start = (new Date()).setDate(date.getDate() - 90) + +const apiKeys = { + apiKey: 'fake', + apiSecret: 'fake' +} +const email = 'fake@email.fake' +const password = '123Qwerty' +const isSubAccount = false + +describe('Interrupt operations', () => { + const params = { + processorQueue: null, + aggregatorQueue: null, + basePath, + auth: { + email, + password, + isSubAccount + }, + apiKeys, + date, + end, + start + } + const auth = { token: 'user-token' } + + before(async function () { + this.timeout(20000) + + mockRESTv2Srv = createMockRESTv2SrvWithDate(start, end, 100) + + await rmRf(reportFolderPath) + await rmAllFiles(tempDirPath, ['README.md']) + await rmDB(dbDirPath) + const env = await startEnvironment(false, false, 1) + + wrkReportServiceApi = env.wrksReportServiceApi[0] + const rService = wrkReportServiceApi.grc_bfx.api + const rServiceProxy = getRServiceProxy(rService, { + async _getPublicTrades (targetMethod, context, argsList) { + await setTimeout(5000) + + return Reflect.apply(...arguments) + } + }) + rService._transactionTaxReport.rService = rServiceProxy + params.processorQueue = wrkReportServiceApi.lokue_processor.q + params.aggregatorQueue = wrkReportServiceApi.lokue_aggregator.q + + await emptyDB() + }) + + after(async function () { + this.timeout(20000) + + await stopEnvironment() + await rmDB(dbDirPath) + await rmAllFiles(tempDirPath, ['README.md']) + await rmRf(reportFolderPath) + + try { + await mockRESTv2Srv.close() + } catch (err) { } + }) + + signUpTestCase(agent, params, (token) => { auth.token = token }) + + it('it should be successfully performed by the syncNow method', async function () { + this.timeout(60000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'syncNow', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isOk( + typeof res.body.result === 'number' || + res.body.result === 'SYNCHRONIZATION_IS_STARTED' + ) + }) + + getSyncProgressTestCase(agent, { basePath, auth }) + + it('it should interrupt transaction tax report', async function () { + this.timeout(60000) + + const trxTaxReportPromise = agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'getTransactionTaxReport', + params: { + end, + start: start + (45 * 24 * 60 * 60 * 1000), + strategy: 'LIFO' + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + const interruptOperationsPromise = setTimeout(1000).then(() => { + return agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'interruptOperations', + params: { + names: ['TRX_REPORT_INTERRUPTER'] + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + }) + + const [ + trxTaxReport, + interruptOperations + ] = await Promise.all([ + trxTaxReportPromise, + interruptOperationsPromise + ]) + + assert.isObject(interruptOperations.body) + assert.propertyVal(interruptOperations.body, 'id', 5) + assert.isBoolean(interruptOperations.body.result) + assert.isOk(interruptOperations.body.result) + + assert.isObject(trxTaxReport.body) + assert.propertyVal(trxTaxReport.body, 'id', 5) + assert.isArray(trxTaxReport.body.result) + assert.lengthOf(trxTaxReport.body.result, 0) + }) + + it('it should interrupt transaction tax report after sign-out', async function () { + this.timeout(60000) + + const trxTaxReportPromise = agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'getTransactionTaxReport', + params: { + end, + start: start + (45 * 24 * 60 * 60 * 1000), + strategy: 'LIFO' + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + const signOutPromise = setTimeout(1000).then(() => { + return agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'signOut', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + }) + + const [ + trxTaxReport, + signOut + ] = await Promise.all([ + trxTaxReportPromise, + signOutPromise + ]) + + assert.isObject(signOut.body) + assert.propertyVal(signOut.body, 'id', 5) + assert.isBoolean(signOut.body.result) + assert.isOk(signOut.body.result) + + assert.isObject(trxTaxReport.body) + assert.propertyVal(trxTaxReport.body, 'id', 5) + assert.isArray(trxTaxReport.body.result) + assert.lengthOf(trxTaxReport.body.result, 0) + }) +}) From 56242a6ff6d95f817706a77235311c1853db0920 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 14 May 2024 08:58:57 +0300 Subject: [PATCH 105/106] Add test case with wrong param for interruptOperations endpoint --- test/7-interrupt-operations.spec.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/7-interrupt-operations.spec.js b/test/7-interrupt-operations.spec.js index 8b497536d..117b6d3e4 100644 --- a/test/7-interrupt-operations.spec.js +++ b/test/7-interrupt-operations.spec.js @@ -190,6 +190,31 @@ describe('Interrupt operations', () => { assert.lengthOf(trxTaxReport.body.result, 0) }) + it('it should not be successfully performed by the interruptOperations method', async function () { + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'interruptOperations', + params: { + names: [ + 'FAKE_INTERRUPTER', + 'TRX_REPORT_INTERRUPTER' + ] + }, + id: 5 + }) + .expect('Content-Type', /json/) + .expect(400) + + assert.isObject(res.body) + assert.isObject(res.body.error) + assert.propertyVal(res.body.error, 'code', 400) + assert.propertyVal(res.body.error, 'message', 'Args params is not valid') + assert.propertyVal(res.body, 'id', 5) + }) + it('it should interrupt transaction tax report after sign-out', async function () { this.timeout(60000) From cfb863687361072582977cebba115aceb43d2ec7 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 14 May 2024 09:54:44 +0300 Subject: [PATCH 106/106] Fix interrupter name for trx tax report --- test/7-interrupt-operations.spec.js | 4 ++-- workers/loc.api/helpers/schema.js | 2 +- workers/loc.api/sync/transaction.tax.report/index.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/7-interrupt-operations.spec.js b/test/7-interrupt-operations.spec.js index 117b6d3e4..e75ad4ae1 100644 --- a/test/7-interrupt-operations.spec.js +++ b/test/7-interrupt-operations.spec.js @@ -163,7 +163,7 @@ describe('Interrupt operations', () => { auth, method: 'interruptOperations', params: { - names: ['TRX_REPORT_INTERRUPTER'] + names: ['TRX_TAX_REPORT_INTERRUPTER'] }, id: 5 }) @@ -200,7 +200,7 @@ describe('Interrupt operations', () => { params: { names: [ 'FAKE_INTERRUPTER', - 'TRX_REPORT_INTERRUPTER' + 'TRX_TAX_REPORT_INTERRUPTER' ] }, id: 5 diff --git a/workers/loc.api/helpers/schema.js b/workers/loc.api/helpers/schema.js index 572cb2fdd..da5b45021 100644 --- a/workers/loc.api/helpers/schema.js +++ b/workers/loc.api/helpers/schema.js @@ -58,7 +58,7 @@ const paramsSchemaForInterruptOperations = { items: { type: 'string', enum: [ - 'TRX_REPORT_INTERRUPTER' + 'TRX_TAX_REPORT_INTERRUPTER' ] } } diff --git a/workers/loc.api/sync/transaction.tax.report/index.js b/workers/loc.api/sync/transaction.tax.report/index.js index 6056bbbdb..d19941435 100644 --- a/workers/loc.api/sync/transaction.tax.report/index.js +++ b/workers/loc.api/sync/transaction.tax.report/index.js @@ -88,7 +88,7 @@ class TransactionTaxReport { .verifyRequestUser({ auth }) const interrupter = this.interrupterFactory({ user, - name: INTERRUPTER_NAMES.TRX_REPORT_INTERRUPTER + name: INTERRUPTER_NAMES.TRX_TAX_REPORT_INTERRUPTER }) const isFIFO = strategy === TRX_TAX_STRATEGIES.FIFO