pkg/exchange: implement order trade user stream

This commit is contained in:
Edwin 2023-11-14 11:23:30 +08:00
parent 720fe2e12e
commit 4f94f7acc0
6 changed files with 718 additions and 4 deletions

View File

@ -362,3 +362,120 @@ func toGlobalKLines(symbol string, interval types.Interval, kLines v2.KLineRespo
}
return gKLines
}
func toGlobalTimeInForce(force v2.OrderForce) (types.TimeInForce, error) {
switch force {
case v2.OrderForceFOK:
return types.TimeInForceFOK, nil
case v2.OrderForceGTC, v2.OrderForcePostOnly:
return types.TimeInForceGTC, nil
case v2.OrderForceIOC:
return types.TimeInForceIOC, nil
default:
return "", fmt.Errorf("unexpected time-in-force: %s", force)
}
}
func (o *Order) processMarketBuyQuantity() (fixedpoint.Value, error) {
switch o.Status {
case v2.OrderStatusLive, v2.OrderStatusNew, v2.OrderStatusInit, v2.OrderStatusCancelled:
return fixedpoint.Zero, nil
case v2.OrderStatusPartialFilled:
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 v2.OrderStatusFilled:
return o.AccBaseVolume, nil
default:
return fixedpoint.Zero, fmt.Errorf("unexpected status: %s", o.Status)
}
}
func (o *Order) toGlobalOrder() (types.Order, error) {
side, err := toGlobalSideType(o.Side)
if err != nil {
return types.Order{}, err
}
orderType, err := toGlobalOrderType(o.OrderType)
if err != nil {
return types.Order{}, err
}
timeInForce, err := toGlobalTimeInForce(o.Force)
if err != nil {
return types.Order{}, err
}
status, err := toGlobalOrderStatus(o.Status)
if err != nil {
return types.Order{}, err
}
qty := o.Size
if orderType == types.OrderTypeMarket && side == types.SideTypeBuy {
qty, err = o.processMarketBuyQuantity()
if err != nil {
return types.Order{}, err
}
}
return types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: o.ClientOrderId,
Symbol: o.InstId,
Side: side,
Type: orderType,
Quantity: qty,
Price: o.PriceAvg,
TimeInForce: timeInForce,
},
Exchange: types.ExchangeBitget,
OrderID: uint64(o.OrderId),
UUID: strconv.FormatInt(int64(o.OrderId), 10),
Status: status,
ExecutedQuantity: o.AccBaseVolume,
IsWorking: o.Status.IsWorking(),
CreationTime: types.Time(o.CTime.Time()),
UpdateTime: types.Time(o.UTime.Time()),
}, nil
}
func (o *Order) toGlobalTrade() (types.Trade, error) {
if o.Status != v2.OrderStatusPartialFilled {
return types.Trade{}, fmt.Errorf("failed to convert to global trade, unexpected status: %s", o.Status)
}
side, err := toGlobalSideType(o.Side)
if err != nil {
return types.Trade{}, err
}
isMaker, err := o.isMaker()
if err != nil {
return types.Trade{}, err
}
return types.Trade{
ID: uint64(o.TradeId),
OrderID: uint64(o.OrderId),
Exchange: types.ExchangeBitget,
Price: o.FillPrice,
Quantity: o.BaseVolume,
QuoteQuantity: o.FillPrice.Mul(o.BaseVolume),
Symbol: o.InstId,
Side: side,
IsBuyer: side == types.SideTypeBuy,
IsMaker: isMaker,
Time: types.Time(o.FillTime),
Fee: o.FillFee.Abs(),
FeeCurrency: o.FillFeeCoin,
}, nil
}

View File

