2020-10-25 10:26:10 +00:00
|
|
|
package binance
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2021-01-11 05:36:49 +00:00
|
|
|
"github.com/adshao/go-binance/v2"
|
2020-11-30 10:29:22 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-10-25 10:26:10 +00:00
|
|
|
|
2021-02-06 04:32:21 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/datatype"
|
2020-10-25 10:26:10 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
|
|
|
)
|
|
|
|
|
2021-02-18 08:17:40 +00:00
|
|
|
func toGlobalTicker(stats *binance.PriceChangeStats) types.Ticker {
|
|
|
|
return types.Ticker{
|
|
|
|
Volume: util.MustParseFloat(stats.Volume),
|
|
|
|
Last: util.MustParseFloat(stats.LastPrice),
|
|
|
|
Open: util.MustParseFloat(stats.OpenPrice),
|
|
|
|
High: util.MustParseFloat(stats.HighPrice),
|
|
|
|
Low: util.MustParseFloat(stats.LowPrice),
|
|
|
|
Buy: util.MustParseFloat(stats.BidPrice),
|
|
|
|
Sell: util.MustParseFloat(stats.AskPrice),
|
|
|
|
Time: time.Unix(0, stats.CloseTime*int64(time.Millisecond)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
|
|
|
|
switch orderType {
|
|
|
|
case types.OrderTypeLimit:
|
|
|
|
return binance.OrderTypeLimit, nil
|
|
|
|
|
|
|
|
case types.OrderTypeStopLimit:
|
|
|
|
return binance.OrderTypeStopLossLimit, nil
|
|
|
|
|
|
|
|
case types.OrderTypeStopMarket:
|
|
|
|
return binance.OrderTypeStopLoss, nil
|
|
|
|
|
|
|
|
case types.OrderTypeMarket:
|
|
|
|
return binance.OrderTypeMarket, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("order type %s not supported", orderType)
|
|
|
|
}
|
|
|
|
|
2021-01-18 11:35:40 +00:00
|
|
|
func ToGlobalOrders(binanceOrders []*binance.Order) (orders []types.Order, err error) {
|
|
|
|
for _, binanceOrder := range binanceOrders {
|
2021-01-19 17:24:29 +00:00
|
|
|
order, err := ToGlobalOrder(binanceOrder, false)
|
2021-01-18 11:35:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return orders, err
|
|
|
|
}
|
|
|
|
|
|
|
|
orders = append(orders, *order)
|
|
|
|
}
|
|
|
|
|
|
|
|
return orders, err
|
|
|
|
}
|
|
|
|
|
2021-01-19 17:24:29 +00:00
|
|
|
func ToGlobalOrder(binanceOrder *binance.Order, isMargin bool) (*types.Order, error) {
|
2020-10-25 10:26:10 +00:00
|
|
|
return &types.Order{
|
|
|
|
SubmitOrder: types.SubmitOrder{
|
2020-10-25 16:26:17 +00:00
|
|
|
ClientOrderID: binanceOrder.ClientOrderID,
|
|
|
|
Symbol: binanceOrder.Symbol,
|
|
|
|
Side: toGlobalSideType(binanceOrder.Side),
|
|
|
|
Type: toGlobalOrderType(binanceOrder.Type),
|
|
|
|
Quantity: util.MustParseFloat(binanceOrder.OrigQuantity),
|
|
|
|
Price: util.MustParseFloat(binanceOrder.Price),
|
|
|
|
TimeInForce: string(binanceOrder.TimeInForce),
|
2020-10-25 10:26:10 +00:00
|
|
|
},
|
2020-11-05 03:00:51 +00:00
|
|
|
Exchange: types.ExchangeBinance.String(),
|
2020-11-03 10:45:14 +00:00
|
|
|
IsWorking: binanceOrder.IsWorking,
|
2020-10-25 10:26:10 +00:00
|
|
|
OrderID: uint64(binanceOrder.OrderID),
|
|
|
|
Status: toGlobalOrderStatus(binanceOrder.Status),
|
|
|
|
ExecutedQuantity: util.MustParseFloat(binanceOrder.ExecutedQuantity),
|
2021-02-06 06:30:00 +00:00
|
|
|
CreationTime: datatype.Time(millisecondTime(binanceOrder.Time)),
|
|
|
|
UpdateTime: datatype.Time(millisecondTime(binanceOrder.UpdateTime)),
|
2021-01-19 17:24:29 +00:00
|
|
|
IsMargin: isMargin,
|
|
|
|
IsIsolated: binanceOrder.IsIsolated,
|
2020-10-25 10:26:10 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-11-05 05:35:04 +00:00
|
|
|
func millisecondTime(t int64) time.Time {
|
|
|
|
return time.Unix(0, t*int64(time.Millisecond))
|
|
|
|
}
|
|
|
|
|
2021-01-19 17:24:29 +00:00
|
|
|
func ToGlobalTrade(t binance.TradeV3, isMargin bool) (*types.Trade, error) {
|
2020-10-25 10:26:10 +00:00
|
|
|
// skip trade ID that is the same. however this should not happen
|
|
|
|
var side types.SideType
|
|
|
|
if t.IsBuyer {
|
|
|
|
side = types.SideTypeBuy
|
|
|
|
} else {
|
|
|
|
side = types.SideTypeSell
|
|
|
|
}
|
|
|
|
|
|
|
|
price, err := strconv.ParseFloat(t.Price, 64)
|
|
|
|
if err != nil {
|
2020-11-30 10:29:22 +00:00
|
|
|
return nil, errors.Wrapf(err, "price parse error, price: %+v", t.Price)
|
2020-10-25 10:26:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
quantity, err := strconv.ParseFloat(t.Quantity, 64)
|
|
|
|
if err != nil {
|
2020-11-30 10:29:22 +00:00
|
|
|
return nil, errors.Wrapf(err, "quantity parse error, quantity: %+v", t.Quantity)
|
2020-10-25 10:26:10 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 10:29:22 +00:00
|
|
|
var quoteQuantity = 0.0
|
|
|
|
if len(t.QuoteQuantity) > 0 {
|
|
|
|
quoteQuantity, err = strconv.ParseFloat(t.QuoteQuantity, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "quote quantity parse error, quoteQuantity: %+v", t.QuoteQuantity)
|
|
|
|
}
|
2021-02-18 10:24:00 +00:00
|
|
|
} else {
|
|
|
|
quoteQuantity = price * quantity
|
2020-10-25 10:26:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fee, err := strconv.ParseFloat(t.Commission, 64)
|
|
|
|
if err != nil {
|
2020-11-30 10:29:22 +00:00
|
|
|
return nil, errors.Wrapf(err, "commission parse error, commission: %+v", t.Commission)
|
2020-10-25 10:26:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &types.Trade{
|
|
|
|
ID: t.ID,
|
2020-11-03 09:55:20 +00:00
|
|
|
OrderID: uint64(t.OrderID),
|
2020-10-25 10:26:10 +00:00
|
|
|
Price: price,
|
|
|
|
Symbol: t.Symbol,
|
|
|
|
Exchange: "binance",
|
|
|
|
Quantity: quantity,
|
2021-02-18 10:24:00 +00:00
|
|
|
QuoteQuantity: quoteQuantity,
|
2020-10-25 10:26:10 +00:00
|
|
|
Side: side,
|
|
|
|
IsBuyer: t.IsBuyer,
|
|
|
|
IsMaker: t.IsMaker,
|
|
|
|
Fee: fee,
|
|
|
|
FeeCurrency: t.CommissionAsset,
|
2021-02-06 04:32:21 +00:00
|
|
|
Time: datatype.Time(millisecondTime(t.Time)),
|
2021-01-19 17:24:29 +00:00
|
|
|
IsMargin: isMargin,
|
|
|
|
IsIsolated: t.IsIsolated,
|
2020-10-25 10:26:10 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalSideType(side binance.SideType) types.SideType {
|
|
|
|
switch side {
|
|
|
|
case binance.SideTypeBuy:
|
|
|
|
return types.SideTypeBuy
|
|
|
|
|
|
|
|
case binance.SideTypeSell:
|
|
|
|
return types.SideTypeSell
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Errorf("unknown side type: %v", side)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalOrderType(orderType binance.OrderType) types.OrderType {
|
|
|
|
switch orderType {
|
|
|
|
|
2021-01-19 18:32:55 +00:00
|
|
|
case binance.OrderTypeLimit,
|
|
|
|
binance.OrderTypeLimitMaker, binance.OrderTypeTakeProfitLimit:
|
2020-10-25 10:26:10 +00:00
|
|
|
return types.OrderTypeLimit
|
|
|
|
|
|
|
|
case binance.OrderTypeMarket:
|
|
|
|
return types.OrderTypeMarket
|
|
|
|
|
|
|
|
case binance.OrderTypeStopLossLimit:
|
|
|
|
return types.OrderTypeStopLimit
|
|
|
|
|
|
|
|
case binance.OrderTypeStopLoss:
|
|
|
|
return types.OrderTypeStopMarket
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Errorf("unsupported order type: %v", orderType)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalOrderStatus(orderStatus binance.OrderStatusType) types.OrderStatus {
|
|
|
|
switch orderStatus {
|
|
|
|
case binance.OrderStatusTypeNew:
|
|
|
|
return types.OrderStatusNew
|
|
|
|
|
|
|
|
case binance.OrderStatusTypeRejected:
|
|
|
|
return types.OrderStatusRejected
|
|
|
|
|
|
|
|
case binance.OrderStatusTypeCanceled:
|
|
|
|
return types.OrderStatusCanceled
|
|
|
|
|
|
|
|
case binance.OrderStatusTypePartiallyFilled:
|
|
|
|
return types.OrderStatusPartiallyFilled
|
|
|
|
|
|
|
|
case binance.OrderStatusTypeFilled:
|
|
|
|
return types.OrderStatusFilled
|
|
|
|
}
|
|
|
|
|
|
|
|
return types.OrderStatus(orderStatus)
|
|
|
|
}
|
2020-11-30 10:29:22 +00:00
|
|
|
|
|
|
|
// ConvertTrades converts the binance v3 trade into the global trade type
|
|
|
|
func ConvertTrades(remoteTrades []*binance.TradeV3) (trades []types.Trade, err error) {
|
|
|
|
for _, t := range remoteTrades {
|
2021-01-19 17:24:29 +00:00
|
|
|
trade, err := ToGlobalTrade(*t, false)
|
2020-11-30 10:29:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "binance v3 trade parse error, trade: %+v", *t)
|
|
|
|
}
|
|
|
|
|
|
|
|
trades = append(trades, *trade)
|
|
|
|
}
|
|
|
|
|
|
|
|
return trades, err
|
|
|
|
}
|