Skip to content

Commit

Permalink
Add PnL util
Browse files Browse the repository at this point in the history
  • Loading branch information
prevostc committed Mar 23, 2024
1 parent fd13dc9 commit 45b62a6
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 39 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"codegen": "rm -Rf generated && graph codegen",
"build": "graph build",
"format": "prettier . --write",
"test": "yarn run --silent test:lint && yarn run --silent test:graph",
"test:graph": "graph test",
"test": "yarn run --silent test:lint && yarn run --silent test:unit",
"test:unit": "graph test",
"test:lint": "prettier . --check",
"infra:start": "docker compose up -d",
"infra:stop": "docker compose down",
Expand Down
43 changes: 9 additions & 34 deletions src/mapping/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,41 +112,16 @@ export function getNativePriceUSD(): BigDecimal {
}

class PriceRange {
_min: BigDecimal
_max: BigDecimal
constructor(min: BigDecimal, max: BigDecimal) {
this._min = min
this._max = max
}

get min(): BigDecimal {
return this._min
}

get max(): BigDecimal {
return this._max
}
constructor(
public min: BigDecimal,
public max: BigDecimal,
) {}
}

class VaultPrices {
_token0ToNative: BigDecimal
_token1ToNative: BigDecimal
_nativeToUsd: BigDecimal
constructor(_token0ToNative: BigDecimal, _token1ToNative: BigDecimal, _nativeToUsd: BigDecimal) {
this._token0ToNative = _token0ToNative
this._token1ToNative = _token1ToNative
this._nativeToUsd = _nativeToUsd
}

get token0ToNative(): BigDecimal {
return this._token0ToNative
}

get token1ToNative(): BigDecimal {
return this._token1ToNative
}

get nativeToUsd(): BigDecimal {
return this._nativeToUsd
}
constructor(
public token0ToNative: BigDecimal,
public token1ToNative: BigDecimal,
public nativeToUsd: BigDecimal,
) {}
}
6 changes: 6 additions & 0 deletions src/utils/decimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,9 @@ function safeDiv(amount0: BigDecimal, amount1: BigDecimal): BigDecimal {
return amount0.div(amount1)
}
}


@inline
export function bigMin(a: BigDecimal, b: BigDecimal): BigDecimal {
return a.lt(b) ? a : b
}
129 changes: 129 additions & 0 deletions src/utils/pnl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { BigDecimal } from "@graphprotocol/graph-ts"
import { ZERO_BD, bigMin } from "./decimal"

class PnLStateEntry {
constructor(
public boughtShares: BigDecimal,
public remainingShares: BigDecimal,
public entryPrice: BigDecimal,
) {}
}

export class PnLState {
constructor(
public realizedPnl: BigDecimal,
public sharesFifo: Array<PnLStateEntry>,
) {}

serialize(): Array<BigDecimal> {
let res = new Array<BigDecimal>()
res.push(this.realizedPnl)
for (let idx = 0; idx < this.sharesFifo.length; idx++) {
let entry = this.sharesFifo[idx]
res.push(entry.boughtShares)
res.push(entry.remainingShares)
res.push(entry.entryPrice)
}

return res
}

static deserialize(data: Array<BigDecimal>): PnLState {
let realizedPnl = data.shift() as BigDecimal
let sharesFifo = new Array<PnLStateEntry>()
while (data.length > 0) {
let boughtShares = data.shift() as BigDecimal
let remainingShares = data.shift() as BigDecimal
let entryPrice = data.shift() as BigDecimal
sharesFifo.push(new PnLStateEntry(boughtShares, remainingShares, entryPrice))
}

return new PnLState(realizedPnl, sharesFifo)
}
}