@ -686,3 +686,452 @@ func Test_toGlobalKLines(t *testing.T) {
assert.Equal(t, toGlobalKLines(symbol, interval, resp), expKlines)
}
func Test_toGlobalTimeInForce(t *testing.T) {
force, err := toGlobalTimeInForce(v2.OrderForceFOK)
assert.NoError(t, err)
assert.Equal(t, types.TimeInForceFOK, force)
force, err = toGlobalTimeInForce(v2.OrderForceGTC)
assert.NoError(t, err)
assert.Equal(t, types.TimeInForceGTC, force)
force, err = toGlobalTimeInForce(v2.OrderForcePostOnly)
assert.NoError(t, err)
assert.Equal(t, types.TimeInForceGTC, force)
force, err = toGlobalTimeInForce(v2.OrderForceIOC)
assert.NoError(t, err)
assert.Equal(t, types.TimeInForceIOC, force)
_, err = toGlobalTimeInForce("xxx")
assert.ErrorContains(t, err, "xxx")
}
func TestOrder_processMarketBuyQuantity(t *testing.T) {
t.Run("zero qty", func(t *testing.T) {
o := Order{}
for _, s := range []v2.OrderStatus{v2.OrderStatusLive, v2.OrderStatusNew, v2.OrderStatusInit, v2.OrderStatusCancelled} {
o.Status = s
qty, err := o.processMarketBuyQuantity()
assert.NoError(t, err)
assert.Equal(t, fixedpoint.Zero, qty)
}
})
t.Run("calculate qty", func(t *testing.T) {
o := Order{
Size: fixedpoint.NewFromFloat(2),
Trade: Trade{
FillPrice: fixedpoint.NewFromFloat(1),
},
Status: v2.OrderStatusPartialFilled,
}
qty, err := o.processMarketBuyQuantity()
assert.NoError(t, err)
assert.Equal(t, fixedpoint.NewFromFloat(2), qty)
})
t.Run("return accumulated balance", func(t *testing.T) {
o := Order{
AccBaseVolume: fixedpoint.NewFromFloat(5),
Status: v2.OrderStatusFilled,
}
qty, err := o.processMarketBuyQuantity()
assert.NoError(t, err)
assert.Equal(t, fixedpoint.NewFromFloat(5), qty)
})
t.Run("unexpected status", func(t *testing.T) {
o := Order{
Status: "xxx",
}
_, err := o.processMarketBuyQuantity()
assert.ErrorContains(t, err, "xxx")
})
}
func TestOrder_toGlobalOrder(t *testing.T) {
o := Order{
Trade: Trade{
FillPrice: fixedpoint.NewFromFloat(0.49016),
TradeId: types.StrInt64(1107950490073112582),
BaseVolume: fixedpoint.NewFromFloat(33.6558),
FillTime: types.NewMillisecondTimestampFromInt(1699881902235),
FillFee: fixedpoint.NewFromFloat(-0.0336558),
FillFeeCoin: "BGB",
TradeScope: "T",
},
InstId: "BGBUSDT",
OrderId: types.StrInt64(1107950489998626816),
ClientOrderId: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Size: fixedpoint.NewFromFloat(39.0),
Notional: fixedpoint.NewFromFloat(39.0),
OrderType: v2.OrderTypeMarket,
Force: v2.OrderForceGTC,
Side: v2.SideTypeBuy,
AccBaseVolume: fixedpoint.NewFromFloat(33.6558),
PriceAvg: fixedpoint.NewFromFloat(0.49016),
Status: v2.OrderStatusPartialFilled,
CTime: types.NewMillisecondTimestampFromInt(1699881902217),
UTime: types.NewMillisecondTimestampFromInt(1699881902248),
FeeDetail: nil,
EnterPointSource: "API",
}
// market buy example:
// {
// "instId":"BGBUSDT",
// "orderId":"1107950489998626816",
// "clientOid":"cc73aab9-1e44-4022-8458-60d8c6a08753",
// "size":"39.0000",
// "notional":"39.000000",
// "orderType":"market",
// "force":"gtc",
// "side":"buy",
// "fillPrice":"0.49016",
// "tradeId":"1107950490073112582",
// "baseVolume":"33.6558",
// "fillTime":"1699881902235",
// "fillFee":"-0.0336558",
// "fillFeeCoin":"BGB",
// "tradeScope":"T",
// "accBaseVolume":"33.6558",
// "priceAvg":"0.49016",
// "status":"partially_filled",
// "cTime":"1699881902217",
// "uTime":"1699881902248",
// "feeDetail":[
// {
// "feeCoin":"BGB",
// "fee":"-0.0336558"
// }
// ],
// "enterPointSource":"API"
// }
t.Run("market buy", func(t *testing.T) {
newO := o
res, err := newO.toGlobalOrder()
assert.NoError(t, err)
assert.Equal(t, types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Symbol: "BGBUSDT",
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: newO.Size.Div(newO.FillPrice),
Price: newO.PriceAvg,
TimeInForce: types.TimeInForceGTC,
},
Exchange: types.ExchangeBitget,
OrderID: uint64(newO.OrderId),
UUID: strconv.FormatInt(int64(newO.OrderId), 10),
Status: types.OrderStatusPartiallyFilled,
ExecutedQuantity: newO.AccBaseVolume,
IsWorking: newO.Status.IsWorking(),
CreationTime: types.Time(newO.CTime),
UpdateTime: types.Time(newO.UTime),
}, res)
})
// market sell example:
// {
// "instId":"BGBUSDT",
// "orderId":"1107940456212631553",
// "clientOid":"088bb971-858e-48e2-b503-85c3274edd89",
// "size":"285.0000",
// "orderType":"market",
// "force":"gtc",
// "side":"sell",
// "fillPrice":"0.48706",
// "tradeId":"1107940456278728706",
// "baseVolume":"22.5840",
// "fillTime":"1699879509992",
// "fillFee":"-0.01099976304",
// "fillFeeCoin":"USDT",
// "tradeScope":"T",
// "accBaseVolume":"45.1675",
// "priceAvg":"0.48706",
// "status":"partially_filled",
// "cTime":"1699879509976",
// "uTime":"1699879510007",
// "feeDetail":[
// {
// "feeCoin":"USDT",
// "fee":"-0.02199928255"
// }
// ],
// "enterPointSource":"API"
// }
t.Run("market sell", func(t *testing.T) {
newO := o
newO.OrderType = v2.OrderTypeMarket
newO.Side = v2.SideTypeSell
res, err := newO.toGlobalOrder()
assert.NoError(t, err)
assert.Equal(t, types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Symbol: "BGBUSDT",
Side: types.SideTypeSell,
Type: types.OrderTypeMarket,
Quantity: newO.Size,
Price: newO.PriceAvg,
TimeInForce: types.TimeInForceGTC,
},
Exchange: types.ExchangeBitget,
OrderID: uint64(newO.OrderId),
UUID: strconv.FormatInt(int64(newO.OrderId), 10),
Status: types.OrderStatusPartiallyFilled,
ExecutedQuantity: newO.AccBaseVolume,
IsWorking: newO.Status.IsWorking(),
CreationTime: types.Time(newO.CTime),
UpdateTime: types.Time(newO.UTime),
}, res)
})
// limit buy example:
// {
// "instId":"BGBUSDT",
// "orderId":"1107955329902481408",
// "clientOid":"c578164a-bf34-44ba-8bb7-a1538f33b1b8",
// "price":"0.49998",
// "size":"24.9990",
// "notional":"24.999000",
// "orderType":"limit",
// "force":"gtc",
// "side":"buy",
// "fillPrice":"0.49998",
// "tradeId":"1107955401758285828",
// "baseVolume":"15.9404",
// "fillTime":"1699883073272",
// "fillFee":"-0.0159404",
// "fillFeeCoin":"BGB",
// "tradeScope":"M",
// "accBaseVolume":"15.9404",
// "priceAvg":"0.49998",
// "status":"partially_filled",
// "cTime":"1699883056140",
// "uTime":"1699883073285",
// "feeDetail":[
// {
// "feeCoin":"BGB",
// "fee":"-0.0159404"
// }
// ],
// "enterPointSource":"API"
// }
t.Run("limit buy", func(t *testing.T) {
newO := o
newO.OrderType = v2.OrderTypeLimit
res, err := newO.toGlobalOrder()
assert.NoError(t, err)
assert.Equal(t, types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Symbol: "BGBUSDT",
Side: types.SideTypeBuy,
Type: types.OrderTypeLimit,
Quantity: newO.Size,
Price: newO.PriceAvg,
TimeInForce: types.TimeInForceGTC,
},
Exchange: types.ExchangeBitget,
OrderID: uint64(newO.OrderId),
UUID: strconv.FormatInt(int64(newO.OrderId), 10),
Status: types.OrderStatusPartiallyFilled,
ExecutedQuantity: newO.AccBaseVolume,
IsWorking: newO.Status.IsWorking(),
CreationTime: types.Time(newO.CTime),
UpdateTime: types.Time(newO.UTime),
}, res)
})
// limit sell example:
// {
// "instId":"BGBUSDT",
// "orderId":"1107936497259417600",
// "clientOid":"02d4592e-091c-4b5a-aef3-6a7cf57b5e82",
// "price":"0.48710",
// "size":"280.0000",
// "orderType":"limit",
// "force":"gtc",
// "side":"sell",
// "fillPrice":"0.48710",
// "tradeId":"1107937053540556809",
// "baseVolume":"41.0593",
// "fillTime":"1699878698716",
// "fillFee":"-0.01999998503",
// "fillFeeCoin":"USDT",
// "tradeScope":"M",
// "accBaseVolume":"146.3209",
// "priceAvg":"0.48710",
// "status":"partially_filled",
// "cTime":"1699878566088",
// "uTime":"1699878698746",
// "feeDetail":[
// {
// "feeCoin":"USDT",
// "fee":"-0.07127291039"
// }
// ],
// "enterPointSource":"API"
// }
t.Run("limit sell", func(t *testing.T) {
newO := o
newO.OrderType = v2.OrderTypeLimit
newO.Side = v2.SideTypeSell
res, err := newO.toGlobalOrder()
assert.NoError(t, err)
assert.Equal(t, types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Symbol: "BGBUSDT",
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Quantity: newO.Size,
Price: newO.PriceAvg,
TimeInForce: types.TimeInForceGTC,
},
Exchange: types.ExchangeBitget,
OrderID: uint64(newO.OrderId),
UUID: strconv.FormatInt(int64(newO.OrderId), 10),
Status: types.OrderStatusPartiallyFilled,
ExecutedQuantity: newO.AccBaseVolume,
IsWorking: newO.Status.IsWorking(),
CreationTime: types.Time(newO.CTime),
UpdateTime: types.Time(newO.UTime),
}, res)
})
t.Run("unexpected status", func(t *testing.T) {
newO := o
newO.Status = "xxx"
_, err := newO.toGlobalOrder()
assert.ErrorContains(t, err, "xxx")
})
t.Run("unexpected time-in-force", func(t *testing.T) {
newO := o
newO.Force = "xxx"
_, err := newO.toGlobalOrder()
assert.ErrorContains(t, err, "xxx")
})
t.Run("unexpected order type", func(t *testing.T) {
newO := o
newO.OrderType = "xxx"
_, err := newO.toGlobalOrder()
assert.ErrorContains(t, err, "xxx")
})
t.Run("unexpected side", func(t *testing.T) {
newO := o
newO.Side = "xxx"
_, err := newO.toGlobalOrder()
assert.ErrorContains(t, err, "xxx")
})
}
func TestOrder_toGlobalTrade(t *testing.T) {
// market buy example:
// {
// "instId":"BGBUSDT",
// "orderId":"1107950489998626816",
// "clientOid":"cc73aab9-1e44-4022-8458-60d8c6a08753",
// "size":"39.0000",
// "notional":"39.000000",
// "orderType":"market",
// "force":"gtc",
// "side":"buy",
// "fillPrice":"0.49016",
// "tradeId":"1107950490073112582",
// "baseVolume":"33.6558",
// "fillTime":"1699881902235",
// "fillFee":"-0.0336558",
// "fillFeeCoin":"BGB",
// "tradeScope":"T",
// "accBaseVolume":"33.6558",
// "priceAvg":"0.49016",
// "status":"partially_filled",
// "cTime":"1699881902217",
// "uTime":"1699881902248",
// "feeDetail":[
// {
// "feeCoin":"BGB",
// "fee":"-0.0336558"
// }
// ],
// "enterPointSource":"API"
// }
o := Order{
Trade: Trade{
FillPrice: fixedpoint.NewFromFloat(0.49016),
TradeId: types.StrInt64(1107950490073112582),
BaseVolume: fixedpoint.NewFromFloat(33.6558),
FillTime: types.NewMillisecondTimestampFromInt(1699881902235),
FillFee: fixedpoint.NewFromFloat(-0.0336558),
FillFeeCoin: "BGB",
TradeScope: "T",
},
InstId: "BGBUSDT",
OrderId: types.StrInt64(1107950489998626816),
ClientOrderId: "cc73aab9-1e44-4022-8458-60d8c6a08753",
Size: fixedpoint.NewFromFloat(39.0),
Notional: fixedpoint.NewFromFloat(39.0),
OrderType: v2.OrderTypeMarket,
Force: v2.OrderForceGTC,
Side: v2.SideTypeBuy,
AccBaseVolume: fixedpoint.NewFromFloat(33.6558),
PriceAvg: fixedpoint.NewFromFloat(0.49016),
Status: v2.OrderStatusPartialFilled,
CTime: types.NewMillisecondTimestampFromInt(1699881902217),
UTime: types.NewMillisecondTimestampFromInt(1699881902248),
FeeDetail: nil,
EnterPointSource: "API",
}
t.Run("succeeds", func(t *testing.T) {
res, err := o.toGlobalTrade()
assert.NoError(t, err)
assert.Equal(t, types.Trade{
ID: uint64(o.TradeId),
OrderID: uint64(o.OrderId),
Exchange: types.ExchangeBitget,
Price: o.FillPrice,
Quantity: o.BaseVolume,
QuoteQuantity: o.FillPrice.Mul(o.BaseVolume),
Symbol: "BGBUSDT",
Side: types.SideTypeBuy,
IsBuyer: true,
IsMaker: false,
Time: types.Time(o.FillTime),
Fee: o.FillFee.Abs(),
FeeCurrency: "BGB",
}, res)
})
t.Run("unexpected trade scope", func(t *testing.T) {
newO := o
newO.TradeScope = "xxx"
_, err := newO.toGlobalTrade()
assert.ErrorContains(t, err, "xxx")
})
t.Run("unexpected side type", func(t *testing.T) {
newO := o
newO.Side = "xxx"
_, err := newO.toGlobalTrade()
assert.ErrorContains(t, err, "xxx")
})
t.Run("unexpected side type", func(t *testing.T) {
newO := o
newO.Status = "xxx"
_, err := newO.toGlobalTrade()
assert.ErrorContains(t, err, "xxx")
})
}

