bbgo_origin/pkg/exchange/binance/parse.go

1182 lines
35 KiB
Go

package binance
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/adshao/go-binance/v2/futures"
"github.com/slack-go/slack"
"github.com/adshao/go-binance/v2"
"github.com/valyala/fastjson"
"github.com/c9s/bbgo/pkg/exchange/binance/binanceapi"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
type EventType = string
const (
EventTypeKLine EventType = "kline"
EventTypeOutboundAccountPosition EventType = "outboundAccountPosition"
EventTypeOutboundAccountInfo EventType = "outboundAccountInfo"
EventTypeBalanceUpdate EventType = "balanceUpdate"
EventTypeExecutionReport EventType = "executionReport"
EventTypeDepthUpdate EventType = "depthUpdate"
EventTypeListenKeyExpired EventType = "listenKeyExpired"
EventTypeTrade EventType = "trade"
EventTypeAggTrade EventType = "aggTrade"
EventTypeForceOrder EventType = "forceOrder"
// Our side defines the following event types since binance doesn't
// define the event name from the server messages.
//
EventTypeBookTicker EventType = "bookTicker"
EventTypePartialDepth EventType = "partialDepth"
)
type EventBase struct {
Event string `json:"e"` // event name
Time types.MillisecondTimestamp `json:"E"` // event time
}
/*
executionReport
{
"e": "executionReport", // Event type
"E": 1499405658658, // Event time
"s": "ETHBTC", // Symbol
"c": "mUvoqJxFIILMdfAW5iGSOW", // Client order ID
"S": "BUY", // Side
"o": "LIMIT", // Order type
"f": "GTC", // Time in force
"q": "1.00000000", // Order quantity
"p": "0.10264410", // Order price
"P": "0.00000000", // Stop price
"F": "0.00000000", // Iceberg quantity
"g": -1, // OrderListId
"C": null, // Original client order ID; This is the ID of the order being canceled
"x": "NEW", // Current execution type
"X": "NEW", // Current order status
"r": "NONE", // Order reject reason; will be an error code.
"i": 4293153, // Order ID
"l": "0.00000000", // Last executed quantity
"z": "0.00000000", // Cumulative filled quantity
"L": "0.00000000", // Last executed price
"n": "0", // Commission amount
"N": null, // Commission asset
"T": 1499405658657, // Transaction time
"t": -1, // Trade ID
"I": 8641984, // Ignore
"w": true, // Is the order on the book?
"m": false, // Is this trade the maker side?
"M": false, // Ignore
"O": 1499405658657, // Order creation time
"Z": "0.00000000", // Cumulative quote asset transacted quantity
"Y": "0.00000000", // Last quote asset transacted quantity (i.e. lastPrice * lastQty)
"Q": "0.00000000" // Quote Order Quantity
}
*/
type ExecutionReportEvent struct {
EventBase
Symbol string `json:"s"`
Side string `json:"S"`
ClientOrderID string `json:"c"`
OriginalClientOrderID string `json:"C"`
OrderType string `json:"o"`
OrderCreationTime int64 `json:"O"`
TimeInForce string `json:"f"`
IcebergQuantity fixedpoint.Value `json:"F"`
OrderQuantity fixedpoint.Value `json:"q"`
QuoteOrderQuantity fixedpoint.Value `json:"Q"`
OrderPrice fixedpoint.Value `json:"p"`
StopPrice fixedpoint.Value `json:"P"`
IsOnBook bool `json:"w"`
WorkingTime types.MillisecondTimestamp `json:"W"`
TrailingTime types.MillisecondTimestamp `json:"D"`
IsMaker bool `json:"m"`
Ignore bool `json:"M"`
CommissionAmount fixedpoint.Value `json:"n"`
CommissionAsset string `json:"N"`
CurrentExecutionType string `json:"x"`
CurrentOrderStatus string `json:"X"`
OrderID int64 `json:"i"`
Ignored int64 `json:"I"`
TradeID int64 `json:"t"`
TransactionTime int64 `json:"T"`
LastExecutedQuantity fixedpoint.Value `json:"l"`
LastExecutedPrice fixedpoint.Value `json:"L"`
CumulativeFilledQuantity fixedpoint.Value `json:"z"`
CumulativeQuoteAssetTransactedQuantity fixedpoint.Value `json:"Z"`
LastQuoteAssetTransactedQuantity fixedpoint.Value `json:"Y"`
}
func (e *ExecutionReportEvent) Order() (*types.Order, error) {
switch e.CurrentExecutionType {
case "NEW", "CANCELED", "REJECTED", "EXPIRED":
case "REPLACED":
case "TRADE": // For Order FILLED status. And the order has been completed.
default:
return nil, errors.New("execution report type is not for order")
}
orderCreationTime := time.Unix(0, e.OrderCreationTime*int64(time.Millisecond))
return &types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: e.ClientOrderID,
Symbol: e.Symbol,
Side: toGlobalSideType(binance.SideType(e.Side)),
Type: toGlobalOrderType(binance.OrderType(e.OrderType)),
Quantity: e.OrderQuantity,
Price: e.OrderPrice,
StopPrice: e.StopPrice,
TimeInForce: types.TimeInForce(e.TimeInForce),
ReduceOnly: false,
ClosePosition: false,
},
Exchange: types.ExchangeBinance,
IsWorking: e.IsOnBook,
OrderID: uint64(e.OrderID),
Status: toGlobalOrderStatus(binance.OrderStatusType(e.CurrentOrderStatus)),
ExecutedQuantity: e.CumulativeFilledQuantity,
CreationTime: types.Time(orderCreationTime),
UpdateTime: types.Time(orderCreationTime),
}, nil
}
func (e *ExecutionReportEvent) Trade() (*types.Trade, error) {
if e.CurrentExecutionType != "TRADE" {
return nil, errors.New("execution report is not a trade")
}
tt := time.Unix(0, e.TransactionTime*int64(time.Millisecond))
return &types.Trade{
ID: uint64(e.TradeID),
Exchange: types.ExchangeBinance,
Symbol: e.Symbol,
OrderID: uint64(e.OrderID),
Side: toGlobalSideType(binance.SideType(e.Side)),
Price: e.LastExecutedPrice,
Quantity: e.LastExecutedQuantity,
QuoteQuantity: e.LastQuoteAssetTransactedQuantity,
IsBuyer: e.Side == "BUY",
IsMaker: e.IsMaker,
Time: types.Time(tt),
Fee: e.CommissionAmount,
FeeCurrency: e.CommissionAsset,
}, nil
}
/*
event: balanceUpdate
Balance Update occurs during the following:
Deposits or withdrawals from the account
Transfer of funds between accounts (e.g. Spot to Margin)
{
"e": "balanceUpdate", //KLineEvent Type
"E": 1573200697110, //KLineEvent Time
"a": "BTC", //Asset
"d": "100.00000000", //Balance Delta
"T": 1573200697068 //Clear Time
}
This event is only for Spot
*/
type BalanceUpdateEvent struct {
EventBase
Asset string `json:"a"`
Delta fixedpoint.Value `json:"d"`
ClearTime types.MillisecondTimestamp `json:"T"`
}
func (e *BalanceUpdateEvent) SlackAttachment() slack.Attachment {
return slack.Attachment{
Title: "Binance Balance Update Event",
Color: "warning",
Fields: []slack.AttachmentField{
{
Title: "Asset",
Value: e.Asset,
Short: true,
},
{
Title: "Delta",
Value: e.Delta.String(),
Short: true,
},
{
Title: "Time",
Value: e.ClearTime.String(),
Short: true,
},
},
}
}
/*
outboundAccountInfo
{
"e": "outboundAccountInfo", // KLineEvent type
"E": 1499405658849, // KLineEvent time
"m": 0, // Maker commission rate (bips)
"t": 0, // Taker commission rate (bips)
"b": 0, // Buyer commission rate (bips)
"s": 0, // Seller commission rate (bips)
"T": true, // Can trade?
"W": true, // Can withdraw?
"D": true, // Can deposit?
"u": 1499405658848, // Time of last account update
"B": [ // AccountBalances array
{
"a": "LTC", // Asset
"f": "17366.18538083", // Free amount
"l": "0.00000000" // Locked amount
},
{
"a": "BTC",
"f": "10537.85314051",
"l": "2.19464093"
},
{
"a": "ETH",
"f": "17902.35190619",
"l": "0.00000000"
},
{
"a": "BNC",
"f": "1114503.29769312",
"l": "0.00000000"
},
{
"a": "NEO",
"f": "0.00000000",
"l": "0.00000000"
}
],
"P": [ // Account Permissions
"SPOT"
]
}
*/
type Balance struct {
Asset string `json:"a"`
Free fixedpoint.Value `json:"f"`
Locked fixedpoint.Value `json:"l"`
}
type OutboundAccountPositionEvent struct {
EventBase
LastAccountUpdateTime int `json:"u"`
Balances []Balance `json:"B,omitempty"`
}
type OutboundAccountInfoEvent struct {
EventBase
MakerCommissionRate int `json:"m"`
TakerCommissionRate int `json:"t"`
BuyerCommissionRate int `json:"b"`
SellerCommissionRate int `json:"s"`
CanTrade bool `json:"T"`
CanWithdraw bool `json:"W"`
CanDeposit bool `json:"D"`
LastAccountUpdateTime int `json:"u"`
Balances []Balance `json:"B,omitempty"`
Permissions []string `json:"P,omitempty"`
}
type ResultEvent struct {
Result interface{} `json:"result,omitempty"`
ID int `json:"id"`
}
var parserPool fastjson.ParserPool
func parseWebSocketEvent(message []byte) (interface{}, error) {
parser := parserPool.Get()
val, err := parser.ParseBytes(message)
if err != nil {
return nil, err
}
eventType := string(val.GetStringBytes("e"))
if eventType == "" {
if isBookTicker(val) {
eventType = EventTypeBookTicker
} else if isPartialDepth(val) {
eventType = EventTypePartialDepth
}
}
switch eventType {
case EventTypeOutboundAccountPosition:
var event OutboundAccountPositionEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeOutboundAccountInfo:
var event OutboundAccountInfoEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeBalanceUpdate:
var event BalanceUpdateEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeExecutionReport:
var event ExecutionReportEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeDepthUpdate:
return parseDepthEvent(val)
case EventTypeTrade:
var event MarketTradeEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeBookTicker:
var event BookTickerEvent
err := json.Unmarshal(message, &event)
event.Event = eventType
return &event, err
case EventTypePartialDepth:
var depth binanceapi.Depth
err := json.Unmarshal(message, &depth)
return &PartialDepthEvent{
EventBase: EventBase{
Event: EventTypePartialDepth,
Time: types.MillisecondTimestamp(time.Now()),
},
Depth: depth,
}, err
case EventTypeKLine:
var event KLineEvent
err := json.Unmarshal(message, &event)
return &event, err
case EventTypeListenKeyExpired:
var event ListenKeyExpired
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeAggTrade:
var event AggTradeEvent
err = json.Unmarshal(message, &event)
return &event, err
case EventTypeForceOrder:
var event ForceOrderEvent
err = json.Unmarshal(message, &event)
return &event, err
}
// events for futures
switch eventType {
// futures market data stream
// ========================================================
case "continuousKline":
var event ContinuousKLineEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
case "markPriceUpdate":
var event MarkPriceUpdateEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
// futures user data stream
// ========================================================
case "ORDER_TRADE_UPDATE":
var event OrderTradeUpdateEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
// Event: Balance and Position Update
case "ACCOUNT_UPDATE":
var event AccountUpdateEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
// Event: Order Update
case "ACCOUNT_CONFIG_UPDATE":
var event AccountConfigUpdateEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
case "MARGIN_CALL":
var event MarginCallEvent
err = json.Unmarshal([]byte(message), &event)
return &event, err
default:
id := val.GetInt("id")
if id > 0 {
return &ResultEvent{ID: id}, nil
}
}
return nil, fmt.Errorf("unsupported binance websocket message: %s", message)
}
// isBookTicker document ref :https://binance-docs.github.io/apidocs/spot/en/#individual-symbol-book-ticker-streams
// use key recognition because there's no identification in the content.
func isBookTicker(val *fastjson.Value) bool {
return val.Exists("u") && val.Exists("s") &&
val.Exists("b") && val.Exists("B") &&
val.Exists("a") && val.Exists("A")
}
func isPartialDepth(val *fastjson.Value) bool {
return val.Exists("lastUpdateId") &&
val.Exists("bids") && val.Exists("bids")
}
type DepthEntry struct {
PriceLevel fixedpoint.Value
Quantity fixedpoint.Value
}
type DepthEvent struct {
EventBase
Symbol string `json:"s"`
FirstUpdateID int64 `json:"U"`
FinalUpdateID int64 `json:"u"`
Bids types.PriceVolumeSlice `json:"b"`
Asks types.PriceVolumeSlice `json:"a"`
}
func (e *DepthEvent) String() (o string) {
o += fmt.Sprintf("Depth %s bid/ask = ", e.Symbol)
if len(e.Bids) == 0 {
o += "empty"
} else {
o += e.Bids[0].Price.String()
}
o += "/"
if len(e.Asks) == 0 {
o += "empty"
} else {
o += e.Asks[0].Price.String()
}
o += fmt.Sprintf(" %d ~ %d", e.FirstUpdateID, e.FinalUpdateID)
return o
}
func (e *DepthEvent) OrderBook() (book types.SliceOrderBook) {
book.Symbol = e.Symbol
book.Time = e.EventBase.Time.Time()
// already in descending order
book.Bids = e.Bids
book.Asks = e.Asks
return book
}
func parseDepthEntry(val *fastjson.Value) (pv types.PriceVolume, err error) {
arr, err := val.Array()
if err != nil {
return pv, err
}
if len(arr) < 2 {
err = errors.New("incorrect depth entry element length")
return pv, err
}
pv.Price, err = fixedpoint.NewFromString(string(arr[0].GetStringBytes()))
if err != nil {
return pv, err
}
pv.Volume, err = fixedpoint.NewFromString(string(arr[1].GetStringBytes()))
if err != nil {
return pv, err
}
return pv, err
}
func parseDepthEvent(val *fastjson.Value) (depth *DepthEvent, err error) {
depth = &DepthEvent{
EventBase: EventBase{
Event: string(val.GetStringBytes("e")),
Time: types.NewMillisecondTimestampFromInt(val.GetInt64("E")),
},
Symbol: string(val.GetStringBytes("s")),
FirstUpdateID: val.GetInt64("U"),
FinalUpdateID: val.GetInt64("u"),
Bids: make(types.PriceVolumeSlice, 0, 50),
Asks: make(types.PriceVolumeSlice, 0, 50),
}
for _, ev := range val.GetArray("b") {
entry, err2 := parseDepthEntry(ev)
if err2 != nil {
err = err2
continue
}
depth.Bids = append(depth.Bids, entry)
}
for _, ev := range val.GetArray("a") {
entry, err2 := parseDepthEntry(ev)
if err2 != nil {
err = err2
continue
}
depth.Asks = append(depth.Asks, entry)
}
return depth, err
}
type ForceOrderEventInner struct {
Symbol string `json:"s"`
TradeTime types.MillisecondTimestamp `json:"T"`
Side string `json:"S"`
OrderType string `json:"o"`
TimeInForce string `json:"f"`
Quantity fixedpoint.Value `json:"q"`
Price fixedpoint.Value `json:"p"`
AveragePrice fixedpoint.Value `json:"ap"`
OrderStatus string `json:"X"`
LastFilledQuantity fixedpoint.Value `json:"l"`
LastFilledAccQuantity fixedpoint.Value `json:"z"`
}
type ForceOrderEvent struct {
EventBase
Order ForceOrderEventInner `json:"o"`
}
func (e *ForceOrderEvent) LiquidationInfo() types.LiquidationInfo {
o := e.Order
return types.LiquidationInfo{
Symbol: o.Symbol,
Side: types.SideType(o.Side),
OrderType: types.OrderType(o.OrderType),
TimeInForce: types.TimeInForce(o.TimeInForce),
Quantity: o.Quantity,
Price: o.Price,
AveragePrice: o.AveragePrice,
OrderStatus: types.OrderStatus(o.OrderStatus),
TradeTime: types.Time(o.TradeTime),
}
}
/*
ForceOrderEvent
{
"E" : 1689303434028,
"e" : "forceOrder",
"o" : {
"S" : "BUY", // Side
"T" : 1689303434025, // Order Trade Time
"X" : "FILLED", // Order Status
"ap" : "2011.09", // Average Price
"f" : "IOC", // TimeInForce
"l" : "0.003", // Last filled Quantity
"o" : "LIMIT", // Order Type
"p" : "2021.37", // Price
"q" : "0.003", // Original Quantity
"s" : "ETHUSDT", // Symbol
"z" : "0.003" // Order Filed Accumulated Quantity
}
}
*/
type MarketTradeEvent struct {
EventBase
Symbol string `json:"s"`
Quantity fixedpoint.Value `json:"q"`
Price fixedpoint.Value `json:"p"`
BuyerOrderId int64 `json:"b"`
SellerOrderId int64 `json:"a"`
OrderTradeTime int64 `json:"T"`
TradeId int64 `json:"t"`
IsMaker bool `json:"m"`
Dummy bool `json:"M"`
}
/*
market trade
{
"e": "trade", // Event type
"E": 123456789, // Event time
"s": "BNBBTC", // Symbol
"t": 12345, // Trade ID
"p": "0.001", // Price
"q": "100", // Quantity
"b": 88, // Buyer order ID
"a": 50, // Seller order ID
"T": 123456785, // Trade time
"m": true, // Is the buyer the market maker?
"M": true // Ignore
}
*/
func (e *MarketTradeEvent) Trade() types.Trade {
tt := time.Unix(0, e.OrderTradeTime*int64(time.Millisecond))
var orderId int64
var side types.SideType
var isBuyer bool
if e.IsMaker {
orderId = e.SellerOrderId // seller is taker
side = types.SideTypeSell
isBuyer = false
} else {
orderId = e.BuyerOrderId // buyer is taker
side = types.SideTypeBuy
isBuyer = true
}
return types.Trade{
ID: uint64(e.TradeId),
Exchange: types.ExchangeBinance,
Symbol: e.Symbol,
OrderID: uint64(orderId),
Side: side,
Price: e.Price,
Quantity: e.Quantity,
QuoteQuantity: e.Quantity.Mul(e.Price),
IsBuyer: isBuyer,
IsMaker: e.IsMaker,
Time: types.Time(tt),
Fee: fixedpoint.Zero,
FeeCurrency: "",
}
}
type AggTradeEvent struct {
EventBase
Symbol string `json:"s"`
Quantity fixedpoint.Value `json:"q"`
Price fixedpoint.Value `json:"p"`
FirstTradeId int64 `json:"f"`
LastTradeId int64 `json:"l"`
OrderTradeTime int64 `json:"T"`
IsMaker bool `json:"m"`
Dummy bool `json:"M"`
}
/*
aggregate trade
{
"e": "aggTrade", // Event type
"E": 123456789, // Event time
"s": "BNBBTC", // Symbol
"a": 12345, // Aggregate trade ID
"p": "0.001", // Price
"q": "100", // Quantity
"f": 100, // First trade ID
"l": 105, // Last trade ID
"T": 123456785, // Trade time
"m": true, // Is the buyer the market maker?
"M": true // Ignore
}
*/
func (e *AggTradeEvent) Trade() types.Trade {
tt := time.Unix(0, e.OrderTradeTime*int64(time.Millisecond))
var side types.SideType
var isBuyer bool
if e.IsMaker {
side = types.SideTypeSell
isBuyer = false
} else {
side = types.SideTypeBuy
isBuyer = true
}
return types.Trade{
ID: uint64(e.LastTradeId),
Exchange: types.ExchangeBinance,
Symbol: e.Symbol,
OrderID: 0,
Side: side,
Price: e.Price,
Quantity: e.Quantity,
QuoteQuantity: e.Quantity.Mul(e.Price),
IsBuyer: isBuyer,
IsMaker: e.IsMaker,
Time: types.Time(tt),
Fee: fixedpoint.Zero,
FeeCurrency: "",
}
}
type KLine struct {
StartTime int64 `json:"t"`
EndTime int64 `json:"T"`
Symbol string `json:"s"`
Interval string `json:"i"`
Open fixedpoint.Value `json:"o"`
Close fixedpoint.Value `json:"c"`
High fixedpoint.Value `json:"h"`
Low fixedpoint.Value `json:"l"`
Volume fixedpoint.Value `json:"v"` // base asset volume (like 10 BTC)
QuoteVolume fixedpoint.Value `json:"q"` // quote asset volume
TakerBuyBaseAssetVolume fixedpoint.Value `json:"V"` // taker buy base asset volume (like 10 BTC)
TakerBuyQuoteAssetVolume fixedpoint.Value `json:"Q"` // taker buy quote asset volume (like 1000USDT)
LastTradeID int `json:"L"`
NumberOfTrades int64 `json:"n"`
Closed bool `json:"x"`
}
/*
kline
{
"e": "kline", // KLineEvent type
"E": 123456789, // KLineEvent time
"s": "BNBBTC", // Symbol
"k": {
"t": 123400000, // Kline start time
"T": 123460000, // Kline close time
"s": "BNBBTC", // Symbol
"i": "1m", // Interval
"f": 100, // First trade ID
"L": 200, // Last trade ID
"o": "0.0010", // Open price
"c": "0.0020", // Close price
"h": "0.0025", // High price
"l": "0.0015", // Low price
"v": "1000", // Base asset volume
"n": 100, // Number of trades
"x": false, // Is this kline closed?
"q": "1.0000", // Quote asset volume
"V": "500", // Taker buy base asset volume
"Q": "0.500", // Taker buy quote asset volume
"B": "123456" // Ignore
}
}
*/
type KLineEvent struct {
EventBase
Symbol string `json:"s"`
KLine KLine `json:"k,omitempty"`
}
func (k *KLine) KLine() types.KLine {
return types.KLine{
Exchange: types.ExchangeBinance,
Symbol: k.Symbol,
Interval: types.Interval(k.Interval),
StartTime: types.NewTimeFromUnix(0, k.StartTime*int64(time.Millisecond)),
EndTime: types.NewTimeFromUnix(0, k.EndTime*int64(time.Millisecond)),
Open: k.Open,
Close: k.Close,
High: k.High,
Low: k.Low,
Volume: k.Volume,
QuoteVolume: k.QuoteVolume,
TakerBuyBaseAssetVolume: k.TakerBuyBaseAssetVolume,
TakerBuyQuoteAssetVolume: k.TakerBuyQuoteAssetVolume,
LastTradeID: uint64(k.LastTradeID),
NumberOfTrades: uint64(k.NumberOfTrades),
Closed: k.Closed,
}
}
type ListenKeyExpired struct {
EventBase
}
type MarkPriceUpdateEvent struct {
EventBase
Symbol string `json:"s"`
MarkPrice fixedpoint.Value `json:"p"`
IndexPrice fixedpoint.Value `json:"i"`
EstimatedPrice fixedpoint.Value `json:"P"`
FundingRate fixedpoint.Value `json:"r"`
NextFundingTime int64 `json:"T"`
}
/*
{
"e": "markPriceUpdate", // Event type
"E": 1562305380000, // Event time
"s": "BTCUSDT", // Symbol
"p": "11794.15000000", // Mark price
"i": "11784.62659091", // Index price
"P": "11784.25641265", // Estimated Settle Price, only useful in the last hour before the settlement starts
"r": "0.00038167", // Funding rate
"T": 1562306400000 // Next funding time
}
*/
type ContinuousKLineEvent struct {
EventBase
Symbol string `json:"ps"`
CT string `json:"ct"`
KLine KLine `json:"k,omitempty"`
}
/*
{
"e":"continuous_kline", // Event type
"E":1607443058651, // Event time
"ps":"BTCUSDT", // Pair
"ct":"PERPETUAL" // Contract type
"k":{
"t":1607443020000, // Kline start time
"T":1607443079999, // Kline close time
"i":"1m", // Interval
"f":116467658886, // First trade ID
"L":116468012423, // Last trade ID
"o":"18787.00", // Open price
"c":"18804.04", // Close price
"h":"18804.04", // High price
"l":"18786.54", // Low price
"v":"197.664", // volume
"n": 543, // Number of trades
"x":false, // Is this kline closed?
"q":"3715253.19494", // Quote asset volume
"V":"184.769", // Taker buy volume
"Q":"3472925.84746", //Taker buy quote asset volume
"B":"0" // Ignore
}
}
*/
// Similar to the ExecutionReportEvent's fields. But with totally different json key.
// e.g., Stop price. So that, we can not merge them.
type OrderTrade struct {
Symbol string `json:"s"`
ClientOrderID string `json:"c"`
Side string `json:"S"`
OrderType string `json:"o"`
TimeInForce string `json:"f"`
OriginalQuantity fixedpoint.Value `json:"q"`
OriginalPrice fixedpoint.Value `json:"p"`
AveragePrice fixedpoint.Value `json:"ap"`
StopPrice fixedpoint.Value `json:"sp"`
CurrentExecutionType string `json:"x"`
CurrentOrderStatus string `json:"X"`
OrderId int64 `json:"i"`
OrderLastFilledQuantity fixedpoint.Value `json:"l"`
OrderFilledAccumulatedQuantity fixedpoint.Value `json:"z"`
LastFilledPrice fixedpoint.Value `json:"L"`
CommissionAmount fixedpoint.Value `json:"n"`
CommissionAsset string `json:"N"`
OrderTradeTime types.MillisecondTimestamp `json:"T"`
TradeId int64 `json:"t"`
BidsNotional string `json:"b"`
AskNotional string `json:"a"`
IsMaker bool `json:"m"`
IsReduceOnly bool ` json:"r"`
StopPriceWorkingType string `json:"wt"`
OriginalOrderType string `json:"ot"`
PositionSide string `json:"ps"`
RealizedProfit string `json:"rp"`
}
type OrderTradeUpdateEvent struct {
EventBase
Transaction int64 `json:"T"`
OrderTrade OrderTrade `json:"o"`
}
// {
// "e":"ORDER_TRADE_UPDATE", // Event Type
// "E":1568879465651, // Event Time
// "T":1568879465650, // Transaction Time
// "o":{
// "s":"BTCUSDT", // Symbol
// "c":"TEST", // Client Order Id
// // special client order id:
// // starts with "autoclose-": liquidation order
// // "adl_autoclose": ADL auto close order
// "S":"SELL", // Side
// "o":"TRAILING_STOP_MARKET", // Order Type
// "f":"GTC", // Time in Force
// "q":"0.001", // Original Quantity
// "p":"0", // Original Price
// "ap":"0", // Average Price
// "sp":"7103.04", // Stop Price. Please ignore with TRAILING_STOP_MARKET order
// "x":"NEW", // Execution Type
// "X":"NEW", // Order Status
// "i":8886774, // Order Id
// "l":"0", // Order Last Filled Quantity
// "z":"0", // Order Filled Accumulated Quantity
// "L":"0", // Last Filled Price
// "N":"USDT", // Commission Asset, will not push if no commission
// "n":"0", // Commission, will not push if no commission
// "T":1568879465651, // Order Trade Time
// "t":0, // Trade Id
// "b":"0", // Bids Notional
// "a":"9.91", // Ask Notional
// "m":false, // Is this trade the maker side?
// "R":false, // Is this reduce only
// "wt":"CONTRACT_PRICE", // Stop Price Working Type
// "ot":"TRAILING_STOP_MARKET", // Original Order Type
// "ps":"LONG", // Position Side
// "cp":false, // If Close-All, pushed with conditional order
// "AP":"7476.89", // Activation Price, only puhed with TRAILING_STOP_MARKET order
// "cr":"5.0", // Callback Rate, only puhed with TRAILING_STOP_MARKET order
// "rp":"0" // Realized Profit of the trade
// }
// }
func (e *OrderTradeUpdateEvent) OrderFutures() (*types.Order, error) {
switch e.OrderTrade.CurrentExecutionType {
case "NEW", "CANCELED", "EXPIRED":
case "CALCULATED - Liquidation Execution":
case "TRADE": // For Order FILLED status. And the order has been completed.
default:
return nil, errors.New("execution report type is not for futures order")
}
return &types.Order{
Exchange: types.ExchangeBinance,
SubmitOrder: types.SubmitOrder{
Symbol: e.OrderTrade.Symbol,
ClientOrderID: e.OrderTrade.ClientOrderID,
Side: toGlobalFuturesSideType(futures.SideType(e.OrderTrade.Side)),
Type: toGlobalFuturesOrderType(futures.OrderType(e.OrderTrade.OrderType)),
Quantity: e.OrderTrade.OriginalQuantity,
Price: e.OrderTrade.OriginalPrice,
TimeInForce: types.TimeInForce(e.OrderTrade.TimeInForce),
},
OrderID: uint64(e.OrderTrade.OrderId),
Status: toGlobalFuturesOrderStatus(futures.OrderStatusType(e.OrderTrade.CurrentOrderStatus)),
ExecutedQuantity: e.OrderTrade.OrderFilledAccumulatedQuantity,
CreationTime: types.Time(e.OrderTrade.OrderTradeTime.Time()), // FIXME: find the correct field for creation time
UpdateTime: types.Time(e.OrderTrade.OrderTradeTime.Time()),
}, nil
}
func (e *OrderTradeUpdateEvent) TradeFutures() (*types.Trade, error) {
if e.OrderTrade.CurrentExecutionType != "TRADE" {
return nil, errors.New("execution report is not a futures trade")
}
return &types.Trade{
ID: uint64(e.OrderTrade.TradeId),
Exchange: types.ExchangeBinance,
Symbol: e.OrderTrade.Symbol,
OrderID: uint64(e.OrderTrade.OrderId),
Side: toGlobalSideType(binance.SideType(e.OrderTrade.Side)),
Price: e.OrderTrade.LastFilledPrice,
Quantity: e.OrderTrade.OrderLastFilledQuantity,
QuoteQuantity: e.OrderTrade.LastFilledPrice.Mul(e.OrderTrade.OrderLastFilledQuantity),
IsBuyer: e.OrderTrade.Side == "BUY",
IsMaker: e.OrderTrade.IsMaker,
Time: types.Time(e.OrderTrade.OrderTradeTime.Time()),
Fee: e.OrderTrade.CommissionAmount,
FeeCurrency: e.OrderTrade.CommissionAsset,
}, nil
}
type FuturesStreamBalance struct {
Asset string `json:"a"`
WalletBalance fixedpoint.Value `json:"wb"`
CrossWalletBalance fixedpoint.Value `json:"cw"`
BalanceChange fixedpoint.Value `json:"bc"`
}
type FuturesStreamPosition struct {
Symbol string `json:"s"`
PositionAmount fixedpoint.Value `json:"pa"`
EntryPrice fixedpoint.Value `json:"ep"`
AccumulatedRealizedPnL fixedpoint.Value `json:"cr"` // (Pre-fee) Accumulated Realized PnL
UnrealizedPnL fixedpoint.Value `json:"up"`
MarginType string `json:"mt"`
IsolatedWallet fixedpoint.Value `json:"iw"`
PositionSide string `json:"ps"`
}
type AccountUpdateEventReasonType string
const (
AccountUpdateEventReasonDeposit AccountUpdateEventReasonType = "DEPOSIT"
AccountUpdateEventReasonWithdraw AccountUpdateEventReasonType = "WITHDRAW"
AccountUpdateEventReasonOrder AccountUpdateEventReasonType = "ORDER"
AccountUpdateEventReasonFundingFee AccountUpdateEventReasonType = "FUNDING_FEE"
AccountUpdateEventReasonMarginTransfer AccountUpdateEventReasonType = "MARGIN_TRANSFER"
AccountUpdateEventReasonMarginTypeChange AccountUpdateEventReasonType = "MARGIN_TYPE_CHANGE"
AccountUpdateEventReasonAssetTransfer AccountUpdateEventReasonType = "ASSET_TRANSFER"
AccountUpdateEventReasonAdminDeposit AccountUpdateEventReasonType = "ADMIN_DEPOSIT"
AccountUpdateEventReasonAdminWithdraw AccountUpdateEventReasonType = "ADMIN_WITHDRAW"
)
type AccountUpdate struct {
// m: DEPOSIT WITHDRAW
// ORDER FUNDING_FEE
// WITHDRAW_REJECT ADJUSTMENT
// INSURANCE_CLEAR
// ADMIN_DEPOSIT ADMIN_WITHDRAW
// MARGIN_TRANSFER MARGIN_TYPE_CHANGE
// ASSET_TRANSFER
// OPTIONS_PREMIUM_FEE OPTIONS_SETTLE_PROFIT
// AUTO_EXCHANGE
// COIN_SWAP_DEPOSIT COIN_SWAP_WITHDRAW
EventReasonType AccountUpdateEventReasonType `json:"m"`
Balances []FuturesStreamBalance `json:"B,omitempty"`
Positions []FuturesStreamPosition `json:"P,omitempty"`
}
type MarginCallEvent struct {
EventBase
CrossWalletBalance fixedpoint.Value `json:"cw"`
P []struct {
Symbol string `json:"s"`
PositionSide string `json:"ps"`
PositionAmount fixedpoint.Value `json:"pa"`
MarginType string `json:"mt"`
IsolatedWallet fixedpoint.Value `json:"iw"`
MarkPrice fixedpoint.Value `json:"mp"`
UnrealizedPnL fixedpoint.Value `json:"up"`
MaintenanceMarginRequired fixedpoint.Value `json:"mm"`
} `json:"p"` // Position(s) of Margin Call
}
// AccountUpdateEvent is only used in the futures user data stream
type AccountUpdateEvent struct {
EventBase
Transaction int64 `json:"T"`
AccountUpdate AccountUpdate `json:"a"`
}
type AccountConfigUpdateEvent struct {
EventBase
Transaction int64 `json:"T"`
// When the leverage of a trade pair changes,
// the payload will contain the object ac to represent the account configuration of the trade pair,
// where s represents the specific trade pair and l represents the leverage
AccountConfig struct {
Symbol string `json:"s"`
Leverage fixedpoint.Value `json:"l"`
} `json:"ac"`
// When the user Multi-Assets margin mode changes the payload will contain the object ai representing the user account configuration,
// where j represents the user Multi-Assets margin mode
MarginModeConfig struct {
MultiAssetsMode bool `json:"j"`
} `json:"ai"`
}
/*
{
"lastUpdateId": 160, // Last update ID
"bids": [ // Bids to be updated
[
"0.0024", // Price level to be updated
"10" // Quantity
]
],
"asks": [ // Asks to be updated
[
"0.0026", // Price level to be updated
"100" // Quantity
]
]
}
*/
type PartialDepthEvent struct {
EventBase
binanceapi.Depth
}
/*
{
"u":400900217, // order book updateId
"s":"BNBUSDT", // symbol
"b":"25.35190000", // best bid price
"B":"31.21000000", // best bid qty
"a":"25.36520000", // best ask price
"A":"40.66000000" // best ask qty
}
*/
type BookTickerEvent struct {
EventBase
UpdateID int64 `json:"u"`
Symbol string `json:"s"`
Buy fixedpoint.Value `json:"b"`
BuySize fixedpoint.Value `json:"B"`
Sell fixedpoint.Value `json:"a"`
SellSize fixedpoint.Value `json:"A"`
}
func (k *BookTickerEvent) BookTicker() types.BookTicker {
return types.BookTicker{
Symbol: k.Symbol,
Buy: k.Buy,
BuySize: k.BuySize,
Sell: k.Sell,
SellSize: k.SellSize,
}
}