Skip to content

Commit

Permalink
feat: add orders report (#51)
Browse files Browse the repository at this point in the history
* Add orders report

* Clean up

* [bot] New pkg version: 0.0.0-alpha.6

* Fix tests

* Add asset time series report

* Fix error handling in balance sheet report

* Add missing fields to tokenPrice report

* Add name to assetList report

* Add trancheId to investorList

* Add fromAsset and toAsset in AssetTransaction report

* Fix failing test

---------

Co-authored-by: GitHub Actions <actions@github.com>
  • Loading branch information
sophialittlejohn and actions-user authored Jan 27, 2025
1 parent 1e4b291 commit da1e121
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 104 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@centrifuge/sdk",
"version": "0.0.0-alpha.5",
"version": "0.0.0-alpha.6",
"description": "",
"homepage": "https://github.com/centrifuge/sdk/tree/main#readme",
"author": "",
Expand Down
6 changes: 4 additions & 2 deletions src/IndexerQueries/assetSnapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type AssetSnapshot = {
advanceRate: Rate | undefined
assetId: string
collateralValue: Currency | undefined
currentPrice: Currency | undefined
currentPrice: Currency
discountRate: Rate | undefined
faceValue: Currency | undefined
lossGivenDefault: Rate | undefined
Expand Down Expand Up @@ -81,7 +81,9 @@ export const assetSnapshotsPostProcess = (data: SubqueryAssetSnapshots): AssetSn
collateralValue: tx.asset?.collateralValue
? new Currency(tx.asset?.collateralValue, currencyDecimals)
: undefined,
currentPrice: tx.currentPrice ? new Currency(tx.currentPrice, currencyDecimals).mul(10n ** 18n) : undefined,
currentPrice: tx.currentPrice
? new Currency(tx.currentPrice, currencyDecimals).mul(10n ** 18n)
: new Currency(0n, currencyDecimals),
discountRate: tx.asset.discountRate ? new Rate(tx.asset.discountRate) : undefined,
faceValue:
tx.asset.notional && tx.outstandingQuantity
Expand Down
2 changes: 2 additions & 0 deletions src/IndexerQueries/assetTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ export type AssetTransaction = {
id: string
metadata: string
type: AssetType
name: string
}
toAsset?: {
id: string
metadata: string
type: AssetType
name: string
}
}

Expand Down
95 changes: 95 additions & 0 deletions src/IndexerQueries/epochs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Currency, Price } from '../utils/BigInt.js'

export type EpochFilter = Partial<Record<keyof SubqueryEpochs['epoches']['nodes'][0], any>>

export type Epoch = {
epochId: string
closedAt: string
paidFees: Currency
tokenPrice: Price
sumOutstandingInvestOrders: Currency
sumFulfilledInvestOrders: Currency
sumOutstandingRedeemOrders: Currency
sumFulfilledRedeemOrders: Currency
netAssetValue: Currency
}

type SubqueryEpochs = {
epoches: {
nodes: {
id: string
sumPoolFeesPaidAmount: string
closedAt: string
epochStates: {
nodes: {
tokenPrice: string
sumOutstandingInvestOrders: string
sumFulfilledInvestOrders: string
sumOutstandingRedeemOrders: string
sumFulfilledRedeemOrders: string
}[]
}
pool: {
currency: {
decimals: number
}
}
poolSnapshots: {
nodes: {
netAssetValue: string
}[]
}
}[]
}
}

export const epochsPostProcess = (data: SubqueryEpochs): Epoch[] => {
return data.epoches.nodes.map((order) => {
const index = order.epochStates.nodes.length > 1 ? order.epochStates.nodes.length - 1 : 0
const epochStates = order.epochStates.nodes[index]
const currencyDecimals = order.pool.currency.decimals
return {
epochId: order.id,
closedAt: order.closedAt,
paidFees: new Currency(order.sumPoolFeesPaidAmount, currencyDecimals),
tokenPrice: new Price(epochStates?.tokenPrice ?? '0'),
sumOutstandingInvestOrders: new Currency(epochStates?.sumOutstandingInvestOrders ?? '0', currencyDecimals),
sumFulfilledInvestOrders: new Currency(epochStates?.sumFulfilledInvestOrders ?? '0', currencyDecimals),
sumOutstandingRedeemOrders: new Currency(epochStates?.sumOutstandingRedeemOrders ?? '0', currencyDecimals),
sumFulfilledRedeemOrders: new Currency(epochStates?.sumFulfilledRedeemOrders ?? '0', currencyDecimals),
netAssetValue: new Currency(order.poolSnapshots.nodes[index]?.netAssetValue ?? '0', currencyDecimals),
} satisfies Epoch
})
}