View File

@ -25,12 +25,15 @@ var (
type Stream struct {
types.StandardStream
privateChannelSymbols []string
key, secret, passphrase string
bookEventCallbacks []func(o BookEvent)
marketTradeEventCallbacks []func(o MarketTradeEvent)
KLineEventCallbacks []func(o KLineEvent)
accountEventCallbacks []func(e AccountEvent)
accountEventCallbacks []func(e AccountEvent)
orderTradeEventCallbacks []func(e OrderTradeEvent)
lastCandle map[string]types.KLine
}
@ -56,6 +59,7 @@ func NewStream(key, secret, passphrase string) *Stream {
stream.OnAuth(stream.handleAuth)
stream.OnAccountEvent(stream.handleAccountEvent)
stream.OnOrderTradeEvent(stream.handleOrderTradeEvent)
return stream
}
@ -129,25 +133,52 @@ func (s *Stream) dispatchEvent(event interface{}) {
case *AccountEvent:
s.EmitAccountEvent(*e)
case *OrderTradeEvent:
s.EmitOrderTradeEvent(*e)
case []byte:
// We only handle the 'pong' case. Others are unexpected.
if !bytes.Equal(e, pongBytes) {
log.Errorf("invalid event: %q", e)
}
}
}
// handleAuth subscribe private stream channels. Because Bitget doesn't allow authentication and subscription to be used
// consecutively, we subscribe after authentication confirmation.
func (s *Stream) handleAuth() {
if err := s.Conn.WriteJSON(WsOp{
op := WsOp{
Op: WsEventSubscribe,
Args: []WsArg{
{
InstType: instSpV2,
Channel: ChannelAccount,
Coin: "default", // default all
Coin: "default", // all coins
},
},
}); err != nil {
}
if len(s.privateChannelSymbols) > 0 {
for _, symbol := range s.privateChannelSymbols {
op.Args = append(op.Args, WsArg{
InstType: instSpV2,
Channel: ChannelOrders,
InstId: symbol,
})
}
} else {
log.Warnf("you have not subscribed to any order channels")
}
if err := s.Conn.WriteJSON(op); err != nil {
log.WithError(err).Error("failed to send subscription request")
return
}
}
func (s *Stream) SetPrivateChannelSymbols(symbols []string) {
s.privateChannelSymbols = symbols
}
func (s *Stream) handlerConnect() {
if s.PublicOnly {
// errors are handled in the syncSubscriptions, so they are skipped here.
@ -279,6 +310,17 @@ func parseEvent(in []byte) (interface{}, error) {
book.instId = event.Arg.InstId
return &book, nil
case ChannelOrders:
var order OrderTradeEvent
err = json.Unmarshal(event.Data, &order.Orders)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal data into OrderTradeEvent, Arg: %+v Data: %s, err: %w", event.Arg, string(event.Data), err)
}
order.actionType = event.Action
order.instId = event.Arg.InstId
return &order, nil
case ChannelTrade:
var trade MarketTradeEvent
err = json.Unmarshal(event.Data, &trade.Events)
@ -364,3 +406,31 @@ func (s *Stream) handleAccountEvent(m AccountEvent) {
}
s.StandardStream.EmitBalanceSnapshot(balanceMap)
}
func (s *Stream) handleOrderTradeEvent(m OrderTradeEvent) {
if len(m.Orders) == 0 {
return
}
for _, order := range m.Orders {
globalOrder, err := order.toGlobalOrder()
if err != nil {
log.Errorf("failed to convert order to global: %s", err)
continue
}
// The bitget support only snapshot on orders channel, so we use snapshot as update to emit data.
if m.actionType != ActionTypeSnapshot {
continue
}
s.StandardStream.EmitOrderUpdate(globalOrder)
if globalOrder.Status == types.OrderStatusPartiallyFilled {
trade, err := order.toGlobalTrade()
if err != nil {
log.Errorf("failed to convert trade to global: %s", err)
continue
}
s.StandardStream.EmitTradeUpdate(trade)
}
}
}

View File

@ -43,3 +43,13 @@ func (s *Stream) EmitAccountEvent(e AccountEvent) {
cb(e)
}
}
func (s *Stream) OnOrderTradeEvent(cb func(e OrderTradeEvent)) {
s.orderTradeEventCallbacks = append(s.orderTradeEventCallbacks, cb)
}
func (s *Stream) EmitOrderTradeEvent(e OrderTradeEvent) {
for _, cb := range s.orderTradeEventCallbacks {
cb(e)
}
}

View File

@ -134,6 +134,12 @@ func TestStream(t *testing.T) {
s.OnBalanceUpdate(func(balances types.BalanceMap) {
t.Log("get update", balances)
})
s.OnOrderUpdate(func(order types.Order) {
t.Log("order update", order)
})
s.OnTradeUpdate(func(trade types.Trade) {
t.Log("trade update", trade)
})
c := make(chan struct{})
<-c

View File

@ -6,6 +6,7 @@ import (
"fmt"
"time"
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
@ -29,6 +30,7 @@ const (
// ChannelOrderBook15 top 15 order book of "books" that begins from bid1/ask1
ChannelOrderBook15 ChannelType = "books15"
ChannelTrade ChannelType = "trade"
ChannelOrders ChannelType = "orders"
)
type WsArg struct {
@ -460,3 +462,63 @@ type AccountEvent struct {
actionType ActionType
instId string
}
type Trade struct {
// Latest filled price
FillPrice fixedpoint.Value `json:"fillPrice"`
TradeId types.StrInt64 `json:"tradeId"`
// Number of latest filled orders
BaseVolume fixedpoint.Value `json:"baseVolume"`
FillTime types.MillisecondTimestamp `json:"fillTime"`
// Transaction fee of the latest transaction, negative value
FillFee fixedpoint.Value `json:"fillFee"`
// Currency of transaction fee of the latest transaction
FillFeeCoin string `json:"fillFeeCoin"`
// Direction of liquidity of the latest transaction
TradeScope string `json:"tradeScope"`
}
type Order struct {
Trade
InstId string `json:"instId"`
// OrderId are always numeric. It's confirmed with official customer service. https://t.me/bitgetOpenapi/24172
OrderId types.StrInt64 `json:"orderId"`
ClientOrderId string `json:"clientOid"`
// Size is base coin when orderType=limit; quote coin when orderType=market
Size fixedpoint.Value `json:"size"`
// Buy amount, returned when buying at market price
Notional fixedpoint.Value `json:"notional"`
OrderType v2.OrderType `json:"orderType"`
Force v2.OrderForce `json:"force"`
Side v2.SideType `json:"side"`
AccBaseVolume fixedpoint.Value `json:"accBaseVolume"`
PriceAvg fixedpoint.Value `json:"priceAvg"`
Status v2.OrderStatus `json:"status"`
CTime types.MillisecondTimestamp `json:"cTime"`
UTime types.MillisecondTimestamp `json:"uTime"`
FeeDetail []struct {
FeeCoin string `json:"feeCoin"`
Fee string `json:"fee"`
} `json:"feeDetail"`
EnterPointSource string `json:"enterPointSource"`
}
func (o *Order) isMaker() (bool, error) {
switch o.TradeScope {
case "T":
return false, nil
case "M":
return true, nil
default:
return false, fmt.Errorf("unexpected trade scope: %s", o.TradeScope)
}
}
type OrderTradeEvent struct {
Orders []Order
// internal use
actionType ActionType
instId string
}