Skip to content

Commit

Permalink
Store filled orders WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke-Rogerson committed Dec 9, 2023
1 parent 80478b1 commit c83475c
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 84 deletions.
2 changes: 1 addition & 1 deletion data/redisrepo/cancel_orders_for_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func (r *redisRepository) CancelOrdersForUser(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error) {
userOrdersKey := CreateUserOrdersKey(userId)
userOrdersKey := CreateUserOpenOrdersKey(userId)

// Fetch all order IDs for the user
orderIdStrs, err := r.client.ZRange(ctx, userOrdersKey, 0, -1).Result()
Expand Down
20 changes: 10 additions & 10 deletions data/redisrepo/cancel_orders_for_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()})
mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap())
mock.ExpectTxPipeline()
mock.ExpectDel(CreateClientOIDKey(mocks.ClientOId)).SetVal(1)
mock.ExpectZRem(CreateBuySidePricesKey(mocks.Symbol), mocks.OrderId.String()).SetVal(1)
mock.ExpectDel(CreateOrderIDKey(mocks.OrderId)).SetVal(1)
mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetVal(1)
mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetVal(1)
mock.ExpectTxPipelineExec()

orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
Expand All @@ -45,7 +45,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()})
mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap())
mock.ExpectHGetAll(CreateOrderIDKey(orderTwo.Id)).SetVal(orderTwo.OrderToMap())
mock.ExpectTxPipeline()
Expand All @@ -55,7 +55,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
mock.ExpectDel(CreateClientOIDKey(orderTwo.ClientOId)).SetVal(1)
mock.ExpectZRem(CreateSellSidePricesKey(orderTwo.Symbol), orderTwo.Id.String()).SetVal(1)
mock.ExpectDel(CreateOrderIDKey(orderTwo.Id)).SetVal(1)
mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetVal(1)
mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetVal(1)
mock.ExpectTxPipelineExec()

orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
Expand All @@ -72,7 +72,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetErr(assert.AnError)
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetErr(assert.AnError)
orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)

assert.Empty(t, orderIds)
Expand All @@ -86,7 +86,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{})

orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
assert.Empty(t, orderIds)
Expand All @@ -100,7 +100,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{"invalid"})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{"invalid"})
_, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
assert.ErrorContains(t, err, "failed to parse order ID: invalid UUID length: 7")
})
Expand All @@ -112,7 +112,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()})
mock.ExpectHGetAll(CreateOrderIDKey(orderTwo.Id)).SetErr(models.ErrNotFound) // order not found - break out of loop iteration

orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
Expand All @@ -127,13 +127,13 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) {
client: db,
}

mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()})
mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()})
mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap())
mock.ExpectTxPipeline()
mock.ExpectDel(CreateClientOIDKey(mocks.ClientOId)).SetVal(1)
mock.ExpectZRem(CreateBuySidePricesKey(mocks.Symbol), mocks.OrderId.String()).SetVal(1)
mock.ExpectDel(CreateOrderIDKey(mocks.OrderId)).SetVal(1)
mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetErr(assert.AnError)
mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetErr(assert.AnError)
mock.ExpectTxPipelineExec().SetErr(assert.AnError)

orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId)
Expand Down
2 changes: 1 addition & 1 deletion data/redisrepo/get_orders_for_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
func (r *redisRepository) GetOrdersForUser(ctx context.Context, userId uuid.UUID) (orders []models.Order, totalOrders int, err error) {
start, stop := utils.PaginationBounds(ctx)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

count, err := r.client.ZCard(ctx, key).Result()

Expand Down
10 changes: 5 additions & 5 deletions data/redisrepo/get_orders_for_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) {

ctx := mocks.AddPaginationToCtx(1, 10)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

mock.ExpectZCard(key).SetVal(1)
mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"00000000-0000-0000-0000-000000000001"})
Expand All @@ -56,7 +56,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) {

ctx := mocks.AddPaginationToCtx(1, 10)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

mock.ExpectZCard(key).SetErr(assert.AnError)

Expand All @@ -77,7 +77,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) {

ctx := mocks.AddPaginationToCtx(1, 10)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

mock.ExpectZCard(key).SetVal(1)
mock.ExpectZRange(key, int64(0), int64(10)).SetErr(assert.AnError)
Expand All @@ -99,7 +99,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) {

ctx := mocks.AddPaginationToCtx(1, 10)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

mock.ExpectZCard(key).SetVal(1)
mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"bad-uuid"})
Expand All @@ -121,7 +121,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) {

ctx := mocks.AddPaginationToCtx(1, 10)

key := CreateUserOrdersKey(userId)
key := CreateUserOpenOrdersKey(userId)

mock.ExpectZCard(key).SetVal(1)
mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"00000000-0000-0000-0000-000000000001"})
Expand Down
2 changes: 1 addition & 1 deletion data/redisrepo/remove_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r *redisRepository) RemoveOrder(ctx context.Context, order models.Order) e
transaction.ZRem(ctx, sellPricesKey, order.Id.String())
}

userOrdersKey := CreateUserOrdersKey(order.UserId)
userOrdersKey := CreateUserOpenOrdersKey(order.UserId)
transaction.ZRem(ctx, userOrdersKey, order.Id.String())

clientOIdKey := CreateClientOIDKey(order.ClientOId)
Expand Down
1 change: 1 addition & 0 deletions data/redisrepo/remove_order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
var ctx = context.Background()
var orderId = uuid.MustParse("00000000-0000-0000-0000-000000000001")
var clientOId = uuid.MustParse("00000000-0000-0000-0000-000000000002")
var userId = uuid.MustParse("00000000-0000-0000-0000-000000000003")
var size, _ = decimal.NewFromString("10000324.123456789")

