Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add unrealized P&L #197

Merged
merged 2 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chains-cfg/demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ network:
file: ./dist/chaintypes.js
dataSources:
- kind: substrate/Runtime
startBlock: 2291900 # April 1st 2024
startBlock: 2758750 # June 10th 2024
2 changes: 1 addition & 1 deletion chains-cfg/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ network:
file: ./dist/chaintypes.js
dataSources:
- kind: substrate/Runtime
startBlock: 6870 # block first pool was created at
startBlock: 1093800 # June 10th 2024
filter:
modulo: 1000
2 changes: 1 addition & 1 deletion chains-evm/eth/demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ network:
endpoint: "https://eth-sepolia.api.onfinality.io/rpc?apikey=${ONFINALITY_API_KEY}"
dataSources:
- kind: ethereum/Runtime
startBlock: 5602400 # April 1st 2024
startBlock: 6074950 # June 10th 2024
options:
address: '0x5c8657b827a138D52a4e3f03683A28B1FaD86893'
11 changes: 11 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type Pool @entity {
sumPoolFeesPendingAmount: BigInt #Applies to All

sumRealizedProfitFifoByPeriod: BigInt
sumUnrealizedProfitAtMarketPrice: BigInt
sumUnrealizedProfitAtNotional: BigInt

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

sumRealizedProfitFifoByPeriod: BigInt
sumUnrealizedProfitAtMarketPrice: BigInt
sumUnrealizedProfitAtNotional: BigInt

# Cumulated transaction data since pool creation
sumBorrowedAmount: BigInt
Expand Down Expand Up @@ -415,6 +419,7 @@ type Asset @entity {
presentValue: BigInt
currentPrice: BigInt
outstandingQuantity: BigInt
notional: BigInt

actualMaturityDate: Date
timeToMaturity: Int
Expand All @@ -436,6 +441,9 @@ type Asset @entity {
writtenOffAmountByPeriod: BigInt
penaltyInterestRatePerSec: BigInt

unrealizedProfitAtMarketPrice: BigInt
unrealizedProfitAtNotional: BigInt

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

Expand Down Expand Up @@ -471,6 +479,9 @@ type AssetSnapshot @entity {
writtenOffPercentageByPeriod: BigInt
writtenOffAmountByPeriod: BigInt
penaltyInterestRatePerSec: BigInt

unrealizedProfitAtMarketPrice: BigInt
unrealizedProfitAtNotional: BigInt
}

type AssetPosition @entity {
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ export interface LoanPricing extends Enum {
asExternal: {
priceId: CfgOracleKey
maxBorrowAmount: LoanExternalPricingMaxBorrowAmount
notional: u128,
maxPriceVariation: u128
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/mappings/handlers/blockHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Tranche,
TrancheSnapshot,
} from '../../types/models'
import { AssetPositionService } from '../services/assetPositionService'

const timekeeper = TimekeeperService.init()

Expand Down Expand Up @@ -61,11 +62,18 @@ async function _handleBlock(block: SubstrateBlock): Promise<void> {
// Asset operations
const activeLoanData = await pool.getPortfolio()
pool.resetOffchainCashValue()
pool.resetUnrealizedProfit()
for (const loanId in activeLoanData) {
const asset = await AssetService.getById(pool.id, loanId)
await asset.updateActiveAssetData(activeLoanData[loanId])
await pool.increaseInterestAccrued(asset.interestAccruedByPeriod)
await asset.updateUnrealizedProfit(
await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.currentPrice),
await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.notional)
)
await asset.save()
await pool.increaseInterestAccrued(asset.interestAccruedByPeriod)
if (asset.isNonCash())
pool.increaseUnrealizedProfit(asset.unrealizedProfitAtMarketPrice, asset.unrealizedProfitAtNotional)
if (asset.actualMaturityDate < block.timestamp) pool.increaseDebtOverdue(asset.outstandingDebt)
if (asset.isOffchainCash()) pool.increaseOffchainCashValue(asset.presentValue)
}
Expand Down
2 changes: 2 additions & 0 deletions src/mappings/handlers/loansHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {

const isInternal = loanInfo.pricing.isInternal
const internalLoanPricing = isInternal ? loanInfo.pricing.asInternal : null
const externalLoanPricing = !isInternal ? loanInfo.pricing.asExternal : null

const assetType: AssetType =
isInternal && internalLoanPricing.valuationMethod.isCash ? AssetType.OffchainCash : AssetType.Other
Expand Down Expand Up @@ -71,6 +72,7 @@ async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {
maturityDate: loanInfo.schedule.maturity.isFixed
? new Date(loanInfo.schedule.maturity.asFixed.date.toNumber() * 1000)
: null,
notional: !isInternal ? externalLoanPricing.notional.toBigInt() : null,
}

await asset.updateAssetSpecs(assetSpecs)
Expand Down
19 changes: 19 additions & 0 deletions src/mappings/services/assetPositionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class AssetPositionService extends AssetPosition {
`Selling positions for ${assetId} ` +
`sellingQuantity: ${sellingQuantity.toString(10)} sellingPrice: ${sellingPrice.toString(10)}`
)
if (sellingQuantity <= BigInt(0)) return BigInt(0)
const positions = await this.getByAssetId(assetId)
positions.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf())

Expand Down Expand Up @@ -62,4 +63,22 @@ export class AssetPositionService extends AssetPosition {
await Promise.all(dbUpdates)
return profitFromSale - costOfBuy
}

static async computeUnrealizedProfitAtPrice(assetId: string, sellingPrice: bigint) {
if (!sellingPrice || sellingPrice <= BigInt(0)) return BigInt(0)
logger.info(`Computing unrealizedProfit at price ${sellingPrice} for asset ${assetId}`)
const sellingPositions = await this.getByAssetId(assetId)
const sellingQuantity = sellingPositions.reduce<bigint>(
(result, position) => result + position.holdingQuantity,
BigInt(0)
)
const profitFromSale = nToBigInt(bnToBn(sellingPrice).mul(bnToBn(sellingQuantity)).div(WAD))
const costOfBuy = nToBigInt(
sellingPositions.reduce<BN>(
(totalCost, line) => totalCost.add(bnToBn(line.purchasePrice).mul(bnToBn(line.holdingQuantity).div(WAD))),
new BN(0)
)
)
return profitFromSale - costOfBuy
}
}
13 changes: 13 additions & 0 deletions src/mappings/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class AssetService extends Asset {
asset.presentValue = BigInt(0)
asset.outstandingQuantity = BigInt(0)
asset.currentPrice = BigInt(0)
asset.notional = BigInt(0)
asset.writeOffPercentage = BigInt(0)
asset.totalBorrowed = BigInt(0)
asset.totalRepaid = BigInt(0)
Expand All @@ -56,6 +57,9 @@ export class AssetService extends Asset {
asset.writtenOffAmountByPeriod = BigInt(0)
asset.interestAccruedByPeriod = BigInt(0)

asset.unrealizedProfitAtMarketPrice = BigInt(0)
asset.unrealizedProfitAtNotional = BigInt(0)

return asset
}

Expand Down Expand Up @@ -207,6 +211,14 @@ export class AssetService extends Asset {
`currentPrice: ${latestSettlementPrice.toString(10)} for asset ${this.id}`
)
}

public updateUnrealizedProfit(atMarketPrice: bigint, atNotional: bigint) {
logger.info(
`Updating unrealizedProfit for asset ${this.id} atMarketPrice: ${atMarketPrice} atNotional: ${atNotional}`
)
this.unrealizedProfitAtMarketPrice = atMarketPrice
this.unrealizedProfitAtNotional = atNotional
}
}

interface AssetSpecs {
Expand All @@ -217,6 +229,7 @@ interface AssetSpecs {
discountRate?: bigint
maturityDate?: Date
currentPrice?: bigint
notional?: bigint
}

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

this.sumRealizedProfitFifoByPeriod = BigInt(0)
this.sumUnrealizedProfitAtMarketPrice = BigInt(0)
this.sumUnrealizedProfitAtNotional = BigInt(0)

this.sumBorrowedAmount = BigInt(0)
this.sumRepaidAmount = BigInt(0)
Expand Down Expand Up @@ -402,6 +405,18 @@ export class PoolService extends Pool {
logger.info(`Increasing umRealizedProfitFifoByPeriod for pool ${this.id} by ${amount.toString(10)}`)
this.sumRealizedProfitFifoByPeriod += amount
}

public resetUnrealizedProfit() {
logger.info(`Resetting unrealizedProfit for pool ${this.id}`)
this.sumUnrealizedProfitAtMarketPrice = BigInt(0)
this.sumUnrealizedProfitAtNotional = BigInt(0)
}

public increaseUnrealizedProfit(atMarket: bigint, atNotional: bigint) {
logger.info(`Increasing unrealizedProfit for pool ${this.id} atMarket: ${atMarket} notional: ${atNotional}`)
this.sumUnrealizedProfitAtMarketPrice += atMarket
this.sumUnrealizedProfitAtNotional += atNotional
}
}

export interface ActiveLoanData {
Expand Down
Loading