Skip to content

Commit

Permalink
pkg/exchange: refactor order trade event by json.Unmarshal
Browse files Browse the repository at this point in the history
  • Loading branch information
bailantaotao committed Jan 16, 2024
1 parent 735123b commit 91913f0
Show file tree
Hide file tree
Showing 8 changed files with 512 additions and 54 deletions.
33 changes: 32 additions & 1 deletion pkg/exchange/okex/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,25 @@ func tradeToGlobal(trade okexapi.Trade) types.Trade {
}
}

func processMarketBuySize(o *okexapi.OrderDetail) (fixedpoint.Value, error) {
switch o.State {
case okexapi.OrderStateLive, okexapi.OrderStateCanceled:
return fixedpoint.Zero, nil

case okexapi.OrderStatePartiallyFilled:
if o.FillPrice.IsZero() {
return fixedpoint.Zero, fmt.Errorf("fillPrice for a partialFilled should not be zero")
}
return o.Size.Div(o.FillPrice), nil

case okexapi.OrderStateFilled:
return o.AccumulatedFillSize, nil

default:
return fixedpoint.Zero, fmt.Errorf("unexpected status: %s", o.State)
}
}

func orderDetailToGlobal(order *okexapi.OrderDetail) (*types.Order, error) {
side := toGlobalSide(order.Side)

Expand All @@ -196,14 +215,26 @@ func orderDetailToGlobal(order *okexapi.OrderDetail) (*types.Order, error) {
return nil, err
}

size := order.Size
if order.Side == okexapi.SideTypeBuy &&
order.OrderType == okexapi.OrderTypeMarket &&
order.TargetCurrency == okexapi.TargetCurrencyQuote {

size, err = processMarketBuySize(order)
if err != nil {
return nil, err
}
}

return &types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: order.ClientOrderId,
Symbol: toGlobalSymbol(order.InstrumentID),
Side: side,
Type: orderType,
Price: order.Price,
Quantity: order.Size,
Quantity: size,
AveragePrice: order.AvgPrice,
TimeInForce: timeInForce,
},
Exchange: types.ExchangeOKEx,
Expand Down
71 changes: 71 additions & 0 deletions pkg/exchange/okex/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ func Test_orderDetailToGlobal(t *testing.T) {
assert.Equal(expOrder, order)
})

t.Run("succeeds with market/buy/targetQuoteCurrency", func(t *testing.T) {
newOrder := *openOrder
newOrder.OrderType = okexapi.OrderTypeMarket
newOrder.Side = okexapi.SideTypeBuy
newOrder.TargetCurrency = okexapi.TargetCurrencyQuote
newOrder.FillPrice = fixedpoint.NewFromFloat(100)
newOrder.Size = fixedpoint.NewFromFloat(10000)
newOrder.State = okexapi.OrderStatePartiallyFilled

newExpOrder := *expOrder
newExpOrder.Side = types.SideTypeBuy
newExpOrder.Type = types.OrderTypeMarket
newExpOrder.Quantity = fixedpoint.NewFromFloat(100)
newExpOrder.Status = types.OrderStatusPartiallyFilled
newExpOrder.OriginalStatus = string(okexapi.OrderStatePartiallyFilled)
order, err := orderDetailToGlobal(&newOrder)
assert.NoError(err)
assert.Equal(&newExpOrder, order)
})

t.Run("unexpected order status", func(t *testing.T) {
newOrder := *openOrder
newOrder.State = "xxx"
Expand Down Expand Up @@ -172,3 +192,54 @@ func Test_tradeToGlobal(t *testing.T) {
})
})
}