var order = models.Order{
Expand Down
57 changes: 57 additions & 0 deletions data/redisrepo/store_filled_orders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package redisrepo

import (
"context"
"fmt"

"github.com/orbs-network/order-book/models"
"github.com/orbs-network/order-book/utils/logger"
"github.com/orbs-network/order-book/utils/logger/logctx"
"github.com/redis/go-redis/v9"
)

// This should be used to store FILLED orders in Redis.
//
// `StoreOpenOrder` or `StoreOpenOrders` should be used to store unfilled or partially filled orders.
func (r *redisRepository) StoreFilledOrders(ctx context.Context, orders []models.Order) error {
transaction := r.client.TxPipeline()

for _, order := range orders {
err := storeFilledOrderTx(ctx, transaction, order)
if err != nil {
return err
}

}
_, err := transaction.Exec(ctx)
if err != nil {
logctx.Error(ctx, "failed to store filled orders in Redis", logger.Error(err), logger.Strings("orderIds", models.OrderIdsToStrings(ctx, &orders)))
return fmt.Errorf("failed to store filled orders in Redis: %v", err)
}
return nil
}

func storeFilledOrderTx(ctx context.Context, transaction redis.Pipeliner, order models.Order) error {
// 1. Remove the order from the user's open orders set
userOrdersKey := CreateUserOpenOrdersKey(order.UserId)
transaction.ZRem(ctx, userOrdersKey, order.Id.String())

// 2. Store the order in the filled orders set
userFilledOrdersKey := CreateUserFilledOrdersKey(order.UserId)
userFilledOrdersScore := float64(order.Timestamp.UTC().UnixNano())
transaction.ZAdd(ctx, userFilledOrdersKey, redis.Z{
Score: userFilledOrdersScore,
Member: order.Id.String(),
})

// 3. Remove the order from the buy/sell prices set for that pair
buyPricesKey := CreateBuySidePricesKey(order.Symbol)
sellPricesKey := CreateSellSidePricesKey(order.Symbol)
if order.Side == models.BUY {
transaction.ZRem(ctx, buyPricesKey, order.Id.String())
} else {
transaction.ZRem(ctx, sellPricesKey, order.Id.String())
}

return nil
}
104 changes: 104 additions & 0 deletions data/redisrepo/store_filled_orders_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package redisrepo

import (
"testing"
"time"

"github.com/go-redis/redismock/v9"
"github.com/orbs-network/order-book/models"
"github.com/redis/go-redis/v9"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
)

func TestRedisRepository_StoreFilledOrders(t *testing.T) {
timestamp := time.Date(2023, 10, 10, 12, 0, 0, 0, time.UTC)

t.Run("store filled order success (buy side) - should remove order from user open orders set, add order to user filled orders set, remove order from buy prices sorted set", func(t *testing.T) {
var buyOrder = models.Order{
Id: orderId,
ClientOId: clientOId,
Price: price,
Size: size,
Symbol: symbol,
Side: models.BUY,
Timestamp: timestamp,
SizePending: decimal.Zero,
SizeFilled: size,
UserId: userId,
}

db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

mock.ExpectTxPipeline()
mock.ExpectZRem(CreateUserOpenOrdersKey(buyOrder.UserId), buyOrder.Id.String()).SetVal(1)
mock.ExpectZAdd(CreateUserFilledOrdersKey(buyOrder.UserId), redis.Z{
Score: float64(timestamp.UnixNano()),
Member: buyOrder.Id.String(),
}).SetVal(1)
mock.ExpectZRem(CreateBuySidePricesKey(buyOrder.Symbol), buyOrder.Id.String()).SetVal(1)
mock.ExpectTxPipelineExec()

err := repo.StoreFilledOrders(ctx, []models.Order{buyOrder})

assert.NoError(t, err, "should not return error")
})

t.Run("store filled order success (sell side) - should remove order from user open orders set, add order to user filled orders set, remove order from sell prices sorted set", func(t *testing.T) {
var sellOrder = models.Order{
Id: orderId,
ClientOId: clientOId,
Price: price,
Size: size,
Symbol: symbol,
Side: models.SELL,
Timestamp: timestamp,
SizePending: decimal.Zero,
SizeFilled: size,
UserId: userId,
}

db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

mock.ExpectTxPipeline()
mock.ExpectZRem(CreateUserOpenOrdersKey(sellOrder.UserId), sellOrder.Id.String()).SetVal(1)
mock.ExpectZAdd(CreateUserFilledOrdersKey(sellOrder.UserId), redis.Z{
Score: float64(timestamp.UnixNano()),
Member: sellOrder.Id.String(),
}).SetVal(1)
mock.ExpectZRem(CreateSellSidePricesKey(sellOrder.Symbol), sellOrder.Id.String()).SetVal(1)
mock.ExpectTxPipelineExec()

err := repo.StoreFilledOrders(ctx, []models.Order{sellOrder})

assert.NoError(t, err, "should not return error")
})

t.Run("store filled order unexpected error - should return error", func(t *testing.T) {
var sellOrder = models.Order{
UserId: userId,
}

db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

mock.ExpectTxPipeline()
mock.ExpectZRem(CreateUserOpenOrdersKey(sellOrder.UserId), sellOrder.Id.String()).SetErr(assert.AnError)

err := repo.StoreFilledOrders(ctx, []models.Order{sellOrder})

assert.ErrorContains(t, err, "failed to store filled orders in Redis")
assert.NoError(t, mock.ExpectationsWereMet())
})
}
Loading

0 comments on commit c83475c

Please sign in to comment.