2020-10-25 10:26:10 +00:00
|
|
|
package binance
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
2021-05-25 11:13:10 +00:00
|
|
|
"strings"
|
2020-10-25 10:26:10 +00:00
|
|
|
"time"
|
|
|
|
|
2021-01-11 05:36:49 +00:00
|
|
|
"github.com/adshao/go-binance/v2"
|
2021-12-08 16:08:25 +00:00
|
|
|
"github.com/adshao/go-binance/v2/futures"
|
2020-11-30 10:29:22 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-10-25 10:26:10 +00:00
|
|
|
|
2021-02-28 06:52:14 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
2020-10-25 10:26:10 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
|
|
|
)
|
|
|
|
|
2021-12-08 16:05:36 +00:00
|
|
|
func toGlobalMarket(symbol binance.Symbol) types.Market {
|
|
|
|
market := types.Market{
|
|
|
|
Symbol: symbol.Symbol,
|
|
|
|
LocalSymbol: symbol.Symbol,
|
|
|
|
PricePrecision: symbol.QuotePrecision,
|
|
|
|
VolumePrecision: symbol.BaseAssetPrecision,
|
|
|
|
QuoteCurrency: symbol.QuoteAsset,
|
|
|
|
BaseCurrency: symbol.BaseAsset,
|
|
|
|
}
|
|
|
|
|
|
|
|
if f := symbol.MinNotionalFilter(); f != nil {
|
|
|
|
market.MinNotional = util.MustParseFloat(f.MinNotional)
|
|
|
|
market.MinAmount = util.MustParseFloat(f.MinNotional)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
|
|
|
|
// There are 3 parts:
|
|
|
|
// minQty defines the minimum quantity/icebergQty allowed.
|
|
|
|
// maxQty defines the maximum quantity/icebergQty allowed.
|
|
|
|
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
|
|
|
|
if f := symbol.LotSizeFilter(); f != nil {
|
|
|
|
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
|
|
|
|
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
|
|
|
|
market.StepSize = util.MustParseFloat(f.StepSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
if f := symbol.PriceFilter(); f != nil {
|
|
|
|
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
|
|
|
|
market.MinPrice = util.MustParseFloat(f.MinPrice)
|
|
|
|
market.TickSize = util.MustParseFloat(f.TickSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
return market
|
|
|
|
}
|
|
|
|
|
2022-01-03 19:21:38 +00:00
|
|
|
// TODO: Cuz it returns types.Market as well, merge following to the above function
|
|
|
|
func toGlobalFuturesMarket(symbol futures.Symbol) types.Market {
|
|
|
|
market := types.Market{
|
|
|
|
Symbol: symbol.Symbol,
|
|
|
|
LocalSymbol: symbol.Symbol,
|
|
|
|
PricePrecision: symbol.QuotePrecision,
|
|
|
|
VolumePrecision: symbol.BaseAssetPrecision,
|
|
|
|
QuoteCurrency: symbol.QuoteAsset,
|
|
|
|
BaseCurrency: symbol.BaseAsset,
|
|
|
|
}
|
|
|
|
|
|
|
|
if f := symbol.MinNotionalFilter(); f != nil {
|
|
|
|
market.MinNotional = util.MustParseFloat(f.Notional)
|
|
|
|
market.MinAmount = util.MustParseFloat(f.Notional)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
|
|
|
|
// There are 3 parts:
|
|
|
|
// minQty defines the minimum quantity/icebergQty allowed.
|
|
|
|
// maxQty defines the maximum quantity/icebergQty allowed.
|
|
|
|
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
|
|
|
|
if f := symbol.LotSizeFilter(); f != nil {
|
|
|
|
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
|
|
|
|
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
|
|
|
|
market.StepSize = util.MustParseFloat(f.StepSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
if f := symbol.PriceFilter(); f != nil {
|
|
|
|
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
|
|
|
|
market.MinPrice = util.MustParseFloat(f.MinPrice)
|
|
|
|
market.TickSize = util.MustParseFloat(f.TickSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
return market
|
|
|
|
}
|
|
|
|
|
2021-02-28 06:52:14 +00:00
|
|
|
func toGlobalIsolatedUserAsset(userAsset binance.IsolatedUserAsset) types.IsolatedUserAsset {
|
|
|
|
return types.IsolatedUserAsset{
|
|
|
|
Asset: userAsset.Asset,
|
|
|
|
Borrowed: fixedpoint.MustNewFromString(userAsset.Borrowed),
|
|
|
|
Free: fixedpoint.MustNewFromString(userAsset.Free),
|
|
|
|
Interest: fixedpoint.MustNewFromString(userAsset.Interest),
|
|
|
|
Locked: fixedpoint.MustNewFromString(userAsset.Locked),
|
|
|
|
NetAsset: fixedpoint.MustNewFromString(userAsset.NetAsset),
|
|
|
|
NetAssetOfBtc: fixedpoint.MustNewFromString(userAsset.NetAssetOfBtc),
|
|
|
|
BorrowEnabled: userAsset.BorrowEnabled,
|
|
|
|
RepayEnabled: userAsset.RepayEnabled,
|
|
|
|
TotalAsset: fixedpoint.MustNewFromString(userAsset.TotalAsset),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalIsolatedMarginAsset(asset binance.IsolatedMarginAsset) types.IsolatedMarginAsset {
|
|
|
|
return types.IsolatedMarginAsset{
|
|
|
|
Symbol: asset.Symbol,
|
|
|
|
QuoteAsset: toGlobalIsolatedUserAsset(asset.QuoteAsset),
|
|
|
|
BaseAsset: toGlobalIsolatedUserAsset(asset.BaseAsset),
|
|
|
|
IsolatedCreated: asset.IsolatedCreated,
|
|
|
|
MarginLevel: fixedpoint.MustNewFromString(asset.MarginLevel),
|
|
|
|
MarginLevelStatus: asset.MarginLevelStatus,
|
|
|
|
MarginRatio: fixedpoint.MustNewFromString(asset.MarginRatio),
|
|
|
|
IndexPrice: fixedpoint.MustNewFromString(asset.IndexPrice),
|
|
|
|
LiquidatePrice: fixedpoint.MustNewFromString(asset.LiquidatePrice),
|
|
|
|
LiquidateRate: fixedpoint.MustNewFromString(asset.LiquidateRate),
|
|
|
|
TradeEnabled: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:27:04 +00:00
|
|
|
func toGlobalIsolatedMarginAssets(assets []binance.IsolatedMarginAsset) (retAssets types.IsolatedMarginAssetMap) {
|
|
|
|
retMarginAssets := make(types.IsolatedMarginAssetMap)
|
|
|
|
for _, marginAsset := range assets {
|
|
|
|
retMarginAssets[marginAsset.Symbol] = toGlobalIsolatedMarginAsset(marginAsset)
|
2021-02-28 06:52:14 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:27:04 +00:00
|
|
|
return retMarginAssets
|
2021-02-28 06:52:14 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:27:04 +00:00
|
|
|
//func toGlobalIsolatedMarginAccount(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccount {
|
|
|
|
// return &types.IsolatedMarginAccount{
|
|
|
|
// TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
|
|
|
// TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
|
|
|
// TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
|
|
|
// Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
|
|
|
func toGlobalMarginUserAssets(assets []binance.UserAsset) types.MarginAssetMap {
|
|
|
|
retMarginAssets := make(types.MarginAssetMap)
|
|
|
|
for _, marginAsset := range assets {
|
|
|
|
retMarginAssets[marginAsset.Asset] = types.MarginUserAsset{
|
|
|
|
Asset: marginAsset.Asset,
|
|
|
|
Borrowed: fixedpoint.MustNewFromString(marginAsset.Borrowed),
|
|
|
|
Free: fixedpoint.MustNewFromString(marginAsset.Free),
|
|
|
|
Interest: fixedpoint.MustNewFromString(marginAsset.Interest),
|
|
|
|
Locked: fixedpoint.MustNewFromString(marginAsset.Locked),
|
|
|
|
NetAsset: fixedpoint.MustNewFromString(marginAsset.NetAsset),
|
|
|
|
}
|
2021-02-28 06:52:14 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:27:04 +00:00
|
|
|
return retMarginAssets
|
2021-02-28 06:58:43 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 11:27:04 +00:00
|
|
|
func toGlobalMarginAccountInfo(account *binance.MarginAccount) *types.MarginAccountInfo {
|
|
|
|
return &types.MarginAccountInfo{
|
2021-02-28 06:58:43 +00:00
|
|
|
BorrowEnabled: account.BorrowEnabled,
|
|
|
|
MarginLevel: fixedpoint.MustNewFromString(account.MarginLevel),
|
|
|
|
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
|
|
|
TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
|
|
|
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
|
|
|
TradeEnabled: account.TradeEnabled,
|
|
|
|
TransferEnabled: account.TransferEnabled,
|
2022-01-13 11:27:04 +00:00
|
|
|
Assets: toGlobalMarginUserAssets(account.UserAssets),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalIsolatedMarginAccountInfo(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccountInfo {
|
|
|
|
return &types.IsolatedMarginAccountInfo{
|
|
|
|
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
|
|
|
TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
|
|
|
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
|
|
|
Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
2021-02-28 06:58:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 17:32:01 +00:00
|
|
|
func toGlobalFuturesAccountInfo(account *futures.Account) *types.FuturesAccountInfo {
|
|
|
|
return &types.FuturesAccountInfo{
|
|
|
|
Assets: toGlobalFuturesUserAssets(account.Assets),
|
|
|
|
Positions: toGlobalFuturesPositions(account.Positions),
|
|
|
|
TotalInitialMargin: fixedpoint.MustNewFromString(account.TotalInitialMargin),
|
|
|
|
TotalMaintMargin: fixedpoint.MustNewFromString(account.TotalMaintMargin),
|
|
|
|
TotalMarginBalance: fixedpoint.MustNewFromString(account.TotalMarginBalance),
|
|
|
|
TotalOpenOrderInitialMargin: fixedpoint.MustNewFromString(account.TotalOpenOrderInitialMargin),
|
|
|
|
TotalPositionInitialMargin: fixedpoint.MustNewFromString(account.TotalPositionInitialMargin),
|
|
|
|
TotalUnrealizedProfit: fixedpoint.MustNewFromString(account.TotalUnrealizedProfit),
|
|
|
|
TotalWalletBalance: fixedpoint.MustNewFromString(account.TotalWalletBalance),
|
|
|
|
UpdateTime: account.UpdateTime,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toGlobalFuturesBalance(balances []*futures.Balance) types.BalanceMap {
|
|
|
|
retBalances := make(types.BalanceMap)
|
|
|
|
for _, balance := range balances {
|
|
|
|
retBalances[balance.Asset] = types.Balance{
|
|
|
|
Currency: balance.Asset,
|
|
|
|
Available: fixedpoint.MustNewFromString(balance.AvailableBalance),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retBalances
|
|
|
|
}
|
|
|
|
|
2021-12-30 13:42:33 +00:00
|
|
|
func toGlobalFuturesPositions(futuresPositions []*futures.AccountPosition) types.FuturesPositionMap {
|
|
|
|
retFuturesPositions := make(types.FuturesPositionMap)
|
|
|
|
for _, futuresPosition := range futuresPositions {
|
2022-01-12 14:27:42 +00:00
|
|
|
retFuturesPositions[futuresPosition.Symbol] = types.FuturesPosition{ // TODO: types.FuturesPosition
|
2021-12-30 13:42:33 +00:00
|
|
|
Isolated: futuresPosition.Isolated,
|
2021-12-23 17:32:01 +00:00
|
|
|
PositionRisk: &types.PositionRisk{
|
2021-12-30 13:42:33 +00:00
|
|
|
Leverage: fixedpoint.MustNewFromString(futuresPosition.Leverage),
|
2021-12-23 17:32:01 +00:00
|
|
|
},
|
2021-12-30 13:42:33 +00:00
|
|
|
Symbol: futuresPosition.Symbol,
|
|
|
|
UpdateTime: futuresPosition.UpdateTime,
|
2021-12-23 17:32:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-30 13:42:33 +00:00
|
|
|
return retFuturesPositions
|
2021-12-23 17:32:01 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 19:21:38 +00:00
|
|
|
func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets types.FuturesAssetMap) {
|
|
|
|
retFuturesAssets := make(types.FuturesAssetMap)
|
|
|
|
for _, futuresAsset := range assets {
|
|
|
|
retFuturesAssets[futuresAsset.Asset] = types.FuturesUserAsset{
|
|
|
|
Asset: futuresAsset.Asset,
|
|
|
|
InitialMargin: fixedpoint.MustNewFromString(futuresAsset.InitialMargin),
|
|
|
|
MaintMargin: fixedpoint.MustNewFromString(futuresAsset.MaintMargin),
|
|
|
|
MarginBalance: fixedpoint.MustNewFromString(futuresAsset.MarginBalance),
|
|
|
|
MaxWithdrawAmount: fixedpoint.MustNewFromString(futuresAsset.MaxWithdrawAmount),
|
|
|
|
OpenOrderInitialMargin: fixedpoint.MustNewFromString(futuresAsset.OpenOrderInitialMargin),
|
|
|
|
PositionInitialMargin: fixedpoint.MustNewFromString(futuresAsset.PositionInitialMargin),
|
|
|
|
UnrealizedProfit: fixedpoint.MustNewFromString(futuresAsset.UnrealizedProfit),
|
|
|
|
WalletBalance: fixedpoint.MustNewFromString(futuresAsset.WalletBalance),
|
2021-12-23 17:32:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-03 19:21:38 +00:00
|
|
|
return retFuturesAssets
|
2021-12-23 17:32:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-08 16:05:36 +00:00
|
|
|
func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) {
|
|
|
|
return &types.Ticker{
|
2021-02-18 08:17:40 +00:00
|
|
|
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)),
|
2021-12-12 07:39:06 +00:00
|
|
|
}, nil
|
2021-02-18 08:17:40 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
|
|
|
|
switch orderType {
|
2021-10-17 16:41:41 +00:00
|
|
|
|
|
|
|
case types.OrderTypeLimitMaker:
|
|
|
|
return binance.OrderTypeLimitMaker, nil
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-10-17 16:41:41 +00:00
|
|
|
return "", fmt.Errorf("can not convert to local order, order type %s not supported", orderType)
|
2020-10-25 10:26:10 +00:00
|
|
|
}
|
|
|
|
|
2021-12-13 15:19:14 +00:00
|
|
|
func toLocalFuturesOrderType(orderType types.OrderType) (futures.OrderType, error) {
|
|
|
|
switch orderType {
|
|
|
|
|
|
|
|
// case types.OrderTypeLimitMaker:
|
|
|
|
// return futures.OrderTypeLimitMaker, nil //TODO
|
|
|
|
|
|
|
|
case types.OrderTypeLimit:
|
|
|
|
return futures.OrderTypeLimit, nil
|
|
|
|
|
|
|
|
// case types.OrderTypeStopLimit:
|
|
|
|
// return futures.OrderTypeStopLossLimit, nil //TODO
|
|
|
|
|
|
|
|
// case types.OrderTypeStopMarket:
|
|
|
|
// return futures.OrderTypeStopLoss, nil //TODO
|
|
|
|
|
|
|
|
case types.OrderTypeMarket:
|
|
|
|
return futures.OrderTypeMarket, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("can not convert to local order, order type %s not supported", orderType)
|
|
|
|
}
|
|
|
|
|
2021-05-28 16:26:39 +00:00
|
|
|
func toGlobalOrders(binanceOrders []*binance.Order) (orders []types.Order, err error) {
|
2021-01-18 11:35:40 +00:00
|
|
|
for _, binanceOrder := range binanceOrders {
|
2021-05-28 16:26:39 +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-12-13 15:19:14 +00:00
|
|
|
func toGlobalFuturesOrders(futuresOrders []*futures.Order) (orders []types.Order, err error) {
|
|
|
|
for _, futuresOrder := range futuresOrders {
|
|
|
|
order, err := toGlobalFuturesOrder(futuresOrder, false)
|
|
|
|
if err != nil {
|
|
|
|
return orders, err
|
|
|
|
}
|
|
|
|
|
|
|
|
orders = append(orders, *order)
|
|
|
|
}
|
|
|
|
|
|
|
|
return orders, err
|
|
|
|
}
|
|
|
|
|
2021-05-28 16:26:39 +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
|
|
|
},
|
2021-05-16 09:02:23 +00:00
|
|
|
Exchange: types.ExchangeBinance,
|
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-05-19 17:32:26 +00:00
|
|
|
CreationTime: types.Time(millisecondTime(binanceOrder.Time)),
|
|
|
|
UpdateTime: types.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
|
|
|
|
}
|
|
|
|
|
2021-12-13 15:19:14 +00:00
|
|
|
func toGlobalFuturesOrder(futuresOrder *futures.Order, isMargin bool) (*types.Order, error) {
|
|
|
|
return &types.Order{
|
|
|
|
SubmitOrder: types.SubmitOrder{
|
|
|
|
ClientOrderID: futuresOrder.ClientOrderID,
|
|
|
|
Symbol: futuresOrder.Symbol,
|
|
|
|
Side: toGlobalFuturesSideType(futuresOrder.Side),
|
|
|
|
Type: toGlobalFuturesOrderType(futuresOrder.Type),
|
|
|
|
ReduceOnly: futuresOrder.ReduceOnly,
|
|
|
|
ClosePosition: futuresOrder.ClosePosition,
|
|
|
|
Quantity: util.MustParseFloat(futuresOrder.OrigQuantity),
|
|
|
|
Price: util.MustParseFloat(futuresOrder.Price),
|
|
|
|
TimeInForce: string(futuresOrder.TimeInForce),
|
|
|
|
},
|
2021-12-14 12:38:56 +00:00
|
|
|
Exchange: types.ExchangeBinance,
|
2021-12-13 15:19:14 +00:00
|
|
|
OrderID: uint64(futuresOrder.OrderID),
|
|
|
|
Status: toGlobalFuturesOrderStatus(futuresOrder.Status),
|
|
|
|
ExecutedQuantity: util.MustParseFloat(futuresOrder.ExecutedQuantity),
|
|
|
|
CreationTime: types.Time(millisecondTime(futuresOrder.Time)),
|
|
|
|
UpdateTime: types.Time(millisecondTime(futuresOrder.UpdateTime)),
|
|
|
|
IsMargin: isMargin,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-11-05 05:35:04 +00:00
|
|
|
func millisecondTime(t int64) time.Time {
|
|
|
|
return time.Unix(0, t*int64(time.Millisecond))
|
|
|
|
}
|
|
|
|
|
2021-12-12 07:39:06 +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{
|
2021-12-23 05:15:27 +00:00
|
|
|
ID: uint64(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-05-19 17:32:26 +00:00
|
|
|
Time: types.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
|
|
|
|
}
|
|
|
|
|
2021-12-14 10:01:24 +00:00
|
|
|
func toGlobalFuturesTrade(t futures.AccountTrade) (*types.Trade, error) {
|
|
|
|
// skip trade ID that is the same. however this should not happen
|
|
|
|
var side types.SideType
|
|
|
|
if t.Buyer {
|
|
|
|
side = types.SideTypeBuy
|
|
|
|
} else {
|
|
|
|
side = types.SideTypeSell
|
|
|
|
}
|
|
|
|
|
|
|
|
price, err := strconv.ParseFloat(t.Price, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "price parse error, price: %+v", t.Price)
|
|
|
|
}
|
|
|
|
|
|
|
|
quantity, err := strconv.ParseFloat(t.Quantity, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "quantity parse error, quantity: %+v", t.Quantity)
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quoteQuantity = price * quantity
|
|
|
|
}
|
|
|
|
|
|
|
|
fee, err := strconv.ParseFloat(t.Commission, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "commission parse error, commission: %+v", t.Commission)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &types.Trade{
|
2021-12-23 05:15:27 +00:00
|
|
|
ID: uint64(t.ID),
|
2021-12-14 10:01:24 +00:00
|
|
|
OrderID: uint64(t.OrderID),
|
|
|
|
Price: price,
|
|
|
|
Symbol: t.Symbol,
|
|
|
|
Exchange: "binance",
|
|
|
|
Quantity: quantity,
|
|
|
|
QuoteQuantity: quoteQuantity,
|
|
|
|
Side: side,
|
|
|
|
IsBuyer: t.Buyer,
|
|
|
|
IsMaker: t.Maker,
|
|
|
|
Fee: fee,
|
|
|
|
FeeCurrency: t.CommissionAsset,
|
|
|
|
Time: types.Time(millisecondTime(t.Time)),
|
|
|
|
IsFutures: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
func toGlobalSideType(side binance.SideType) types.SideType {
|
|
|
|
switch side {
|
|
|
|
case binance.SideTypeBuy:
|
|
|
|
return types.SideTypeBuy
|
|
|
|
|
|
|
|
case binance.SideTypeSell:
|
|
|
|
return types.SideTypeSell
|
|
|
|
|
|
|
|
default:
|
2021-10-17 16:41:41 +00:00
|
|
|
log.Errorf("can not convert binance side type, unknown side type: %q", side)
|
2020-10-25 10:26:10 +00:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 07:39:06 +00:00
|
|
|
func toGlobalFuturesSideType(side futures.SideType) types.SideType {
|
|
|
|
switch side {
|
|
|
|
case futures.SideTypeBuy:
|
|
|
|
return types.SideTypeBuy
|
|
|
|
|
|
|
|
case futures.SideTypeSell:
|
|
|
|
return types.SideTypeSell
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Errorf("can not convert futures side type, unknown side type: %q", side)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
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 ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 07:39:06 +00:00
|
|
|
func toGlobalFuturesOrderType(orderType futures.OrderType) types.OrderType {
|
|
|
|
switch orderType {
|
|
|
|
// TODO
|
|
|
|
case futures.OrderTypeLimit: // , futures.OrderTypeLimitMaker, futures.OrderTypeTakeProfitLimit:
|
|
|
|
return types.OrderTypeLimit
|
|
|
|
|
|
|
|
case futures.OrderTypeMarket:
|
|
|
|
return types.OrderTypeMarket
|
|
|
|
// TODO
|
|
|
|
// case futures.OrderTypeStopLossLimit:
|
|
|
|
// return types.OrderTypeStopLimit
|
|
|
|
// TODO
|
|
|
|
// case futures.OrderTypeStopLoss:
|
|
|
|
// return types.OrderTypeStopMarket
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Errorf("unsupported order type: %v", orderType)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 10:26:10 +00:00
|
|
|
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
|
|
|
|
2021-12-12 07:39:06 +00:00
|
|
|
func toGlobalFuturesOrderStatus(orderStatus futures.OrderStatusType) types.OrderStatus {
|
|
|
|
switch orderStatus {
|
|
|
|
case futures.OrderStatusTypeNew:
|
|
|
|
return types.OrderStatusNew
|
|
|
|
|
|
|
|
case futures.OrderStatusTypeRejected:
|
|
|
|
return types.OrderStatusRejected
|
|
|
|
|
|
|
|
case futures.OrderStatusTypeCanceled:
|
|
|
|
return types.OrderStatusCanceled
|
|
|
|
|
|
|
|
case futures.OrderStatusTypePartiallyFilled:
|
|
|
|
return types.OrderStatusPartiallyFilled
|
|
|
|
|
|
|
|
case futures.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-12-12 07:39:06 +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
|
|
|
|
}
|
2021-05-25 11:13:10 +00:00
|
|
|
|
|
|
|
func convertSubscription(s types.Subscription) string {
|
|
|
|
// binance uses lower case symbol name,
|
|
|
|
// for kline, it's "<symbol>@kline_<interval>"
|
|
|
|
// for depth, it's "<symbol>@depth OR <symbol>@depth@100ms"
|
|
|
|
switch s.Channel {
|
|
|
|
case types.KLineChannel:
|
|
|
|
return fmt.Sprintf("%s@%s_%s", strings.ToLower(s.Symbol), s.Channel, s.Options.String())
|
|
|
|
case types.BookChannel:
|
2022-01-12 14:17:07 +00:00
|
|
|
// depth values: 5, 10, 20
|
|
|
|
// Stream Names: <symbol>@depth<levels> OR <symbol>@depth<levels>@100ms.
|
|
|
|
// Update speed: 1000ms or 100ms
|
2022-01-12 14:27:42 +00:00
|
|
|
n := strings.ToLower(s.Symbol) + "@depth"
|
|
|
|
switch s.Options.Depth {
|
|
|
|
case types.DepthLevel5:
|
|
|
|
n += "5"
|
|
|
|
|
|
|
|
case types.DepthLevelMedium:
|
|
|
|
n += "20"
|
|
|
|
|
|
|
|
case types.DepthLevelFull:
|
|
|
|
default:
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch s.Options.Speed {
|
|
|
|
case types.SpeedHigh:
|
|
|
|
n += "@100ms"
|
|
|
|
|
|
|
|
case types.SpeedLow:
|
|
|
|
n += "@1000ms"
|
|
|
|
|
|
|
|
}
|
|
|
|
return n
|
2021-12-22 13:01:11 +00:00
|
|
|
case types.BookTickerChannel:
|
|
|
|
return fmt.Sprintf("%s@bookTicker", strings.ToLower(s.Symbol))
|
2021-05-25 11:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s@%s", strings.ToLower(s.Symbol), s.Channel)
|
|
|
|
}
|
2021-12-08 16:05:36 +00:00
|
|
|
|
2021-12-08 16:10:18 +00:00
|
|
|
func convertPremiumIndex(index *futures.PremiumIndex) (*types.PremiumIndex, error) {
|
2021-12-08 16:08:25 +00:00
|
|
|
markPrice, err := fixedpoint.NewFromString(index.MarkPrice)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lastFundingRate, err := fixedpoint.NewFromString(index.LastFundingRate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
nextFundingTime := time.Unix(0, index.NextFundingTime*int64(time.Millisecond))
|
|
|
|
t := time.Unix(0, index.Time*int64(time.Millisecond))
|
|
|
|
|
2021-12-08 16:10:18 +00:00
|
|
|
return &types.PremiumIndex{
|
2021-12-08 16:08:25 +00:00
|
|
|
Symbol: index.Symbol,
|
|
|
|
MarkPrice: markPrice,
|
|
|
|
NextFundingTime: nextFundingTime,
|
|
|
|
LastFundingRate: lastFundingRate,
|
|
|
|
Time: t,
|
|
|
|
}, nil
|
|
|
|
}
|
2021-12-23 17:32:01 +00:00
|
|
|
|
|
|
|
func convertPositionRisk(risk *futures.PositionRisk) (*types.PositionRisk, error) {
|
|
|
|
leverage, err := fixedpoint.NewFromString(risk.Leverage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
liquidationPrice, err := fixedpoint.NewFromString(risk.LiquidationPrice)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &types.PositionRisk{
|
|
|
|
Leverage: leverage,
|
|
|
|
LiquidationPrice: liquidationPrice,
|
|
|
|
}, nil
|
|
|
|
}
|