Skip to content

Commit ac3e8ec

Browse files
committed
feature: Add median price
1 parent 9ad4b03 commit ac3e8ec

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

internal/route/ticker/handler.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"log/slog"
77
"math/big"
88
"net/http"
9+
"sort"
910
"strings"
1011

1112
"github.com/gin-gonic/gin"
@@ -66,6 +67,8 @@ func (a *API) GetPrice(c *gin.Context) {
6667
switch method {
6768
case "average":
6869
price, err = a.averagePrice(symbol, exchanges)
70+
case "median":
71+
price, err = a.medianPrice(symbol, exchanges)
6972
default:
7073
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid method"})
7174
return
@@ -109,3 +112,26 @@ func (a *API) averagePrice(symbol string, exchanges []string) (*big.Float, error
109112

110113
return average, nil
111114
}
115+
116+
func (a *API) medianPrice(symbol string, exchanges []string) (*big.Float, error) {
117+
prices := []*big.Float{}
118+
for _, exchange := range exchanges {
119+
ticker, err := a.store.Get(fmt.Sprintf("%s:%s", exchange, symbol))
120+
if err != nil {
121+
return nil, fmt.Errorf("ticker not found: %w", err)
122+
}
123+
prices = append(prices, ticker.Price)
124+
}
125+
126+
sort.Slice(prices, func(i, j int) bool {
127+
return prices[i].Cmp(prices[j]) < 0
128+
})
129+
130+
mid := len(prices) / 2
131+
if len(prices)%2 == 1 {
132+
return prices[mid], nil
133+
} else {
134+
sum := new(big.Float).Add(prices[mid-1], prices[mid])
135+
return new(big.Float).Quo(sum, big.NewFloat(2)), nil
136+
}
137+
}

internal/route/ticker/handler_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,74 @@ func TestAveragePrice(t *testing.T) {
6161
assert.Nil(t, price)
6262
})
6363
}
64+
65+
func TestMedianPrice(t *testing.T) {
66+
t.Run("BTCUSDT median price", func(t *testing.T) {
67+
memory := storage.NewMemory[connector.TickerUpdate]()
68+
api := &API{
69+
store: memory,
70+
}
71+
72+
api.store.Store("binance:BTCUSDT", connector.TickerUpdate{
73+
Price: big.NewFloat(10000),
74+
})
75+
api.store.Store("okx:BTCUSDT", connector.TickerUpdate{
76+
Price: big.NewFloat(10010),
77+
})
78+
api.store.Store("bybit:BTCUSDT", connector.TickerUpdate{
79+
Price: big.NewFloat(10020),
80+
})
81+
82+
price, err := api.medianPrice("BTCUSDT", []string{"binance", "okx", "bybit"})
83+
assert.NoError(t, err)
84+
assert.Equal(t, big.NewFloat(10010), price)
85+
})
86+
87+
t.Run("BTCUSDT median price with even number of exchanges", func(t *testing.T) {
88+
memory := storage.NewMemory[connector.TickerUpdate]()
89+
api := &API{
90+
store: memory,
91+
}
92+
93+
api.store.Store("binance:BTCUSDT", connector.TickerUpdate{
94+
Price: big.NewFloat(10000),
95+
})
96+
api.store.Store("okx:BTCUSDT", connector.TickerUpdate{
97+
Price: big.NewFloat(10010),
98+
})
99+
100+
price, err := api.medianPrice("BTCUSDT", []string{"binance", "okx"})
101+
assert.NoError(t, err)
102+
assert.Equal(t, big.NewFloat(10005), price)
103+
})
104+
105+
t.Run("Exchange not found", func(t *testing.T) {
106+
memory := storage.NewMemory[connector.TickerUpdate]()
107+
api := &API{
108+
store: memory,
109+
}
110+
111+
api.store.Store("binance:BTCUSDT", connector.TickerUpdate{
112+
Price: big.NewFloat(10000),
113+
})
114+
115+
price, err := api.medianPrice("BTCUSDT", []string{"binance", "okx"})
116+
assert.Error(t, err)
117+
assert.Nil(t, price)
118+
})
119+
120+
t.Run("Symbol not found", func(t *testing.T) {
121+
memory := storage.NewMemory[connector.TickerUpdate]()
122+
api := &API{
123+
store: memory,
124+
}
125+
126+
api.store.Store("binance:BTCUSDT", connector.TickerUpdate{
127+
Price: big.NewFloat(10000),
128+
})
129+
130+
price, err := api.medianPrice("ETHUSDT", []string{"binance"})
131+
assert.Error(t, err)
132+
assert.Nil(t, price)
133+
})
134+
}

0 commit comments

Comments
 (0)