func Test_processMarketBuyQuantity(t *testing.T) {
var (
assert = assert.New(t)
)

t.Run("zero", func(t *testing.T) {
size, err := processMarketBuySize(&okexapi.OrderDetail{State: okexapi.OrderStateLive})
assert.NoError(err)
assert.Equal(fixedpoint.Zero, size)

size, err = processMarketBuySize(&okexapi.OrderDetail{State: okexapi.OrderStateCanceled})
assert.NoError(err)
assert.Equal(fixedpoint.Zero, size)
})

t.Run("estimated size", func(t *testing.T) {
size, err := processMarketBuySize(&okexapi.OrderDetail{
FillPrice: fixedpoint.NewFromFloat(2),
Size: fixedpoint.NewFromFloat(4),
State: okexapi.OrderStatePartiallyFilled,
})
assert.NoError(err)
assert.Equal(fixedpoint.NewFromFloat(2), size)
})

t.Run("unexpected fill price", func(t *testing.T) {
_, err := processMarketBuySize(&okexapi.OrderDetail{
FillPrice: fixedpoint.Zero,
Size: fixedpoint.NewFromFloat(4),
State: okexapi.OrderStatePartiallyFilled,
})
assert.ErrorContains(err, "fillPrice")
})

t.Run("accumulatedFillsize", func(t *testing.T) {
size, err := processMarketBuySize(&okexapi.OrderDetail{
AccumulatedFillSize: fixedpoint.NewFromFloat(1000),
State: okexapi.OrderStateFilled,
})
assert.NoError(err)
assert.Equal(fixedpoint.NewFromFloat(1000), size)
})

t.Run("unexpected status", func(t *testing.T) {
_, err := processMarketBuySize(&okexapi.OrderDetail{
State: "XXXXXXX",
})
assert.ErrorContains(err, "unexpected")
})
}
4 changes: 2 additions & 2 deletions pkg/exchange/okex/okexapi/get_order_history_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type OrderDetail struct {
Side SideType `json:"side"`
State OrderState `json:"state"`
Size fixedpoint.Value `json:"sz"`
TargetCurrency string `json:"tgtCcy"`
TargetCurrency TargetCurrency `json:"tgtCcy"`
UpdatedTime types.MillisecondTimestamp `json:"uTime"`

// Margin currency
Expand Down Expand Up @@ -69,7 +69,7 @@ type OrderDetail struct {
// Self trade prevention mode. Return "" if self trade prevention is not applicable
StpMode string `json:"stpMode"`
Tag string `json:"tag"`
TradeMode string `json:"tdMode"`
TradeMode TradeMode `json:"tdMode"`
TpOrdPx fixedpoint.Value `json:"tpOrdPx"`
TpTriggerPx fixedpoint.Value `json:"tpTriggerPx"`
TpTriggerPxType string `json:"tpTriggerPxType"`
Expand Down
88 changes: 69 additions & 19 deletions pkg/exchange/okex/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"

"github.com/valyala/fastjson"

"github.com/c9s/bbgo/pkg/exchange/okex/okexapi"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
Expand All @@ -22,7 +21,7 @@ const (
ChannelCandlePrefix Channel = "candle"
ChannelAccount Channel = "account"
ChannelMarketTrades Channel = "trades"
ChannelOrders Channel = "orders"
ChannelOrderTrades Channel = "orders"
)

type ActionType string
Expand All @@ -33,13 +32,8 @@ const (
)

func parseWebSocketEvent(in []byte) (interface{}, error) {
v, err := fastjson.ParseBytes(in)
if err != nil {
return nil, err
}

var event WebSocketEvent
err = json.Unmarshal(in, &event)
err := json.Unmarshal(in, &event)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -73,9 +67,14 @@ func parseWebSocketEvent(in []byte) (interface{}, error) {
}
return trade, nil

case ChannelOrders:
// TODO: remove fastjson
return parseOrder(v)
case ChannelOrderTrades:
var orderTrade []OrderTradeEvent
err := json.Unmarshal(event.Data, &orderTrade)
if err != nil {
return nil, err
}

return orderTrade, nil

default:
if strings.HasPrefix(string(event.Arg.Channel), string(ChannelCandlePrefix)) {
Expand Down Expand Up @@ -391,16 +390,67 @@ func parseAccount(v []byte) (*okexapi.Account, error) {
return &accounts[0], nil
}

func parseOrder(v *fastjson.Value) ([]okexapi.OrderDetails, error) {
data := v.Get("data").MarshalTo(nil)
type OrderTradeEvent struct {
okexapi.OrderDetail

Code types.StrInt64 `json:"code"`
Msg string `json:"msg"`
AmendResult string `json:"amendResult"`
ExecutionType okexapi.LiquidityType `json:"execType"`
// FillFee last filled fee amount or rebate amount:
// Negative number represents the user transaction fee charged by the platform;
// Positive number represents rebate
FillFee fixedpoint.Value `json:"fillFee"`
// FillFeeCurrency last filled fee currency or rebate currency.
// It is fee currency when fillFee is less than 0; It is rebate currency when fillFee>=0.
FillFeeCurrency string `json:"fillFeeCcy"`
// FillNotionalUsd Filled notional value in USD of order
FillNotionalUsd fixedpoint.Value `json:"fillNotionalUsd"`
FillPnl fixedpoint.Value `json:"fillPnl"`
// NotionalUsd Estimated national value in USD of order
NotionalUsd fixedpoint.Value `json:"notionalUsd"`
// ReqId Client Request ID as assigned by the client for order amendment. "" will be returned if there is no order amendment.
ReqId string `json:"reqId"`
LastPrice fixedpoint.Value `json:"lastPx"`
// QuickMgnType Quick Margin type, Only applicable to Quick Margin Mode of isolated margin
// manual, auto_borrow, auto_repay
QuickMgnType string `json:"quickMgnType"`
// AmendSource Source of the order amendation.
AmendSource string `json:"amendSource"`
// CancelSource Source of the order cancellation.
CancelSource string `json:"cancelSource"`

// Only applicable to options; return "" for other instrument types
FillPriceVolume string `json:"fillPxVol"`
FillPriceUsd string `json:"fillPxUsd"`
FillMarkVolume string `json:"fillMarkVol"`
FillFwdPrice string `json:"fillFwdPx"`
FillMarkPrice string `json:"fillMarkPx"`
}

var orderDetails []okexapi.OrderDetails
err := json.Unmarshal(data, &orderDetails)
func (o *OrderTradeEvent) toGlobalTrade() (types.Trade, error) {
side := toGlobalSide(o.Side)
tradeId, err := strconv.ParseUint(o.TradeId, 10, 64)
if err != nil {
return nil, err
return types.Trade{}, fmt.Errorf("unexpected trade id [%s] format: %w", o.TradeId, err)
}

return orderDetails, nil
return types.Trade{
ID: tradeId,
OrderID: uint64(o.OrderId),
Exchange: types.ExchangeOKEx,
Price: o.FillPrice,
Quantity: o.FillSize,
QuoteQuantity: o.FillPrice.Mul(o.FillSize),
Symbol: toGlobalSymbol(o.InstrumentID),
Side: side,
IsBuyer: side == types.SideTypeBuy,
IsMaker: o.ExecutionType == okexapi.LiquidityTypeMaker,
Time: types.Time(o.FillTime.Time()),
// charged by the platform is positive in our design, so added the `Neg()`.
Fee: o.FillFee.Neg(),
FeeCurrency: o.FeeCurrency,
FeeDiscounted: false,
}, nil
}

func toGlobalSideType(side okexapi.SideType) (types.SideType, error) {
Expand Down
Loading

0 comments on commit 91913f0

Please sign in to comment.