Skip to content

Commit

Permalink
feat: Data reports (#23)
Browse files Browse the repository at this point in the history
* Refactor processing and fix caching and fix tests

* Clean up processor and add tests and mocks

* Clean up types

* Add cashflow report and tests

* Remove defer and use querywith poolid

* Refactor tests and add comments

* Add query for metadata and utils around it

* Add data for pool fee snapshots and use in cashflow statments

* Group tranche data by id to prevent double processing

* fix zero case in fromFloat wrapper

* Prevent fees and endCashBalance from being aggregated

* Add fee snapshot mocks and improve fee testing

* Add a few more fee test

* Differentiate public and private credit for cashflows

* Remove identifying info

* Fix data aggregation and improve test coverage with real data

* Add code coverage support

* Add coverage summary

* Exclude types from coverage

* And P&L report and processor tests

* Add tests for applyGrouping

* Add readme for reports

* Add test to applyGrouping

* Fix test

* Fix BigInt tests

* Clean up metadata fetching

* Remove cache time

* Remove filtering in keys

* Fix import so that decimal.js-light works in browser and test env

* Update README.md

Co-authored-by: Jeroen <1748621+hieronx@users.noreply.github.com>

* Update README.md

Co-authored-by: Jeroen <1748621+hieronx@users.noreply.github.com>

* Use purchases and financings instead of acquisistions

* Remove coverage in favor of nyc in ci PR

* Remove c8 config

* Restore sinon stub to fix error

* Add fix to P&L report

* Add investor tx data and report

* Provide access to P&L

* Add tests

* Add filters and tests to investor transaction report

* Add more test cases

* Test investor report generation

* Optimize processing

* Add asset transaction report

* Fix typo

* Rename so it doesn't appear as main README in repo

* Fix asset transaction filtering

* Add fee transactions report

* Make filtering more efficient

* Add token price report

* Move types for reports so they're excluded from coverage

* Convert date to UTC to prevent timezone bugs in grouping

* Fix filtering by type for fee transactions

* Improve test coverage and fix some filtering bugs

* Remove log and comment

* More test coverage improvements

* Move indexer querying into separate class

* Add asset list report

* Add tests for asset list

* Add investor list report

* ci: fix issue with pr_number check

* ci: fix release script logic

* ci: debugging pr-check script

* ci: final fix for PR checks on release events

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

* ci: final cleanup

---------

Co-authored-by: Jeroen <1748621+hieronx@users.noreply.github.com>
Co-authored-by: Guillermo Perez <gpmayorga@users.noreply.github.com>
Co-authored-by: GitHub Actions <actions@github.com>
  • Loading branch information
4 people authored Jan 8, 2025
1 parent a9f0527 commit 7d2f6c0
Show file tree
Hide file tree
Showing 26 changed files with 2,354 additions and 168 deletions.
2 changes: 0 additions & 2 deletions .github/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ This workflow is responsible for versioning and preparing releases based on pull

- **Alpha Versioning**: When a PR includes the `alpha` label, versions are created with an `-alpha.N` suffix (e.g., `1.2.3-alpha.1`, `1.2.3-alpha.2`). This continues until the alpha label is removed, at which point the version returns to standard semantic versioning.


### 2. `build-test-report.yml`

This workflow handles building, testing, and reporting.
Expand Down Expand Up @@ -56,4 +55,3 @@ This workflow handles the publishing of releases to NPM.
2. **Draft Release Creation**: The `create-release.yml` workflow creates a draft release if `package.json` contains a new version on the `main` branch.

3. **Publishing**: When a release is published on GitHub (moved from draft to released), the `publish-release.yml` workflow is triggered. It builds the project and publishes it to NPM, ensuring the package is available for public use.

161 changes: 161 additions & 0 deletions src/IndexerQueries/assetSnapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Currency, Rate } from '../utils/BigInt.js'

export type AssetSnapshotFilter = Partial<Record<keyof SubqueryAssetSnapshots['assetSnapshots']['nodes'][0], any>>

export type AssetSnapshot = {
actualMaturityDate: string | undefined
actualOriginationDate: number | undefined
advanceRate: Rate | undefined
assetId: string
collateralValue: Currency | undefined
currentPrice: Currency | undefined
discountRate: Rate | undefined
faceValue: Currency | undefined
lossGivenDefault: Rate | undefined
name: string
outstandingDebt: Currency | undefined
outstandingInterest: Currency | undefined
outstandingPrincipal: Currency | undefined
outstandingQuantity: Currency | undefined
presentValue: Currency | undefined
probabilityOfDefault: Rate | undefined
status: string
sumRealizedProfitFifo: Currency | undefined
timestamp: string
totalRepaidInterest: Currency | undefined
totalRepaidPrincipal: Currency | undefined
totalRepaidUnscheduled: Currency | undefined
unrealizedProfitAtMarketPrice: Currency | undefined
valuationMethod: string | undefined
}