export const epochsQuery = `
query($filter: EpochFilter) {
epoches(filter: $filter) {
nodes {
poolId
id
sumPoolFeesPaidAmount
closedAt
epochStates {
nodes {
tokenPrice
sumOutstandingInvestOrders
sumFulfilledInvestOrders
sumOutstandingRedeemOrders
sumFulfilledRedeemOrders
}
}
pool {
currency {
decimals
}
}
poolSnapshots {
nodes {
netAssetValue
}
}
}
}
}
`
6 changes: 5 additions & 1 deletion src/IndexerQueries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { AssetSnapshotFilter, assetSnapshotsPostProcess, assetSnapshotsQuery } f
import {
trancheCurrencyBalancePostProcessor,
trancheCurrencyBalanceQuery,
TrancheCurrencyBalanceFilter,
TrancheBalanceFilter,
CurrencyBalanceFilter,
} from './trancheCurrencyBalance.js'
import { EpochFilter, epochsPostProcess, epochsQuery } from './epochs.js'

export class IndexerQueries extends Entity {
constructor(
Expand All @@ -44,6 +44,10 @@ export class IndexerQueries extends Entity {
return this._root._queryIndexer(trancheSnapshotsQuery, { filter }, trancheSnapshotsPostProcess)
}

poolEpochsQuery(filter?: EpochFilter) {
return this._root._queryIndexer(epochsQuery, { filter }, epochsPostProcess)
}

investorTransactionsQuery(filter?: InvestorTransactionFilter) {
return this._root._queryIndexer(investorTransactionsQuery, { filter }, investorTransactionsPostProcess)
}
Expand Down
42 changes: 0 additions & 42 deletions src/IndexerQueries/trancheCurrencyBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,48 +95,6 @@ export type SubqueryCurrencyBalances = {
}
}

export const trancheBalancesQuery = `
query($filter: TrancheBalanceFilter) {
trancheBalances(filter: $filter) {
nodes {
accountId
trancheId
account {
chainId
evmAddress
}
pool {
currency {
decimals
}
}
pendingInvestCurrency
claimableTrancheTokens
sumClaimedTrancheTokens
pendingRedeemTrancheTokens
claimableCurrency
sumClaimedCurrency
}
}
}`

export const currencyBalancesQuery = `
query($filter: CurrencyBalanceFilter) {
currencyBalances(filter: $filter) {
nodes {
accountId
account {
chainId
evmAddress
}
currency {
trancheId
}
amount
}
}
}`

export const trancheCurrencyBalanceQuery = `
query($filterTranches: TrancheBalanceFilter, $filterCurrencies: CurrencyBalanceFilter) {
trancheBalances(filter: $filterTranches) {
Expand Down
83 changes: 75 additions & 8 deletions src/Reports/Processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { mockInvestorTransactions } from '../tests/mocks/mockInvestorTransaction
import { mockAssetTransactions } from '../tests/mocks/mockAssetTransactions.js'
import { mockAssetSnapshots } from '../tests/mocks/mockAssetSnapshots.js'
import { mockInvestorCurrencyBalances } from '../tests/mocks/mockInvestorCurrencyBalance.js'
import { mockEpochs } from '../tests/mocks/mockEpochs.js'
import { PoolSnapshot } from '../IndexerQueries/poolSnapshots.js'
import { Currency, Price, Token } from '../utils/BigInt.js'
import { PoolFeeSnapshot, PoolFeeSnapshotsByDate } from '../IndexerQueries/poolFeeSnapshots.js'
Expand All @@ -30,14 +31,6 @@ describe('Processor', () => {
})
).to.deep.equal([])
})
it('should throw error when no tranches found', () => {
expect(() =>
processor.balanceSheet({
poolSnapshots: mockPoolSnapshots,
trancheSnapshots: {},
})
).to.throw('No tranches found for snapshot')
})
it('should process pool and tranche data correctly', () => {
const result = processor.balanceSheet({
poolSnapshots: mockPoolSnapshots,
Expand Down Expand Up @@ -542,6 +535,19 @@ describe('Processor', () => {
expect(result).to.have.lengthOf(expected)
}
})
it('should make sure all the fields exist', () => {
const result = processor.assetTransactions({ assetTransactions: mockAssetTransactions })
expect(result).to.have.lengthOf(3)
expect(result[0]).to.have.property('type')
expect(result[0]).to.have.property('timestamp')
expect(result[0]).to.have.property('assetId')
expect(result[0]).to.have.property('transactionType')
expect(result[0]).to.have.property('amount')
expect(result[0]).to.have.property('epoch')
expect(result[0]).to.have.property('transactionHash')
expect(result[0]).to.have.property('fromAsset') // optional
expect(result[1]).to.have.property('toAsset') // optional
})
})

describe('fee transactions processor', () => {
Expand Down Expand Up @@ -593,6 +599,22 @@ describe('Processor', () => {
const result = processor.tokenPrice({ trancheSnapshots: mockTrancheSnapshots }, { groupBy: 'month' })
expect(result).to.have.lengthOf(1)
})
it('should make sure all fields are present', () => {
const result = processor.tokenPrice({ trancheSnapshots: mockTrancheSnapshots }, { groupBy: 'day' })
expect(result).to.have.lengthOf(2)
expect(result[0]).to.have.property('tranches')
expect(result[0]).to.have.property('timestamp')
expect(result[0]?.tranches[0]).to.have.property('id')
expect(result[0]?.tranches[0]).to.have.property('yieldMTD')
expect(result[0]?.tranches[0]).to.have.property('yieldQTD')
expect(result[0]?.tranches[0]).to.have.property('yieldYTD')
expect(result[0]?.tranches[0]).to.have.property('yield7daysAnnualized')
expect(result[0]?.tranches[0]).to.have.property('yield30daysAnnualized')
expect(result[0]?.tranches[0]).to.have.property('yield90daysAnnualized')
expect(result[0]?.tranches[0]).to.have.property('timestamp')
expect(result[0]?.tranches[0]).to.have.property('supply')
expect(result[0]?.tranches[0]).to.have.property('price')
})
})

describe('asset list processor', () => {
Expand Down Expand Up @@ -647,6 +669,7 @@ describe('Processor', () => {
expect(result?.[0]).to.have.property('probabilityOfDefault')
expect(result?.[0]).to.have.property('lossGivenDefault')
expect(result?.[0]).to.have.property('discountRate')
expect(result?.[0]).to.have.property('name')
})
it('should return the correct data for public credit pools', () => {
const result = processor.assetList(
Expand All @@ -665,6 +688,7 @@ describe('Processor', () => {
expect(result?.[0]).to.have.property('currentPrice')
expect(result?.[0]).to.have.property('unrealizedProfit')
expect(result?.[0]).to.have.property('realizedProfit')
expect(result?.[0]).to.have.property('name')
})
})

Expand Down Expand Up @@ -702,6 +726,49 @@ describe('Processor', () => {
)
expect(result2).to.have.lengthOf(0)
})
it('should make sure all the fields exist', () => {
const result = processor.investorList({ trancheCurrencyBalance: mockInvestorCurrencyBalances })
expect(result).to.have.lengthOf(2)
expect(result[0]).to.have.property('type')
expect(result[0]).to.have.property('chainId')
expect(result[0]).to.have.property('accountId')
expect(result[0]).to.have.property('evmAddress')
expect(result[0]).to.have.property('position')
expect(result[0]).to.have.property('poolPercentage')
expect(result[0]).to.have.property('pendingInvest')
expect(result[0]).to.have.property('pendingRedeem')
expect(result[0]).to.have.property('trancheId')
})
})

describe('orders list processor', () => {
it('should return empty array when no epochs found', () => {
expect(processor.ordersList({ poolEpochs: [] })).to.deep.equal([])
})
it('should process orders list correctly', () => {
const result = processor.ordersList({ poolEpochs: mockEpochs })
expect(result).to.have.lengthOf(2)
expect(result[0]?.epoch).to.equal('1')
expect(result[0]?.netAssetValue.toString()).to.equal(Currency.fromFloat(1000, 6).toString())
expect(result[0]?.navPerShare.toString()).to.equal(new Price(1000000000000000000n).toString())
expect(result[0]?.lockedInvestments.toString()).to.equal(Currency.fromFloat(1000, 6).toString())
expect(result[0]?.lockedRedemptions.toString()).to.equal(Currency.fromFloat(100, 6).toString())
expect(result[0]?.executedInvestments.toString()).to.equal(Currency.fromFloat(900, 6).toString())
expect(result[0]?.executedRedemptions.toString()).to.equal(Currency.fromFloat(90, 6).toString())
expect(result[0]?.paidFees.toString()).to.equal(Currency.fromFloat(100, 6).toString())
})
})

describe('asset time series processor', () => {
it('should return empty array when no snapshots found', () => {
expect(processor.assetTimeSeries({ assetSnapshots: [] })).to.deep.equal([])
})
it('should process asset time series correctly', () => {
const result = processor.assetTimeSeries({ assetSnapshots: mockAssetSnapshots })
expect(result).to.have.lengthOf(2)
expect(result[0]?.assetId).to.equal('1')
expect(result[0]?.currentPrice.toString()).to.equal(Currency.fromFloat(1, 6).toString())
})
})

describe('applyGrouping', () => {
Expand Down
Loading

0 comments on commit da1e121

Please sign in to comment.