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: update prices #199

Merged
merged 35 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
98b6a7f
feat: update prices
hieronx Jun 11, 2024
9983b93
fix: add spec version checks
hieronx Jun 11, 2024
0d9d433
fix: non-null filtering
hieronx Jun 11, 2024
6703ef3
fix: comments
hieronx Jun 11, 2024
111534a
fix: add explicit saves
hieronx Jun 11, 2024
a324680
chore: add log
hieronx Jun 11, 2024
4d51a3a
fix: dont update
hieronx Jun 11, 2024
1bd2411
chore: cleanup
hieronx Jun 11, 2024
e3cedb9
chore: add logging
hieronx Jun 11, 2024
a04d194
fix: more logs
hieronx Jun 11, 2024
889da66
fix: alternative calculation
hieronx Jun 11, 2024
8f24884
fix: missing schema value
hieronx Jun 11, 2024
c5fb1b9
fix: calculation
hieronx Jun 11, 2024
1ed52ec
chore: more logging
hieronx Jun 11, 2024
83d4d44
fix: lgo
hieronx Jun 11, 2024
c86f1bb
chore: more logging
hieronx Jun 11, 2024
cc6497a
fix: to big int
hieronx Jun 11, 2024
5f27952
fix: missing import
hieronx Jun 11, 2024
fdd53ab
chore: refactor
hieronx Jun 11, 2024
5c147ef
chore: computing of unrealizedProfitByPeriod
filo87 Jun 11, 2024
eed2b4e
feat: use previous price
hieronx Jun 12, 2024
d3f0835
fix: store periodPrice
hieronx Jun 12, 2024
4d5ee5c
fix: math
hieronx Jun 12, 2024
2ca965e
fix: conversions
hieronx Jun 12, 2024
db05b99
fix: divide by wad
hieronx Jun 12, 2024
d362f27
fix: ignore unrealized profit on origination day
hieronx Jun 12, 2024
27dd160
fix: else clause
hieronx Jun 12, 2024
b5b95ec
chore: add log
hieronx Jun 12, 2024
a9b6f1c
chore: add log
hieronx Jun 12, 2024
aa0e9b4
fix: log
hieronx Jun 12, 2024
7ae344d
fix: check previous price
hieronx Jun 12, 2024
e8dfdf2
fix: init
hieronx Jun 12, 2024
5455f73
chore: refactor to use asset snapshots
filo87 Jun 12, 2024
e607cfd
chore: remove period price
filo87 Jun 12, 2024
3597a31
chore: remove periodPrice
filo87 Jun 12, 2024
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
1 change: 1 addition & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ type AssetSnapshot @entity {

unrealizedProfitAtMarketPrice: BigInt
unrealizedProfitAtNotional: BigInt
unrealizedProfitByPeriod: BigInt
}

type AssetPosition @entity {
Expand Down
5 changes: 2 additions & 3 deletions src/mappings/handlers/blockHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,11 @@ async function _handleBlock(block: SubstrateBlock): Promise<void> {
pool.resetUnrealizedProfit()
for (const loanId in activeLoanData) {
const asset = await AssetService.getById(pool.id, loanId)
const previousPrice = asset.currentPrice
await asset.loadSnapshot(lastPeriodStart)
await asset.updateActiveAssetData(activeLoanData[loanId])
await asset.updateUnrealizedProfit(
await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.currentPrice),
await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.notional),
await AssetPositionService.computeUnrealizedProfitByPeriod(asset.id, asset.currentPrice, previousPrice)
await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.notional)
)
await asset.save()
await pool.increaseInterestAccrued(asset.interestAccruedByPeriod)
Expand Down
20 changes: 19 additions & 1 deletion src/mappings/handlers/loansHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {
export const handleLoanBorrowed = errorHandler(_handleLoanBorrowed)
async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedEvent>): Promise<void> {
const [poolId, loanId, borrowAmount] = event.event.data
const specVersion = api.runtimeVersion.specVersion.toNumber()

const pool = await PoolService.getById(poolId.toString())
if (!pool) throw missingPool
Expand Down Expand Up @@ -144,6 +145,9 @@ async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedEvent>): Pr
await asset.borrow(amount)