export type SubqueryAssetSnapshots = {
assetSnapshots: {
nodes: {
asset: {
pool: {
currency: {
decimals: number
}
}
actualOriginationDate: number
advanceRate: string | undefined
collateralValue: string | undefined
discountRate: string | undefined
id: string
lossGivenDefault: string | undefined
actualMaturityDate: string | undefined
name: string
probabilityOfDefault: string | undefined
status: string
sumRealizedProfitFifo: string | undefined
unrealizedProfitAtMarketPrice: string | undefined
valuationMethod: string
notional: string | undefined
}
timestamp: string
assetId: string
presentValue: string | undefined
currentPrice: string | undefined
outstandingPrincipal: string | undefined
outstandingInterest: string | undefined
outstandingDebt: string | undefined
outstandingQuantity: string | undefined
totalRepaidPrincipal: string | undefined
totalRepaidInterest: string | undefined
totalRepaidUnscheduled: string | undefined
}[]
}
}

export const assetSnapshotsPostProcess = (data: SubqueryAssetSnapshots): AssetSnapshot[] => {
return data!.assetSnapshots.nodes.map((tx) => {
const currencyDecimals = tx.asset.pool.currency.decimals
return {
...tx,
timestamp: tx.timestamp,
assetId: tx.assetId,
actualMaturityDate: tx.asset.actualMaturityDate || undefined,
actualOriginationDate: tx.asset.actualOriginationDate || undefined,
advanceRate: new Rate(tx.asset.advanceRate || '0'),
collateralValue: tx.asset?.collateralValue
? new Currency(tx.asset?.collateralValue, currencyDecimals)
: undefined,
currentPrice: tx.currentPrice ? new Currency(tx.currentPrice, currencyDecimals).mul(10n ** 18n) : undefined,
discountRate: tx.asset.discountRate ? new Rate(tx.asset.discountRate) : undefined,
faceValue:
tx.asset.notional && tx.outstandingQuantity
? new Currency(tx.asset.notional, currencyDecimals).mul(BigInt(tx.outstandingQuantity))
: undefined,
lossGivenDefault: tx.asset.lossGivenDefault ? new Rate(tx.asset.lossGivenDefault) : undefined,
name: tx.asset.name,
outstandingDebt: tx.outstandingDebt ? new Currency(tx.outstandingDebt, currencyDecimals) : undefined,
outstandingInterest: tx.outstandingInterest ? new Currency(tx.outstandingInterest, currencyDecimals) : undefined,
outstandingPrincipal: tx.outstandingPrincipal
? new Currency(tx.outstandingPrincipal, currencyDecimals)
: undefined,
outstandingQuantity: tx.outstandingQuantity ? new Currency(tx.outstandingQuantity, 18) : undefined,
presentValue: tx.presentValue ? new Currency(tx.presentValue, currencyDecimals) : undefined,
probabilityOfDefault: tx.asset.probabilityOfDefault ? new Rate(tx.asset.probabilityOfDefault) : undefined,
status: tx.asset.status,
sumRealizedProfitFifo: tx.asset.sumRealizedProfitFifo
? new Currency(tx.asset.sumRealizedProfitFifo, currencyDecimals)
: undefined,
totalRepaidInterest: tx.totalRepaidInterest ? new Currency(tx.totalRepaidInterest, currencyDecimals) : undefined,
totalRepaidPrincipal: tx.totalRepaidPrincipal
? new Currency(tx.totalRepaidPrincipal, currencyDecimals)
: undefined,
totalRepaidUnscheduled: tx.totalRepaidUnscheduled
? new Currency(tx.totalRepaidUnscheduled, currencyDecimals)
: undefined,
unrealizedProfitAtMarketPrice: tx.asset.unrealizedProfitAtMarketPrice
? new Currency(tx.asset.unrealizedProfitAtMarketPrice, currencyDecimals)
: undefined,
valuationMethod: tx.asset.valuationMethod,
}
}) satisfies AssetSnapshot[]
}

