From b7ae1dfcdddf54a9f60a8e2cbd5aca387a098c4b Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Sep 2025 12:23:50 +0300 Subject: [PATCH 01/26] Implement method to make ledger map filtered by ccy grouped by category --- .../loc.api/sync/summary.by.asset/index.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 93106646..29f1c56b 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -161,6 +161,7 @@ class SummaryByAsset { ? 1 : calcedActualRate + // TODO: const ledgersForCurrency = ledgers.filter((ledger) => ( ledger[this.ledgersSymbolFieldName] === currency )) @@ -318,6 +319,39 @@ class SummaryByAsset { : accum[propName] } } + + #makeLedgerMapFilteredByCcyGroupedByCategory ( + ledgers, ccy, categories + ) { + const ledgerMap = new Map() + + if ( + !Array.isArray(categories) || + categories.length === 0 + ) { + return ledgerMap + } + + for (const category of categories) { + ledgerMap.set(category, []) + } + + for (const ledger of ledgers) { + if (ledger?.[this.ledgersSymbolFieldName] !== ccy) { + continue + } + + for (const [category, arr] of ledgerMap) { + if (ledger?._category !== category) { + continue + } + + arr.push(ledger) + } + } + + return ledgerMap + } } decorateInjectable(SummaryByAsset, depsTypes) From bd884f8a40e045de1efeae710b0c324c6abef9ac Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Sep 2025 13:03:06 +0300 Subject: [PATCH 02/26] Calc all fees usd by ccy --- .../loc.api/sync/summary.by.asset/index.js | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 29f1c56b..43c03085 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -3,6 +3,8 @@ const { omit } = require('lib-js-util-base') const moment = require('moment') +const { pushLargeArr } = require('../../helpers/utils') + const { decorateInjectable } = require('../../di/utils') const depsTypes = (TYPES) => [ @@ -15,6 +17,9 @@ const depsTypes = (TYPES) => [ TYPES.Trades ] class SummaryByAsset { + #ledgerFeeCats = [201, 204, 207, 222, 224, 228, 241, + 243, 251, 254, 255, 258, 905] + constructor ( dao, syncSchema, @@ -161,19 +166,21 @@ class SummaryByAsset { ? 1 : calcedActualRate - // TODO: - const ledgersForCurrency = ledgers.filter((ledger) => ( - ledger[this.ledgersSymbolFieldName] === currency - )) + const ledgerMap = this.#makeLedgerMapFilteredByCcyGroupedByCategory( + ledgers, + currency, + [28, ...this.#ledgerFeeCats] + ) const tradesForCurrency = trades.filter((trade) => ( trade?.baseCurrency === currency )) - const marginFundingPaymentLedgers = ledgersForCurrency.filter((ledger) => ( - ledger._category === 28 - )) - const tradingFeeLedgers = ledgersForCurrency.filter((ledger) => ( - ledger._category === 201 - )) + const marginFundingPaymentLedgers = ledgerMap.get(28) ?? [] + const tradingFeeLedgers = ledgerMap.get(201) ?? [] + const allFeeLedgers = this.#ledgerFeeCats.reduce((accum, cat) => { + pushLargeArr(accum, ledgerMap.get(cat) ?? []) + + return accum + }, []) const volume = this.#calcFieldByName( tradesForCurrency, @@ -196,6 +203,10 @@ class SummaryByAsset { tradingFeeLedgers, 'amountUsd' ) + const calcedAllFeeUsdLedgers = this.#calcFieldByName( + allFeeLedgers, + 'amountUsd' + ) const balanceChange = calcedEndWalletBalance - calcedStartWalletBalance const balanceChangePerc = calcedStartWalletBalance === 0 @@ -206,6 +217,7 @@ class SummaryByAsset { // In the Ledgers amount of fee is negative value, skip sign for UI const tradingFees = Math.abs(calcedTradingFeeLedgers) const tradingFeesUsd = Math.abs(calcedTradingFeeUsdLedgers) + const allFeesUsd = Math.abs(calcedAllFeeUsdLedgers) if ( calcedEndWalletBalanceUsd < 0.01 && @@ -228,6 +240,7 @@ class SummaryByAsset { volumeUsd, tradingFees, tradingFeesUsd, + allFeesUsd, marginFundingPayment, // It's used to total perc calc From 16181bbb22f779a9267aca12f49bf1f0c1e945e1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 18 Sep 2025 13:04:29 +0300 Subject: [PATCH 03/26] Calc total fees usd --- workers/loc.api/sync/summary.by.asset/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 43c03085..0fe65934 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -297,6 +297,7 @@ class SummaryByAsset { balanceChangePerc: 0, volumeUsd: 0, tradingFeesUsd: 0, + allFeesUsd: 0, calcedStartWalletBalanceUsd: 0 } @@ -310,6 +311,7 @@ class SummaryByAsset { 'balanceChangeUsd', 'volumeUsd', 'tradingFeesUsd', + 'allFeesUsd', 'calcedStartWalletBalanceUsd' ] From 8bb2ac06a3a16150097001c08e40e778dffb0d29 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Fri, 19 Sep 2025 10:21:25 +0300 Subject: [PATCH 04/26] Add deposits-withdrawals usd statistic info --- .../loc.api/sync/summary.by.asset/index.js | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 0fe65934..5d25cb36 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -75,17 +75,35 @@ class SummaryByAsset { auth, params: { end } }) + const withdrawalsPromise = this.movements.getMovements({ + auth, + start, + end, + isWithdrawals: true, + isExcludePrivate: false + }) + const depositsPromise = this.movements.getMovements({ + auth, + start, + end, + isDeposits: true, + isExcludePrivate: false + }) const [ trades, ledgers, startWallets, - endWallets + endWallets, + withdrawals, + deposits ] = await Promise.all([ tradesPromise, ledgersPromise, startWalletsPromise, - endWalletsPromise + endWalletsPromise, + withdrawalsPromise, + depositsPromise ]) const _summaryByAsset = await this.#calcSummaryByAsset({ @@ -94,7 +112,11 @@ class SummaryByAsset { startWallets, endWallets }) - const total = this.#calcTotal(_summaryByAsset) + const total = this.#calcTotal( + _summaryByAsset, + withdrawals, + deposits + ) const summaryByAsset = _summaryByAsset.map((item) => ( omit(item, [ 'balanceChangeUsd', @@ -290,7 +312,11 @@ class SummaryByAsset { ) } - #calcTotal (summaryByAsset) { + #calcTotal ( + summaryByAsset, + withdrawals, + deposits + ) { const initTotal = { balanceUsd: 0, balanceChangeUsd: 0, @@ -299,7 +325,8 @@ class SummaryByAsset { tradingFeesUsd: 0, allFeesUsd: 0, - calcedStartWalletBalanceUsd: 0 + calcedStartWalletBalanceUsd: 0, + depositsWithdrawalsUsd: 0 } const res = summaryByAsset.reduce((accum, curr) => { @@ -324,6 +351,20 @@ class SummaryByAsset { return accum }, initTotal) + const calcedWithdrawalsUsd = this.#calcFieldByName( + withdrawals, + 'amountUsd' + ) + const calcedDepositsUsd = this.#calcFieldByName( + deposits, + 'amountUsd' + ) + res.depositsWithdrawalsUsd = ( + res.depositsWithdrawalsUsd + + calcedWithdrawalsUsd + + calcedDepositsUsd + ) + return omit(res, ['calcedStartWalletBalanceUsd']) } From a2eee28761debaa5c05ba439e7b149fcf5b3b5cd Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Sep 2025 09:48:29 +0300 Subject: [PATCH 05/26] Inject movements into SummaryByAsset service --- workers/loc.api/sync/summary.by.asset/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 5d25cb36..1b7a57a1 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -14,7 +14,8 @@ const depsTypes = (TYPES) => [ TYPES.SYNC_API_METHODS, TYPES.ALLOWED_COLLS, TYPES.Wallets, - TYPES.Trades + TYPES.Trades, + TYPES.Movements ] class SummaryByAsset { #ledgerFeeCats = [201, 204, 207, 222, 224, 228, 241, @@ -27,7 +28,8 @@ class SummaryByAsset { SYNC_API_METHODS, ALLOWED_COLLS, wallets, - trades + trades, + movements ) { this.dao = dao this.syncSchema = syncSchema @@ -36,6 +38,7 @@ class SummaryByAsset { this.ALLOWED_COLLS = ALLOWED_COLLS this.wallets = wallets this.trades = trades + this.movements = movements this.ledgersMethodColl = this.syncSchema.getMethodCollMap() .get(this.SYNC_API_METHODS.LEDGERS) From 6f6e9f3bf7caa2165794e4d23d2c76a9961ece30 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Sep 2025 10:36:08 +0300 Subject: [PATCH 06/26] Omit redundant res field from getSummaryByAsset endpoint --- workers/loc.api/sync/summary.by.asset/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 1b7a57a1..75a03291 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -124,7 +124,8 @@ class SummaryByAsset { omit(item, [ 'balanceChangeUsd', 'tradingFeesUsd', - 'calcedStartWalletBalanceUsd' + 'calcedStartWalletBalanceUsd', + 'allFeesUsd' ]) )) From 30417cec16587efddd4eebf1e91e596ae16483ad Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Sep 2025 10:38:14 +0300 Subject: [PATCH 07/26] Update data consistency checker for getSummaryByAsset endpoint --- workers/loc.api/sync/data.consistency.checker/checkers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/workers/loc.api/sync/data.consistency.checker/checkers.js b/workers/loc.api/sync/data.consistency.checker/checkers.js index ff5b666e..aaff50dc 100644 --- a/workers/loc.api/sync/data.consistency.checker/checkers.js +++ b/workers/loc.api/sync/data.consistency.checker/checkers.js @@ -181,6 +181,7 @@ class Checkers { auth, params: { schema: [ + this.SYNC_API_METHODS.TRADES, this.SYNC_API_METHODS.LEDGERS, this.SYNC_API_METHODS.CANDLES, this.SYNC_API_METHODS.MOVEMENTS From 6948cd3c722479e6edfc451ac5f8702bc695157f Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 24 Sep 2025 10:39:22 +0300 Subject: [PATCH 08/26] Update test cases for getSummaryByAsset endpoint --- .../additional-api-sync-mode-sqlite-test-cases.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 b4cd8fc6..cc4226f3 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 @@ -819,7 +819,9 @@ module.exports = ( 'balanceChangeUsd', 'balanceChangePerc', 'volumeUsd', - 'tradingFeesUsd' + 'tradingFeesUsd', + 'allFeesUsd', + 'depositsWithdrawalsUsd' ]) }) @@ -866,7 +868,9 @@ module.exports = ( 'balanceChangeUsd', 'balanceChangePerc', 'volumeUsd', - 'tradingFeesUsd' + 'tradingFeesUsd', + 'allFeesUsd', + 'depositsWithdrawalsUsd' ]) }) From a95c0856c298b8e4073e4cc60fbe5ae223cdd5f1 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 29 Sep 2025 14:00:55 +0300 Subject: [PATCH 09/26] Adapt WinLossVSAccountBalance service to get daily returns --- .../sync/win.loss.vs.account.balance/index.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 7b426985..734acdd8 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 @@ -32,6 +32,7 @@ class WinLossVSAccountBalance { timeframe = 'day', start = 0, end = Date.now(), + shouldTimeframePLBeReturned, isUnrealizedProfitExcluded, isVSPrevDayBalance } = params ?? {} @@ -60,6 +61,7 @@ class WinLossVSAccountBalance { const getWinLossPercByTimeframe = isVSPrevDayBalance ? this._getWinLossPrevDayBalanceByTimeframe( { + shouldTimeframePLBeReturned, isUnrealizedProfitExcluded, firstWalletsVals } @@ -193,6 +195,7 @@ class WinLossVSAccountBalance { } _getWinLossPrevDayBalanceByTimeframe ({ + shouldTimeframePLBeReturned, isUnrealizedProfitExcluded, firstWalletsVals }) { @@ -200,6 +203,7 @@ class WinLossVSAccountBalance { let prevWallets = 0 let prevPL = 0 let prevMultiplying = 1 + let totalMovements = 0 return ({ walletsGroupedByTimeframe = {}, @@ -228,6 +232,7 @@ class WinLossVSAccountBalance { const movements = Number.isFinite(movementsRes[symb]) ? movementsRes[symb] : 0 + totalMovements += movements const wallets = Number.isFinite(walletsGroupedByTimeframe[symb]) ? walletsGroupedByTimeframe[symb] : 0 @@ -241,6 +246,7 @@ class WinLossVSAccountBalance { : pl - prevPL const winLoss = realized + unrealized + const balanceWithoutMovements = wallets - totalMovements prevWallets = wallets prevPL = pl @@ -249,6 +255,16 @@ class WinLossVSAccountBalance { !Number.isFinite(winLoss) || prevWallets === 0 ) { + if (shouldTimeframePLBeReturned) { + return { + balanceWithoutMovements, + pl: Number.isFinite(winLoss) + ? winLoss + : 0, + perc: prevPerc + } + } + return { perc: prevPerc } } @@ -256,6 +272,16 @@ class WinLossVSAccountBalance { const perc = (prevMultiplying - 1) * 100 prevPerc = perc + if (shouldTimeframePLBeReturned) { + return { + balanceWithoutMovements, + pl: Number.isFinite(winLoss) + ? winLoss + : 0, + perc: prevPerc + } + } + return { perc } } } From 4fc46d843d2094e1bbbf5010b096d85f090dca07 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Mon, 29 Sep 2025 14:01:17 +0300 Subject: [PATCH 10/26] Get daily balances and pl for summary stats --- .../loc.api/sync/summary.by.asset/index.js | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 75a03291..f2eaeaa4 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -15,7 +15,8 @@ const depsTypes = (TYPES) => [ TYPES.ALLOWED_COLLS, TYPES.Wallets, TYPES.Trades, - TYPES.Movements + TYPES.Movements, + TYPES.WinLossVSAccountBalance ] class SummaryByAsset { #ledgerFeeCats = [201, 204, 207, 222, 224, 228, 241, @@ -29,7 +30,8 @@ class SummaryByAsset { ALLOWED_COLLS, wallets, trades, - movements + movements, + winLossVSAccountBalance ) { this.dao = dao this.syncSchema = syncSchema @@ -39,6 +41,7 @@ class SummaryByAsset { this.wallets = wallets this.trades = trades this.movements = movements + this.winLossVSAccountBalance = winLossVSAccountBalance this.ledgersMethodColl = this.syncSchema.getMethodCollMap() .get(this.SYNC_API_METHODS.LEDGERS) @@ -92,6 +95,19 @@ class SummaryByAsset { isDeposits: true, isExcludePrivate: false }) + const dailyBalancesAndPLPromise = this.winLossVSAccountBalance + .getWinLossVSAccountBalance({ + auth, + params: { + start, + end, + timeframe: 'day', + isUnrealizedProfitExcluded: args?.params + ?.isUnrealizedProfitExcluded, + isVSPrevDayBalance: true, + shouldTimeframePLBeReturned: true + } + }) const [ trades, @@ -99,15 +115,18 @@ class SummaryByAsset { startWallets, endWallets, withdrawals, - deposits + deposits, + dailyBalancesAndPL ] = await Promise.all([ tradesPromise, ledgersPromise, startWalletsPromise, endWalletsPromise, withdrawalsPromise, - depositsPromise + depositsPromise, + dailyBalancesAndPLPromise ]) + console.log('[dailyBalancesAndPL]:', dailyBalancesAndPL) const _summaryByAsset = await this.#calcSummaryByAsset({ trades, From 34ab709ddc46224b09eaf0ee3ae437c2e3adb654 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 11:03:26 +0300 Subject: [PATCH 11/26] Omit unnecessary zero first res --- workers/loc.api/sync/summary.by.asset/index.js | 2 -- .../sync/win.loss.vs.account.balance/index.js | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index f2eaeaa4..f84e2071 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -104,7 +104,6 @@ class SummaryByAsset { timeframe: 'day', isUnrealizedProfitExcluded: args?.params ?.isUnrealizedProfitExcluded, - isVSPrevDayBalance: true, shouldTimeframePLBeReturned: true } }) @@ -126,7 +125,6 @@ class SummaryByAsset { depositsPromise, dailyBalancesAndPLPromise ]) - console.log('[dailyBalancesAndPL]:', dailyBalancesAndPL) const _summaryByAsset = await this.#calcSummaryByAsset({ trades, 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 734acdd8..c57cd748 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 @@ -58,7 +58,10 @@ class WinLossVSAccountBalance { plGroupedByTimeframe } = await this.winLoss.getDataToCalcWinLoss(args) - const getWinLossPercByTimeframe = isVSPrevDayBalance + const getWinLossPercByTimeframe = ( + isVSPrevDayBalance || + shouldTimeframePLBeReturned + ) ? this._getWinLossPrevDayBalanceByTimeframe( { shouldTimeframePLBeReturned, @@ -99,10 +102,14 @@ class WinLossVSAccountBalance { return res }) - pickedRes.push({ - mts: start, - perc: 0 - }) + + if (!shouldTimeframePLBeReturned) { + pickedRes.push({ + mts: start, + perc: 0 + }) + } + const res = this.winLoss.shiftMtsToNextTimeframe( pickedRes, { timeframe, end } From 7ac4136842a584fdc64b250c788299db92dd3cda Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 12:24:10 +0300 Subject: [PATCH 12/26] Add ability to get daily returns --- .../sync/win.loss.vs.account.balance/index.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) 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 c57cd748..3cec3e0d 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 @@ -253,9 +253,12 @@ class WinLossVSAccountBalance { : pl - prevPL const winLoss = realized + unrealized + const balanceWithoutMovements = wallets - totalMovements + const fullBalanceWithoutMovements = isUnrealizedProfitExcluded + ? balanceWithoutMovements + : balanceWithoutMovements + pl - prevWallets = wallets prevPL = pl if ( @@ -264,10 +267,8 @@ class WinLossVSAccountBalance { ) { if (shouldTimeframePLBeReturned) { return { - balanceWithoutMovements, - pl: Number.isFinite(winLoss) - ? winLoss - : 0, + balanceWithoutMovementsUsd: fullBalanceWithoutMovements, + returns: 0, perc: prevPerc } } @@ -277,15 +278,16 @@ class WinLossVSAccountBalance { prevMultiplying = ((prevWallets + winLoss) / prevWallets) * prevMultiplying const perc = (prevMultiplying - 1) * 100 + const returns = winLoss / prevWallets + prevPerc = perc + prevWallets = wallets if (shouldTimeframePLBeReturned) { return { - balanceWithoutMovements, - pl: Number.isFinite(winLoss) - ? winLoss - : 0, - perc: prevPerc + balanceWithoutMovementsUsd: fullBalanceWithoutMovements, + returns, + perc } } From 2ca171bb801dec2dd744b31c8fc4e3fec79a26bf Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 12:44:26 +0300 Subject: [PATCH 13/26] Calc pl usd for summary statistic --- .../loc.api/sync/summary.by.asset/index.js | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index f84e2071..eeb97251 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -126,7 +126,7 @@ class SummaryByAsset { dailyBalancesAndPLPromise ]) - const _summaryByAsset = await this.#calcSummaryByAsset({ + const _summaryByAsset = this.#calcSummaryByAsset({ trades, ledgers, startWallets, @@ -135,7 +135,8 @@ class SummaryByAsset { const total = this.#calcTotal( _summaryByAsset, withdrawals, - deposits + deposits, + dailyBalancesAndPL ) const summaryByAsset = _summaryByAsset.map((item) => ( omit(item, [ @@ -152,7 +153,7 @@ class SummaryByAsset { } } - async #calcSummaryByAsset ({ + #calcSummaryByAsset ({ trades, ledgers, startWallets, @@ -336,8 +337,19 @@ class SummaryByAsset { #calcTotal ( summaryByAsset, withdrawals, - deposits + deposits, + dailyBalancesAndPL ) { + const calcedWithdrawalsUsd = this.#calcFieldByName( + withdrawals, + 'amountUsd' + ) + const calcedDepositsUsd = this.#calcFieldByName( + deposits, + 'amountUsd' + ) + const plUsd = this.#calcPLUsd(dailyBalancesAndPL) + const initTotal = { balanceUsd: 0, balanceChangeUsd: 0, @@ -347,7 +359,12 @@ class SummaryByAsset { allFeesUsd: 0, calcedStartWalletBalanceUsd: 0, - depositsWithdrawalsUsd: 0 + + depositsWithdrawalsUsd: ( + calcedWithdrawalsUsd + + calcedDepositsUsd + ), + plUsd } const res = summaryByAsset.reduce((accum, curr) => { @@ -372,20 +389,6 @@ class SummaryByAsset { return accum }, initTotal) - const calcedWithdrawalsUsd = this.#calcFieldByName( - withdrawals, - 'amountUsd' - ) - const calcedDepositsUsd = this.#calcFieldByName( - deposits, - 'amountUsd' - ) - res.depositsWithdrawalsUsd = ( - res.depositsWithdrawalsUsd + - calcedWithdrawalsUsd + - calcedDepositsUsd - ) - return omit(res, ['calcedStartWalletBalanceUsd']) } @@ -429,6 +432,22 @@ class SummaryByAsset { return ledgerMap } + + #calcPLUsd (dailyBalancesAndPL) { + if ( + !Array.isArray(dailyBalancesAndPL) || + dailyBalancesAndPL.length === 0 + ) { + return 0 + } + + const lastBalance = dailyBalancesAndPL?.[0] + ?.balanceWithoutMovementsUsd ?? 0 + const firstBalance = dailyBalancesAndPL?.[dailyBalancesAndPL.length - 1] + ?.balanceWithoutMovementsUsd ?? 0 + + return lastBalance - firstBalance + } } decorateInjectable(SummaryByAsset, depsTypes) From 4db01bb29ba764b272f324a2d1c01d44c7321c7d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 12:47:44 +0300 Subject: [PATCH 14/26] Add isUnrealizedProfitExcluded flag to data validation schema --- workers/loc.api/data-validator/schemas/getSummaryByAssetReq.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/workers/loc.api/data-validator/schemas/getSummaryByAssetReq.js b/workers/loc.api/data-validator/schemas/getSummaryByAssetReq.js index fbb0b21d..70e7e29d 100644 --- a/workers/loc.api/data-validator/schemas/getSummaryByAssetReq.js +++ b/workers/loc.api/data-validator/schemas/getSummaryByAssetReq.js @@ -12,6 +12,9 @@ module.exports = { }, end: { $ref: 'defs#/definitions/end' + }, + isUnrealizedProfitExcluded: { + $ref: 'fwDefs#/definitions/isUnrealizedProfitExcluded' } } } From c1943ad991393e8f917f9af2305910e4b4d002ab Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 13:07:05 +0300 Subject: [PATCH 15/26] Get daily returns --- workers/loc.api/sync/summary.by.asset/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index eeb97251..d853ba96 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -349,6 +349,7 @@ class SummaryByAsset { 'amountUsd' ) const plUsd = this.#calcPLUsd(dailyBalancesAndPL) + const returns = this.#getDailyReturns(dailyBalancesAndPL) const initTotal = { balanceUsd: 0, @@ -448,6 +449,14 @@ class SummaryByAsset { return lastBalance - firstBalance } + + #getDailyReturns (dailyBalancesAndPL) { + if (!Array.isArray(dailyBalancesAndPL)) { + return [] + } + + return dailyBalancesAndPL.map((item) => item?.returns) + } } decorateInjectable(SummaryByAsset, depsTypes) From 9bddd94acf8e76c03aad93e716a6f4a4de4afaed Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Tue, 30 Sep 2025 13:24:51 +0300 Subject: [PATCH 16/26] Calc volatility percentage --- package-lock.json | 89 +++++++++++++++++++ package.json | 1 + .../loc.api/sync/summary.by.asset/index.js | 9 +- 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 5cec6e1b..f1f176dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "inversify": "6.0.1", "lib-js-util-base": "git+https://github.com/bitfinexcom/lib-js-util-base.git", "lodash": "4.17.21", + "mathjs": "14.8.1", "moment": "2.29.4", "puppeteer": "24.1.0", "uuid": "9.0.0", @@ -2067,6 +2068,19 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/complex.js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz", + "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -2410,6 +2424,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2848,6 +2868,12 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3759,6 +3785,19 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -5010,6 +5049,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "license": "MIT" + }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -5466,6 +5511,29 @@ "node": ">= 0.4" } }, + "node_modules/mathjs": { + "version": "14.8.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.8.1.tgz", + "integrity": "sha512-7UDitJFsXoPbFdK2V7S6uJp7krx9B5bQSzAvCnh7ecsceSK4w0PBbEt9B3IRg9lHqzjk3YJNT1fZg/BNK+4CQw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.26.10", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7114,6 +7182,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -7916,6 +7990,12 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8120,6 +8200,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/typed-query-selector": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", diff --git a/package.json b/package.json index fa48288a..7bb6e35f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "inversify": "6.0.1", "lib-js-util-base": "git+https://github.com/bitfinexcom/lib-js-util-base.git", "lodash": "4.17.21", + "mathjs": "14.8.1", "moment": "2.29.4", "puppeteer": "24.1.0", "uuid": "9.0.0", diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index d853ba96..4e1d0316 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -2,6 +2,7 @@ const { omit } = require('lib-js-util-base') const moment = require('moment') +const math = require('mathjs') const { pushLargeArr } = require('../../helpers/utils') @@ -350,6 +351,7 @@ class SummaryByAsset { ) const plUsd = this.#calcPLUsd(dailyBalancesAndPL) const returns = this.#getDailyReturns(dailyBalancesAndPL) + const volatilityPerc = this.#calcVolatilityPerc(returns) const initTotal = { balanceUsd: 0, @@ -365,7 +367,8 @@ class SummaryByAsset { calcedWithdrawalsUsd + calcedDepositsUsd ), - plUsd + plUsd, + volatilityPerc } const res = summaryByAsset.reduce((accum, curr) => { @@ -457,6 +460,10 @@ class SummaryByAsset { return dailyBalancesAndPL.map((item) => item?.returns) } + + #calcVolatilityPerc (dailyReturns) { + return math.std(dailyReturns) * Math.sqrt(365) * 100 + } } decorateInjectable(SummaryByAsset, depsTypes) From 5cd77f1a58a839eba7cef543eadf5454b581cfd3 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 1 Oct 2025 10:42:09 +0300 Subject: [PATCH 17/26] Rework volatility perc calc --- workers/loc.api/sync/summary.by.asset/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 4e1d0316..3cbd34ea 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -351,7 +351,9 @@ class SummaryByAsset { ) const plUsd = this.#calcPLUsd(dailyBalancesAndPL) const returns = this.#getDailyReturns(dailyBalancesAndPL) - const volatilityPerc = this.#calcVolatilityPerc(returns) + const { + volatilityPerc + } = this.#calcDailyReturnStatistics(returns) const initTotal = { balanceUsd: 0, @@ -461,8 +463,14 @@ class SummaryByAsset { return dailyBalancesAndPL.map((item) => item?.returns) } - #calcVolatilityPerc (dailyReturns) { - return math.std(dailyReturns) * Math.sqrt(365) * 100 + #calcDailyReturnStatistics (dailyReturns) { + const returnsStd = math.std(dailyReturns) + + const volatilityPerc = returnsStd * Math.sqrt(365) * 100 + + return { + volatilityPerc + } } } From a304650bf99d93c29d289a6cb3c35f5aa890b7ad Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 1 Oct 2025 10:52:28 +0300 Subject: [PATCH 18/26] Calc sharpe ratio --- workers/loc.api/sync/summary.by.asset/index.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 3cbd34ea..47253ec3 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -352,7 +352,8 @@ class SummaryByAsset { const plUsd = this.#calcPLUsd(dailyBalancesAndPL) const returns = this.#getDailyReturns(dailyBalancesAndPL) const { - volatilityPerc + volatilityPerc, + sharpeRatio } = this.#calcDailyReturnStatistics(returns) const initTotal = { @@ -370,7 +371,8 @@ class SummaryByAsset { calcedDepositsUsd ), plUsd, - volatilityPerc + volatilityPerc, + sharpeRatio } const res = summaryByAsset.reduce((accum, curr) => { @@ -464,12 +466,16 @@ class SummaryByAsset { } #calcDailyReturnStatistics (dailyReturns) { - const returnsStd = math.std(dailyReturns) + const returnStd = math.std(dailyReturns) + const avgReturn = math.mean(dailyReturns) + const sqrt365 = Math.sqrt(365) - const volatilityPerc = returnsStd * Math.sqrt(365) * 100 + const volatilityPerc = returnStd * sqrt365 * 100 + const sharpeRatio = (avgReturn / returnStd) * sqrt365 return { - volatilityPerc + volatilityPerc, + sharpeRatio } } } From 3924ab4f4740cbbcee1482190bfd5804fb662453 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 1 Oct 2025 11:00:44 +0300 Subject: [PATCH 19/26] Calc sortino ratio --- workers/loc.api/sync/summary.by.asset/index.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 47253ec3..52ac87f2 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -353,7 +353,8 @@ class SummaryByAsset { const returns = this.#getDailyReturns(dailyBalancesAndPL) const { volatilityPerc, - sharpeRatio + sharpeRatio, + sortinoRatio } = this.#calcDailyReturnStatistics(returns) const initTotal = { @@ -372,7 +373,8 @@ class SummaryByAsset { ), plUsd, volatilityPerc, - sharpeRatio + sharpeRatio, + sortinoRatio } const res = summaryByAsset.reduce((accum, curr) => { @@ -473,9 +475,16 @@ class SummaryByAsset { const volatilityPerc = returnStd * sqrt365 * 100 const sharpeRatio = (avgReturn / returnStd) * sqrt365 + const negativeReturns = dailyReturns.filter((r) => r < 0) + const downsideStd = negativeReturns.length > 0 + ? math.std(negativeReturns) + : 0.00001 + const sortinoRatio = (avgReturn / downsideStd) * sqrt365 + return { volatilityPerc, - sharpeRatio + sharpeRatio, + sortinoRatio } } } From a83f99945e6150bcc4c55d5f9987227b7a1fe9f6 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Wed, 1 Oct 2025 11:27:51 +0300 Subject: [PATCH 20/26] Calc max drawdown perc --- .../loc.api/sync/summary.by.asset/index.js | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 52ac87f2..d8796397 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -4,6 +4,7 @@ const { omit } = require('lib-js-util-base') const moment = require('moment') const math = require('mathjs') +const { getBackIterable } = require('../helpers') const { pushLargeArr } = require('../../helpers/utils') const { decorateInjectable } = require('../../di/utils') @@ -356,6 +357,7 @@ class SummaryByAsset { sharpeRatio, sortinoRatio } = this.#calcDailyReturnStatistics(returns) + const maxDrawdownPerc = this.#calcMaxDrawdownPerc(dailyBalancesAndPL) const initTotal = { balanceUsd: 0, @@ -374,7 +376,8 @@ class SummaryByAsset { plUsd, volatilityPerc, sharpeRatio, - sortinoRatio + sortinoRatio, + maxDrawdownPerc } const res = summaryByAsset.reduce((accum, curr) => { @@ -487,6 +490,38 @@ class SummaryByAsset { sortinoRatio } } + + #calcMaxDrawdownPerc (dailyBalancesAndPL) { + if ( + !Array.isArray(dailyBalancesAndPL) || + dailyBalancesAndPL.length === 0 + ) { + return 0 + } + + const iterator = getBackIterable(dailyBalancesAndPL) + let peak = dailyBalancesAndPL?.[dailyBalancesAndPL.length - 1] + ?.balanceWithoutMovementsUsd ?? 0 + let maxDrawdown = 0 + + for (const item of iterator) { + const balance = item?.balanceWithoutMovementsUsd ?? 0 + + if (balance > peak) { + peak = balance + } + + const drawdown = peak !== 0 + ? (peak - balance) / peak + : 0 + + if (drawdown > maxDrawdown) { + maxDrawdown = drawdown + } + } + + return maxDrawdown * 100 + } } decorateInjectable(SummaryByAsset, depsTypes) From 15572880ea7dd16865e03c7d541479b6dfb42d74 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 12:15:12 +0300 Subject: [PATCH 21/26] Enhance data consistency checker for summary stats --- workers/loc.api/sync/data.consistency.checker/checkers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workers/loc.api/sync/data.consistency.checker/checkers.js b/workers/loc.api/sync/data.consistency.checker/checkers.js index aaff50dc..9133b3a5 100644 --- a/workers/loc.api/sync/data.consistency.checker/checkers.js +++ b/workers/loc.api/sync/data.consistency.checker/checkers.js @@ -184,7 +184,9 @@ class Checkers { this.SYNC_API_METHODS.TRADES, this.SYNC_API_METHODS.LEDGERS, this.SYNC_API_METHODS.CANDLES, - this.SYNC_API_METHODS.MOVEMENTS + this.SYNC_API_METHODS.MOVEMENTS, + this.SYNC_API_METHODS.POSITIONS_SNAPSHOT, + this.SYNC_API_METHODS.POSITIONS_HISTORY ] } }) From 24fc8834a20de7729cea35c41e435958a46c984a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 12:15:35 +0300 Subject: [PATCH 22/26] Drop redundant dao service injection --- workers/loc.api/sync/win.loss/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/workers/loc.api/sync/win.loss/index.js b/workers/loc.api/sync/win.loss/index.js index 61f780e9..d6b0bd7f 100644 --- a/workers/loc.api/sync/win.loss/index.js +++ b/workers/loc.api/sync/win.loss/index.js @@ -12,7 +12,6 @@ const { const { decorateInjectable } = require('../../di/utils') const depsTypes = (TYPES) => [ - TYPES.DAO, TYPES.SyncSchema, TYPES.BalanceHistory, TYPES.PositionsSnapshot, @@ -24,7 +23,6 @@ const depsTypes = (TYPES) => [ ] class WinLoss { constructor ( - dao, syncSchema, balanceHistory, positionsSnapshot, @@ -34,7 +32,6 @@ class WinLoss { movements, wallets ) { - this.dao = dao this.syncSchema = syncSchema this.balanceHistory = balanceHistory this.positionsSnapshot = positionsSnapshot From b3d8ed933673e17f0b551cc86fc917cfc1763a7a Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 12:16:11 +0300 Subject: [PATCH 23/26] Enhance test coverage for summary stats --- .../additional-api-sync-mode-sqlite-test-cases.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 cc4226f3..5b79c8d9 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 @@ -821,7 +821,12 @@ module.exports = ( 'volumeUsd', 'tradingFeesUsd', 'allFeesUsd', - 'depositsWithdrawalsUsd' + 'depositsWithdrawalsUsd', + 'plUsd', + 'volatilityPerc', + 'sharpeRatio', + 'sortinoRatio', + 'maxDrawdownPerc' ]) }) @@ -870,7 +875,12 @@ module.exports = ( 'volumeUsd', 'tradingFeesUsd', 'allFeesUsd', - 'depositsWithdrawalsUsd' + 'depositsWithdrawalsUsd', + 'plUsd', + 'volatilityPerc', + 'sharpeRatio', + 'sortinoRatio', + 'maxDrawdownPerc' ]) }) From 6e3bd79146068fc390b8d39f926505ae570cf73d Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 12:31:54 +0300 Subject: [PATCH 24/26] Enhance daily return stat calc --- workers/loc.api/sync/summary.by.asset/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index d8796397..1f82f074 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -476,13 +476,17 @@ class SummaryByAsset { const sqrt365 = Math.sqrt(365) const volatilityPerc = returnStd * sqrt365 * 100 - const sharpeRatio = (avgReturn / returnStd) * sqrt365 + const sharpeRatio = returnStd !== 0 + ? (avgReturn / returnStd) * sqrt365 + : 0 const negativeReturns = dailyReturns.filter((r) => r < 0) const downsideStd = negativeReturns.length > 0 ? math.std(negativeReturns) : 0.00001 - const sortinoRatio = (avgReturn / downsideStd) * sqrt365 + const sortinoRatio = downsideStd !== 0 + ? (avgReturn / downsideStd) * sqrt365 + : 0 return { volatilityPerc, From b7eb3209ae34a843deac9495742b6a16fd29e725 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 12:57:29 +0300 Subject: [PATCH 25/26] Adjust ledger categories --- workers/loc.api/sync/summary.by.asset/index.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 1f82f074..58111114 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -21,8 +21,14 @@ const depsTypes = (TYPES) => [ TYPES.WinLossVSAccountBalance ] class SummaryByAsset { - #ledgerFeeCats = [201, 204, 207, 222, 224, 228, 241, - 243, 251, 254, 255, 258, 905] + #ledgerMarginFundingPaymentCat = 28 + #ledgerTradingFeeCat = 201 + + #ledgerFeeCats = [this.#ledgerTradingFeeCat, 204, 207, 222, 224, + 228, 241, 243, 251, 254, 255, 258, 905] + + #allUsedLedgerCats = [this.#ledgerMarginFundingPaymentCat, + ...this.#ledgerFeeCats] constructor ( dao, @@ -215,13 +221,15 @@ class SummaryByAsset { const ledgerMap = this.#makeLedgerMapFilteredByCcyGroupedByCategory( ledgers, currency, - [28, ...this.#ledgerFeeCats] + this.#allUsedLedgerCats ) const tradesForCurrency = trades.filter((trade) => ( trade?.baseCurrency === currency )) - const marginFundingPaymentLedgers = ledgerMap.get(28) ?? [] - const tradingFeeLedgers = ledgerMap.get(201) ?? [] + const marginFundingPaymentLedgers = ledgerMap + .get(this.#ledgerMarginFundingPaymentCat) ?? [] + const tradingFeeLedgers = ledgerMap + .get(this.#ledgerTradingFeeCat) ?? [] const allFeeLedgers = this.#ledgerFeeCats.reduce((accum, cat) => { pushLargeArr(accum, ledgerMap.get(cat) ?? []) From 41850cd5655491e599c1c025536f933948032955 Mon Sep 17 00:00:00 2001 From: Vladimir Voronkov Date: Thu, 2 Oct 2025 13:05:22 +0300 Subject: [PATCH 26/26] Optimaze ledger map creation --- workers/loc.api/sync/summary.by.asset/index.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/workers/loc.api/sync/summary.by.asset/index.js b/workers/loc.api/sync/summary.by.asset/index.js index 58111114..36a8dd31 100644 --- a/workers/loc.api/sync/summary.by.asset/index.js +++ b/workers/loc.api/sync/summary.by.asset/index.js @@ -438,17 +438,14 @@ class SummaryByAsset { } for (const ledger of ledgers) { - if (ledger?.[this.ledgersSymbolFieldName] !== ccy) { + if ( + ledger?.[this.ledgersSymbolFieldName] !== ccy || + !ledgerMap.has(ledger?._category) + ) { continue } - for (const [category, arr] of ledgerMap) { - if (ledger?._category !== category) { - continue - } - - arr.push(ledger) - } + ledgerMap.get(ledger?._category).push(ledger) } return ledgerMap