Skip to content

Commit

Permalink
feat: add AssetPosition entity and track realized p&l (#193)
Browse files Browse the repository at this point in the history
* feat: add AssetPosition entity and track realized p&l
Fixes #185

* fix: scaling of realized profit

* chore: sell to sellFifo
  • Loading branch information
filo87 authored Jun 7, 2024
1 parent 6a7b554 commit cc358ee
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
16 changes: 16 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ type Pool @entity {
sumPoolFeesPaidAmount: BigInt #Applies to All
sumPoolFeesPendingAmount: BigInt #Applies to All

sumRealizedProfitFifoByPeriod: BigInt

# Cumulated transaction data since pool creation
sumBorrowedAmount: BigInt
sumRepaidAmount: BigInt
Expand Down Expand Up @@ -123,6 +125,8 @@ type PoolSnapshot @entity {
sumPoolFeesPaidAmount: BigInt #Applies to All
sumPoolFeesPendingAmount: BigInt #Applies to All

sumRealizedProfitFifoByPeriod: BigInt

# Cumulated transaction data since pool creation
sumBorrowedAmount: BigInt
sumRepaidAmount: BigInt
Expand Down Expand Up @@ -314,6 +318,8 @@ type AssetTransaction @entity {
# only applies to debt transfers
fromAsset: Asset
toAsset: Asset

realizedProfitFifo: BigInt
}

type OracleTransaction @entity {
Expand Down Expand Up @@ -429,6 +435,8 @@ type Asset @entity {
writtenOffPercentageByPeriod: BigInt
writtenOffAmountByPeriod: BigInt
penaltyInterestRatePerSec: BigInt

positions: [AssetPosition] @derivedFrom(field: "asset")
}

type AssetSnapshot @entity {
Expand Down Expand Up @@ -465,6 +473,14 @@ type AssetSnapshot @entity {
penaltyInterestRatePerSec: BigInt
}

type AssetPosition @entity {
id: ID!
asset: Asset! @index
timestamp: Date!
holdingQuantity: BigInt!
purchasePrice: BigInt!
}

type PureProxy @entity {
id: ID!

Expand Down
51 changes: 46 additions & 5 deletions src/mappings/handlers/loansHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { EpochService } from '../services/epochService'
import { AssetType, AssetValuationMethod } from '../../types'
import { bnToBn, nToBigInt } from '@polkadot/util'
import { WAD } from '../../config'
import { AssetPositionService } from '../services/assetPositionService'

export const handleLoanCreated = errorHandler(_handleLoanCreated)
async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {
Expand Down Expand Up @@ -142,6 +143,13 @@ async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedEvent>): Pr

if (borrowAmount.isExternal) {
await asset.increaseQuantity(borrowAmount.asExternal.quantity.toBigInt())
await AssetPositionService.buy(
asset.id,
assetTransactionBaseData.hash,
assetTransactionBaseData.timestamp,
assetTransactionBaseData.quantity,
assetTransactionBaseData.settlementPrice
)
}

const at = await AssetTransactionService.borrowed(assetTransactionBaseData)
Expand Down Expand Up @@ -205,11 +213,19 @@ async function _handleLoanRepaid(event: SubstrateEvent<LoanRepaidEvent>) {
} else {
await asset.repay(amount)

let realizedProfitFifo: bigint
if (principal.isExternal) {
await asset.decreaseQuantity(principal.asExternal.quantity.toBigInt())
const { quantity, settlementPrice } = principal.asExternal
await asset.decreaseQuantity(quantity.toBigInt())
realizedProfitFifo = await AssetPositionService.sellFifo(
asset.id,
quantity.toBigInt(),
settlementPrice.toBigInt()
)
await pool.increaseRealizedProfitFifo(realizedProfitFifo)
}

const at = await AssetTransactionService.repaid(assetTransactionBaseData)
const at = await AssetTransactionService.repaid({ ...assetTransactionBaseData, realizedProfitFifo })
await at.save()

// Update pool info
Expand Down Expand Up @@ -313,8 +329,16 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
//Track repayment
await fromAsset.activate()
await fromAsset.repay(repaidAmount)
let realizedProfitFifo: bigint
if (_repaidAmount.principal.isExternal) {
await toAsset.decreaseQuantity(_repaidAmount.principal.asExternal.quantity.toBigInt())
const { quantity, settlementPrice } = _repaidAmount.principal.asExternal
await fromAsset.decreaseQuantity(quantity.toBigInt())
realizedProfitFifo = await AssetPositionService.sellFifo(
fromAsset.id,
quantity.toBigInt(),
settlementPrice.toBigInt()
)
await pool.increaseRealizedProfitFifo(realizedProfitFifo)
}
await fromAsset.updateIpfsAssetName()
await fromAsset.save()
Expand All @@ -339,6 +363,7 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
: null,
fromAssetId: fromLoanId.toString(10),
toAssetId: toLoanId.toString(10),
realizedProfitFifo,
})
await principalRepayment.save()
}
Expand All @@ -348,7 +373,15 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
await toAsset.activate()
await toAsset.borrow(borrowPrincipalAmount)
if (_borrowAmount.isExternal) {
await toAsset.increaseQuantity(_borrowAmount.asExternal.quantity.toBigInt())
const { quantity, settlementPrice } = _borrowAmount.asExternal
await toAsset.increaseQuantity(quantity.toBigInt())
await AssetPositionService.buy(
toAsset.id,
txData.hash,
txData.timestamp,
quantity.toBigInt(),
settlementPrice.toBigInt()
)
}
await toAsset.updateIpfsAssetName()
await toAsset.save()
Expand Down Expand Up @@ -412,6 +445,10 @@ async function _handleLoanDebtTransferred1024(event: SubstrateEvent<LoanDebtTran
await fromAsset.repay(amount)
await fromAsset.save()

const quantity = nToBigInt(bnToBn(amount).mul(WAD).div(bnToBn(fromAsset.currentPrice)))
const realizedProfitFifo = await AssetPositionService.sellFifo(toAsset.id, quantity, toAsset.currentPrice)

await pool.increaseRealizedProfitFifo(realizedProfitFifo)
await pool.increaseRepayments1024(amount)
await pool.save()

Expand All @@ -427,6 +464,7 @@ async function _handleLoanDebtTransferred1024(event: SubstrateEvent<LoanDebtTran
toAssetId: toLoanId.toString(10),
settlementPrice: fromAsset.currentPrice,
quantity: nToBigInt(bnToBn(amount).mul(WAD).div(bnToBn(fromAsset.currentPrice))),
realizedProfitFifo,
})
await principalRepayment.save()
}
Expand All @@ -445,6 +483,9 @@ async function _handleLoanDebtTransferred1024(event: SubstrateEvent<LoanDebtTran
await epoch.increaseBorrowings(amount)
await epoch.save()

const quantity = nToBigInt(bnToBn(amount).mul(WAD).div(bnToBn(toAsset.currentPrice)))
await AssetPositionService.buy(toAsset.id, txData.hash, txData.timestamp, quantity, toAsset.currentPrice)

// purchase transaction
const purchaseTransaction = await AssetTransactionService.borrowed({
...txData,
Expand All @@ -454,7 +495,7 @@ async function _handleLoanDebtTransferred1024(event: SubstrateEvent<LoanDebtTran
fromAssetId: fromLoanId.toString(10),
toAssetId: toLoanId.toString(10),
settlementPrice: toAsset.currentPrice,
quantity: nToBigInt(bnToBn(amount).mul(WAD).div(bnToBn(toAsset.currentPrice))) ,
quantity: quantity,
})
await purchaseTransaction.save()
}
Expand Down
65 changes: 65 additions & 0 deletions src/mappings/services/assetPositionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { AssetPosition } from '../../types/models/AssetPosition'
import { WAD } from '../../config'
import { nToBigInt, bnToBn, BN } from '@polkadot/util'

export class AssetPositionService extends AssetPosition {
static init(assetId: string, hash: string, timestamp: Date, quantity: bigint, price: bigint) {
const id = `${assetId}-${hash}`
logger.info(
`Initialising new assetPosition with Id ${id} ` +
`holdingQuantity: ${quantity.toString(10)} price: ${price.toString(10)}`
)
return new this(id, assetId, timestamp, quantity, price)
}

static buy(assetId: string, hash: string, timestamp: Date, quantity: bigint, price: bigint) {
if (quantity > BigInt(0)) {
return this.init(assetId, hash, timestamp, quantity, price).save()
} else {
logger.warn(`Skipping asset position ${assetId}-${hash} as quantity is 0`)
return Promise.resolve()
}
}

static async sellFifo(assetId: string, sellingQuantity: bigint, sellingPrice: bigint) {
logger.info(
`Selling positions for ${assetId} ` +
`sellingQuantity: ${sellingQuantity.toString(10)} sellingPrice: ${sellingPrice.toString(10)}`
)
const positions = await this.getByAssetId(assetId)
positions.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf())

const sellPositions: [assetPosition: AssetPosition, sellQuantity: bigint][] = []
let remainingQuantity = sellingQuantity
while (remainingQuantity > BigInt(0)) {
const currentPosition = positions.pop()
if (!currentPosition) throw new Error(`No positions to sell for asset ${assetId}`)
if (remainingQuantity > currentPosition.holdingQuantity) {
const soldQuantity = currentPosition.holdingQuantity
currentPosition['holdingQuantity'] = BigInt(0)
sellPositions.push([currentPosition, soldQuantity])
remainingQuantity -= soldQuantity
} else {
const soldQuantity = remainingQuantity
currentPosition['holdingQuantity'] -= soldQuantity
sellPositions.push([currentPosition, soldQuantity])
remainingQuantity -= soldQuantity
}
}

const profitFromSale = nToBigInt(bnToBn(sellingPrice).mul(bnToBn(sellingQuantity)).div(WAD))
const costOfBuy = nToBigInt(
sellPositions.reduce<BN>(
(totalCost, line) => totalCost.add(bnToBn(line[0].purchasePrice).mul(bnToBn(line[1]).div(WAD))),
new BN(0)
)
)

const dbUpdates = sellPositions.map((line) =>
line[0].holdingQuantity > BigInt(0) ? line[0].save() : this.remove(line[0].id)
)

await Promise.all(dbUpdates)
return profitFromSale - costOfBuy
}
}
3 changes: 2 additions & 1 deletion src/mappings/services/assetTransactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface AssetTransactionData {
readonly assetId: string
readonly fromAssetId?: string
readonly toAssetId?: string
readonly realizedProfitFifo?: bigint
}

export class AssetTransactionService extends AssetTransaction {
Expand All @@ -38,7 +39,7 @@ export class AssetTransactionService extends AssetTransaction {
tx.settlementPrice = data.settlementPrice ?? null
tx.fromAssetId = data.fromAssetId ? `${data.poolId}-${data.fromAssetId}` : null
tx.toAssetId = data.toAssetId ? `${data.poolId}-${data.toAssetId}` : null

tx.realizedProfitFifo = data.realizedProfitFifo ?? null
return tx
}

Expand Down
6 changes: 6 additions & 0 deletions src/mappings/services/poolService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class PoolService extends Pool {
this.sumPoolFeesPaidAmountByPeriod = BigInt(0)
this.deltaPortfolioValuationByPeriod = BigInt(0)
this.sumInterestAccruedByPeriod = BigInt(0)
this.sumRealizedProfitFifoByPeriod = BigInt(0)

this.sumBorrowedAmount = BigInt(0)
this.sumRepaidAmount = BigInt(0)
Expand Down Expand Up @@ -403,6 +404,11 @@ export class PoolService extends Pool {
logger.info(`Updating sumPoolFeesPendingAmount for pool ${this.id} to ${pendingAmount.toString(10)}`)
this.sumPoolFeesPendingAmount = pendingAmount
}

public increaseRealizedProfitFifo(amount: bigint) {
logger.info(`Increasing umRealizedProfitFifoByPeriod for pool ${this.id} by ${amount.toString(10)}`)
this.sumRealizedProfitFifoByPeriod += amount
}
}

export interface ActiveLoanData {
Expand Down

0 comments on commit cc358ee

Please sign in to comment.