diff --git a/workers/loc.api/generate-csv/csv.job.data.js b/workers/loc.api/generate-csv/csv.job.data.js index 85d8be357..854d07be6 100644 --- a/workers/loc.api/generate-csv/csv.job.data.js +++ b/workers/loc.api/generate-csv/csv.job.data.js @@ -641,6 +641,9 @@ class CsvJobData extends BaseCsvJobData { ) const csvArgs = getCsvArgs(args) + const suffix = args?.params?.isVSPrevDayBalance + ? 'balance' + : 'deposits' const jobData = { userInfo, @@ -648,7 +651,7 @@ class CsvJobData extends BaseCsvJobData { name: 'getWinLossVSAccountBalance', fileNamesMap: [[ 'getWinLossVSAccountBalance', - 'win-loss-vs-account-balance' + `win-loss-percentage-gains-vs-${suffix}` ]], args: csvArgs, propNameForPagination: null, diff --git a/workers/loc.api/sync/balance.history/index.js b/workers/loc.api/sync/balance.history/index.js index 39d71322f..89b35af79 100644 --- a/workers/loc.api/sync/balance.history/index.js +++ b/workers/loc.api/sync/balance.history/index.js @@ -477,7 +477,7 @@ class BalanceHistory { auth: _auth = {}, params = {} } = {}, - isSubCalc = false + opts = {} ) { const auth = await this.authenticator .verifyRequestUser({ auth: _auth }) @@ -488,6 +488,9 @@ class BalanceHistory { end = Date.now(), isUnrealizedProfitExcluded } = params ?? {} + const { + isSubCalc = false + } = opts ?? {} if (Number.isInteger(timeframe)) { return this._getWalletsGroupedByOneTimeframe( diff --git a/workers/loc.api/sync/helpers/group-by-timeframe.js b/workers/loc.api/sync/helpers/group-by-timeframe.js index bafa878e3..2b8156950 100644 --- a/workers/loc.api/sync/helpers/group-by-timeframe.js +++ b/workers/loc.api/sync/helpers/group-by-timeframe.js @@ -171,12 +171,7 @@ module.exports = async ( const isLastIter = i === 0 const nextItem = data[i - 1] - if ( - item && - typeof item === 'object' && - Number.isInteger(item[dateFieldName]) && - typeof item[symbolFieldName] === 'string' - ) { + if (Number.isInteger(item?.[dateFieldName])) { subRes.unshift(item) } diff --git a/workers/loc.api/sync/movements/index.js b/workers/loc.api/sync/movements/index.js index e4464e6cc..a9d659e32 100644 --- a/workers/loc.api/sync/movements/index.js +++ b/workers/loc.api/sync/movements/index.js @@ -39,7 +39,8 @@ class Movements { exclude = ['user_id'], isExcludePrivate = true, isWithdrawals = false, - isDeposits = false + isDeposits = false, + isMovementsWithoutSATransferLedgers = false } = params ?? {} const user = await this.authenticator @@ -83,8 +84,13 @@ class Movements { isExcludePrivate } ) + + if (isMovementsWithoutSATransferLedgers) { + return movementsPromise + } + const ledgersOrder = this._getLedgersOrder(sort) - const ledgersPromise = this._getLedgers({ + const ledgersPromise = this.getSubAccountsTransferLedgers({ auth: user, start, end, @@ -123,7 +129,7 @@ class Movements { * Consider the `SA(nameAccount1->nameAccount2)` transfers * as internal movements for win/loss and tax calculations */ - _getLedgers (params = {}) { + getSubAccountsTransferLedgers (params = {}) { const { auth = {}, start = 0, diff --git a/workers/loc.api/sync/win.loss.vs.account.balance/index.js b/workers/loc.api/sync/win.loss.vs.account.balance/index.js index 548034f82..b2b3d42eb 100644 --- a/workers/loc.api/sync/win.loss.vs.account.balance/index.js +++ b/workers/loc.api/sync/win.loss.vs.account.balance/index.js @@ -1,7 +1,8 @@ 'use strict' const { - calcGroupedData + calcGroupedData, + groupByTimeframe } = require('../helpers') const { decorateInjectable } = require('../../di/utils') @@ -31,12 +32,17 @@ class WinLossVSAccountBalance { timeframe = 'day', start = 0, end = Date.now(), - isUnrealizedProfitExcluded + isUnrealizedProfitExcluded, + isVSPrevDayBalance } = params ?? {} const args = { auth: user, params: { - timeframe, + /* + * We have to get day timeframe data for all timeframes + * and then pick data for non-day timeframes due to accuracy issue + */ + timeframe: 'day', start, end, isUnrealizedProfitExcluded @@ -44,12 +50,27 @@ class WinLossVSAccountBalance { } const { + firstWalletsVals, walletsGroupedByTimeframe, withdrawalsGroupedByTimeframe, depositsGroupedByTimeframe, plGroupedByTimeframe } = await this.winLoss.getDataToCalcWinLoss(args) + const getWinLossPercByTimeframe = isVSPrevDayBalance + ? this._getWinLossPrevDayBalanceByTimeframe( + { + isUnrealizedProfitExcluded, + firstWalletsVals + } + ) + : this._getWinLossVSAccountBalanceByTimeframe( + { + isUnrealizedProfitExcluded, + firstWalletsVals + } + ) + const groupedData = await calcGroupedData( { walletsGroupedByTimeframe, @@ -58,29 +79,46 @@ class WinLossVSAccountBalance { plGroupedByTimeframe }, false, - this._winLossVSAccountBalanceByTimeframe( - { isUnrealizedProfitExcluded } - ), + getWinLossPercByTimeframe, true ) - groupedData.push({ + const pickedRes = timeframe === 'day' + ? groupedData + : (await groupByTimeframe( + groupedData, + { timeframe, start, end }, + null, + 'mts', + null, + (data = []) => data[0] + )).map((obj) => { + const res = obj?.vals ?? {} + res.mts = obj.mts + + return res + }) + pickedRes.push({ mts: start, perc: 0 }) const res = this.winLoss.shiftMtsToNextTimeframe( - groupedData, + pickedRes, { timeframe, end } ) return res } - _winLossVSAccountBalanceByTimeframe ({ - isUnrealizedProfitExcluded + _getWinLossVSAccountBalanceByTimeframe ({ + isUnrealizedProfitExcluded, + firstWalletsVals }) { - let firstWalletsVals = {} - let firstPLVals = 0 - let prevMovementsRes = 0 + let firstPLVals = {} + let prevMovementsRes = {} + let percCorrection = 0 + let prevPerc = 0 + let firstWallets = 0 + let prevWallets = 0 return ({ walletsGroupedByTimeframe = {}, @@ -92,22 +130,27 @@ class WinLossVSAccountBalance { const isFirst = (i + 1) === arr.length if (isFirst) { - firstWalletsVals = walletsGroupedByTimeframe firstPLVals = plGroupedByTimeframe + firstWallets = Number.isFinite(firstWalletsVals[symb]) + ? firstWalletsVals[symb] + : 0 + prevWallets = firstWallets } - prevMovementsRes = this.winLoss.sumMovementsWithPrevRes( - prevMovementsRes, + const newMovementsRes = this.winLoss.sumMovementsWithPrevRes( + {}, withdrawalsGroupedByTimeframe, depositsGroupedByTimeframe ) - - const movements = Number.isFinite(prevMovementsRes[symb]) - ? prevMovementsRes[symb] - : 0 - const firstWallets = Number.isFinite(firstWalletsVals[symb]) - ? firstWalletsVals[symb] + const newMovements = Number.isFinite(newMovementsRes[symb]) + ? newMovementsRes[symb] : 0 + const hasNewMovements = !!newMovements + prevMovementsRes = this.winLoss.sumMovementsWithPrevRes( + hasNewMovements ? {} : prevMovementsRes, + newMovementsRes + ) + const wallets = Number.isFinite(walletsGroupedByTimeframe[symb]) ? walletsGroupedByTimeframe[symb] : 0 @@ -118,7 +161,13 @@ class WinLossVSAccountBalance { ? plGroupedByTimeframe[symb] : 0 - const realized = (wallets - movements) - firstWallets + if (hasNewMovements) { + firstWallets = prevWallets + newMovements + } + + prevWallets = wallets + + const realized = wallets - firstWallets const unrealized = isUnrealizedProfitExcluded ? 0 : pl - firstPL @@ -129,10 +178,83 @@ class WinLossVSAccountBalance { !Number.isFinite(winLoss) || firstWallets === 0 ) { - return { perc: 0 } + return { perc: percCorrection } + } + + if (newMovements) { + percCorrection = prevPerc + } + + const perc = ((winLoss / firstWallets) * 100) + percCorrection + prevPerc = perc + + return { perc } + } + } + + _getWinLossPrevDayBalanceByTimeframe ({ + isUnrealizedProfitExcluded, + firstWalletsVals + }) { + let prevPerc = 0 + let prevWallets = 0 + let prevPL = 0 + let prevMultiplying = 1 + + return ({ + walletsGroupedByTimeframe = {}, + withdrawalsGroupedByTimeframe = {}, + depositsGroupedByTimeframe = {}, + plGroupedByTimeframe = {} + } = {}, i, arr) => { + const symb = 'USD' + const isFirst = (i + 1) === arr.length + + if (isFirst) { + prevWallets = Number.isFinite(firstWalletsVals[symb]) + ? firstWalletsVals[symb] + : 0 + prevPL = Number.isFinite(plGroupedByTimeframe[symb]) + ? plGroupedByTimeframe[symb] + : 0 + } + + const movementsRes = this.winLoss.sumMovementsWithPrevRes( + {}, + withdrawalsGroupedByTimeframe, + depositsGroupedByTimeframe + ) + + const movements = Number.isFinite(movementsRes[symb]) + ? movementsRes[symb] + : 0 + const wallets = Number.isFinite(walletsGroupedByTimeframe[symb]) + ? walletsGroupedByTimeframe[symb] + : 0 + const pl = Number.isFinite(plGroupedByTimeframe[symb]) + ? plGroupedByTimeframe[symb] + : 0 + + const realized = (wallets - movements) - prevWallets + const unrealized = isUnrealizedProfitExcluded + ? 0 + : pl - prevPL + + const winLoss = realized + unrealized + + prevWallets = wallets + prevPL = pl + + if ( + !Number.isFinite(winLoss) || + prevWallets === 0 + ) { + return { perc: prevPerc } } - const perc = (winLoss / firstWallets) * 100 + prevMultiplying = ((prevWallets + winLoss) / prevWallets) * prevMultiplying + const perc = (prevMultiplying - 1) * 100 + prevPerc = perc return { perc } } diff --git a/workers/loc.api/sync/win.loss/index.js b/workers/loc.api/sync/win.loss/index.js index dd0c37d88..70a4ea605 100644 --- a/workers/loc.api/sync/win.loss/index.js +++ b/workers/loc.api/sync/win.loss/index.js @@ -46,7 +46,10 @@ class WinLoss { this.movementsMethodColl = this.syncSchema.getMethodCollMap() .get(this.SYNC_API_METHODS.MOVEMENTS) + this.ledgersMethodColl = this.syncSchema.getMethodCollMap() + .get(this.SYNC_API_METHODS.LEDGERS) this.movementsSymbolFieldName = this.movementsMethodColl.symbolFieldName + this.ledgersSymbolFieldName = this.ledgersMethodColl.symbolFieldName } sumMovementsWithPrevRes ( @@ -216,11 +219,15 @@ class WinLoss { }, []) } - async getDataToCalcWinLoss (args = {}) { + async getDataToCalcWinLoss (args = {}, opts = {}) { const { auth = {}, params = {} } = args ?? {} + const { + isSubAccountsTransferLedgersAdded, + isMovementsWithoutSATransferLedgers + } = opts ?? {} const { timeframe = 'day', @@ -245,29 +252,41 @@ class WinLoss { isUnrealizedProfitExcluded: true } }, - true + { isSubCalc: true } ) const withdrawalsPromise = this.movements.getMovements({ auth, start, end, sort: [['mtsStarted', -1]], - isWithdrawals: true + isWithdrawals: true, + isMovementsWithoutSATransferLedgers }) const depositsPromise = this.movements.getMovements({ auth, start, end, sort: [['mtsUpdated', -1]], - isDeposits: true + isDeposits: true, + isMovementsWithoutSATransferLedgers }) + const subAccountsTransferLedgersPromise = isSubAccountsTransferLedgersAdded + ? this.movements.getSubAccountsTransferLedgers({ + auth, + start, + end, + sort: [['mts', -1], ['id', -1]] + }) + : null const [ withdrawals, - deposits + deposits, + subAccountsTransferLedgers ] = await Promise.all([ withdrawalsPromise, - depositsPromise + depositsPromise, + subAccountsTransferLedgersPromise ]) const withdrawalsGroupedByTimeframePromise = groupByTimeframe( @@ -286,19 +305,32 @@ class WinLoss { this.movementsSymbolFieldName, this._calcMovements.bind(this) ) + const subAccountsTransferLedgersGroupedByTimeframePromise = isSubAccountsTransferLedgersAdded + ? groupByTimeframe( + subAccountsTransferLedgers, + { timeframe, start, end }, + this.FOREX_SYMBS, + 'mts', + this.ledgersSymbolFieldName, + // NOTE: The movements fn may be used to calc ledgers + this._calcMovements.bind(this) + ) + : null const [ firstWallets, withdrawalsGroupedByTimeframe, depositsGroupedByTimeframe, walletsGroupedByTimeframe, - plGroupedByTimeframe + plGroupedByTimeframe, + subAccountsTransferLedgersGroupedByTimeframe ] = await Promise.all([ firstWalletsPromise, withdrawalsGroupedByTimeframePromise, depositsGroupedByTimeframePromise, walletsGroupedByTimeframePromise, - plGroupedByTimeframePromise + plGroupedByTimeframePromise, + subAccountsTransferLedgersGroupedByTimeframePromise ]) const firstWalletsVals = firstWallets.reduce((accum, curr = {}) => { @@ -331,7 +363,8 @@ class WinLoss { withdrawalsGroupedByTimeframe, depositsGroupedByTimeframe, walletsGroupedByTimeframe, - plGroupedByTimeframe + plGroupedByTimeframe, + subAccountsTransferLedgersGroupedByTimeframe } }