From 62cb381b824e412c0f61e1ed97ecdb5d3ca0f328 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:34:22 +0100 Subject: [PATCH 1/2] feat: calculate spread-based fees for Scrypt exchange trades Scrypt reports feeAmount=0 for all trades, but the real cost is hidden in the price spread between execution price and market rate. This adds automatic spread fee calculation using historical asset prices from the AssetPrice table via getPriceAt(). After deploy, a one-time resync is needed for existing trades: syncExchanges(new Date('2026-01-19'), ExchangeName.SCRYPT) --- .../exchange/services/exchange-tx.service.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/integration/exchange/services/exchange-tx.service.ts b/src/integration/exchange/services/exchange-tx.service.ts index 8c8a581f71..bda2231d1a 100644 --- a/src/integration/exchange/services/exchange-tx.service.ts +++ b/src/integration/exchange/services/exchange-tx.service.ts @@ -56,8 +56,36 @@ export class ExchangeTxService { externalId: transaction.externalId, type: transaction.type, }); + + // Preserve calculated spread fee for Scrypt (DTO's feeAmount=0 would overwrite it) + const preservedSpreadFee = + entity?.exchange === ExchangeName.SCRYPT && entity.feeAmountChf != null + ? { feeAmount: entity.feeAmount, feeCurrency: entity.feeCurrency, feeAmountChf: entity.feeAmountChf } + : undefined; + entity = entity ? Object.assign(entity, transaction) : this.exchangeTxRepo.create(transaction); + if (preservedSpreadFee) { + entity.feeAmount = preservedSpreadFee.feeAmount; + entity.feeCurrency = preservedSpreadFee.feeCurrency; + entity.feeAmountChf = preservedSpreadFee.feeAmountChf; + } + + // Calculate spread fee for new Scrypt trades + if ( + entity.exchange === ExchangeName.SCRYPT && + entity.type === ExchangeTxType.TRADE && + entity.price && + entity.amount && + entity.feeAmountChf == null + ) { + try { + await this.calculateSpreadFee(entity); + } catch (e) { + this.logger.warn(`Failed to calculate spread fee for Scrypt trade ${entity.externalId}:`, e); + } + } + if (entity.feeAmount && !entity.feeAmountChf) { if (entity.feeCurrency === 'CHF') { entity.feeAmountChf = entity.feeAmount; @@ -119,6 +147,47 @@ export class ExchangeTxService { }); } + private async calculateSpreadFee(entity: ExchangeTx): Promise { + const [baseCurrency, quoteCurrency] = entity.symbol.split('/'); + const tradeDate = entity.externalCreated ?? entity.created; + + // Resolve base asset (e.g., USDT, BTC) + const baseAsset = await this.assetService.getAssetByQuery({ + name: baseCurrency, + blockchain: undefined, + type: undefined, + }); + if (!baseAsset) return; + + // Resolve quote currency (fiat like CHF/EUR, or asset like USDT) + const quoteActive = + (await this.fiatService.getFiatByName(quoteCurrency)) ?? + (await this.assetService.getAssetByQuery({ + name: quoteCurrency, + blockchain: undefined, + type: undefined, + })); + if (!quoteActive) return; + + // Get historical market rate: quote per base (e.g., 0.858 EUR per USDT) + const marketPrice = await this.pricingService.getPriceAt(quoteActive, baseAsset, tradeDate); + const marketRate = marketPrice.price; + + // Get base price in CHF for fee conversion + const basePriceChf = await this.pricingService.getPriceAt(PriceCurrency.CHF, baseAsset, tradeDate); + const quotePriceChf = basePriceChf.price / marketRate; + + // Spread fee in quote currency + const spreadFee = + entity.side === 'buy' + ? entity.amount * (entity.price - marketRate) + : entity.amount * (marketRate - entity.price); + + entity.feeAmount = Util.round(spreadFee, Config.defaultVolumeDecimal); + entity.feeCurrency = quoteCurrency; + entity.feeAmountChf = Util.round(spreadFee * quotePriceChf, Config.defaultVolumeDecimal); + } + private async getSyncSinceDate(exchange: ExchangeName): Promise { const defaultSince = Util.minutesBefore(Config.exchangeTxSyncLimit); From f7acc17f2cd12410fb3d510241499249652440cb Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:39:12 +0100 Subject: [PATCH 2/2] fix: prettier formatting --- src/integration/exchange/services/exchange-tx.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/integration/exchange/services/exchange-tx.service.ts b/src/integration/exchange/services/exchange-tx.service.ts index bda2231d1a..84ca48897e 100644 --- a/src/integration/exchange/services/exchange-tx.service.ts +++ b/src/integration/exchange/services/exchange-tx.service.ts @@ -179,9 +179,7 @@ export class ExchangeTxService { // Spread fee in quote currency const spreadFee = - entity.side === 'buy' - ? entity.amount * (entity.price - marketRate) - : entity.amount * (marketRate - entity.price); + entity.side === 'buy' ? entity.amount * (entity.price - marketRate) : entity.amount * (marketRate - entity.price); entity.feeAmount = Util.round(spreadFee, Config.defaultVolumeDecimal); entity.feeCurrency = quoteCurrency;