mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #1412 from c9s/edwin/bitget/add-order-event
FEATURE: [bitget] implement order, trade user stream
This commit is contained in:
commit
579e8b0ae5
|
@ -58,6 +58,10 @@ type ExchangeSession struct {
|
||||||
// This option is exchange specific
|
// This option is exchange specific
|
||||||
PrivateChannels []string `json:"privateChannels,omitempty" yaml:"privateChannels,omitempty"`
|
PrivateChannels []string `json:"privateChannels,omitempty" yaml:"privateChannels,omitempty"`
|
||||||
|
|
||||||
|
// PrivateChannelSymbols is used for filtering the private user data channel, .e.g, order symbol subscription.
|
||||||
|
// This option is exchange specific
|
||||||
|
PrivateChannelSymbols []string `json:"privateChannelSymbols,omitempty" yaml:"privateChannelSymbols,omitempty"`
|
||||||
|
|
||||||
Margin bool `json:"margin,omitempty" yaml:"margin"`
|
Margin bool `json:"margin,omitempty" yaml:"margin"`
|
||||||
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
|
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
|
||||||
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
|
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
|
||||||
|
@ -248,6 +252,11 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment)
|
||||||
setter.SetPrivateChannels(session.PrivateChannels)
|
setter.SetPrivateChannels(session.PrivateChannels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(session.PrivateChannelSymbols) > 0 {
|
||||||
|
if setter, ok := session.UserDataStream.(types.PrivateChannelSymbolSetter); ok {
|
||||||
|
setter.SetPrivateChannelSymbols(session.PrivateChannelSymbols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.Infof("querying account balances...")
|
logger.Infof("querying account balances...")
|
||||||
account, err := session.Exchange.QueryAccount(ctx)
|
account, err := session.Exchange.QueryAccount(ctx)
|
||||||
|
|
|
@ -51,8 +51,8 @@ type OrderDetail struct {
|
||||||
// The value is json string, so we unmarshal it after unmarshal OrderDetail
|
// The value is json string, so we unmarshal it after unmarshal OrderDetail
|
||||||
FeeDetailRaw string `json:"feeDetail"`
|
FeeDetailRaw string `json:"feeDetail"`
|
||||||
OrderSource string `json:"orderSource"`
|
OrderSource string `json:"orderSource"`
|
||||||
CTime types.MillisecondTimestamp `json:"cTime"`
|
CreatedTime types.MillisecondTimestamp `json:"cTime"`
|
||||||
UTime types.MillisecondTimestamp `json:"uTime"`
|
UpdatedTime types.MillisecondTimestamp `json:"uTime"`
|
||||||
|
|
||||||
FeeDetail FeeDetail
|
FeeDetail FeeDetail
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ func TestOrderDetail_UnmarshalJSON(t *testing.T) {
|
||||||
EnterPointSource: "API",
|
EnterPointSource: "API",
|
||||||
FeeDetailRaw: "",
|
FeeDetailRaw: "",
|
||||||
OrderSource: "normal",
|
OrderSource: "normal",
|
||||||
CTime: types.NewMillisecondTimestampFromInt(1699021576683),
|
CreatedTime: types.NewMillisecondTimestampFromInt(1699021576683),
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1699021649099),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1699021649099),
|
||||||
FeeDetail: FeeDetail{},
|
FeeDetail: FeeDetail{},
|
||||||
}, od)
|
}, od)
|
||||||
})
|
})
|
||||||
|
@ -98,8 +98,8 @@ func TestOrderDetail_UnmarshalJSON(t *testing.T) {
|
||||||
EnterPointSource: "API",
|
EnterPointSource: "API",
|
||||||
FeeDetailRaw: `{"newFees":{"c":0,"d":0,"deduction":false,"r":-0.0070005,"t":-0.0070005,"totalDeductionFee":0},"USDT":{"deduction":false,"feeCoinCode":"USDT","totalDeductionFee":0,"totalFee":-0.007000500000}}`,
|
FeeDetailRaw: `{"newFees":{"c":0,"d":0,"deduction":false,"r":-0.0070005,"t":-0.0070005,"totalDeductionFee":0},"USDT":{"deduction":false,"feeCoinCode":"USDT","totalDeductionFee":0,"totalFee":-0.007000500000}}`,
|
||||||
OrderSource: "normal",
|
OrderSource: "normal",
|
||||||
CTime: types.NewMillisecondTimestampFromInt(1699020564659),
|
CreatedTime: types.NewMillisecondTimestampFromInt(1699020564659),
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1699020564688),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1699020564688),
|
||||||
FeeDetail: FeeDetail{
|
FeeDetail: FeeDetail{
|
||||||
NewFees: struct {
|
NewFees: struct {
|
||||||
DeductedByCoupon fixedpoint.Value `json:"c"`
|
DeductedByCoupon fixedpoint.Value `json:"c"`
|
||||||
|
|
|
@ -36,19 +36,19 @@ type TradeFee struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Trade struct {
|
type Trade struct {
|
||||||
UserId types.StrInt64 `json:"userId"`
|
UserId types.StrInt64 `json:"userId"`
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
OrderId types.StrInt64 `json:"orderId"`
|
OrderId types.StrInt64 `json:"orderId"`
|
||||||
TradeId types.StrInt64 `json:"tradeId"`
|
TradeId types.StrInt64 `json:"tradeId"`
|
||||||
OrderType OrderType `json:"orderType"`
|
OrderType OrderType `json:"orderType"`
|
||||||
Side SideType `json:"side"`
|
Side SideType `json:"side"`
|
||||||
PriceAvg fixedpoint.Value `json:"priceAvg"`
|
PriceAvg fixedpoint.Value `json:"priceAvg"`
|
||||||
Size fixedpoint.Value `json:"size"`
|
Size fixedpoint.Value `json:"size"`
|
||||||
Amount fixedpoint.Value `json:"amount"`
|
Amount fixedpoint.Value `json:"amount"`
|
||||||
FeeDetail TradeFee `json:"feeDetail"`
|
FeeDetail TradeFee `json:"feeDetail"`
|
||||||
TradeScope TradeScope `json:"tradeScope"`
|
TradeScope TradeScope `json:"tradeScope"`
|
||||||
CTime types.MillisecondTimestamp `json:"cTime"`
|
CreatedTime types.MillisecondTimestamp `json:"cTime"`
|
||||||
UTime types.MillisecondTimestamp `json:"uTime"`
|
UpdatedTime types.MillisecondTimestamp `json:"uTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/api/v2/spot/trade/fills" -type GetTradeFillsRequest -responseDataType []Trade
|
//go:generate GetRequest -url "/api/v2/spot/trade/fills" -type GetTradeFillsRequest -responseDataType []Trade
|
||||||
|
|
|
@ -27,8 +27,8 @@ type UnfilledOrder struct {
|
||||||
QuoteVolume fixedpoint.Value `json:"quoteVolume"`
|
QuoteVolume fixedpoint.Value `json:"quoteVolume"`
|
||||||
EnterPointSource string `json:"enterPointSource"`
|
EnterPointSource string `json:"enterPointSource"`
|
||||||
OrderSource string `json:"orderSource"`
|
OrderSource string `json:"orderSource"`
|
||||||
CTime types.MillisecondTimestamp `json:"cTime"`
|
CreatedTime types.MillisecondTimestamp `json:"cTime"`
|
||||||
UTime types.MillisecondTimestamp `json:"uTime"`
|
UpdatedTime types.MillisecondTimestamp `json:"uTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/api/v2/spot/trade/unfilled-orders" -type GetUnfilledOrdersRequest -responseDataType []UnfilledOrder
|
//go:generate GetRequest -url "/api/v2/spot/trade/unfilled-orders" -type GetUnfilledOrdersRequest -responseDataType []UnfilledOrder
|
||||||
|
|
|
@ -158,7 +158,7 @@ func toGlobalTrade(trade v2.Trade) (*types.Trade, error) {
|
||||||
Side: side,
|
Side: side,
|
||||||
IsBuyer: side == types.SideTypeBuy,
|
IsBuyer: side == types.SideTypeBuy,
|
||||||
IsMaker: isMaker,
|
IsMaker: isMaker,
|
||||||
Time: types.Time(trade.CTime),
|
Time: types.Time(trade.CreatedTime),
|
||||||
Fee: trade.FeeDetail.TotalFee.Abs(),
|
Fee: trade.FeeDetail.TotalFee.Abs(),
|
||||||
FeeCurrency: trade.FeeDetail.FeeCoin,
|
FeeCurrency: trade.FeeDetail.FeeCoin,
|
||||||
FeeDiscounted: isDiscount,
|
FeeDiscounted: isDiscount,
|
||||||
|
@ -211,8 +211,8 @@ func unfilledOrderToGlobalOrder(order v2.UnfilledOrder) (*types.Order, error) {
|
||||||
Status: status,
|
Status: status,
|
||||||
ExecutedQuantity: order.BaseVolume,
|
ExecutedQuantity: order.BaseVolume,
|
||||||
IsWorking: order.Status.IsWorking(),
|
IsWorking: order.Status.IsWorking(),
|
||||||
CreationTime: types.Time(order.CTime.Time()),
|
CreationTime: types.Time(order.CreatedTime.Time()),
|
||||||
UpdateTime: types.Time(order.UTime.Time()),
|
UpdateTime: types.Time(order.UpdatedTime.Time()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,8 +262,8 @@ func toGlobalOrder(order v2.OrderDetail) (*types.Order, error) {
|
||||||
Status: status,
|
Status: status,
|
||||||
ExecutedQuantity: order.BaseVolume,
|
ExecutedQuantity: order.BaseVolume,
|
||||||
IsWorking: order.Status.IsWorking(),
|
IsWorking: order.Status.IsWorking(),
|
||||||
CreationTime: types.Time(order.CTime.Time()),
|
CreationTime: types.Time(order.CreatedTime.Time()),
|
||||||
UpdateTime: types.Time(order.UTime.Time()),
|
UpdateTime: types.Time(order.UpdatedTime.Time()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,3 +362,120 @@ func toGlobalKLines(symbol string, interval types.Interval, kLines v2.KLineRespo
|
||||||
}
|
}
|
||||||
return gKLines
|
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.CreatedTime.Time()),
|
||||||
|
UpdateTime: types.Time(o.UpdatedTime.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
|
||||||
|
}
|
||||||
|
|
|
@ -222,8 +222,8 @@ func Test_unfilledOrderToGlobalOrder(t *testing.T) {
|
||||||
QuoteVolume: fixedpoint.NewFromFloat(0),
|
QuoteVolume: fixedpoint.NewFromFloat(0),
|
||||||
EnterPointSource: "API",
|
EnterPointSource: "API",
|
||||||
OrderSource: "normal",
|
OrderSource: "normal",
|
||||||
CTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
CreatedTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -296,8 +296,8 @@ func Test_toGlobalOrder(t *testing.T) {
|
||||||
EnterPointSource: "API",
|
EnterPointSource: "API",
|
||||||
FeeDetailRaw: `{\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0070005,\"t\":-0.0070005,\"totalDeductionFee\":0},\"USDT\":{\"deduction\":false,\"feeCoinCode\":\"USDT\",\"totalDeductionFee\":0,\"totalFee\":-0.007000500000}}`,
|
FeeDetailRaw: `{\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0070005,\"t\":-0.0070005,\"totalDeductionFee\":0},\"USDT\":{\"deduction\":false,\"feeCoinCode\":\"USDT\",\"totalDeductionFee\":0,\"totalFee\":-0.007000500000}}`,
|
||||||
OrderSource: "normal",
|
OrderSource: "normal",
|
||||||
CTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
CreatedTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
}
|
}
|
||||||
|
|
||||||
expOrder = &types.Order{
|
expOrder = &types.Order{
|
||||||
|
@ -558,9 +558,9 @@ func Test_toGlobalTrade(t *testing.T) {
|
||||||
TotalDeductionFee: fixedpoint.Zero,
|
TotalDeductionFee: fixedpoint.Zero,
|
||||||
TotalFee: fixedpoint.NewFromFloat(-0.0070005),
|
TotalFee: fixedpoint.NewFromFloat(-0.0070005),
|
||||||
},
|
},
|
||||||
TradeScope: v2.TradeTaker,
|
TradeScope: v2.TradeTaker,
|
||||||
CTime: types.NewMillisecondTimestampFromInt(1699020564676),
|
CreatedTime: types.NewMillisecondTimestampFromInt(1699020564676),
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1699020564687),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1699020564687),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := toGlobalTrade(trade)
|
res, err := toGlobalTrade(trade)
|
||||||
|
@ -597,7 +597,7 @@ func Test_toGlobalBalanceMap(t *testing.T) {
|
||||||
Frozen: fixedpoint.NewFromFloat(0.6),
|
Frozen: fixedpoint.NewFromFloat(0.6),
|
||||||
Locked: fixedpoint.NewFromFloat(0.7),
|
Locked: fixedpoint.NewFromFloat(0.7),
|
||||||
LimitAvailable: fixedpoint.Zero,
|
LimitAvailable: fixedpoint.Zero,
|
||||||
UTime: types.NewMillisecondTimestampFromInt(1699020564676),
|
UpdatedTime: types.NewMillisecondTimestampFromInt(1699020564676),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -686,3 +686,452 @@ func Test_toGlobalKLines(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, toGlobalKLines(symbol, interval, resp), expKlines)
|
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,
|
||||||
|
CreatedTime: types.NewMillisecondTimestampFromInt(1699881902217),
|
||||||
|
UpdatedTime: 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.CreatedTime),
|
||||||
|
UpdateTime: types.Time(newO.UpdatedTime),
|
||||||
|
}, 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.CreatedTime),
|
||||||
|
UpdateTime: types.Time(newO.UpdatedTime),
|
||||||
|
}, 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.CreatedTime),
|
||||||
|
UpdateTime: types.Time(newO.UpdatedTime),
|
||||||
|
}, 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.CreatedTime),
|
||||||
|
UpdateTime: types.Time(newO.UpdatedTime),
|
||||||
|
}, 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,
|
||||||
|
CreatedTime: types.NewMillisecondTimestampFromInt(1699881902217),
|
||||||
|
UpdatedTime: 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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -394,7 +394,7 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
||||||
return orders, nil
|
return orders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryClosedOrders queries closed order by time range(`CTime`) and id. The order of the response is in descending order.
|
// QueryClosedOrders queries closed order by time range(`CreatedTime`) and id. The order of the response is in descending order.
|
||||||
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.ClosedOrderBatchQuery.
|
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.ClosedOrderBatchQuery.
|
||||||
//
|
//
|
||||||
// ** Since is inclusive, Until is exclusive. If you use a time range to query, you must provide both a start time and an end time. **
|
// ** Since is inclusive, Until is exclusive. If you use a time range to query, you must provide both a start time and an end time. **
|
||||||
|
@ -495,7 +495,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryTrades queries fill trades. The trade of the response is in descending order. The time-based query are typically
|
// QueryTrades queries fill trades. The trade of the response is in descending order. The time-based query are typically
|
||||||
// using (`CTime`) as the search criteria.
|
// using (`CreatedTime`) as the search criteria.
|
||||||
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.TradeBatchQuery.
|
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.TradeBatchQuery.
|
||||||
//
|
//
|
||||||
// ** StartTime is inclusive, EndTime is exclusive. If you use the EndTime, the StartTime is required. **
|
// ** StartTime is inclusive, EndTime is exclusive. If you use the EndTime, the StartTime is required. **
|
||||||
|
|
|
@ -25,12 +25,15 @@ var (
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
types.StandardStream
|
types.StandardStream
|
||||||
|
|
||||||
|
privateChannelSymbols []string
|
||||||
|
|
||||||
key, secret, passphrase string
|
key, secret, passphrase string
|
||||||
bookEventCallbacks []func(o BookEvent)
|
bookEventCallbacks []func(o BookEvent)
|
||||||
marketTradeEventCallbacks []func(o MarketTradeEvent)
|
marketTradeEventCallbacks []func(o MarketTradeEvent)
|
||||||
KLineEventCallbacks []func(o KLineEvent)
|
KLineEventCallbacks []func(o KLineEvent)
|
||||||
|
|
||||||
accountEventCallbacks []func(e AccountEvent)
|
accountEventCallbacks []func(e AccountEvent)
|
||||||
|
orderTradeEventCallbacks []func(e OrderTradeEvent)
|
||||||
|
|
||||||
lastCandle map[string]types.KLine
|
lastCandle map[string]types.KLine
|
||||||
}
|
}
|
||||||
|
@ -56,6 +59,7 @@ func NewStream(key, secret, passphrase string) *Stream {
|
||||||
|
|
||||||
stream.OnAuth(stream.handleAuth)
|
stream.OnAuth(stream.handleAuth)
|
||||||
stream.OnAccountEvent(stream.handleAccountEvent)
|
stream.OnAccountEvent(stream.handleAccountEvent)
|
||||||
|
stream.OnOrderTradeEvent(stream.handleOrderTradeEvent)
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,25 +133,52 @@ func (s *Stream) dispatchEvent(event interface{}) {
|
||||||
case *AccountEvent:
|
case *AccountEvent:
|
||||||
s.EmitAccountEvent(*e)
|
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() {
|
func (s *Stream) handleAuth() {
|
||||||
if err := s.Conn.WriteJSON(WsOp{
|
op := WsOp{
|
||||||
Op: WsEventSubscribe,
|
Op: WsEventSubscribe,
|
||||||
Args: []WsArg{
|
Args: []WsArg{
|
||||||
{
|
{
|
||||||
InstType: instSpV2,
|
InstType: instSpV2,
|
||||||
Channel: ChannelAccount,
|
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")
|
log.WithError(err).Error("failed to send subscription request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Stream) SetPrivateChannelSymbols(symbols []string) {
|
||||||
|
s.privateChannelSymbols = symbols
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Stream) handlerConnect() {
|
func (s *Stream) handlerConnect() {
|
||||||
if s.PublicOnly {
|
if s.PublicOnly {
|
||||||
// errors are handled in the syncSubscriptions, so they are skipped here.
|
// 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
|
book.instId = event.Arg.InstId
|
||||||
return &book, nil
|
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:
|
case ChannelTrade:
|
||||||
var trade MarketTradeEvent
|
var trade MarketTradeEvent
|
||||||
err = json.Unmarshal(event.Data, &trade.Events)
|
err = json.Unmarshal(event.Data, &trade.Events)
|
||||||
|
@ -364,3 +406,31 @@ func (s *Stream) handleAccountEvent(m AccountEvent) {
|
||||||
}
|
}
|
||||||
s.StandardStream.EmitBalanceSnapshot(balanceMap)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,3 +43,13 @@ func (s *Stream) EmitAccountEvent(e AccountEvent) {
|
||||||
cb(e)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -134,6 +134,12 @@ func TestStream(t *testing.T) {
|
||||||
s.OnBalanceUpdate(func(balances types.BalanceMap) {
|
s.OnBalanceUpdate(func(balances types.BalanceMap) {
|
||||||
t.Log("get update", balances)
|
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 := make(chan struct{})
|
||||||
<-c
|
<-c
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -29,6 +30,7 @@ const (
|
||||||
// ChannelOrderBook15 top 15 order book of "books" that begins from bid1/ask1
|
// ChannelOrderBook15 top 15 order book of "books" that begins from bid1/ask1
|
||||||
ChannelOrderBook15 ChannelType = "books15"
|
ChannelOrderBook15 ChannelType = "books15"
|
||||||
ChannelTrade ChannelType = "trade"
|
ChannelTrade ChannelType = "trade"
|
||||||
|
ChannelOrders ChannelType = "orders"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WsArg struct {
|
type WsArg struct {
|
||||||
|
@ -450,7 +452,7 @@ type Balance struct {
|
||||||
Locked fixedpoint.Value `json:"locked"`
|
Locked fixedpoint.Value `json:"locked"`
|
||||||
// Restricted availability For spot copy trading
|
// Restricted availability For spot copy trading
|
||||||
LimitAvailable fixedpoint.Value `json:"limitAvailable"`
|
LimitAvailable fixedpoint.Value `json:"limitAvailable"`
|
||||||
UTime types.MillisecondTimestamp `json:"uTime"`
|
UpdatedTime types.MillisecondTimestamp `json:"uTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountEvent struct {
|
type AccountEvent struct {
|
||||||
|
@ -460,3 +462,63 @@ type AccountEvent struct {
|
||||||
actionType ActionType
|
actionType ActionType
|
||||||
instId string
|
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"`
|
||||||
|
CreatedTime types.MillisecondTimestamp `json:"cTime"`
|
||||||
|
UpdatedTime 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
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@ type PrivateChannelSetter interface {
|
||||||
SetPrivateChannels(channels []string)
|
SetPrivateChannels(channels []string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PrivateChannelSymbolSetter interface {
|
||||||
|
SetPrivateChannelSymbols(symbols []string)
|
||||||
|
}
|
||||||
|
|
||||||
type Unsubscriber interface {
|
type Unsubscriber interface {
|
||||||
// Unsubscribe unsubscribes the all subscriptions.
|
// Unsubscribe unsubscribes the all subscriptions.
|
||||||
Unsubscribe()
|
Unsubscribe()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user