mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-22 07:26:50 +00:00
Okx: Websocket order channel fixes (#1346)
* Okx: Fix WS order fields * Fixes float64 with string annotation erroring on empty strings: Okx Order Push Data error json: invalid use of ,string struct tag, trying to unmarshal "" into float64 Specifically this came from px field from a market order * Switch to convert.StringToFloat64 instead of okxNumericalValue * Fix typo in Notional* field names; Ironically prevented them from erroring * Okx: Add tests for first order fields * Okx: CID and maybe set WS order Filled time * Tests: Set TestFixtureToDataHandler to t.Helper * Orders: Add UnmarshalJSON to order.Side * Okx: Fix FillTime not parsed for PendingOrder * Okx: Switch to order.Side Unmarshal throughout * Okx: Add Fee and FeeAsset to order processing * Okx: Fix WS order.Detail amounts and Test This fixes Amount vs QuoteAmount for market sells where tgtCcy is quote_ccy * Add comment to order.Side.UnmarshalJSON * Okx: Replace PendingOrderItem Unmarshal with local types * Okx: string type for WS order reduceOnly Note: Not yet in unit tests, since it's not part of the spot tests I was originally fixing. I'll circle back to adding full test support for Reduce only and deleveraging positions. * Okx: Fix TestOrderPushData Amount We were expecting 0 when we're given a quoteAmount In reality, we'll calculate the size from the price * Okx: Fix order and remAmount in wsOrders Improved handling for Float64 issues and boundaries when the order is fully executed but not yet marked as Filled * Fix ErrSideIsInvalid in tests
This commit is contained in:
@@ -2490,13 +2490,70 @@ func TestBalanceAndPosition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const orderPushDataJSON = `{"arg": { "channel": "orders", "instType": "SPOT", "instId": "BTC-USDT", "uid": "614488474791936"},"data": [ { "accFillSz": "0.001", "amendResult": "", "avgPx": "31527.1", "cTime": "1654084334977", "category": "normal", "ccy": "", "clOrdId": "", "code": "0", "execType": "M", "fee": "-0.02522168", "feeCcy": "USDT", "fillFee": "-0.02522168", "fillFeeCcy": "USDT", "fillNotionalUsd": "31.50818374", "fillPx": "31527.1", "fillSz": "0.001", "fillTime": "1654084353263", "instId": "BTC-USDT", "instType": "SPOT", "lever": "0", "msg": "", "notionalUsd": "31.50818374", "ordId": "452197707845865472", "ordType": "limit", "pnl": "0", "posSide": "", "px": "31527.1", "rebate": "0", "rebateCcy": "BTC", "reduceOnly": "false", "reqId": "", "side": "sell", "slOrdPx": "", "slTriggerPx": "", "slTriggerPxType": "last", "source": "", "state": "filled", "sz": "0.001", "tag": "", "tdMode": "cash", "tgtCcy": "", "tpOrdPx": "", "tpTriggerPx": "", "tpTriggerPxType": "last", "tradeId": "242589207", "uTime": "1654084353264" }]}`
|
||||
|
||||
func TestOrderPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := ok.WsHandleData([]byte(orderPushDataJSON)); err != nil {
|
||||
t.Error("Okx Order Push Data error", err)
|
||||
n := new(Okx)
|
||||
sharedtestvalues.TestFixtureToDataHandler(t, ok, n, "testdata/wsOrders.json", n.WsHandleData)
|
||||
seen := 0
|
||||
for reading := true; reading; {
|
||||
select {
|
||||
default:
|
||||
reading = false
|
||||
case resp := <-n.GetBase().Websocket.DataHandler:
|
||||
seen++
|
||||
switch v := resp.(type) {
|
||||
case *order.Detail:
|
||||
switch seen {
|
||||
case 1:
|
||||
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
|
||||
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
assert.Equal(t, order.Limit, v.Type, "Type")
|
||||
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
|
||||
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
|
||||
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
|
||||
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
|
||||
assert.Equal(t, 0.001, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 31527.1, v.Price, "Price")
|
||||
assert.Equal(t, 0.02522168, v.Fee, "Fee")
|
||||
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
|
||||
case 2:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
|
||||
assert.Equal(t, order.Market, v.Type, "Type")
|
||||
assert.Equal(t, order.Sell, v.Side, "Side")
|
||||
assert.Equal(t, order.Active, v.Status, "Status")
|
||||
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
case 3:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
|
||||
case 4:
|
||||
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
|
||||
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
|
||||
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
|
||||
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
|
||||
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
|
||||
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
|
||||
assert.Equal(t, order.Filled, v.Status, "Status")
|
||||
}
|
||||
case error:
|
||||
t.Error(v)
|
||||
default:
|
||||
t.Errorf("Got unexpected data: %T %v", v, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, seen, "Saw 4 records")
|
||||
}
|
||||
|
||||
const algoOrdersPushDataJSON = `{"arg": {"channel": "orders-algo","uid": "77982378738415879","instType": "FUTURES","instId": "BTC-USD-200329"},"data": [{"instType": "FUTURES","instId": "BTC-USD-200329","ordId": "312269865356374016","ccy": "BTC","algoId": "1234","px": "999","sz": "3","tdMode": "cross","tgtCcy": "","notionalUsd": "","ordType": "trigger","side": "buy","posSide": "long","state": "live","lever": "20","tpTriggerPx": "","tpTriggerPxType": "","tpOrdPx": "","slTriggerPx": "","slTriggerPxType": "","triggerPx": "99","triggerPxType": "last","ordPx": "12","actualSz": "","actualPx": "","tag": "adadadadad","actualSide": "","triggerTime": "1597026383085","cTime": "1597026383000"}]}`
|
||||
|
||||
@@ -139,7 +139,6 @@ func (a *OrderDetail) UnmarshalJSON(data []byte) error {
|
||||
type Alias OrderDetail
|
||||
chil := &struct {
|
||||
*Alias
|
||||
Side string `json:"side"`
|
||||
UpdateTime int64 `json:"uTime,string"`
|
||||
CreationTime int64 `json:"cTime,string"`
|
||||
FillTime string `json:"fillTime"`
|
||||
@@ -152,7 +151,6 @@ func (a *OrderDetail) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
a.UpdateTime = time.UnixMilli(chil.UpdateTime)
|
||||
a.CreationTime = time.UnixMilli(chil.CreationTime)
|
||||
a.Side, err = order.StringToOrderSide(chil.Side)
|
||||
if chil.FillTime == "" {
|
||||
a.FillTime = time.Time{}
|
||||
} else {
|
||||
@@ -169,38 +167,6 @@ func (a *OrderDetail) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes JSON, and timestamp information.
|
||||
func (a *PendingOrderItem) UnmarshalJSON(data []byte) error {
|
||||
type Alias PendingOrderItem
|
||||
chil := &struct {
|
||||
*Alias
|
||||
Side string `json:"side"`
|
||||
UpdateTime string `json:"uTime"`
|
||||
CreationTime string `json:"cTime"`
|
||||
}{
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
err := json.Unmarshal(data, chil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uTime, err := strconv.ParseInt(chil.UpdateTime, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cTime, err := strconv.ParseInt(chil.CreationTime, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Side, err = order.StringToOrderSide(chil.Side)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.CreationTime = time.UnixMilli(cTime)
|
||||
a.UpdateTime = time.UnixMilli(uTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes JSON, and timestamp information.
|
||||
func (a *RfqTradeResponse) UnmarshalJSON(data []byte) error {
|
||||
type Alias RfqTradeResponse
|
||||
@@ -233,29 +199,6 @@ func (a *BlockTicker) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes JSON, and timestamp information.
|
||||
func (a *BlockTrade) UnmarshalJSON(data []byte) error {
|
||||
type Alias BlockTrade
|
||||
chil := &struct {
|
||||
*Alias
|
||||
Side string `json:"side"`
|
||||
}{
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
if err := json.Unmarshal(data, chil); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case strings.EqualFold(chil.Side, "buy"):
|
||||
a.Side = order.Buy
|
||||
case strings.EqualFold(chil.Side, "sell"):
|
||||
a.Side = order.Sell
|
||||
default:
|
||||
a.Side = order.UnknownSide
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes JSON, and timestamp information.
|
||||
func (a *UnitConvertResponse) UnmarshalJSON(data []byte) error {
|
||||
type Alias UnitConvertResponse
|
||||
@@ -277,27 +220,6 @@ func (a *UnitConvertResponse) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes JSON, and timestamp information.
|
||||
func (a *QuoteLeg) UnmarshalJSON(data []byte) error {
|
||||
type Alias QuoteLeg
|
||||
chil := &struct {
|
||||
*Alias
|
||||
Side string `json:"side"`
|
||||
}{
|
||||
Alias: (*Alias)(a),
|
||||
}
|
||||
if err := json.Unmarshal(data, chil); err != nil {
|
||||
return err
|
||||
}
|
||||
chil.Side = strings.ToLower(chil.Side)
|
||||
if chil.Side == "buy" {
|
||||
a.Side = order.Buy
|
||||
} else {
|
||||
a.Side = order.Sell
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialized QuoteLeg instance into bytes
|
||||
func (a *QuoteLeg) MarshalJSON() ([]byte, error) {
|
||||
type Alias QuoteLeg
|
||||
|
||||
@@ -230,7 +230,7 @@ type TradeResponse struct {
|
||||
TradeID string `json:"tradeId"`
|
||||
Price float64 `json:"px,string"`
|
||||
Quantity float64 `json:"sz,string"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
Timestamp okxUnixMilliTime `json:"ts"`
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ type LiquidationOrderDetailItem struct {
|
||||
BankruptcyPx string `json:"bkPx"`
|
||||
Currency string `json:"ccy"`
|
||||
PosSide string `json:"posSide"`
|
||||
Side string `json:"side"`
|
||||
Side string `json:"side"` // May be empty
|
||||
QuantityOfLiquidation float64 `json:"sz,string"`
|
||||
Timestamp okxUnixMilliTime `json:"ts"`
|
||||
}
|
||||
@@ -717,42 +717,42 @@ type OrderHistoryRequestParams struct {
|
||||
|
||||
// PendingOrderItem represents a pending order Item in pending orders list.
|
||||
type PendingOrderItem struct {
|
||||
AccumulatedFillSize okxNumericalValue `json:"accFillSz"`
|
||||
AveragePrice okxNumericalValue `json:"avgPx"`
|
||||
CreationTime time.Time `json:"cTime"`
|
||||
Category string `json:"category"`
|
||||
Currency string `json:"ccy"`
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
TransactionFee string `json:"fee"`
|
||||
FeeCurrency string `json:"feeCcy"`
|
||||
LastFilledPrice string `json:"fillPx"`
|
||||
LastFilledSize okxNumericalValue `json:"fillSz"`
|
||||
FillTime string `json:"fillTime"`
|
||||
InstrumentID string `json:"instId"`
|
||||
InstrumentType string `json:"instType"`
|
||||
Leverage okxNumericalValue `json:"lever"`
|
||||
OrderID string `json:"ordId"`
|
||||
OrderType string `json:"ordType"`
|
||||
ProfitAndLose string `json:"pnl"`
|
||||
PositionSide string `json:"posSide"`
|
||||
RebateAmount string `json:"rebate"`
|
||||
RebateCurrency string `json:"rebateCcy"`
|
||||
Side order.Side `json:"side"`
|
||||
StopLossOrdPrice string `json:"slOrdPx"`
|
||||
StopLossTriggerPrice string `json:"slTriggerPx"`
|
||||
StopLossTriggerPriceType string `json:"slTriggerPxType"`
|
||||
State string `json:"state"`
|
||||
Price float64 `json:"px,string"`
|
||||
Size float64 `json:"sz,string"`
|
||||
Tag string `json:"tag"`
|
||||
QuantityType string `json:"tgtCcy"`
|
||||
TradeMode string `json:"tdMode"`
|
||||
Source string `json:"source"`
|
||||
TakeProfitOrdPrice string `json:"tpOrdPx"`
|
||||
TakeProfitTriggerPrice string `json:"tpTriggerPx"`
|
||||
TakeProfitTriggerPriceType string `json:"tpTriggerPxType"`
|
||||
TradeID string `json:"tradeId"`
|
||||
UpdateTime time.Time `json:"uTime"`
|
||||
AccumulatedFillSize convert.StringToFloat64 `json:"accFillSz"`
|
||||
AveragePrice convert.StringToFloat64 `json:"avgPx"`
|
||||
CreationTime okxUnixMilliTime `json:"cTime"`
|
||||
Category string `json:"category"`
|
||||
Currency string `json:"ccy"`
|
||||
ClientOrderID string `json:"clOrdId"`
|
||||
Fee convert.StringToFloat64 `json:"fee"`
|
||||
FeeCurrency currency.Code `json:"feeCcy"`
|
||||
LastFilledPrice convert.StringToFloat64 `json:"fillPx"`
|
||||
LastFilledSize convert.StringToFloat64 `json:"fillSz"`
|
||||
FillTime okxUnixMilliTime `json:"fillTime"`
|
||||
InstrumentID string `json:"instId"`
|
||||
InstrumentType string `json:"instType"`
|
||||
Leverage convert.StringToFloat64 `json:"lever"`
|
||||
OrderID string `json:"ordId"`
|
||||
OrderType string `json:"ordType"`
|
||||
ProfitAndLoss string `json:"pnl"`
|
||||
PositionSide string `json:"posSide"`
|
||||
RebateAmount convert.StringToFloat64 `json:"rebate"`
|
||||
RebateCurrency string `json:"rebateCcy"`
|
||||
Side order.Side `json:"side"`
|
||||
StopLossOrdPrice convert.StringToFloat64 `json:"slOrdPx"`
|
||||
StopLossTriggerPrice convert.StringToFloat64 `json:"slTriggerPx"`
|
||||
StopLossTriggerPriceType string `json:"slTriggerPxType"`
|
||||
State string `json:"state"`
|
||||
Price convert.StringToFloat64 `json:"px"`
|
||||
Size convert.StringToFloat64 `json:"sz"`
|
||||
Tag string `json:"tag"`
|
||||
SizeType string `json:"tgtCcy"`
|
||||
TradeMode string `json:"tdMode"`
|
||||
Source string `json:"source"`
|
||||
TakeProfitOrdPrice convert.StringToFloat64 `json:"tpOrdPx"`
|
||||
TakeProfitTriggerPrice convert.StringToFloat64 `json:"tpTriggerPx"`
|
||||
TakeProfitTriggerPriceType string `json:"tpTriggerPxType"`
|
||||
TradeID string `json:"tradeId"`
|
||||
UpdateTime okxUnixMilliTime `json:"uTime"`
|
||||
}
|
||||
|
||||
// TransactionDetailRequestParams retrieve recently-filled transaction details in the last 3 day.
|
||||
@@ -780,7 +780,7 @@ type TransactionDetail struct {
|
||||
Tag string `json:"tag"`
|
||||
FillPrice float64 `json:"fillPx,string"`
|
||||
FillSize float64 `json:"fillSz,string"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
PositionSide string `json:"posSide"`
|
||||
ExecType string `json:"execType"`
|
||||
FeeCurrency string `json:"feeCcy"`
|
||||
@@ -862,7 +862,7 @@ type AlgoOrderResponse struct {
|
||||
AlgoOrderID string `json:"algoId"`
|
||||
Quantity string `json:"sz"`
|
||||
OrderType string `json:"ordType"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
PositionSide string `json:"posSide"`
|
||||
TradeMode string `json:"tdMode"`
|
||||
QuantityType string `json:"tgtCcy"`
|
||||
@@ -1175,7 +1175,7 @@ type EstimateQuoteResponse struct {
|
||||
QuoteTime okxUnixMilliTime `json:"quoteTime"`
|
||||
RfqSize string `json:"rfqSz"`
|
||||
RfqSizeCurrency string `json:"rfqSzCcy"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
TTLMs string `json:"ttlMs"` // Validity period of quotation in milliseconds
|
||||
}
|
||||
|
||||
@@ -1201,7 +1201,7 @@ type ConvertTradeResponse struct {
|
||||
InstrumentID string `json:"instId"`
|
||||
QuoteCurrency string `json:"quoteCcy"`
|
||||
QuoteID string `json:"quoteId"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
State string `json:"state"`
|
||||
TradeID string `json:"tradeId"`
|
||||
Timestamp okxUnixMilliTime `json:"ts"`
|
||||
@@ -1210,7 +1210,7 @@ type ConvertTradeResponse struct {
|
||||
// ConvertHistory holds convert trade history response
|
||||
type ConvertHistory struct {
|
||||
InstrumentID string `json:"instId"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
FillPrice float64 `json:"fillPx,string"`
|
||||
BaseCurrency string `json:"baseCcy"`
|
||||
QuoteCurrency string `json:"quoteCcy"`
|
||||
@@ -1482,12 +1482,12 @@ type LeverageResponse struct {
|
||||
|
||||
// MaximumLoanInstrument represents maximum loan of an instrument id.
|
||||
type MaximumLoanInstrument struct {
|
||||
InstrumentID string `json:"instId"`
|
||||
MgnMode string `json:"mgnMode"`
|
||||
MgnCcy string `json:"mgnCcy"`
|
||||
MaxLoan string `json:"maxLoan"`
|
||||
Ccy string `json:"ccy"`
|
||||
Side string `json:"side"`
|
||||
InstrumentID string `json:"instId"`
|
||||
MgnMode string `json:"mgnMode"`
|
||||
MgnCcy string `json:"mgnCcy"`
|
||||
MaxLoan string `json:"maxLoan"`
|
||||
Ccy string `json:"ccy"`
|
||||
Side order.Side `json:"side"`
|
||||
}
|
||||
|
||||
// TradeFeeRate holds trade fee rate information for a given instrument type.
|
||||
@@ -1564,7 +1564,7 @@ type LoanBorrowAndReplay struct {
|
||||
Currency string `json:"ccy"`
|
||||
LoanQuota string `json:"loanQuota"`
|
||||
PosLoan string `json:"posLoan"`
|
||||
Side string `json:"side"`
|
||||
Side string `json:"side"` // borrow or repay
|
||||
UsedLoan string `json:"usedLoan"`
|
||||
}
|
||||
|
||||
@@ -2374,7 +2374,7 @@ type WSTradeData struct {
|
||||
TradeID string `json:"tradeId"`
|
||||
Price float64 `json:"px,string"`
|
||||
Size float64 `json:"sz,string"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
Timestamp okxUnixMilliTime `json:"ts"`
|
||||
}
|
||||
|
||||
@@ -2500,16 +2500,16 @@ type WsBalanceAndPosition struct {
|
||||
// WsOrder represents a websocket order.
|
||||
type WsOrder struct {
|
||||
PendingOrderItem
|
||||
AmendResult string `json:"amendResult"`
|
||||
Code string `json:"code"`
|
||||
ExecType string `json:"execType"`
|
||||
FillFee string `json:"fillFee"`
|
||||
FillFeeCurrency string `json:"fillFeeCcy"`
|
||||
FillNationalUsd float64 `json:"fillNationalUsd,string"`
|
||||
Msg string `json:"msg"`
|
||||
NationalUSD string `json:"nationalUsd"`
|
||||
ReduceOnly bool `json:"reduceOnly"`
|
||||
RequestID string `json:"reqId"`
|
||||
AmendResult string `json:"amendResult"`
|
||||
Code string `json:"code"`
|
||||
ExecType string `json:"execType"`
|
||||
FillFee convert.StringToFloat64 `json:"fillFee"`
|
||||
FillFeeCurrency string `json:"fillFeeCcy"`
|
||||
FillNotionalUsd convert.StringToFloat64 `json:"fillNotionalUsd"`
|
||||
Msg string `json:"msg"`
|
||||
NotionalUSD convert.StringToFloat64 `json:"notionalUsd"`
|
||||
ReduceOnly bool `json:"reduceOnly,string"`
|
||||
RequestID string `json:"reqId"`
|
||||
}
|
||||
|
||||
// WsOrderResponse holds order list push data through the websocket connection
|
||||
@@ -2537,7 +2537,7 @@ type WsAlgoOrderDetail struct {
|
||||
TargetCurrency string `json:"tgtCcy"`
|
||||
NotionalUsd string `json:"notionalUsd"`
|
||||
OrderType string `json:"ordType"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
PositionSide string `json:"posSide"`
|
||||
State string `json:"state"`
|
||||
Leverage string `json:"lever"`
|
||||
@@ -2581,7 +2581,7 @@ type WsAdvancedAlgoOrderDetail struct {
|
||||
PriceLimit string `json:"pxLimit"`
|
||||
PriceSpread string `json:"pxSpread"`
|
||||
PriceVariation string `json:"pxVar"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
StopLossOrderPrice string `json:"slOrdPx"`
|
||||
StopLossTriggerPrice string `json:"slTriggerPx"`
|
||||
State string `json:"state"`
|
||||
@@ -2839,7 +2839,7 @@ type GridSubOrderData struct {
|
||||
ProfitAdLoss string `json:"pnl"`
|
||||
PositionSide string `json:"posSide"`
|
||||
Price string `json:"px"`
|
||||
Side string `json:"side"`
|
||||
Side order.Side `json:"side"`
|
||||
State string `json:"state"`
|
||||
Size string `json:"sz"`
|
||||
Tag string `json:"tag"`
|
||||
|
||||
@@ -1009,18 +1009,13 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(response.Data[i].Side)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for j := range assets {
|
||||
trades = append(trades, trade.Data{
|
||||
Amount: response.Data[i].Quantity,
|
||||
AssetType: assets[j],
|
||||
CurrencyPair: pair,
|
||||
Exchange: ok.Name,
|
||||
Side: side,
|
||||
Side: response.Data[i].Side,
|
||||
Timestamp: response.Data[i].Timestamp.Time(),
|
||||
TID: response.Data[i].TradeID,
|
||||
Price: response.Data[i].Price,
|
||||
@@ -1060,40 +1055,62 @@ func (ok *Okx) wsProcessOrders(respRaw []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
avgPrice := response.Data[x].AveragePrice.Float64()
|
||||
orderAmount := response.Data[x].Size
|
||||
orderAmount := response.Data[x].Size.Float64()
|
||||
execAmount := response.Data[x].AccumulatedFillSize.Float64()
|
||||
|
||||
var quoteAmount float64
|
||||
if response.Data[x].QuantityType == "quote_ccy" {
|
||||
if response.Data[x].SizeType == "quote_ccy" {
|
||||
// Size is quote amount.
|
||||
quoteAmount = orderAmount
|
||||
if avgPrice > 0 {
|
||||
orderAmount /= avgPrice
|
||||
if orderStatus == order.Filled {
|
||||
// We prefer to take execAmount over calculating from quoteAmount / avgPrice
|
||||
// because it avoids rounding issues
|
||||
orderAmount = execAmount
|
||||
} else {
|
||||
// Size not in Base, and we can't derive a sane value for it
|
||||
orderAmount = 0
|
||||
if avgPrice > 0 {
|
||||
orderAmount /= avgPrice
|
||||
} else {
|
||||
// Size not in Base, and we can't derive a sane value for it
|
||||
orderAmount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var remainingAmount float64
|
||||
if orderStatus != order.Filled {
|
||||
remainingAmount = orderAmount - response.Data[x].AccumulatedFillSize.Float64()
|
||||
// Float64 rounding may lead to execAmount > orderAmount by a tiny fraction
|
||||
// noting that the order can be fully executed before it's marked as status Filled
|
||||
if orderStatus != order.Filled && orderAmount > execAmount {
|
||||
remainingAmount = orderAmount - execAmount
|
||||
}
|
||||
ok.Websocket.DataHandler <- &order.Detail{
|
||||
Price: response.Data[x].Price,
|
||||
|
||||
d := &order.Detail{
|
||||
Amount: orderAmount,
|
||||
QuoteAmount: quoteAmount,
|
||||
ExecutedAmount: response.Data[x].AccumulatedFillSize.Float64(),
|
||||
RemainingAmount: remainingAmount,
|
||||
AssetType: a,
|
||||
AverageExecutedPrice: avgPrice,
|
||||
Exchange: ok.Name,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
ClientOrderID: response.Data[x].ClientOrderID,
|
||||
Type: orderType,
|
||||
Date: response.Data[x].CreationTime.Time(),
|
||||
Exchange: ok.Name,
|
||||
ExecutedAmount: execAmount,
|
||||
Fee: 0.0 - response.Data[x].Fee.Float64(),
|
||||
FeeAsset: response.Data[x].FeeCurrency,
|
||||
OrderID: response.Data[x].OrderID,
|
||||
Pair: pair,
|
||||
Price: response.Data[x].Price.Float64(),
|
||||
QuoteAmount: quoteAmount,
|
||||
RemainingAmount: remainingAmount,
|
||||
Side: response.Data[x].Side,
|
||||
Status: orderStatus,
|
||||
AssetType: a,
|
||||
Date: response.Data[x].CreationTime,
|
||||
Pair: pair,
|
||||
Type: orderType,
|
||||
}
|
||||
if orderStatus == order.Filled {
|
||||
d.CloseTime = response.Data[x].FillTime.Time()
|
||||
if d.Amount == 0 {
|
||||
d.Amount = d.ExecutedAmount
|
||||
}
|
||||
}
|
||||
ok.Websocket.DataHandler <- d
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -684,18 +684,13 @@ func (ok *Okx) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a
|
||||
}
|
||||
|
||||
resp := make([]trade.Data, len(tradeData))
|
||||
var side order.Side
|
||||
for x := range tradeData {
|
||||
side, err = order.StringToOrderSide(tradeData[x].Side)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp[x] = trade.Data{
|
||||
TID: tradeData[x].TradeID,
|
||||
Exchange: ok.Name,
|
||||
CurrencyPair: p,
|
||||
AssetType: assetType,
|
||||
Side: side,
|
||||
Side: tradeData[x].Side,
|
||||
Price: tradeData[x].Price,
|
||||
Amount: tradeData[x].Quantity,
|
||||
Timestamp: tradeData[x].Timestamp.Time(),
|
||||
@@ -744,11 +739,6 @@ allTrades:
|
||||
// reached end of trades to crawl
|
||||
break allTrades
|
||||
}
|
||||
var tradeSide order.Side
|
||||
tradeSide, err = order.StringToOrderSide(trades[i].Side)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = append(resp, trade.Data{
|
||||
TID: trades[i].TradeID,
|
||||
Exchange: ok.Name,
|
||||
@@ -757,7 +747,7 @@ allTrades:
|
||||
Price: trades[i].Price,
|
||||
Amount: trades[i].Quantity,
|
||||
Timestamp: trades[i].Timestamp.Time(),
|
||||
Side: tradeSide,
|
||||
Side: trades[i].Side,
|
||||
})
|
||||
}
|
||||
tradeIDEnd = trades[len(trades)-1].TradeID
|
||||
|
||||
4
exchanges/okx/testdata/wsOrders.json
vendored
Normal file
4
exchanges/okx/testdata/wsOrders.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{"arg":{"channel":"orders","instType":"SPOT","instId":"BTC-USDT","uid":"614488474791936"},"data":[{"accFillSz":"0.001","amendResult":"","avgPx":"31527.1","cTime":"1654084334977","category":"normal","ccy":"","clOrdId":"HamsterParty14","code":"0","execType":"M","fee":"-0.02522168","feeCcy":"USDT","fillFee":"-0.02522168","fillFeeCcy":"USDT","fillNotionalUsd":"31.50818374","fillPx":"31527.1","fillSz":"0.001","fillTime":"1654084353263","instId":"BTC-USDT","instType":"SPOT","lever":"0","msg":"","notionalUsd":"31.50818374","ordId":"452197707845865472","ordType":"limit","pnl":"0","posSide":"","px":"31527.1","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","reqId":"","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"last","source":"","state":"filled","sz":"0.001","tag":"","tdMode":"cash","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"last","tradeId":"242589207","uTime":"1654084353264"}]}
|
||||
{"arg":{"channel":"orders","instType":"SPOT","uid":"448743607034327908"},"data":[{"accFillSz":"0","algoClOrdId":"","algoId":"","amendResult":"","amendSource":"","attachAlgoClOrdId":"","avgPx":"0","cTime":"1694153250532","cancelSource":"","category":"normal","ccy":"","clOrdId":"","code":"0","execType":"","fee":"0","feeCcy":"USDT","fillFee":"0","fillFeeCcy":"","fillFwdPx":"","fillMarkPx":"","fillMarkVol":"","fillNotionalUsd":"","fillPnl":"0","fillPx":"","fillPxUsd":"","fillPxVol":"","fillSz":"0","fillTime":"","instId":"BTC-USDT","instType":"SPOT","lever":"0","msg":"","notionalUsd":"10.000599999999999","ordId":"620258920632008725","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","reqId":"","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"live","stpId":"","stpMode":"","sz":"10","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"","uTime":"1694153250532"}]}
|
||||
{"arg":{"channel":"orders","instType":"SPOT","uid":"448743607034327908"},"data":[{"accFillSz":"0.00038128","algoClOrdId":"","algoId":"","amendResult":"","amendSource":"","attachAlgoClOrdId":"","avgPx":"26228.1","cTime":"1694153250532","cancelSource":"","category":"normal","ccy":"","clOrdId":"","code":"0","execType":"T","fee":"-0.010000249968","feeCcy":"USDT","fillFee":"-0.010000249968","fillFeeCcy":"USDT","fillFwdPx":"","fillMarkPx":"","fillMarkVol":"","fillNotionalUsd":"10.00084998299808","fillPnl":"0","fillPx":"26228.1","fillPxUsd":"","fillPxVol":"","fillSz":"0.00038128","fillTime":"1694153250535","instId":"BTC-USDT","instType":"SPOT","lever":"0","msg":"","notionalUsd":"10.000599999999999","ordId":"620258920632008725","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","reqId":"","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"partially_filled","stpId":"","stpMode":"","sz":"10","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"435550732","uTime":"1694153250535"}]}
|
||||
{"arg":{"channel":"orders","instType":"SPOT","uid":"448743607034327908"},"data":[{"accFillSz":"0.00038128","algoClOrdId":"","algoId":"","amendResult":"","amendSource":"","attachAlgoClOrdId":"","avgPx":"26228.1","cTime":"1694153250532","cancelSource":"","category":"normal","ccy":"","clOrdId":"","code":"0","execType":"","fee":"-0.010000249968","feeCcy":"USDT","fillFee":"0","fillFeeCcy":"","fillFwdPx":"","fillMarkPx":"","fillMarkVol":"","fillNotionalUsd":"10.00084998299808","fillPnl":"0","fillPx":"","fillPxUsd":"","fillPxVol":"","fillSz":"0","fillTime":"","instId":"BTC-USDT","instType":"SPOT","lever":"0","msg":"","notionalUsd":"10.000599999999999","ordId":"620258920632008725","ordType":"market","pnl":"0","posSide":"","px":"","pxType":"","pxUsd":"","pxVol":"","quickMgnType":"","rebate":"0","rebateCcy":"BTC","reduceOnly":"false","reqId":"","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"","source":"","state":"filled","stpId":"","stpMode":"","sz":"10","tag":"","tdMode":"cash","tgtCcy":"quote_ccy","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"","tradeId":"","uTime":"1694153250535"}]}
|
||||
@@ -1,6 +1,7 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
@@ -2045,3 +2047,13 @@ func TestAdjustQuoteAmount(t *testing.T) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.22222222)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSideUnmarshal(t *testing.T) {
|
||||
t.Parallel()
|
||||
var s Side
|
||||
assert.Nil(t, s.UnmarshalJSON([]byte(`"SELL"`)), "Quoted valid side okay")
|
||||
assert.Equal(t, Sell, s, "Correctly set order Side")
|
||||
assert.ErrorIs(t, s.UnmarshalJSON([]byte(`"STEAL"`)), ErrSideIsInvalid, "Quoted invalid side errors")
|
||||
var jErr *json.UnmarshalTypeError
|
||||
assert.ErrorAs(t, s.UnmarshalJSON([]byte(`14`)), &jErr, "non-string valid json is rejected")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -1060,6 +1063,18 @@ func StringToOrderSide(side string) (Side, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the JSON-encoded order side and stores the result
|
||||
// It expects a quoted string input, and uses StringToOrderSide to parse it
|
||||
func (s *Side) UnmarshalJSON(data []byte) (err error) {
|
||||
if !bytes.HasPrefix(data, []byte(`"`)) {
|
||||
// Note that we don't need to worry about invalid JSON here, it wouldn't have made it past the deserialiser far
|
||||
// TODO: Can use reflect.TypeFor[s]() when it's released, probably 1.21
|
||||
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(s), Offset: 1}
|
||||
}
|
||||
*s, err = StringToOrderSide(string(data[1 : len(data)-1])) // Remove quotes
|
||||
return
|
||||
}
|
||||
|
||||
// StringToOrderType for converting case insensitive order type
|
||||
// and returning a real Type
|
||||
func StringToOrderType(oType string) (Type, error) {
|
||||
|
||||
@@ -154,6 +154,7 @@ func ForceFileStandard(t *testing.T, pattern string) error {
|
||||
// TestFixtureToDataHandler takes a new empty exchange and configures a new websocket handler for it, and squirts the json path contents to it
|
||||
// It accepts a reader function, which is probably e.wsHandleData but could be anything
|
||||
func TestFixtureToDataHandler(t *testing.T, seed, e exchange.IBotExchange, fixturePath string, reader func([]byte) error) {
|
||||
t.Helper()
|
||||
b := e.GetBase()
|
||||
seedBase := seed.GetBase()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user