export const assetSnapshotsQuery = `
query($filter: AssetSnapshotFilter) {
assetSnapshots(
first: 1000,
orderBy: TIMESTAMP_ASC,
filter: $filter
) {
nodes {
assetId
timestamp
totalRepaidUnscheduled
outstandingInterest
totalRepaidInterest
currentPrice
outstandingPrincipal
totalRepaidPrincipal
outstandingQuantity
presentValue
outstandingDebt
asset {
pool {
currency {
decimals
}
}
actualMaturityDate
actualOriginationDate
advanceRate
collateralValue
discountRate
lossGivenDefault
name
notional
probabilityOfDefault
status
sumRealizedProfitFifo
unrealizedProfitAtMarketPrice
valuationMethod
}
}
}
}
`
174 changes: 174 additions & 0 deletions src/IndexerQueries/assetTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Currency, Price } from '../utils/BigInt.js'

export type AssetTransactionFilter = Partial<
Record<keyof SubqueryAssetTransactions['assetTransactions']['nodes'][0], any>
>

export type AssetTransaction = {
id: string
timestamp: Date
poolId: string
accountId: string
epochId: string
type: AssetTransactionType
amount: Currency
settlementPrice: Price | null
quantity: string | null
principalAmount: Currency | undefined
interestAmount: Currency | undefined
hash: string
realizedProfitFifo: Currency | undefined
unrealizedProfitAtMarketPrice: Currency | undefined
asset: {
id: string
metadata: string
type: AssetType
currentPrice: string | null
}
fromAsset?: {
id: string
metadata: string
type: AssetType
}
toAsset?: {
id: string
metadata: string
type: AssetType
}
}

export enum AssetType {
OnchainCash = 'OnchainCash',
OffchainCash = 'OffchainCash',
Other = 'Other',
}

export type AssetTransactionType =
| 'CREATED'
| 'PRICED'
| 'BORROWED'
| 'REPAID'
| 'CLOSED'
| 'CASH_TRANSFER'
| 'DEPOSIT_FROM_INVESTMENTS'
| 'WITHDRAWAL_FOR_REDEMPTIONS'
| 'WITHDRAWAL_FOR_FEES'
| 'INCREASE_DEBT'
| 'DECREASE_DEBT'

type SubqueryAssetTransactions = {
assetTransactions: {
nodes: {
__typename?: 'AssetTransaction'
id: string
timestamp: string
poolId: string
accountId: string
hash: string
epochId: string
type: AssetTransactionType
amount: string | undefined
principalAmount: string | undefined
interestAmount: string | undefined
settlementPrice: string | null
quantity: string | null
realizedProfitFifo: string | undefined
pool: {
currency: {
decimals: number
}
}
asset: {
id: string
metadata: string
name: string
type: AssetType
sumRealizedProfitFifo: string
unrealizedProfitAtMarketPrice: string
currentPrice: string
}
fromAsset?: {
id: string
metadata: string
name: string
type: AssetType
}
toAsset?: {
id: string
metadata: string
name: string
type: AssetType
}
}[]
}
}

export const assetTransactionsPostProcess = (data: SubqueryAssetTransactions): AssetTransaction[] => {
return (
data.assetTransactions.nodes.map((tx) => {
const decimals = tx.pool.currency.decimals
return {
...tx,
settlementPrice: tx.settlementPrice ? new Price(tx.settlementPrice) : null,
amount: new Currency(tx?.amount ?? 0n, decimals),
principalAmount: tx.principalAmount ? new Currency(tx.principalAmount, decimals) : undefined,
interestAmount: tx.interestAmount ? new Currency(tx.interestAmount, decimals) : undefined,
realizedProfitFifo: tx.realizedProfitFifo ? new Currency(tx.realizedProfitFifo, decimals) : undefined,
sumRealizedProfitFifo: tx.asset.sumRealizedProfitFifo
? new Currency(tx.asset.sumRealizedProfitFifo, decimals)
: undefined,
unrealizedProfitAtMarketPrice: tx.asset.unrealizedProfitAtMarketPrice
? new Currency(tx.asset.unrealizedProfitAtMarketPrice, decimals)
: undefined,
timestamp: new Date(`${tx.timestamp}+00:00`),
}
}) || ([] satisfies AssetTransaction[])
)
}

export const assetTransactionsQuery = `
query($filter: AssetTransactionFilter) {
assetTransactions(
orderBy: TIMESTAMP_ASC,
filter: $filter
) {
nodes {
principalAmount
interestAmount
epochId
type
timestamp
amount
settlementPrice
quantity
hash
realizedProfitFifo
pool {
currency {
decimals
}
}
asset {
id
metadata
name
type
sumRealizedProfitFifo
unrealizedProfitAtMarketPrice
}
fromAsset {
id
metadata
name
type
}
toAsset {
id
metadata
name
type
}
}
}
}
`
Loading

0 comments on commit 7d2f6c0

Please sign in to comment.