diff --git a/exchanges/okx/okx_test.go b/exchanges/okx/okx_test.go index 15325657..bcbca667 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -3125,3 +3125,100 @@ func TestGetIntervalEnum(t *testing.T) { }) } } + +const instrumentJSON = `{"alias":"","baseCcy":"","category":"1","ctMult":"1","ctType":"linear","ctVal":"0.0001","ctValCcy":"BTC","expTime":"","instFamily":"BTC-USDC","instId":"BTC-USDC-SWAP","instType":"SWAP","lever":"125","listTime":"1666076190000","lotSz":"1","maxIcebergSz":"100000000.0000000000000000","maxLmtSz":"100000000","maxMktSz":"85000","maxStopSz":"85000","maxTriggerSz":"100000000.0000000000000000","maxTwapSz":"","minSz":"1","optType":"","quoteCcy":"","settleCcy":"USDC","state":"live","stk":"","tickSz":"0.1","uly":"BTC-USDC"}` + +func TestInstrument(t *testing.T) { + t.Parallel() + + var i Instrument + err := json.Unmarshal([]byte(instrumentJSON), &i) + if err != nil { + t.Error(err) + } + + if i.Alias != "" { + t.Error("expected empty alias") + } + if i.BaseCurrency != "" { + t.Error("expected empty base currency") + } + if i.Category != "1" { + t.Error("expected 1 category") + } + if i.ContractMultiplier != "1" { + t.Error("expected 1 contract multiplier") + } + if i.ContractType != "linear" { + t.Error("expected linear contract type") + } + if i.ContractValue != "0.0001" { + t.Error("expected 0.0001 contract value") + } + if i.ContractValueCurrency != currency.BTC.String() { + t.Error("expected BTC contract value currency") + } + if !i.ExpTime.IsZero() { + t.Error("expected empty expiry time") + } + if i.InstrumentFamily != "BTC-USDC" { + t.Error("expected BTC-USDC instrument family") + } + if i.InstrumentID != "BTC-USDC-SWAP" { + t.Error("expected BTC-USDC-SWAP instrument ID") + } + if i.InstrumentType != asset.PerpetualSwap { + t.Error("expected SWAP instrument type") + } + if i.MaxLeverage != 125 { + t.Error("expected 125 leverage") + } + if i.ListTime.UnixMilli() != 1666076190000 { + t.Error("expected 1666076190000 listing time") + } + if i.LotSize != 1 { + t.Error("expected 1 lot size") + } + if i.MaxSpotIcebergSize != 100000000.0000000000000000 { + t.Error("expected 100000000.0000000000000000 max iceberg order size") + } + if i.MaxQuantityOfSpotLimitOrder != 100000000 { + t.Error("expected 100000000 max limit order size") + } + if i.MaxQuantityOfMarketLimitOrder != 85000 { + t.Error("expected 85000 max market order size") + } + if i.MaxStopSize != 85000 { + t.Error("expected 85000 max stop order size") + } + if i.MaxTriggerSize != 100000000.0000000000000000 { + t.Error("expected 100000000.0000000000000000 max trigger order size") + } + if i.MaxQuantityOfSpotTwapLimitOrder != 0 { + t.Error("expected empty max TWAP size") + } + if i.MinimumOrderSize != 1 { + t.Error("expected 1 min size") + } + if i.OptionType != "" { + t.Error("expected empty option type") + } + if i.QuoteCurrency != "" { + t.Error("expected empty quote currency") + } + if i.SettlementCurrency != currency.USDC.String() { + t.Error("expected USDC settlement currency") + } + if i.State != "live" { + t.Error("expected live state") + } + if i.StrikePrice != "" { + t.Error("expected empty strike price") + } + if i.TickSize != 0.1 { + t.Error("expected 0.1 tick size") + } + if i.Underlying != "BTC-USDC" { + t.Error("expected BTC-USDC underlying") + } +} diff --git a/exchanges/okx/okx_type_convert.go b/exchanges/okx/okx_type_convert.go index 132339c6..acd0d746 100644 --- a/exchanges/okx/okx_type_convert.go +++ b/exchanges/okx/okx_type_convert.go @@ -2,7 +2,6 @@ package okx import ( "encoding/json" - "regexp" "strconv" "strings" "time" @@ -65,40 +64,84 @@ func (a *okxUnixMilliTime) Time() time.Time { return time.UnixMilli(int64(*a)) } -// numbersOnlyRegexp for checking the value is numerics only -var numbersOnlyRegexp = regexp.MustCompile(`^\d*$`) +type okxTime struct { + time.Time +} + +// UnmarshalJSON deserializes byte data to okxTime instance. +func (t *okxTime) UnmarshalJSON(data []byte) error { + var num string + err := json.Unmarshal(data, &num) + if err != nil { + return err + } + if num == "" { + return nil + } + value, err := strconv.ParseInt(num, 10, 64) + if err != nil { + return err + } + t.Time = time.UnixMilli(value) + return nil +} + +type okxAssetType struct { + asset.Item +} + +// UnmarshalJSON deserializes JSON, and timestamp information. +func (a *okxAssetType) UnmarshalJSON(data []byte) error { + var t string + err := json.Unmarshal(data, &t) + if err != nil { + return err + } + + a.Item, err = GetAssetTypeFromInstrumentType(strings.ToUpper(t)) + + return err +} // UnmarshalJSON deserializes JSON, and timestamp information. func (a *Instrument) UnmarshalJSON(data []byte) error { type Alias Instrument chil := &struct { *Alias - ListTime string `json:"listTime"` - ExpTime string `json:"expTime"` - InstrumentType string `json:"instType"` + ListTime okxTime `json:"listTime"` + ExpTime okxTime `json:"expTime"` + InstrumentType okxAssetType `json:"instType"` + MaxLeverage okxNumericalValue `json:"lever"` + TickSize okxNumericalValue `json:"tickSz"` + LotSize okxNumericalValue `json:"lotSz"` + MinimumOrderSize okxNumericalValue `json:"minSz"` + MaxQuantityOfSpotLimitOrder okxNumericalValue `json:"maxLmtSz"` + MaxQuantityOfMarketLimitOrder okxNumericalValue `json:"maxMktSz"` + MaxQuantityOfSpotTwapLimitOrder okxNumericalValue `json:"maxTwapSz"` + MaxSpotIcebergSize okxNumericalValue `json:"maxIcebergSz"` + MaxTriggerSize okxNumericalValue `json:"maxTriggerSz"` + MaxStopSize okxNumericalValue `json:"maxStopSz"` }{ Alias: (*Alias)(a), } - err := json.Unmarshal(data, chil) - if err != nil { - return err - } - if numbersOnlyRegexp.MatchString(chil.ListTime) { - var val int - if val, err = strconv.Atoi(chil.ListTime); err == nil { - a.ListTime = time.UnixMilli(int64(val)) - } - } - if numbersOnlyRegexp.MatchString(chil.ExpTime) { - var val int - if val, err = strconv.Atoi(chil.ExpTime); err == nil { - a.ExpTime = time.UnixMilli(int64(val)) - } - } - chil.InstrumentType = strings.ToUpper(chil.InstrumentType) - if a.InstrumentType, err = GetAssetTypeFromInstrumentType(chil.InstrumentType); err != nil { + if err := json.Unmarshal(data, chil); err != nil { return err } + + a.ListTime = chil.ListTime.Time + a.ExpTime = chil.ExpTime.Time + a.InstrumentType = chil.InstrumentType.Item + a.MaxLeverage = chil.MaxLeverage.Float64() + a.TickSize = chil.TickSize.Float64() + a.LotSize = chil.LotSize.Float64() + a.MinimumOrderSize = chil.MinimumOrderSize.Float64() + a.MaxQuantityOfSpotLimitOrder = chil.MaxQuantityOfSpotLimitOrder.Float64() + a.MaxQuantityOfMarketLimitOrder = chil.MaxQuantityOfMarketLimitOrder.Float64() + a.MaxQuantityOfSpotTwapLimitOrder = chil.MaxQuantityOfSpotTwapLimitOrder.Float64() + a.MaxSpotIcebergSize = chil.MaxSpotIcebergSize.Float64() + a.MaxTriggerSize = chil.MaxTriggerSize.Float64() + a.MaxStopSize = chil.MaxStopSize.Float64() + return nil } diff --git a/exchanges/okx/okx_types.go b/exchanges/okx/okx_types.go index dc08b75f..2a35605f 100644 --- a/exchanges/okx/okx_types.go +++ b/exchanges/okx/okx_types.go @@ -274,33 +274,34 @@ type InstrumentsFetchParams struct { // Instrument representing an instrument with open contract. type Instrument struct { - InstrumentType asset.Item `json:"instType"` - InstrumentID string `json:"instId"` - Underlying string `json:"uly"` - Category string `json:"category"` - BaseCurrency string `json:"baseCcy"` - QuoteCurrency string `json:"quoteCcy"` - SettlementCurrency string `json:"settleCcy"` - ContractValue string `json:"ctVal"` - ContractMultiplier string `json:"ctMult"` - ContractValueCurrency string `json:"ctValCcy"` - OptionType string `json:"optType"` - StrikePrice string `json:"stk"` - ListTime time.Time `json:"listTime"` - ExpTime time.Time `json:"expTime"` - MaxLeverage okxNumericalValue `json:"lever"` - TickSize okxNumericalValue `json:"tickSz"` - LotSize okxNumericalValue `json:"lotSz"` - MinimumOrderSize okxNumericalValue `json:"minSz"` - ContractType string `json:"ctType"` - Alias string `json:"alias"` - State string `json:"state"` - MaxQuantityOfSpotLimitOrder float64 `json:"maxLmtSz,string"` - MaxQuantityOfMarketLimitOrder float64 `json:"maxMktSz,string"` - MaxQuantityOfSpotTwapLimitOrder float64 `json:"maxTwapSz,string"` - MaxSpotIcebergSize float64 `json:"maxIcebergSz,string"` - MaxTriggerSize float64 `json:"maxTriggerSz,string"` - MaxStopSize float64 `json:"maxStopSz,string"` + InstrumentType asset.Item `json:"instType"` + InstrumentID string `json:"instId"` + InstrumentFamily string `json:"instFamily"` + Underlying string `json:"uly"` + Category string `json:"category"` + BaseCurrency string `json:"baseCcy"` + QuoteCurrency string `json:"quoteCcy"` + SettlementCurrency string `json:"settleCcy"` + ContractValue string `json:"ctVal"` + ContractMultiplier string `json:"ctMult"` + ContractValueCurrency string `json:"ctValCcy"` + OptionType string `json:"optType"` + StrikePrice string `json:"stk"` + ListTime time.Time `json:"listTime"` + ExpTime time.Time `json:"expTime"` + MaxLeverage float64 `json:"lever"` + TickSize float64 `json:"tickSz"` + LotSize float64 `json:"lotSz"` + MinimumOrderSize float64 `json:"minSz"` + ContractType string `json:"ctType"` + Alias string `json:"alias"` + State string `json:"state"` + MaxQuantityOfSpotLimitOrder float64 `json:"maxLmtSz"` + MaxQuantityOfMarketLimitOrder float64 `json:"maxMktSz"` + MaxQuantityOfSpotTwapLimitOrder float64 `json:"maxTwapSz"` + MaxSpotIcebergSize float64 `json:"maxIcebergSz"` + MaxTriggerSize float64 `json:"maxTriggerSz"` + MaxStopSize float64 `json:"maxStopSz"` } // DeliveryHistoryDetail holds instrument id and delivery price information detail diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index bdcb411b..617bd89d 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -341,8 +341,8 @@ func (ok *Okx) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) err limits[x] = order.MinMaxLevel{ Pair: pair, Asset: a, - PriceStepIncrementSize: insts[x].TickSize.Float64(), - MinAmount: insts[x].MinimumOrderSize.Float64(), + PriceStepIncrementSize: insts[x].TickSize, + MinAmount: insts[x].MinimumOrderSize, } }