// this one is a FIFO pnl calculator:
// https://money.stackexchange.com/a/144091
export class PnLCalc {
public static from(state: Array<BigDecimal>): PnLCalc {
const pnlState =
state.length === 0 ? new PnLState(ZERO_BD, new Array<PnLStateEntry>()) : PnLState.deserialize(state)
return new PnLCalc(pnlState)
}

private constructor(private state: PnLState) {}

public addTransaction(trxShares: BigDecimal, trxPrice: BigDecimal): void {
if (trxShares.equals(ZERO_BD)) {
return
}

if (trxShares.gt(ZERO_BD)) {
this.state.sharesFifo.push(new PnLStateEntry(trxShares, trxShares, trxPrice))
return
}

let remainingSharesToSell = trxShares.neg()
let trxPnl = ZERO_BD
for (let idx = 0; idx < this.state.sharesFifo.length; idx++) {
let entry = this.state.sharesFifo[idx]
if (entry.remainingShares.equals(ZERO_BD)) {
continue
}

const sharesToSell = bigMin(remainingSharesToSell, entry.remainingShares)
const priceDiff = trxPrice.minus(entry.entryPrice)
trxPnl = trxPnl.plus(sharesToSell.times(priceDiff))
remainingSharesToSell = remainingSharesToSell.minus(sharesToSell)
entry.remainingShares = entry.remainingShares.minus(sharesToSell)

if (remainingSharesToSell.equals(ZERO_BD)) {
break
}
}

this.state.realizedPnl = this.state.realizedPnl.plus(trxPnl)
return
}

public getUnrealizedPnl(currentPrice: BigDecimal): BigDecimal {
let unrealizedPnl = ZERO_BD
for (let idx = 0; idx < this.state.sharesFifo.length; idx++) {
let entry = this.state.sharesFifo[idx]
if (entry.remainingShares.equals(ZERO_BD)) {
continue
}

const priceDiff = currentPrice.minus(entry.entryPrice)
unrealizedPnl = unrealizedPnl.plus(entry.remainingShares.times(priceDiff))
}
return unrealizedPnl
}

public getRealizedPnl(): BigDecimal {
return this.state.realizedPnl
}

public getRemainingShares(): BigDecimal {
let remainingShares = ZERO_BD
for (let idx = 0; idx < this.state.sharesFifo.length; idx++) {
let trx = this.state.sharesFifo[idx]
remainingShares = remainingShares.plus(trx.remainingShares)
}
return remainingShares
}

public getRemainingSharesAvgEntryPrice(): BigDecimal {
let totalShares = ZERO_BD
let totalCost = ZERO_BD
for (let idx = 0; idx < this.state.sharesFifo.length; idx++) {
let entry = this.state.sharesFifo[idx]
totalShares = totalShares.plus(entry.remainingShares)
totalCost = totalCost.plus(entry.remainingShares.times(entry.entryPrice))
}
if (totalShares.equals(ZERO_BD)) {
return ZERO_BD
}
return totalCost.div(totalShares)
}
}
4 changes: 2 additions & 2 deletions tests/.latest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "0.6.0",
"timestamp": 1709327586745
}
"timestamp": 1711192456694
}
11 changes: 10 additions & 1 deletion tests/utils/decimals.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert, clearStore, test, describe, afterAll } from "matchstick-as/assembly/index"
import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"
import { decimalToTokenAmount, exponentToBigDecimal, tokenAmountToDecimal } from "../../src/utils/decimal"
import { bigMin, decimalToTokenAmount, exponentToBigDecimal, tokenAmountToDecimal } from "../../src/utils/decimal"

describe("decimals.tokenAmountToDecimal", () => {
afterAll(() => {
Expand Down Expand Up @@ -109,3 +109,12 @@ describe("decimals.exponentToBigInt", () => {
assert.stringEquals(res.toString(), "1", "Decimal value should match")
})
})

describe("decimals.bigMin", () => {
test("Can return the minimum of two big decimals", () => {
const a = BigDecimal.fromString("1")
const b = BigDecimal.fromString("2")
const res = bigMin(a, b)
assert.stringEquals(res.toString(), "1", "Minimum value should match")
})
})
Loading

0 comments on commit 45b62a6

Please sign in to comment.