From 4edd5bd7ec19c418e4011baff515d26139e6f978 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Thu, 9 May 2024 12:02:23 +0900 Subject: [PATCH] store calculated VWAP for each tokens with 10min interval --- store.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ store_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ vwap.go | 11 +++++++---- vwap_test.go | 34 +++++++++++++++++++--------------- 4 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 store.go create mode 100644 store_test.go diff --git a/store.go b/store.go new file mode 100644 index 0000000..89a8e4a --- /dev/null +++ b/store.go @@ -0,0 +1,46 @@ +package vwap + +// VWAPData represents the specific token's VWAP data. +type VWAPData struct { + TokenName string `json:"token_name"` // VWAP data belongs token name + VWAP float64 `json:"vwap"` // calculated VWAP value + Timestamp int `json:"timestamp"` // timestamp of the VWAP value (UNIX, 10 min interval) +} + +var vwapDataMap map[string][]VWAPData + +func init() { + lastPrices = make(map[string]float64) + vwapDataMap = make(map[string][]VWAPData) +} + +// store stores the VWAP data for the token or updates the existing data. +// +// Parameters: +// +// - tokenName: the token name +// - vwap: the VWAP value to store +// - timestamp: the timestamp of the VWAP value +func store(tokenName string, vwap float64, timestamp int) { + // adjust the timestamp to the 10 minutes interval. + adjustedTimestamp := timestamp - (timestamp % 600) + + // get the VWAP data for the token + lst, ok := vwapDataMap[tokenName] + if !ok { + lst = []VWAPData{} + } + + // check last VWAP data for the list + if len(lst) > 0 { + last := lst[len(lst)-1] + if last.Timestamp == adjustedTimestamp { + last.VWAP = vwap + vwapDataMap[tokenName] = lst + return + } + } + + lst = append(lst, VWAPData{tokenName, vwap, adjustedTimestamp}) + vwapDataMap[tokenName] = lst +} diff --git a/store_test.go b/store_test.go new file mode 100644 index 0000000..7719fd1 --- /dev/null +++ b/store_test.go @@ -0,0 +1,40 @@ +package vwap + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVWAPStorage(t *testing.T) { + vwapDataMap = make(map[string][]VWAPData) + lastPrices = make(map[string]float64) + + trades := generateMockTrades() + + for _, trade := range trades { + VWAP([]TradeData{trade}) + } + + expectedVWAPData := map[string][]VWAPData{ + "gno.land/r/demo/foo": { + {TokenName: "gno.land/r/demo/foo", VWAP: 1.2, Timestamp: 1623200400}, + {TokenName: "gno.land/r/demo/foo", VWAP: 1.5, Timestamp: 1623201000}, + }, + "gno.land/r/demo/bar": { + {TokenName: "gno.land/r/demo/bar", VWAP: 2.1, Timestamp: 1623200400}, + {TokenName: "gno.land/r/demo/bar", VWAP: 2.3, Timestamp: 1623201000}, + }, + } + + assert.Equal(t, expectedVWAPData, vwapDataMap) +} + +func generateMockTrades() []TradeData { + return []TradeData{ + {TokenName: "gno.land/r/demo/foo", Volume: 100, Ratio: 1.2, Timestamp: 1623200400}, + {TokenName: "gno.land/r/demo/bar", Volume: 200, Ratio: 2.1, Timestamp: 1623200400}, + {TokenName: "gno.land/r/demo/foo", Volume: 150, Ratio: 1.5, Timestamp: 1623201000}, + {TokenName: "gno.land/r/demo/bar", Volume: 250, Ratio: 2.3, Timestamp: 1623201000}, + } +} diff --git a/vwap.go b/vwap.go index 0b5a803..1ef3978 100644 --- a/vwap.go +++ b/vwap.go @@ -20,7 +20,7 @@ const ( // TradeData represents the data for a single trade. type TradeData struct { TokenName string - Quantity float64 + Volume float64 Ratio float64 Timestamp int } @@ -35,8 +35,8 @@ func VWAP(trades []TradeData) float64 { var numerator, denominator float64 for _, trade := range trades { - numerator += trade.Quantity * trade.Ratio - denominator += trade.Quantity + numerator += trade.Volume * trade.Ratio + denominator += trade.Volume } // return last price if there is no trade @@ -47,6 +47,8 @@ func VWAP(trades []TradeData) float64 { vwap := numerator / denominator lastPrices[trades[0].TokenName] = vwap // save the last price + store(trades[0].TokenName, vwap, trades[0].Timestamp) + return vwap } @@ -73,11 +75,12 @@ func updateTrades(jsonStr string) ([]TradeData, error) { // TODO; remove testing logic // testing purpose + // TODO: Get volume data by using API tokenName := TokenIdentifier(r.Token) if contains(tradableTokens, tokenName) { trade := TradeData{ TokenName: string(tokenName), - Quantity: 100, + Volume: 100, Ratio: floatRatio, Timestamp: data.Stat.Timestamp, } diff --git a/vwap_test.go b/vwap_test.go index 69240a1..98b8c0d 100644 --- a/vwap_test.go +++ b/vwap_test.go @@ -7,8 +7,10 @@ import ( ) func TestVWAPWithNoTrades(t *testing.T) { + t.Parallel() + trades := []TradeData{ - {TokenName: "Token1", Quantity: 0, Ratio: 0, Timestamp: 1621000000}, + {TokenName: "Token1", Volume: 0, Ratio: 0, Timestamp: 1621000000}, } lastPrices = map[string]float64{ @@ -24,11 +26,11 @@ func TestVWAPWithNoTrades(t *testing.T) { func TestVWAPWith10MinuteInterval(t *testing.T) { // Mock trade data with different timestamps trades := []TradeData{ - {TokenName: "Token1", Quantity: 100, Ratio: 1.5, Timestamp: 1621000000}, - {TokenName: "Token1", Quantity: 200, Ratio: 1.8, Timestamp: 1621000180}, - {TokenName: "Token1", Quantity: 150, Ratio: 1.6, Timestamp: 1621000420}, - {TokenName: "Token1", Quantity: 300, Ratio: 1.7, Timestamp: 1621000600}, - {TokenName: "Token1", Quantity: 250, Ratio: 1.9, Timestamp: 1621000900}, + {TokenName: "Token1", Volume: 100, Ratio: 1.5, Timestamp: 1621000000}, + {TokenName: "Token1", Volume: 200, Ratio: 1.8, Timestamp: 1621000180}, + {TokenName: "Token1", Volume: 150, Ratio: 1.6, Timestamp: 1621000420}, + {TokenName: "Token1", Volume: 300, Ratio: 1.7, Timestamp: 1621000600}, + {TokenName: "Token1", Volume: 250, Ratio: 1.9, Timestamp: 1621000900}, } // Calculate VWAP for each 10-minute interval @@ -72,8 +74,8 @@ func calculateExpectedVWAP(trades []TradeData) float64 { var numerator, denominator float64 for _, trade := range trades { - numerator += trade.Quantity * trade.Ratio - denominator += trade.Quantity + numerator += trade.Volume * trade.Ratio + denominator += trade.Volume } if denominator == 0 { @@ -84,6 +86,8 @@ func calculateExpectedVWAP(trades []TradeData) float64 { } func TestUpdateTrades(t *testing.T) { + t.Parallel() + // Mock the RPC response mockResponse := `{ "stat": { @@ -126,17 +130,17 @@ func TestUpdateTrades(t *testing.T) { assert.Len(t, trades, 6, "Incorrect number of trades returned") expectedTrades := []TradeData{ - {TokenName: string(wugnot), Quantity: 100, Ratio: 1, Timestamp: expectedTimestamp}, - {TokenName: string(foo), Quantity: 100, Ratio: 1.5281713042, Timestamp: expectedTimestamp}, - {TokenName: string(qux), Quantity: 100, Ratio: 0.7640717751, Timestamp: expectedTimestamp}, - {TokenName: string(gns), Quantity: 100, Ratio: 2.980939105, Timestamp: expectedTimestamp}, - {TokenName: string(bar), Quantity: 100, Ratio: 0, Timestamp: expectedTimestamp}, - {TokenName: string(baz), Quantity: 100, Ratio: 0, Timestamp: expectedTimestamp}, + {TokenName: string(wugnot), Volume: 100, Ratio: 1, Timestamp: expectedTimestamp}, + {TokenName: string(foo), Volume: 100, Ratio: 1.5281713042, Timestamp: expectedTimestamp}, + {TokenName: string(qux), Volume: 100, Ratio: 0.7640717751, Timestamp: expectedTimestamp}, + {TokenName: string(gns), Volume: 100, Ratio: 2.980939105, Timestamp: expectedTimestamp}, + {TokenName: string(bar), Volume: 100, Ratio: 0, Timestamp: expectedTimestamp}, + {TokenName: string(baz), Volume: 100, Ratio: 0, Timestamp: expectedTimestamp}, } for i, trade := range trades { assert.Equal(t, expectedTrades[i].TokenName, trade.TokenName, "Incorrect token name") - assert.Equal(t, expectedTrades[i].Quantity, trade.Quantity, "Incorrect quantity") + assert.Equal(t, expectedTrades[i].Volume, trade.Volume, "Incorrect quantity") assert.InDelta(t, expectedTrades[i].Ratio, trade.Ratio, 1e-9, "Incorrect ratio") assert.Equal(t, expectedTrades[i].Timestamp, trade.Timestamp, "Incorrect timestamp") }