Skip to content

Commit b9e62eb

Browse files
Merge pull request #35 from busimus/main
Add historic positions method
2 parents 06f91d6 + ec0e713 commit b9e62eb

9 files changed

+182
-35
lines changed

main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func main() {
1717
var swapStart = flag.Int("swapStart", 0, "Block number to start swap event processing")
1818
var aggStart = flag.Int("aggStart", 0, "Block number to start aggregate event processing")
1919
var balStart = flag.Int("balStart", 0, "Block number to start user balance processing")
20+
var extendedApi = flag.Bool("extendedApi", false, "Expose additional methods in the API")
2021

2122
flag.Parse()
2223

@@ -50,5 +51,5 @@ func main() {
5051

5152
views := views.Views{Cache: cache, OnChain: onChain}
5253
apiServer := server.APIWebServer{Views: &views}
53-
apiServer.Serve(*apiPath)
54+
apiServer.Serve(*apiPath, *extendedApi)
5455
}

model/aprCalc.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (p *PositionTracker) CalcAPR(loc types.PositionLocation) APRCalcResult {
2121

2222
numerator := p.aprNumerator(loc)
2323
denom := p.aprDenominator()
24-
time := p.liqHist.weightedAverageDuration()
24+
time := p.LiqHist.weightedAverageDuration()
2525

2626
apy := normalizeApr(numerator, denom, time)
2727
return APRCalcResult{
@@ -45,7 +45,7 @@ func (p *PositionTracker) aprDenominator() float64 {
4545
if p.IsConcentrated() {
4646
return castBigToFloat(&p.ConcLiq)
4747
} else {
48-
return p.liqHist.netCumulativeLiquidity()
48+
return p.LiqHist.netCumulativeLiquidity()
4949
}
5050
}
5151

model/liquidityHistory.go

+26-26
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ import (
88
)
99

1010
type LiquidityDeltaHist struct {
11-
hist []LiquidityDelta
11+
Hist []LiquidityDelta `json:"hist"`
1212
}
1313

1414
type LiquidityDelta struct {
15-
time int
16-
liqChange float64
15+
Time int
16+
LiqChange float64
1717
resetRewards bool
1818
}
1919

2020
func (l *LiquidityDeltaHist) netCumulativeLiquidity() float64 {
2121
totalLiq := 0.0
2222

23-
for _, delta := range l.hist {
24-
totalLiq += delta.liqChange
23+
for _, delta := range l.Hist {
24+
totalLiq += delta.LiqChange
2525
}
2626

2727
if totalLiq < MIN_NUMERIC_STABLE_FLOW {
@@ -40,25 +40,25 @@ func (l *LiquidityDeltaHist) weightedAverageTime() int {
4040
openLiq := 0.0
4141
openTime := 0.0
4242

43-
for _, delta := range l.hist {
43+
for _, delta := range l.Hist {
4444
if delta.resetRewards == true {
45-
openTime = float64(delta.time)
45+
openTime = float64(delta.Time)
4646
}
4747

48-
if delta.liqChange < 0 {
49-
openLiq = openLiq + delta.liqChange
48+
if delta.LiqChange < 0 {
49+
openLiq = openLiq + delta.LiqChange
5050
if openLiq < 0 || openLiq < MIN_NUMERIC_STABLE_FLOW {
5151
openLiq = 0
5252
}
5353
}
5454

55-
if delta.liqChange > 0 {
56-
weight := openLiq / (openLiq + delta.liqChange)
57-
openTime = openTime*weight + float64(delta.time)*(1.0-weight)
55+
if delta.LiqChange > 0 {
56+
weight := openLiq / (openLiq + delta.LiqChange)
57+
openTime = openTime*weight + float64(delta.Time)*(1.0-weight)
5858
}
5959

60-
if delta.liqChange == 0 && openLiq == 0 {
61-
openTime = float64(delta.time)
60+
if delta.LiqChange == 0 && openLiq == 0 {
61+
openTime = float64(delta.Time)
6262
}
6363
}
6464
return int(openTime)
@@ -69,38 +69,38 @@ func (l *LiquidityDeltaHist) appendChange(r tables.LiqChange) {
6969
l.assertTimeForward(r.Time)
7070

7171
if r.ChangeType == "harvest" {
72-
l.hist = append(l.hist, LiquidityDelta{
73-
time: r.Time,
72+
l.Hist = append(l.Hist, LiquidityDelta{
73+
Time: r.Time,
7474
resetRewards: true,
7575
})
7676

7777
} else {
7878
liqMagn := determineLiquidityMagn(r)
7979

8080
if r.ChangeType == "mint" {
81-
l.hist = append(l.hist, LiquidityDelta{
82-
time: r.Time,
83-
liqChange: liqMagn})
81+
l.Hist = append(l.Hist, LiquidityDelta{
82+
Time: r.Time,
83+
LiqChange: liqMagn})
8484

8585
} else if r.ChangeType == "burn" {
86-
l.hist = append(l.hist, LiquidityDelta{
87-
time: r.Time,
88-
liqChange: -liqMagn})
86+
l.Hist = append(l.Hist, LiquidityDelta{
87+
Time: r.Time,
88+
LiqChange: -liqMagn})
8989
}
9090
}
9191
}
9292

9393
func (l *LiquidityDeltaHist) initHist() {
94-
if l.hist == nil {
95-
l.hist = make([]LiquidityDelta, 0)
94+
if l.Hist == nil {
95+
l.Hist = make([]LiquidityDelta, 0)
9696
}
9797
}
9898

9999
func (l *LiquidityDeltaHist) assertTimeForward(time int) {
100-
if len(l.hist) == 0 {
100+
if len(l.Hist) == 0 {
101101
return
102102
}
103-
lastTime := l.hist[0].time
103+
lastTime := l.Hist[0].Time
104104

105105
if time < lastTime {
106106
log.Fatalf("Liquidity delta history has backward time step %d->%d", lastTime, time)

model/liquidityMath.go

+45-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package model
22

33
import (
44
"math"
5+
"math/big"
56

67
"github.com/CrocSwap/graphcache-go/tables"
78
)
@@ -14,8 +15,12 @@ func derivePriceFromAmbientFlow(baseFlow float64, quoteFlow float64) float64 {
1415
return math.Abs(baseFlow / quoteFlow)
1516
}
1617

17-
func derivePriceFromSwapFlow(baseFlow float64, quoteFlow float64) float64 {
18-
return math.Abs(baseFlow / quoteFlow)
18+
func derivePriceFromSwapFlow(baseFlow float64, quoteFlow float64, feeRate float64, isBuy bool) float64 {
19+
if isBuy {
20+
return math.Abs(baseFlow/quoteFlow) * (1 + feeRate)
21+
} else {
22+
return math.Abs(baseFlow/quoteFlow) * (1 - feeRate)
23+
}
1924
}
2025

2126
func deriveLiquidityFromConcFlow(baseFlow float64, quoteFlow float64,
@@ -76,6 +81,44 @@ func deriveRootFromInRange(baseFlow float64, quoteFlow float64,
7681
}
7782
}
7883

84+
func DeriveTokensFromConcLiquidity(liquidity float64, bidTick int, askTick int, price float64) (baseTokens *big.Int, quoteTokens *big.Int) {
85+
if price == 0 {
86+
return nil, nil
87+
}
88+
bidPriceBig := big.NewFloat(tickToPrice(bidTick))
89+
askPriceBig := big.NewFloat(tickToPrice(askTick))
90+
liquidityBig := big.NewFloat(liquidity)
91+
clampedPriceBig := big.NewFloat(price)
92+
if big.NewFloat(price).Cmp(askPriceBig) == 1 {
93+
clampedPriceBig = askPriceBig
94+
} else if big.NewFloat(price).Cmp(bidPriceBig) == -1 {
95+
clampedPriceBig = bidPriceBig
96+
}
97+
sqrtClampedPriceBig := new(big.Float).Sqrt(clampedPriceBig)
98+
sqrtBidPriceBig := new(big.Float).Sqrt(bidPriceBig)
99+
sqrtAskPriceBig := new(big.Float).Sqrt(askPriceBig)
100+
101+
baseTokensBig := new(big.Float).Sub(sqrtClampedPriceBig, sqrtBidPriceBig)
102+
baseTokensBig.Mul(baseTokensBig, liquidityBig)
103+
104+
quoteTokensBig := new(big.Float).Sub(sqrtAskPriceBig, sqrtClampedPriceBig)
105+
quoteTokensBig.Quo(quoteTokensBig, new(big.Float).Mul(sqrtClampedPriceBig, sqrtAskPriceBig))
106+
quoteTokensBig.Mul(quoteTokensBig, liquidityBig)
107+
baseTokens, _ = baseTokensBig.Int(nil)
108+
quoteTokens, _ = quoteTokensBig.Int(nil)
109+
return
110+
}
111+
112+
func DeriveTokensFromAmbLiquidity(liquidity float64, price float64) (baseTokens *big.Int, quoteTokens *big.Int) {
113+
if price == 0 {
114+
return nil, nil
115+
}
116+
price = math.Sqrt(price)
117+
baseTokens, _ = big.NewFloat(0).Mul(big.NewFloat(liquidity), big.NewFloat(price)).Int(nil)
118+
quoteTokens, _ = big.NewFloat(0).Quo(big.NewFloat(liquidity), big.NewFloat(price)).Int(nil)
119+
return
120+
}
121+
79122
func estLiqAmplification(bidTick int, askTick int) float64 {
80123
midTick := (bidTick + askTick) / 2
81124
bidPrice := math.Sqrt(tickToPrice(bidTick))

model/position.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type PositionTracker struct {
1414
FirstMintTx string `json:"firstMintTx"`
1515
PositionType string `json:"positionType"`
1616
PositionLiquidity
17-
liqHist LiquidityDeltaHist
17+
LiqHist LiquidityDeltaHist `json:"-"`
1818
}
1919

2020
func (p *PositionTracker) UpdatePosition(l tables.LiqChange) {
@@ -32,7 +32,7 @@ func (p *PositionTracker) UpdatePosition(l tables.LiqChange) {
3232
}
3333
p.PositionType = l.PositionType
3434

35-
p.liqHist.appendChange(l)
35+
p.LiqHist.appendChange(l)
3636
}
3737

3838
func (p *PositionTracker) UpdateAmbient(liq big.Int) {

model/tradingHistory.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func (a *AccumPoolStats) accumSwapType(e tables.AggEvent) {
100100
}
101101

102102
if isStable {
103-
price := derivePriceFromAmbientFlow(math.Abs(e.BaseFlow), math.Abs(e.QuoteFlow))
103+
price := derivePriceFromSwapFlow(math.Abs(e.BaseFlow), math.Abs(e.QuoteFlow), a.FeeRate, e.BaseFlow < 0)
104104
a.LastPriceSwap = price
105105
a.LastPriceIndic = price
106106
}

server/server.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package server
33
import (
44
"log"
55
"net/http"
6+
"strconv"
67

8+
"github.com/CrocSwap/graphcache-go/types"
79
"github.com/CrocSwap/graphcache-go/views"
810
"github.com/gin-contrib/gzip"
911
"github.com/gin-gonic/gin"
@@ -13,7 +15,7 @@ type APIWebServer struct {
1315
Views views.IViews
1416
}
1517

16-
func (s *APIWebServer) Serve(prefix string) {
18+
func (s *APIWebServer) Serve(prefix string, extendedApi bool) {
1719
gin.SetMode(gin.ReleaseMode)
1820
r := gin.Default()
1921
r.Use(CORSMiddleware())
@@ -39,6 +41,10 @@ func (s *APIWebServer) Serve(prefix string) {
3941
r.GET(prefix+"/pool_list", s.queryPoolList)
4042
r.GET(prefix+"/chain_stats", s.queryChainStats)
4143

44+
if extendedApi {
45+
r.GET(prefix+"/historic_positions", s.queryHistoricPositions)
46+
}
47+
4248
log.Println("API Serving at", prefix)
4349
r.Run()
4450
}
@@ -145,6 +151,25 @@ func (s *APIWebServer) queryPoolTxHist(c *gin.Context) {
145151
}
146152
}
147153

154+
func (s *APIWebServer) queryHistoricPositions(c *gin.Context) {
155+
// time of the liquidity snapshot
156+
time := parseIntParam(c, "time")
157+
// all pool filters are optional
158+
chainId := types.ValidateChainId(c.Query("chainId"))
159+
base := types.ValidateEthAddr(c.Query("base"))
160+
quote := types.ValidateEthAddr(c.Query("quote"))
161+
poolIdx, _ := strconv.Atoi(c.Query("poolIdx"))
162+
user := types.ValidateEthAddr(c.Query("user"))
163+
omitEmpty := parseBoolOptional(c, "omitEmpty", true)
164+
165+
if len(c.Errors) > 0 {
166+
return
167+
}
168+
169+
resp := s.Views.QueryHistoricPositions(chainId, base, quote, poolIdx, time, user, omitEmpty)
170+
wrapDataErrResp(c, resp, nil)
171+
}
172+
148173
func (s *APIWebServer) queryPoolLimits(c *gin.Context) {
149174
chainId := parseChainParam(c, "chainId")
150175
base := parseAddrParam(c, "base")

views/positions.go

+76
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package views
22

33
import (
44
"encoding/hex"
5+
"math/big"
56
"sort"
67

78
"github.com/CrocSwap/graphcache-go/model"
@@ -15,6 +16,19 @@ type UserPosition struct {
1516
PositionId string `json:"positionId"`
1617
}
1718

19+
type HistoricUserPosition struct {
20+
types.PositionLocation
21+
PositionType string `json:"positionType"`
22+
TimeFirstMint int `json:"timeFirstMint"`
23+
FirstMintTx string `json:"firstMintTx"`
24+
PositionId string `json:"positionId"`
25+
Liq *big.Int `json:"liq"`
26+
BaseTokens *big.Int `json:"baseTokens"`
27+
QuoteTokens *big.Int `json:"quoteTokens"`
28+
PoolPrice float64 `json:"poolPrice"`
29+
LiqHist model.LiquidityDeltaHist `json:"liqHist"`
30+
}
31+
1832
func (v *Views) QueryUserPositions(chainId types.ChainId, user types.EthAddress) []UserPosition {
1933
positions := v.Cache.RetrieveUserPositions(chainId, user)
2034

@@ -122,6 +136,68 @@ func (v *Views) QuerySinglePosition(chainId types.ChainId, user types.EthAddress
122136
return nil
123137
}
124138

139+
func (v *Views) QueryHistoricPositions(chainId types.ChainId, base types.EthAddress, quote types.EthAddress,
140+
poolIdx int, time int, user types.EthAddress, omitEmpty bool) []HistoricUserPosition {
141+
livePositions := make([]HistoricUserPosition, 0)
142+
143+
pools := v.Cache.RetrievePoolSet()
144+
for _, loc := range pools {
145+
if (chainId != "" && loc.ChainId != chainId) || (base != "" && loc.Base != base) || (quote != "" && loc.Quote != quote) || (poolIdx != 0 && loc.PoolIdx != poolIdx) {
146+
continue
147+
}
148+
positions := v.Cache.RetrievePoolPositions(loc)
149+
150+
poolHistPos := make([]HistoricUserPosition, 0)
151+
152+
for key, val := range positions {
153+
if user != "" && user != key.User {
154+
continue
155+
}
156+
histPos := HistoricUserPosition{
157+
PositionLocation: key,
158+
PositionId: formPositionId(key),
159+
TimeFirstMint: val.TimeFirstMint,
160+
FirstMintTx: val.FirstMintTx,
161+
PositionType: val.PositionType,
162+
LiqHist: val.LiqHist,
163+
}
164+
poolHistPos = append(poolHistPos, histPos)
165+
}
166+
167+
lastTrade := v.Cache.RetrievePoolAccumBefore(loc, time)
168+
poolPrice := lastTrade.LastPriceIndic
169+
for _, position := range poolHistPos {
170+
var liqSum float64
171+
for _, liqChange := range position.LiqHist.Hist {
172+
if liqChange.Time <= time {
173+
liqSum += liqChange.LiqChange
174+
}
175+
}
176+
if liqSum <= 0 {
177+
liqSum = 0
178+
if omitEmpty {
179+
continue
180+
}
181+
}
182+
if position.TimeFirstMint > time {
183+
continue
184+
}
185+
if position.PositionType == "concentrated" {
186+
liqSumBig, _ := big.NewFloat(liqSum).Int(nil)
187+
position.Liq = liqSumBig
188+
position.BaseTokens, position.QuoteTokens = model.DeriveTokensFromConcLiquidity(liqSum, position.BidTick, position.AskTick, poolPrice)
189+
} else if position.PositionType == "ambient" {
190+
liqSumBig, _ := big.NewFloat(liqSum).Int(nil)
191+
position.Liq = liqSumBig
192+
position.BaseTokens, position.QuoteTokens = model.DeriveTokensFromAmbLiquidity(liqSum, poolPrice)
193+
}
194+
position.PoolPrice = poolPrice
195+
livePositions = append(livePositions, position)
196+
}
197+
}
198+
return livePositions
199+
}
200+
125201
func formPositionId(loc types.PositionLocation) string {
126202
hash := loc.Hash()
127203
return "pos_" + hex.EncodeToString(hash[:])

0 commit comments

Comments
 (0)