if (borrowAmount.isExternal) {
// Prices were based on the settlement prices only until spec version 1025
if (specVersion < 1025) await asset.updateCurrentPrice(borrowAmount.asExternal.settlementPrice.toBigInt())

await asset.increaseQuantity(borrowAmount.asExternal.quantity.toBigInt())
await AssetPositionService.buy(
asset.id,
Expand Down Expand Up @@ -174,6 +178,7 @@ async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedEvent>): Pr
export const handleLoanRepaid = errorHandler(_handleLoanRepaid)
async function _handleLoanRepaid(event: SubstrateEvent<LoanRepaidEvent>) {
const [poolId, loanId, { principal, interest, unscheduled }] = event.event.data
const specVersion = api.runtimeVersion.specVersion.toNumber()

const pool = await PoolService.getById(poolId.toString())
if (!pool) throw missingPool
Expand Down Expand Up @@ -218,6 +223,10 @@ async function _handleLoanRepaid(event: SubstrateEvent<LoanRepaidEvent>) {
let realizedProfitFifo: bigint
if (principal.isExternal) {
const { quantity, settlementPrice } = principal.asExternal

// Prices were based on the settlement prices only until spec version 1025
if (specVersion < 1025) await asset.updateCurrentPrice(settlementPrice.toBigInt())

await asset.decreaseQuantity(quantity.toBigInt())
realizedProfitFifo = await AssetPositionService.sellFifo(
asset.id,
Expand Down Expand Up @@ -297,7 +306,8 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
if (!pool) throw missingPool

const repaidPrincipalAmount = AssetService.extractPrincipalAmount(_repaidAmount.principal)
const repaidInterestAmount = specVersion < 1029 ? BigInt(0) : _repaidAmount.interest.toBigInt()
// Interest amount until spec version 1100 is off
const repaidInterestAmount = specVersion < 1100 ? BigInt(0) : _repaidAmount.interest.toBigInt()
const repaidUnscheduledAmount = _repaidAmount.unscheduled.toBigInt()
const repaidAmount = repaidPrincipalAmount + repaidInterestAmount + repaidUnscheduledAmount

Expand Down Expand Up @@ -334,6 +344,10 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
let realizedProfitFifo: bigint
if (_repaidAmount.principal.isExternal) {
const { quantity, settlementPrice } = _repaidAmount.principal.asExternal

// Prices were based on the settlement prices only until spec version 1025
if (specVersion < 1025) await fromAsset.updateCurrentPrice(settlementPrice.toBigInt())

await fromAsset.decreaseQuantity(quantity.toBigInt())
realizedProfitFifo = await AssetPositionService.sellFifo(
fromAsset.id,
Expand Down Expand Up @@ -376,6 +390,10 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent<LoanDebtTransfer
await toAsset.borrow(borrowPrincipalAmount)
if (_borrowAmount.isExternal) {
const { quantity, settlementPrice } = _borrowAmount.asExternal

// Prices were based on the settlement prices only until spec version 1025
if (specVersion < 1025) await toAsset.updateCurrentPrice(settlementPrice.toBigInt())

await toAsset.increaseQuantity(quantity.toBigInt())
await AssetPositionService.buy(
toAsset.id,
Expand Down
12 changes: 0 additions & 12 deletions src/mappings/services/assetPositionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,4 @@ export class AssetPositionService extends AssetPosition {
)
return profitFromSale - costOfBuy
}

static async computeUnrealizedProfitByPeriod(assetId: string, sellingPrice: bigint, purchasePrice: bigint) {
if (!sellingPrice || sellingPrice <= BigInt(0)) return BigInt(0)
if (!purchasePrice || purchasePrice <= 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)
)
return nToBigInt(bnToBn(sellingPrice).sub(bnToBn(purchasePrice)).mul(bnToBn(sellingQuantity)).div(WAD))
}
}
50 changes: 41 additions & 9 deletions src/mappings/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { Option } from '@polkadot/types'
import { bnToBn, nToBigInt } from '@polkadot/util'
import { WAD } from '../../config'
import { ApiQueryLoansActiveLoans, LoanPricingAmount, NftItemMetadata } from '../../helpers/types'
import { Asset, AssetType, AssetValuationMethod, AssetStatus } from '../../types'
import { Asset, AssetType, AssetValuationMethod, AssetStatus, AssetSnapshot } from '../../types'
import { ActiveLoanData } from './poolService'
import { cid, readIpfs } from '../../helpers/ipfsFetch'

export const ONCHAIN_CASH_ASSET_ID = '0'
export class AssetService extends Asset {
snapshot?: AssetSnapshot

static init(
poolId: string,
assetId: string,
Expand Down Expand Up @@ -119,6 +121,11 @@ export class AssetService extends Asset {
Object.assign(this, decodedAssetSpecs)
}

public updateCurrentPrice(currentPrice: bigint) {
logger.info(`Updating current price for asset ${this.id} to ${currentPrice}`)
this.currentPrice = currentPrice
}

public activate() {
logger.info(`Activating asset ${this.id}`)
this.isActive = true
Expand All @@ -132,14 +139,19 @@ export class AssetService extends Asset {
}

public async updateActiveAssetData(activeAssetData: ActiveLoanData[keyof ActiveLoanData]) {
const oldOutstaidingInterest = this.outstandingInterest
const oldTotalRepaidInterest = this.totalRepaidInterest
// Current price was always 0 until spec version 1025
const specVersion = api.runtimeVersion.specVersion.toNumber()
if (specVersion < 1025) delete activeAssetData.currentPrice

// Set all active asset values
Object.assign(this, activeAssetData)

const deltaRepaidInterestAmount = this.totalRepaid - oldTotalRepaidInterest
this.interestAccruedByPeriod = this.outstandingInterest - oldOutstaidingInterest + deltaRepaidInterestAmount
if(this.snapshot) {
const deltaRepaidInterestAmount = this.totalRepaid - this.snapshot.totalRepaidInterest
this.interestAccruedByPeriod =
this.outstandingInterest - this.snapshot.outstandingInterest + deltaRepaidInterestAmount
logger.info(`Updated outstanding debt for asset: ${this.id} to ${this.outstandingDebt.toString()}`)
}
}

public async updateItemMetadata() {
Expand Down Expand Up @@ -208,20 +220,40 @@ export class AssetService extends Asset {
if (loanData.pricing.isInternal) throw new Error(`Asset ${this.id} is not of type External!`)
const { outstandingQuantity, latestSettlementPrice } = loanData.pricing.asExternal
this.outstandingQuantity = outstandingQuantity.toBigInt()
this.currentPrice = latestSettlementPrice.toBigInt()
this.updateCurrentPrice(latestSettlementPrice.toBigInt())
logger.info(
`Updated outstandingQuantity: ${outstandingQuantity.toString(10)} ` +
`currentPrice: ${latestSettlementPrice.toString(10)} for asset ${this.id}`
)
}

public updateUnrealizedProfit(atMarketPrice: bigint, atNotional: bigint, byPeriod: bigint) {
public updateUnrealizedProfit(atMarketPrice: bigint, atNotional: bigint) {
logger.info(
`Updating unrealizedProfit for asset ${this.id} atMarketPrice: ${atMarketPrice} atNotional: ${atNotional}`
`Updating unrealizedProfit for asset ${this.id} with atMarketPrice: ${atMarketPrice}, atNotional: ${atNotional}`
)
this.unrealizedProfitAtMarketPrice = atMarketPrice
this.unrealizedProfitAtNotional = atNotional
this.unrealizedProfitByPeriod = byPeriod
if (!!this.snapshot && this.snapshot.outstandingQuantity > 0 && this.snapshot.currentPrice > 0) {
logger.info(`byPeriod: ${this.outstandingQuantity} x (${this.currentPrice} - ${this.snapshot.currentPrice})`)
this.unrealizedProfitByPeriod = nToBigInt(
bnToBn(this.outstandingQuantity)
.mul(bnToBn(this.currentPrice - this.snapshot.currentPrice))
.div(WAD)
)
logger.info(`byPeriod: ${this.unrealizedProfitByPeriod}`)
}
}

public async loadSnapshot(periodStart: Date) {
const snapshots = await AssetSnapshot.getByFields([
['assetId', '=', this.id],
['periodStart', '=', periodStart],
])
if (snapshots.length !== 1) {
logger.warn(`Unable to load snapshot for asset ${this.id} for period ${periodStart.toISOString()}`)
return
}
this.snapshot = snapshots.pop()
}
}

Expand Down
Loading