2020-07-11 05:02:53 +00:00
package binance
import (
"context"
2020-07-11 07:27:26 +00:00
"fmt"
2020-12-21 09:48:30 +00:00
"os"
"strconv"
2021-02-18 08:17:40 +00:00
"strings"
2022-01-01 18:14:04 +00:00
"sync"
2020-07-11 07:19:36 +00:00
"time"
2022-08-19 07:28:03 +00:00
"github.com/adshao/go-binance/v2"
2021-12-23 05:15:27 +00:00
"github.com/adshao/go-binance/v2/futures"
2022-01-01 17:54:47 +00:00
"github.com/spf13/viper"
2021-12-23 05:15:27 +00:00
2021-12-14 11:09:44 +00:00
"go.uber.org/multierr"
2021-12-08 16:01:33 +00:00
"golang.org/x/time/rate"
2020-10-25 10:26:10 +00:00
"github.com/google/uuid"
2020-10-25 11:18:03 +00:00
"github.com/pkg/errors"
2020-08-13 02:11:27 +00:00
"github.com/sirupsen/logrus"
2022-05-28 17:42:08 +00:00
"github.com/c9s/bbgo/pkg/exchange/binance/binanceapi"
2020-11-10 06:19:33 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
2020-10-11 08:46:15 +00:00
"github.com/c9s/bbgo/pkg/types"
2022-03-17 12:15:25 +00:00
"github.com/c9s/bbgo/pkg/util"
2020-07-11 05:02:53 +00:00
)
2021-05-25 18:15:47 +00:00
const BNB = "BNB"
2021-12-30 15:46:43 +00:00
const BinanceUSBaseURL = "https://api.binance.us"
2022-02-25 03:30:40 +00:00
const BinanceTestBaseURL = "https://testnet.binance.vision"
2021-12-30 15:46:43 +00:00
const BinanceUSWebSocketURL = "wss://stream.binance.us:9443"
2022-01-14 16:52:54 +00:00
const WebSocketURL = "wss://stream.binance.com:9443"
2022-02-25 03:30:40 +00:00
const WebSocketTestURL = "wss://testnet.binance.vision"
const FutureTestBaseURL = "https://testnet.binancefuture.com"
2022-01-14 16:52:54 +00:00
const FuturesWebSocketURL = "wss://fstream.binance.com"
2022-02-25 03:30:40 +00:00
const FuturesWebSocketTestURL = "wss://stream.binancefuture.com"
2021-12-30 15:46:43 +00:00
2022-09-12 07:03:01 +00:00
// orderLimiter - the default order limiter apply 5 requests per second and a 2 initial bucket
// this includes SubmitOrder, CancelOrder and QueryClosedOrders
//
// Limit defines the maximum frequency of some events. Limit is represented as number of events per second. A zero Limit allows no events.
2022-01-14 16:52:54 +00:00
var orderLimiter = rate . NewLimiter ( 5 , 2 )
2022-09-12 07:03:01 +00:00
var queryTradeLimiter = rate . NewLimiter ( 1 , 2 )
2021-11-04 17:25:16 +00:00
2020-08-11 00:36:36 +00:00
var log = logrus . WithFields ( logrus . Fields {
"exchange" : "binance" ,
} )
2020-09-19 02:59:43 +00:00
func init ( ) {
_ = types . Exchange ( & Exchange { } )
2021-01-19 18:09:12 +00:00
_ = types . MarginExchange ( & Exchange { } )
2021-12-12 07:40:03 +00:00
_ = types . FuturesExchange ( & Exchange { } )
2022-09-12 07:03:01 +00:00
if n , ok := util . GetEnvVarInt ( "BINANCE_ORDER_RATE_LIMITER" ) ; ok {
2022-12-22 11:07:55 +00:00
orderLimiter = rate . NewLimiter ( rate . Every ( time . Duration ( n ) * time . Minute ) , 2 )
2022-09-12 07:03:01 +00:00
}
if n , ok := util . GetEnvVarInt ( "BINANCE_QUERY_TRADES_RATE_LIMITER" ) ; ok {
2022-12-22 11:07:55 +00:00
queryTradeLimiter = rate . NewLimiter ( rate . Every ( time . Duration ( n ) * time . Minute ) , 2 )
2022-09-12 07:03:01 +00:00
}
2020-12-21 09:48:30 +00:00
}
2021-12-30 15:46:43 +00:00
func isBinanceUs ( ) bool {
v , err := strconv . ParseBool ( os . Getenv ( "BINANCE_US" ) )
return err == nil && v
}
2022-02-25 03:30:40 +00:00
func paperTrade ( ) bool {
2022-03-17 12:15:25 +00:00
v , ok := util . GetEnvVarBool ( "PAPER_TRADE" )
return ok && v
2022-02-25 03:30:40 +00:00
}
2020-07-11 05:02:53 +00:00
type Exchange struct {
2021-01-19 18:09:12 +00:00
types . MarginSettings
2021-10-07 13:29:52 +00:00
types . FuturesSettings
2020-12-02 14:19:31 +00:00
2022-04-26 08:43:40 +00:00
key , secret string
2022-04-26 07:58:12 +00:00
// client is used for spot & margin
2022-04-26 08:43:40 +00:00
client * binance . Client
2022-04-26 07:58:12 +00:00
// futuresClient is used for usdt-m futures
2021-12-12 07:40:03 +00:00
futuresClient * futures . Client // USDT-M Futures
// deliveryClient *delivery.Client // Coin-M Futures
2022-05-28 17:42:08 +00:00
// client2 is a newer version of the binance api client implemented by ourselves.
client2 * binanceapi . RestClient
2020-07-11 05:02:53 +00:00
}
2022-07-26 08:22:57 +00:00
var timeSetterOnce sync . Once
2022-01-01 18:14:04 +00:00
2020-09-19 02:59:43 +00:00
func New ( key , secret string ) * Exchange {
2020-07-11 05:08:50 +00:00
var client = binance . NewClient ( key , secret )
2022-05-28 17:42:08 +00:00
client . HTTPClient = binanceapi . DefaultHttpClient
2022-01-10 08:37:41 +00:00
client . Debug = viper . GetBool ( "debug-binance-client" )
2021-05-31 19:15:19 +00:00
2021-12-12 07:40:03 +00:00
var futuresClient = binance . NewFuturesClient ( key , secret )
2022-05-28 17:42:08 +00:00
futuresClient . HTTPClient = binanceapi . DefaultHttpClient
futuresClient . Debug = viper . GetBool ( "debug-binance-futures-client" )
2021-12-12 07:40:03 +00:00
2021-12-30 15:46:43 +00:00
if isBinanceUs ( ) {
client . BaseURL = BinanceUSBaseURL
}
2022-02-25 03:30:40 +00:00
if paperTrade ( ) {
client . BaseURL = BinanceTestBaseURL
futuresClient . BaseURL = FutureTestBaseURL
}
2022-05-28 17:42:08 +00:00
client2 := binanceapi . NewClient ( client . BaseURL )
2022-07-26 08:25:08 +00:00
ex := & Exchange {
key : key ,
secret : secret ,
client : client ,
futuresClient : futuresClient ,
client2 : client2 ,
}
2021-12-21 17:27:25 +00:00
if len ( key ) > 0 && len ( secret ) > 0 {
2022-05-29 03:52:25 +00:00
client2 . Auth ( key , secret )
2022-07-26 08:22:29 +00:00
ctx := context . Background ( )
2022-07-26 08:22:57 +00:00
go timeSetterOnce . Do ( func ( ) {
2022-07-26 08:25:08 +00:00
ex . setServerTimeOffset ( ctx )
2022-07-26 08:22:29 +00:00
2022-07-26 07:42:34 +00:00
ticker := time . NewTicker ( time . Hour )
defer ticker . Stop ( )
2022-07-26 08:22:29 +00:00
for {
select {
case <- ctx . Done ( ) :
return
2022-07-26 07:42:34 +00:00
2022-07-26 08:22:29 +00:00
case <- ticker . C :
2022-07-26 08:25:08 +00:00
ex . setServerTimeOffset ( ctx )
2022-07-26 07:42:34 +00:00
}
}
2022-07-26 08:22:29 +00:00
} )
2021-12-12 07:40:03 +00:00
}
2022-07-26 08:25:08 +00:00
return ex
}
func ( e * Exchange ) setServerTimeOffset ( ctx context . Context ) {
_ , err := e . client . NewSetServerTimeService ( ) . Do ( ctx )
if err != nil {
log . WithError ( err ) . Error ( "can not set server time" )
}
_ , err = e . futuresClient . NewSetServerTimeService ( ) . Do ( ctx )
if err != nil {
log . WithError ( err ) . Error ( "can not set server time" )
}
if err = e . client2 . SetTimeOffsetFromServer ( ctx ) ; err != nil {
log . WithError ( err ) . Error ( "can not set server time" )
2020-07-11 05:08:50 +00:00
}
}
2020-10-11 12:08:54 +00:00
func ( e * Exchange ) Name ( ) types . ExchangeName {
return types . ExchangeBinance
}
2021-02-18 08:17:40 +00:00
func ( e * Exchange ) QueryTicker ( ctx context . Context , symbol string ) ( * types . Ticker , error ) {
2022-06-15 07:59:21 +00:00
if e . IsFutures {
req := e . futuresClient . NewListPriceChangeStatsService ( )
req . Symbol ( strings . ToUpper ( symbol ) )
stats , err := req . Do ( ctx )
if err != nil {
return nil , err
}
return toGlobalFuturesTicker ( stats [ 0 ] )
}
2022-04-26 07:58:12 +00:00
req := e . client . NewListPriceChangeStatsService ( )
2021-02-18 08:17:40 +00:00
req . Symbol ( strings . ToUpper ( symbol ) )
stats , err := req . Do ( ctx )
if err != nil {
return nil , err
}
2021-12-08 16:05:36 +00:00
return toGlobalTicker ( stats [ 0 ] )
2021-02-18 08:17:40 +00:00
}
2021-02-06 17:35:23 +00:00
func ( e * Exchange ) QueryTickers ( ctx context . Context , symbol ... string ) ( map [ string ] types . Ticker , error ) {
2021-02-18 09:37:49 +00:00
var tickers = make ( map [ string ] types . Ticker )
2021-02-06 17:35:23 +00:00
if len ( symbol ) == 1 {
2021-02-18 09:37:49 +00:00
ticker , err := e . QueryTicker ( ctx , symbol [ 0 ] )
if err != nil {
return nil , err
}
tickers [ strings . ToUpper ( symbol [ 0 ] ) ] = * ticker
return tickers , nil
2021-02-06 17:35:23 +00:00
}
2021-02-07 21:58:30 +00:00
m := make ( map [ string ] struct { } )
exists := struct { } { }
2021-02-06 17:35:23 +00:00
for _ , s := range symbol {
2021-02-07 21:58:30 +00:00
m [ s ] = exists
2021-02-06 17:35:23 +00:00
}
2022-06-15 07:59:21 +00:00
if e . IsFutures {
var req = e . futuresClient . NewListPriceChangeStatsService ( )
changeStats , err := req . Do ( ctx )
if err != nil {
return nil , err
}
for _ , stats := range changeStats {
if _ , ok := m [ stats . Symbol ] ; len ( symbol ) != 0 && ! ok {
continue
}
tick := types . Ticker {
Volume : fixedpoint . MustNewFromString ( stats . Volume ) ,
Last : fixedpoint . MustNewFromString ( stats . LastPrice ) ,
Open : fixedpoint . MustNewFromString ( stats . OpenPrice ) ,
High : fixedpoint . MustNewFromString ( stats . HighPrice ) ,
Low : fixedpoint . MustNewFromString ( stats . LowPrice ) ,
Buy : fixedpoint . MustNewFromString ( stats . LastPrice ) ,
Sell : fixedpoint . MustNewFromString ( stats . LastPrice ) ,
Time : time . Unix ( 0 , stats . CloseTime * int64 ( time . Millisecond ) ) ,
}
tickers [ stats . Symbol ] = tick
}
return tickers , nil
}
var req = e . client . NewListPriceChangeStatsService ( )
changeStats , err := req . Do ( ctx )
if err != nil {
return nil , err
}
2021-02-07 21:58:30 +00:00
for _ , stats := range changeStats {
if _ , ok := m [ stats . Symbol ] ; len ( symbol ) != 0 && ! ok {
2021-02-06 17:35:23 +00:00
continue
}
tick := types . Ticker {
2022-02-03 04:55:25 +00:00
Volume : fixedpoint . MustNewFromString ( stats . Volume ) ,
Last : fixedpoint . MustNewFromString ( stats . LastPrice ) ,
Open : fixedpoint . MustNewFromString ( stats . OpenPrice ) ,
High : fixedpoint . MustNewFromString ( stats . HighPrice ) ,
Low : fixedpoint . MustNewFromString ( stats . LowPrice ) ,
Buy : fixedpoint . MustNewFromString ( stats . BidPrice ) ,
Sell : fixedpoint . MustNewFromString ( stats . AskPrice ) ,
2021-02-07 21:58:30 +00:00
Time : time . Unix ( 0 , stats . CloseTime * int64 ( time . Millisecond ) ) ,
2021-02-06 17:35:23 +00:00
}
2021-02-18 09:37:49 +00:00
tickers [ stats . Symbol ] = tick
2021-02-06 17:35:23 +00:00
}
2021-02-18 09:37:49 +00:00
return tickers , nil
2021-02-04 21:48:35 +00:00
}
2020-10-14 02:16:59 +00:00
func ( e * Exchange ) QueryMarkets ( ctx context . Context ) ( types . MarketMap , error ) {
2022-01-03 19:30:36 +00:00
if e . IsFutures {
exchangeInfo , err := e . futuresClient . NewExchangeInfoService ( ) . Do ( ctx )
if err != nil {
return nil , err
}
markets := types . MarketMap { }
for _ , symbol := range exchangeInfo . Symbols {
markets [ symbol . Symbol ] = toGlobalFuturesMarket ( symbol )
}
return markets , nil
}
2022-04-26 07:58:12 +00:00
exchangeInfo , err := e . client . NewExchangeInfoService ( ) . Do ( ctx )
2020-10-14 02:16:59 +00:00
if err != nil {
return nil , err
}
markets := types . MarketMap { }
for _ , symbol := range exchangeInfo . Symbols {
2021-12-08 16:05:36 +00:00
markets [ symbol . Symbol ] = toGlobalMarket ( symbol )
2020-10-14 02:16:59 +00:00
}
return markets , nil
}
2022-02-03 04:55:25 +00:00
func ( e * Exchange ) QueryAveragePrice ( ctx context . Context , symbol string ) ( fixedpoint . Value , error ) {
2022-04-26 07:58:12 +00:00
resp , err := e . client . NewAveragePriceService ( ) . Symbol ( symbol ) . Do ( ctx )
2020-07-11 05:02:53 +00:00
if err != nil {
2022-02-03 04:55:25 +00:00
return fixedpoint . Zero , err
2020-07-11 05:02:53 +00:00
}
2022-02-03 04:55:25 +00:00
return fixedpoint . MustNewFromString ( resp . Price ) , nil
2020-07-11 05:02:53 +00:00
}
2020-10-03 12:09:22 +00:00
func ( e * Exchange ) NewStream ( ) types . Stream {
2022-04-26 07:58:12 +00:00
stream := NewStream ( e , e . client , e . futuresClient )
2020-12-21 09:48:30 +00:00
stream . MarginSettings = e . MarginSettings
2021-12-12 07:40:03 +00:00
stream . FuturesSettings = e . FuturesSettings
2020-12-21 09:48:30 +00:00
return stream
}
2020-12-02 14:19:31 +00:00
2022-04-22 06:14:01 +00:00
func ( e * Exchange ) QueryMarginAssetMaxBorrowable ( ctx context . Context , asset string ) ( amount fixedpoint . Value , err error ) {
2022-09-19 09:09:34 +00:00
req := e . client2 . NewGetMarginMaxBorrowableRequest ( )
2022-04-22 06:14:01 +00:00
req . Asset ( asset )
if e . IsIsolatedMargin {
req . IsolatedSymbol ( e . IsolatedMarginSymbol )
}
2022-09-19 09:02:50 +00:00
2022-04-22 06:14:01 +00:00
resp , err := req . Do ( ctx )
if err != nil {
2022-04-22 06:45:03 +00:00
return fixedpoint . Zero , err
2022-04-22 06:14:01 +00:00
}
2022-09-19 09:09:34 +00:00
return resp . Amount , nil
2022-04-22 06:14:01 +00:00
}
2022-04-22 06:07:09 +00:00
func ( e * Exchange ) RepayMarginAsset ( ctx context . Context , asset string , amount fixedpoint . Value ) error {
2022-04-26 07:58:12 +00:00
req := e . client . NewMarginRepayService ( )
2022-04-22 06:07:09 +00:00
req . Asset ( asset )
req . Amount ( amount . String ( ) )
if e . IsIsolatedMargin {
2022-09-19 09:02:50 +00:00
req . Symbol ( e . IsolatedMarginSymbol )
2022-04-22 06:07:09 +00:00
}
2022-04-23 07:00:04 +00:00
log . Infof ( "repaying margin asset %s amount %f" , asset , amount . Float64 ( ) )
2022-04-22 06:07:09 +00:00
resp , err := req . Do ( ctx )
2022-04-26 08:51:41 +00:00
if err != nil {
return err
}
2022-04-22 06:07:09 +00:00
log . Debugf ( "margin repayed %f %s, transaction id = %d" , amount . Float64 ( ) , asset , resp . TranID )
return err
2022-04-22 06:05:33 +00:00
}
2022-04-22 06:07:09 +00:00
func ( e * Exchange ) BorrowMarginAsset ( ctx context . Context , asset string , amount fixedpoint . Value ) error {
2022-04-26 07:58:12 +00:00
req := e . client . NewMarginLoanService ( )
2022-04-22 06:05:33 +00:00
req . Asset ( asset )
req . Amount ( amount . String ( ) )
if e . IsIsolatedMargin {
2022-09-19 09:02:50 +00:00
req . Symbol ( e . IsolatedMarginSymbol )
2022-04-22 06:05:33 +00:00
}
2022-04-23 07:00:04 +00:00
log . Infof ( "borrowing margin asset %s amount %f" , asset , amount . Float64 ( ) )
2022-04-22 06:05:33 +00:00
resp , err := req . Do ( ctx )
2022-04-26 08:43:40 +00:00
if err != nil {
return err
}
2022-04-22 06:05:33 +00:00
log . Debugf ( "margin borrowed %f %s, transaction id = %d" , amount . Float64 ( ) , asset , resp . TranID )
return err
}
2022-04-27 14:25:14 +00:00
func ( e * Exchange ) QueryMarginBorrowHistory ( ctx context . Context , asset string ) error {
req := e . client . NewListMarginLoansService ( )
req . Asset ( asset )
history , err := req . Do ( ctx )
if err != nil {
return err
}
_ = history
return nil
}
2023-03-22 18:42:26 +00:00
func ( e * Exchange ) TransferFuturesAccountAsset ( ctx context . Context , asset string , amount fixedpoint . Value , io types . TransferDirection ) error {
2023-03-22 16:55:00 +00:00
req := e . client2 . NewFuturesTransferRequest ( )
req . Asset ( asset )
req . Amount ( amount . String ( ) )
2023-03-22 18:42:26 +00:00
if io == types . TransferIn {
2023-03-22 16:55:00 +00:00
req . TransferType ( binanceapi . FuturesTransferSpotToUsdtFutures )
2023-03-22 18:42:26 +00:00
} else if io == types . TransferOut {
2023-03-22 16:55:00 +00:00
req . TransferType ( binanceapi . FuturesTransferUsdtFuturesToSpot )
2023-03-22 18:42:26 +00:00
} else {
return fmt . Errorf ( "unexpected transfer direction: %d given" , io )
2023-03-22 16:55:00 +00:00
}
resp , err := req . Do ( ctx )
2023-03-22 18:42:26 +00:00
log . Infof ( "futures transfer %s %s, transaction = %+v, err = %+v" , amount . String ( ) , asset , resp , err )
2023-03-22 16:55:00 +00:00
return err
}
2022-04-22 06:05:33 +00:00
// transferCrossMarginAccountAsset transfer asset to the cross margin account or to the main account
2023-03-22 18:42:26 +00:00
func ( e * Exchange ) transferCrossMarginAccountAsset ( ctx context . Context , asset string , amount fixedpoint . Value , io types . TransferDirection ) error {
2022-04-26 07:58:12 +00:00
req := e . client . NewMarginTransferService ( )
2022-04-22 05:57:29 +00:00
req . Asset ( asset )
req . Amount ( amount . String ( ) )
2023-03-22 18:42:26 +00:00
if io == types . TransferIn {
2022-04-22 05:57:29 +00:00
req . Type ( binance . MarginTransferTypeToMargin )
2023-03-22 18:42:26 +00:00
} else if io == types . TransferOut {
2022-04-22 05:57:29 +00:00
req . Type ( binance . MarginTransferTypeToMain )
2023-03-22 18:42:26 +00:00
} else {
return fmt . Errorf ( "unexpected transfer direction: %d given" , io )
2022-04-22 05:57:29 +00:00
}
2023-03-22 18:42:26 +00:00
2022-04-22 05:57:29 +00:00
resp , err := req . Do ( ctx )
2022-04-26 08:43:40 +00:00
if err != nil {
return err
}
2022-04-22 05:57:29 +00:00
2022-04-22 06:05:33 +00:00
log . Debugf ( "cross margin transfer %f %s, transaction id = %d" , amount . Float64 ( ) , asset , resp . TranID )
2022-04-22 05:57:29 +00:00
return err
}
2022-08-24 04:58:06 +00:00
func ( e * Exchange ) QueryCrossMarginAccount ( ctx context . Context ) ( * types . Account , error ) {
2022-04-26 07:58:12 +00:00
marginAccount , err := e . client . NewGetMarginAccountService ( ) . Do ( ctx )
2021-02-28 06:52:14 +00:00
if err != nil {
return nil , err
}
2022-04-22 06:45:03 +00:00
marginLevel := fixedpoint . MustNewFromString ( marginAccount . MarginLevel )
2022-01-13 11:31:33 +00:00
a := & types . Account {
2022-04-22 05:57:29 +00:00
AccountType : types . AccountTypeMargin ,
MarginInfo : toGlobalMarginAccountInfo ( marginAccount ) , // In binance GO api, Account define marginAccount info which mantain []*AccountAsset and []*AccountPosition.
2022-04-22 06:45:03 +00:00
MarginLevel : marginLevel ,
MarginTolerance : calculateMarginTolerance ( marginLevel ) ,
2022-04-22 05:57:29 +00:00
BorrowEnabled : marginAccount . BorrowEnabled ,
2022-04-22 05:41:44 +00:00
TransferEnabled : marginAccount . TransferEnabled ,
2022-01-13 11:31:33 +00:00
}
2022-04-21 07:53:52 +00:00
// convert cross margin user assets into balances
2022-04-13 07:38:13 +00:00
balances := types . BalanceMap { }
2022-04-21 07:53:52 +00:00
for _ , userAsset := range marginAccount . UserAssets {
2022-04-13 07:38:13 +00:00
balances [ userAsset . Asset ] = types . Balance {
Currency : userAsset . Asset ,
Available : fixedpoint . MustNewFromString ( userAsset . Free ) ,
Locked : fixedpoint . MustNewFromString ( userAsset . Locked ) ,
2022-04-21 07:53:52 +00:00
Interest : fixedpoint . MustNewFromString ( userAsset . Interest ) ,
Borrowed : fixedpoint . MustNewFromString ( userAsset . Borrowed ) ,
NetAsset : fixedpoint . MustNewFromString ( userAsset . NetAsset ) ,
2022-04-13 07:38:13 +00:00
}
}
a . UpdateBalances ( balances )
2022-01-13 11:31:33 +00:00
return a , nil
2020-12-21 09:48:30 +00:00
}
2022-08-24 04:58:06 +00:00
func ( e * Exchange ) QueryIsolatedMarginAccount ( ctx context . Context ) ( * types . Account , error ) {
2022-04-26 07:58:12 +00:00
req := e . client . NewGetIsolatedMarginAccountService ( )
2022-04-21 07:53:52 +00:00
req . Symbols ( e . IsolatedMarginSymbol )
2020-12-02 14:19:31 +00:00
2022-04-21 07:53:52 +00:00
marginAccount , err := req . Do ( ctx )
2021-02-28 06:52:14 +00:00
if err != nil {
return nil , err
}
2022-01-13 11:31:33 +00:00
a := & types . Account {
2022-04-22 06:45:45 +00:00
AccountType : types . AccountTypeIsolatedMargin ,
2022-04-21 07:53:52 +00:00
IsolatedMarginInfo : toGlobalIsolatedMarginAccountInfo ( marginAccount ) , // In binance GO api, Account define marginAccount info which mantain []*AccountAsset and []*AccountPosition.
2022-01-13 11:31:33 +00:00
}
2022-06-03 16:39:24 +00:00
if len ( marginAccount . Assets ) == 0 {
return nil , fmt . Errorf ( "empty margin account assets, please check your isolatedMarginSymbol is correctly set: %+v" , marginAccount )
}
2022-04-21 07:53:52 +00:00
// for isolated margin account, we will only have one asset in the Assets array.
if len ( marginAccount . Assets ) > 1 {
return nil , fmt . Errorf ( "unexpected number of user assets returned, got %d user assets" , len ( marginAccount . Assets ) )
}
2022-04-13 07:38:13 +00:00
2022-04-21 07:53:52 +00:00
userAsset := marginAccount . Assets [ 0 ]
2022-04-22 06:45:03 +00:00
marginLevel := fixedpoint . MustNewFromString ( userAsset . MarginLevel )
a . MarginLevel = marginLevel
a . MarginTolerance = calculateMarginTolerance ( marginLevel )
2022-04-22 05:41:44 +00:00
a . MarginRatio = fixedpoint . MustNewFromString ( userAsset . MarginRatio )
2022-04-21 08:23:06 +00:00
a . BorrowEnabled = userAsset . BaseAsset . BorrowEnabled || userAsset . QuoteAsset . BorrowEnabled
2022-04-22 05:41:44 +00:00
a . LiquidationPrice = fixedpoint . MustNewFromString ( userAsset . LiquidatePrice )
a . LiquidationRate = fixedpoint . MustNewFromString ( userAsset . LiquidateRate )
2022-04-21 07:53:52 +00:00
// Convert user assets into balances
balances := types . BalanceMap { }
balances [ userAsset . BaseAsset . Asset ] = types . Balance {
Currency : userAsset . BaseAsset . Asset ,
Available : fixedpoint . MustNewFromString ( userAsset . BaseAsset . Free ) ,
Locked : fixedpoint . MustNewFromString ( userAsset . BaseAsset . Locked ) ,
Interest : fixedpoint . MustNewFromString ( userAsset . BaseAsset . Interest ) ,
Borrowed : fixedpoint . MustNewFromString ( userAsset . BaseAsset . Borrowed ) ,
NetAsset : fixedpoint . MustNewFromString ( userAsset . BaseAsset . NetAsset ) ,
2022-04-13 07:38:13 +00:00
}
2022-04-21 07:53:52 +00:00
balances [ userAsset . QuoteAsset . Asset ] = types . Balance {
Currency : userAsset . QuoteAsset . Asset ,
Available : fixedpoint . MustNewFromString ( userAsset . QuoteAsset . Free ) ,
Locked : fixedpoint . MustNewFromString ( userAsset . QuoteAsset . Locked ) ,
Interest : fixedpoint . MustNewFromString ( userAsset . QuoteAsset . Interest ) ,
Borrowed : fixedpoint . MustNewFromString ( userAsset . QuoteAsset . Borrowed ) ,
NetAsset : fixedpoint . MustNewFromString ( userAsset . QuoteAsset . NetAsset ) ,
}
2022-04-13 07:38:13 +00:00
2022-04-21 07:53:52 +00:00
a . UpdateBalances ( balances )
2022-01-13 11:31:33 +00:00
return a , nil
2020-07-11 05:02:53 +00:00
}
2022-06-02 03:42:03 +00:00
func ( e * Exchange ) Withdraw ( ctx context . Context , asset string , amount fixedpoint . Value , address string , options * types . WithdrawalOptions ) error {
req := e . client2 . NewWithdrawRequest ( )
2021-09-01 16:21:56 +00:00
req . Coin ( asset )
req . Address ( address )
req . Amount ( fmt . Sprintf ( "%f" , amount . Float64 ( ) ) )
2021-05-11 18:15:22 +00:00
2021-05-26 15:24:05 +00:00
if options != nil {
if options . Network != "" {
req . Network ( options . Network )
}
if options . AddressTag != "" {
req . Network ( options . AddressTag )
}
}
response , err := req . Do ( ctx )
2021-05-11 18:15:22 +00:00
if err != nil {
return err
}
log . Infof ( "withdrawal request sent, response: %+v" , response )
return nil
}
2022-06-02 03:40:05 +00:00
func ( e * Exchange ) QueryWithdrawHistory ( ctx context . Context , asset string , since , until time . Time ) ( withdraws [ ] types . Withdraw , err error ) {
2021-03-13 12:49:51 +00:00
var emptyTime = time . Time { }
2022-06-01 18:31:46 +00:00
if since == emptyTime {
since , err = getLaunchDate ( )
2021-03-13 12:49:51 +00:00
if err != nil {
2022-06-02 03:40:05 +00:00
return withdraws , err
2021-03-13 12:49:51 +00:00
}
}
2022-06-01 18:31:46 +00:00
// startTime ~ endTime must be in 90 days
historyDayRangeLimit := time . Hour * 24 * 89
if until . Sub ( since ) >= historyDayRangeLimit {
until = since . Add ( historyDayRangeLimit )
}
2020-08-13 02:11:27 +00:00
2022-06-02 03:40:05 +00:00
req := e . client2 . NewGetWithdrawHistoryRequest ( )
2022-06-01 18:31:46 +00:00
if len ( asset ) > 0 {
req . Coin ( asset )
}
2020-10-12 09:15:33 +00:00
2022-06-02 03:40:05 +00:00
records , err := req .
StartTime ( since ) .
EndTime ( until ) .
Limit ( 1000 ) .
2022-06-01 18:31:46 +00:00
Do ( ctx )
2020-08-13 02:11:27 +00:00
2022-06-01 18:31:46 +00:00
if err != nil {
2022-06-02 03:40:05 +00:00
return withdraws , err
}
2020-08-13 02:11:27 +00:00
2022-06-02 03:40:05 +00:00
for _ , d := range records {
// time format: 2006-01-02 15:04:05
2022-06-01 18:31:46 +00:00
applyTime , err := time . Parse ( "2006-01-02 15:04:05" , d . ApplyTime )
if err != nil {
return nil , err
2020-08-13 02:11:27 +00:00
}
2022-06-02 03:40:05 +00:00
withdraws = append ( withdraws , types . Withdraw {
2022-06-01 18:31:46 +00:00
Exchange : types . ExchangeBinance ,
ApplyTime : types . Time ( applyTime ) ,
Asset : d . Coin ,
2022-06-02 03:40:05 +00:00
Amount : d . Amount ,
2022-06-01 18:31:46 +00:00
Address : d . Address ,
TransactionID : d . TxID ,
2022-06-02 03:40:05 +00:00
TransactionFee : d . TransactionFee ,
2022-06-01 18:31:46 +00:00
WithdrawOrderID : d . WithdrawOrderID ,
Network : d . Network ,
2022-06-02 03:40:05 +00:00
Status : d . Status . String ( ) ,
2022-06-01 18:31:46 +00:00
} )
2020-08-13 02:11:27 +00:00
}
2022-06-02 03:40:05 +00:00
return withdraws , nil
2020-08-13 02:11:27 +00:00
}
2020-10-11 09:35:59 +00:00
func ( e * Exchange ) QueryDepositHistory ( ctx context . Context , asset string , since , until time . Time ) ( allDeposits [ ] types . Deposit , err error ) {
2021-04-21 07:06:53 +00:00
var emptyTime = time . Time { }
2022-06-08 07:27:52 +00:00
if since == emptyTime {
since , err = getLaunchDate ( )
2021-04-21 07:06:53 +00:00
if err != nil {
return nil , err
}
}
2020-10-12 09:15:33 +00:00
2022-06-08 07:27:52 +00:00
// startTime ~ endTime must be in 90 days
historyDayRangeLimit := time . Hour * 24 * 89
if until . Sub ( since ) >= historyDayRangeLimit {
until = since . Add ( historyDayRangeLimit )
}
2020-08-13 02:11:27 +00:00
2022-06-08 07:27:52 +00:00
req := e . client2 . NewGetDepositHistoryRequest ( )
if len ( asset ) > 0 {
req . Coin ( asset )
}
2020-08-13 02:11:27 +00:00
2022-06-08 07:27:52 +00:00
req . StartTime ( since ) .
EndTime ( until )
2020-08-13 02:11:27 +00:00
2022-06-08 07:27:52 +00:00
records , err := req . Do ( ctx )
if err != nil {
return nil , err
}
2020-08-13 02:11:27 +00:00
2022-06-08 07:27:52 +00:00
for _ , d := range records {
// 0(0:pending,6: credited but cannot withdraw, 1:success)
// set the default status
status := types . DepositStatus ( fmt . Sprintf ( "code: %d" , d . Status ) )
switch d . Status {
case 0 :
status = types . DepositPending
case 6 :
// https://www.binance.com/en/support/faq/115003736451
status = types . DepositCredited
case 1 :
status = types . DepositSuccess
2020-08-13 02:11:27 +00:00
}
2022-06-08 07:27:52 +00:00
allDeposits = append ( allDeposits , types . Deposit {
Exchange : types . ExchangeBinance ,
Time : types . Time ( d . InsertTime . Time ( ) ) ,
Asset : d . Coin ,
Amount : d . Amount ,
Address : d . Address ,
AddressTag : d . AddressTag ,
TransactionID : d . TxId ,
Status : status ,
} )
2020-08-13 02:11:27 +00:00
}
return allDeposits , nil
}
2020-10-06 09:32:41 +00:00
func ( e * Exchange ) QueryAccountBalances ( ctx context . Context ) ( types . BalanceMap , error ) {
2020-07-13 04:28:40 +00:00
account , err := e . QueryAccount ( ctx )
if err != nil {
return nil , err
}
2020-10-18 03:30:37 +00:00
return account . Balances ( ) , nil
2020-07-13 04:28:40 +00:00
}
2020-09-19 02:59:43 +00:00
func ( e * Exchange ) PlatformFeeCurrency ( ) string {
2021-05-25 18:15:47 +00:00
return BNB
2020-08-03 12:06:33 +00:00
}
2022-01-03 19:30:36 +00:00
func ( e * Exchange ) QuerySpotAccount ( ctx context . Context ) ( * types . Account , error ) {
2022-04-26 07:58:12 +00:00
account , err := e . client . NewGetAccountService ( ) . Do ( ctx )
2020-07-13 04:28:40 +00:00
if err != nil {
return nil , err
}
var balances = map [ string ] types . Balance { }
2020-07-15 04:20:44 +00:00
for _ , b := range account . Balances {
2020-07-13 04:28:40 +00:00
balances [ b . Asset ] = types . Balance {
Currency : b . Asset ,
2022-01-13 11:31:33 +00:00
Available : fixedpoint . MustNewFromString ( b . Free ) ,
Locked : fixedpoint . MustNewFromString ( b . Locked ) ,
2020-07-13 04:28:40 +00:00
}
}
2020-10-18 03:30:37 +00:00
a := & types . Account {
2022-03-21 09:56:11 +00:00
AccountType : types . AccountTypeSpot ,
CanDeposit : account . CanDeposit , // if can transfer in asset
CanTrade : account . CanTrade , // if can trade
CanWithdraw : account . CanWithdraw , // if can transfer out asset
2020-10-18 03:30:37 +00:00
}
a . UpdateBalances ( balances )
return a , nil
2020-07-13 04:28:40 +00:00
}
2022-08-16 02:55:45 +00:00
// QueryFuturesAccount gets the futures account balances from Binance
// Balance.Available = Wallet Balance(in Binance UI) - Used Margin
// Balance.Locked = Used Margin
2022-01-03 19:30:36 +00:00
func ( e * Exchange ) QueryFuturesAccount ( ctx context . Context ) ( * types . Account , error ) {
account , err := e . futuresClient . NewGetAccountService ( ) . Do ( ctx )
if err != nil {
return nil , err
}
accountBalances , err := e . futuresClient . NewGetBalanceService ( ) . Do ( ctx )
if err != nil {
return nil , err
}
var balances = map [ string ] types . Balance { }
for _ , b := range accountBalances {
2022-08-16 02:55:45 +00:00
balanceAvailable := fixedpoint . Must ( fixedpoint . NewFromString ( b . AvailableBalance ) )
balanceTotal := fixedpoint . Must ( fixedpoint . NewFromString ( b . Balance ) )
2022-08-16 07:06:13 +00:00
unrealizedPnl := fixedpoint . Must ( fixedpoint . NewFromString ( b . CrossUnPnl ) )
2022-01-03 19:30:36 +00:00
balances [ b . Asset ] = types . Balance {
Currency : b . Asset ,
2022-08-16 02:55:45 +00:00
Available : balanceAvailable ,
2022-08-16 07:06:13 +00:00
Locked : balanceTotal . Sub ( balanceAvailable . Sub ( unrealizedPnl ) ) ,
2022-01-03 19:30:36 +00:00
}
}
a := & types . Account {
AccountType : types . AccountTypeFutures ,
FuturesInfo : toGlobalFuturesAccountInfo ( account ) , // In binance GO api, Account define account info which mantain []*AccountAsset and []*AccountPosition.
CanDeposit : account . CanDeposit , // if can transfer in asset
CanTrade : account . CanTrade , // if can trade
CanWithdraw : account . CanWithdraw , // if can transfer out asset
}
a . UpdateBalances ( balances )
return a , nil
}
func ( e * Exchange ) QueryAccount ( ctx context . Context ) ( * types . Account , error ) {
2022-01-13 11:31:33 +00:00
var account * types . Account
var err error
if e . IsFutures {
account , err = e . QueryFuturesAccount ( ctx )
} else if e . IsIsolatedMargin {
2022-08-24 04:58:06 +00:00
account , err = e . QueryIsolatedMarginAccount ( ctx )
2022-01-13 11:31:33 +00:00
} else if e . IsMargin {
2022-08-24 04:58:06 +00:00
account , err = e . QueryCrossMarginAccount ( ctx )
2022-01-13 11:31:33 +00:00
} else {
account , err = e . QuerySpotAccount ( ctx )
2022-01-03 19:30:36 +00:00
}
2022-01-14 08:10:55 +00:00
return account , err
2022-01-03 19:30:36 +00:00
}
2020-10-25 10:26:10 +00:00
func ( e * Exchange ) QueryOpenOrders ( ctx context . Context , symbol string ) ( orders [ ] types . Order , err error ) {
2021-01-19 18:09:12 +00:00
if e . IsMargin {
2022-04-26 07:58:12 +00:00
req := e . client . NewListMarginOpenOrdersService ( ) . Symbol ( symbol )
2021-01-19 18:09:12 +00:00
req . IsIsolated ( e . IsIsolatedMargin )
2021-01-18 13:56:58 +00:00
binanceOrders , err := req . Do ( ctx )
2020-10-25 10:26:10 +00:00
if err != nil {
return orders , err
}
2022-08-19 07:28:03 +00:00
return toGlobalOrders ( binanceOrders , false )
2021-01-18 11:35:40 +00:00
}
2021-12-14 10:01:24 +00:00
if e . IsFutures {
req := e . futuresClient . NewListOpenOrdersService ( ) . Symbol ( symbol )
binanceOrders , err := req . Do ( ctx )
if err != nil {
return orders , err
}
2022-08-19 07:28:03 +00:00
return toGlobalFuturesOrders ( binanceOrders , false )
2021-12-14 10:01:24 +00:00
}
2022-04-26 07:58:12 +00:00
binanceOrders , err := e . client . NewListOpenOrdersService ( ) . Symbol ( symbol ) . Do ( ctx )
2021-01-18 11:35:40 +00:00
if err != nil {
return orders , err
2020-10-25 10:26:10 +00:00
}
2022-08-19 07:28:03 +00:00
return toGlobalOrders ( binanceOrders , false )
2020-10-25 10:26:10 +00:00
}
2022-08-11 18:06:43 +00:00
func ( e * Exchange ) QueryOrderTrades ( ctx context . Context , q types . OrderQuery ) ( [ ] types . Trade , error ) {
orderID , err := strconv . ParseInt ( q . OrderID , 10 , 64 )
if err != nil {
return nil , err
}
2022-08-12 03:54:04 +00:00
if len ( q . Symbol ) == 0 {
return nil , errors . New ( "binance: symbol parameter is a mandatory parameter for querying order trades" )
}
remoteTrades , err := e . client . NewListTradesService ( ) . Symbol ( q . Symbol ) . OrderId ( orderID ) . Do ( ctx )
2022-08-11 18:06:43 +00:00
if err != nil {
return nil , err
}
var trades [ ] types . Trade
for _ , t := range remoteTrades {
localTrade , err := toGlobalTrade ( * t , e . IsMargin )
if err != nil {
2022-08-12 03:54:04 +00:00
log . WithError ( err ) . Errorf ( "binance: can not convert trade: %+v" , t )
2022-08-11 18:06:43 +00:00
continue
}
trades = append ( trades , * localTrade )
}
trades = types . SortTradesAscending ( trades )
return trades , nil
}
2022-02-10 09:48:53 +00:00
func ( e * Exchange ) QueryOrder ( ctx context . Context , q types . OrderQuery ) ( * types . Order , error ) {
orderID , err := strconv . ParseInt ( q . OrderID , 10 , 64 )
if err != nil {
return nil , err
}
var order * binance . Order
if e . IsMargin {
2022-04-26 07:58:12 +00:00
order , err = e . client . NewGetMarginOrderService ( ) . Symbol ( q . Symbol ) . OrderID ( orderID ) . Do ( ctx )
2022-02-10 09:48:53 +00:00
} else {
2022-04-26 07:58:12 +00:00
order , err = e . client . NewGetOrderService ( ) . Symbol ( q . Symbol ) . OrderID ( orderID ) . Do ( ctx )
2022-02-10 09:48:53 +00:00
}
if err != nil {
return nil , err
}
return toGlobalOrder ( order , e . IsMargin )
}
2020-11-05 03:00:51 +00:00
func ( e * Exchange ) QueryClosedOrders ( ctx context . Context , symbol string , since , until time . Time , lastOrderID uint64 ) ( orders [ ] types . Order , err error ) {
2022-01-23 08:19:13 +00:00
// we can only query orders within 24 hours
// if the until-since is more than 24 hours, we should reset the until to:
// new until = since + 24 hours - 1 millisecond
/ *
if until . Sub ( since ) >= 24 * time . Hour {
until = since . Add ( 24 * time . Hour - time . Millisecond )
}
* /
2020-11-05 00:33:57 +00:00
2022-01-10 08:33:19 +00:00
if err := orderLimiter . Wait ( ctx ) ; err != nil {
log . WithError ( err ) . Errorf ( "order rate limiter wait error" )
}
2021-01-19 18:09:12 +00:00
log . Infof ( "querying closed orders %s from %s <=> %s ..." , symbol , since , until )
2020-11-05 00:33:57 +00:00
2021-01-19 18:09:12 +00:00
if e . IsMargin {
2022-04-26 07:58:12 +00:00
req := e . client . NewListMarginOrdersService ( ) . Symbol ( symbol )
2021-01-19 18:09:12 +00:00
req . IsIsolated ( e . IsIsolatedMargin )
2021-01-18 13:56:58 +00:00
if lastOrderID > 0 {
req . OrderID ( int64 ( lastOrderID ) )
} else {
2022-04-11 07:39:03 +00:00
req . StartTime ( since . UnixNano ( ) / int64 ( time . Millisecond ) )
if until . Sub ( since ) < 24 * time . Hour {
req . EndTime ( until . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2021-01-18 13:56:58 +00:00
}
binanceOrders , err := req . Do ( ctx )
if err != nil {
return orders , err
}
2022-08-19 07:28:03 +00:00
return toGlobalOrders ( binanceOrders , e . IsMargin )
2021-01-18 13:56:58 +00:00
}
2021-12-14 10:01:24 +00:00
if e . IsFutures {
req := e . futuresClient . NewListOrdersService ( ) . Symbol ( symbol )
if lastOrderID > 0 {
req . OrderID ( int64 ( lastOrderID ) )
} else {
2022-01-23 08:19:13 +00:00
req . StartTime ( since . UnixNano ( ) / int64 ( time . Millisecond ) )
2022-04-11 07:39:03 +00:00
if until . Sub ( since ) < 24 * time . Hour {
2022-01-23 08:19:13 +00:00
req . EndTime ( until . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2021-12-14 10:01:24 +00:00
}
binanceOrders , err := req . Do ( ctx )
if err != nil {
return orders , err
}
2022-08-19 07:28:03 +00:00
return toGlobalFuturesOrders ( binanceOrders , false )
2021-12-14 10:01:24 +00:00
}
2022-01-23 08:19:13 +00:00
// If orderId is set, it will get orders >= that orderId. Otherwise most recent orders are returned.
// For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time.
// If startTime and/or endTime provided, orderId is not required.
2022-04-26 07:58:12 +00:00
req := e . client . NewListOrdersService ( ) .
2020-11-05 03:00:51 +00:00
Symbol ( symbol )
2020-11-05 00:33:57 +00:00
2020-11-05 03:00:51 +00:00
if lastOrderID > 0 {
req . OrderID ( int64 ( lastOrderID ) )
} else {
2022-01-23 08:19:13 +00:00
req . StartTime ( since . UnixNano ( ) / int64 ( time . Millisecond ) )
2022-04-11 07:39:03 +00:00
if until . Sub ( since ) < 24 * time . Hour {
2022-01-23 08:19:13 +00:00
req . EndTime ( until . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2020-11-05 03:00:51 +00:00
}
2020-11-05 00:33:57 +00:00
2022-01-23 08:19:13 +00:00
// default 500, max 1000
req . Limit ( 1000 )
2020-11-05 03:00:51 +00:00
binanceOrders , err := req . Do ( ctx )
if err != nil {
return orders , err
}
2020-11-05 00:33:57 +00:00
2022-08-19 07:28:03 +00:00
return toGlobalOrders ( binanceOrders , e . IsMargin )
2020-11-05 00:33:57 +00:00
}
2021-12-14 11:09:44 +00:00
func ( e * Exchange ) CancelOrders ( ctx context . Context , orders ... types . Order ) ( err error ) {
2022-01-14 16:52:54 +00:00
if err := orderLimiter . Wait ( ctx ) ; err != nil {
log . WithError ( err ) . Errorf ( "order rate limiter wait error" )
}
2021-12-14 10:01:24 +00:00
if e . IsFutures {
for _ , o := range orders {
var req = e . futuresClient . NewCancelOrderService ( )
// Mandatory
req . Symbol ( o . Symbol )
if o . OrderID > 0 {
req . OrderID ( int64 ( o . OrderID ) )
2022-01-11 08:38:02 +00:00
} else {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError (
fmt . Errorf ( "can not cancel %s order, order does not contain orderID or clientOrderID" , o . Symbol ) ,
o ) )
2022-01-11 08:38:02 +00:00
continue
2021-12-14 10:01:24 +00:00
}
2022-01-11 08:35:49 +00:00
_ , err2 := req . Do ( ctx )
if err2 != nil {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError ( err2 , o ) )
2021-12-14 10:01:24 +00:00
}
}
2021-12-14 11:09:44 +00:00
return err
2021-12-14 10:01:24 +00:00
}
2020-10-25 16:26:17 +00:00
for _ , o := range orders {
2021-12-29 09:35:27 +00:00
if e . IsMargin {
2022-04-26 07:58:12 +00:00
var req = e . client . NewCancelMarginOrderService ( )
2021-12-29 09:36:08 +00:00
req . IsIsolated ( e . IsIsolatedMargin )
2021-12-29 09:35:27 +00:00
req . Symbol ( o . Symbol )
2020-10-25 16:26:17 +00:00
2021-12-29 09:35:27 +00:00
if o . OrderID > 0 {
req . OrderID ( int64 ( o . OrderID ) )
} else if len ( o . ClientOrderID ) > 0 {
2022-01-10 05:29:27 +00:00
req . OrigClientOrderID ( o . ClientOrderID )
2022-01-11 08:38:02 +00:00
} else {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError (
fmt . Errorf ( "can not cancel %s order, order does not contain orderID or clientOrderID" , o . Symbol ) ,
o ) )
2022-01-11 08:38:02 +00:00
continue
2021-12-29 09:35:27 +00:00
}
2020-10-25 16:26:17 +00:00
2022-01-10 05:29:27 +00:00
_ , err2 := req . Do ( ctx )
if err2 != nil {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError ( err2 , o ) )
2021-12-29 09:35:27 +00:00
}
} else {
2022-01-11 08:35:49 +00:00
// SPOT
2022-04-26 07:58:12 +00:00
var req = e . client . NewCancelOrderService ( )
2021-12-29 09:35:27 +00:00
req . Symbol ( o . Symbol )
if o . OrderID > 0 {
req . OrderID ( int64 ( o . OrderID ) )
} else if len ( o . ClientOrderID ) > 0 {
2022-01-10 05:29:27 +00:00
req . OrigClientOrderID ( o . ClientOrderID )
2022-01-11 08:38:02 +00:00
} else {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError (
fmt . Errorf ( "can not cancel %s order, order does not contain orderID or clientOrderID" , o . Symbol ) ,
o ) )
2022-01-11 08:38:02 +00:00
continue
2021-12-29 09:35:27 +00:00
}
2020-10-25 16:26:17 +00:00
2022-01-10 05:29:27 +00:00
_ , err2 := req . Do ( ctx )
if err2 != nil {
2022-01-11 10:00:07 +00:00
err = multierr . Append ( err , types . NewOrderError ( err2 , o ) )
2021-12-29 09:35:27 +00:00
}
2020-10-25 16:26:17 +00:00
}
}
2021-12-14 11:09:44 +00:00
return err
2020-10-25 16:26:17 +00:00
}
2021-01-15 07:11:38 +00:00
func ( e * Exchange ) submitMarginOrder ( ctx context . Context , order types . SubmitOrder ) ( * types . Order , error ) {
2021-01-15 07:46:54 +00:00
orderType , err := toLocalOrderType ( order . Type )
if err != nil {
return nil , err
}
2022-04-26 07:58:12 +00:00
req := e . client . NewCreateMarginOrderService ( ) .
2021-01-15 07:46:54 +00:00
Symbol ( order . Symbol ) .
Type ( orderType ) .
2021-06-06 17:06:38 +00:00
Side ( binance . SideType ( order . Side ) )
clientOrderID := newSpotClientOrderID ( order . ClientOrderID )
if len ( clientOrderID ) > 0 {
req . NewClientOrderID ( clientOrderID )
}
2021-01-15 07:46:54 +00:00
// use response result format
req . NewOrderRespType ( binance . NewOrderRespTypeRESULT )
2021-01-19 18:09:12 +00:00
if e . IsIsolatedMargin {
req . IsIsolated ( e . IsIsolatedMargin )
2021-01-15 07:46:54 +00:00
}
if len ( order . MarginSideEffect ) > 0 {
req . SideEffectType ( binance . SideEffectType ( order . MarginSideEffect ) )
}
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-01-15 23:37:42 +00:00
req . Quantity ( order . Market . FormatQuantity ( order . Quantity ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . Quantity ( order . Quantity . FormatString ( 8 ) )
2021-01-15 23:37:42 +00:00
}
2021-01-15 07:46:54 +00:00
2021-02-10 16:21:56 +00:00
// set price field for limit orders
switch order . Type {
2021-10-17 16:41:41 +00:00
case types . OrderTypeStopLimit , types . OrderTypeLimit , types . OrderTypeLimitMaker :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-02-10 16:21:56 +00:00
req . Price ( order . Market . FormatPrice ( order . Price ) )
2022-01-10 17:36:19 +00:00
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . Price ( order . Price . FormatString ( 8 ) )
2021-02-10 16:21:56 +00:00
}
2021-01-15 07:46:54 +00:00
}
2021-02-10 16:21:56 +00:00
// set stop price
2021-01-15 07:46:54 +00:00
switch order . Type {
2021-02-10 16:21:56 +00:00
2021-01-15 07:46:54 +00:00
case types . OrderTypeStopLimit , types . OrderTypeStopMarket :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
req . StopPrice ( order . Market . FormatPrice ( order . StopPrice ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . StopPrice ( order . StopPrice . FormatString ( 8 ) )
2021-01-15 07:46:54 +00:00
}
}
2021-01-15 23:37:42 +00:00
// could be IOC or FOK
2021-01-15 07:46:54 +00:00
if len ( order . TimeInForce ) > 0 {
// TODO: check the TimeInForce value
req . TimeInForce ( binance . TimeInForceType ( order . TimeInForce ) )
2021-04-13 15:18:40 +00:00
} else {
switch order . Type {
case types . OrderTypeLimit , types . OrderTypeStopLimit :
req . TimeInForce ( binance . TimeInForceTypeGTC )
}
2021-01-15 07:46:54 +00:00
}
response , err := req . Do ( ctx )
if err != nil {
return nil , err
}
log . Infof ( "margin order creation response: %+v" , response )
2021-05-28 16:26:39 +00:00
createdOrder , err := toGlobalOrder ( & binance . Order {
2021-01-15 07:46:54 +00:00
Symbol : response . Symbol ,
OrderID : response . OrderID ,
ClientOrderID : response . ClientOrderID ,
Price : response . Price ,
OrigQuantity : response . OrigQuantity ,
ExecutedQuantity : response . ExecutedQuantity ,
CummulativeQuoteQuantity : response . CummulativeQuoteQuantity ,
Status : response . Status ,
TimeInForce : response . TimeInForce ,
Type : response . Type ,
Side : response . Side ,
UpdateTime : response . TransactTime ,
Time : response . TransactTime ,
2021-01-19 17:24:29 +00:00
IsIsolated : response . IsIsolated ,
} , true )
2021-01-15 07:46:54 +00:00
return createdOrder , err
2021-01-15 07:11:38 +00:00
}
2020-07-11 05:02:53 +00:00
2021-12-13 15:19:14 +00:00
func ( e * Exchange ) submitFuturesOrder ( ctx context . Context , order types . SubmitOrder ) ( * types . Order , error ) {
orderType , err := toLocalFuturesOrderType ( order . Type )
if err != nil {
return nil , err
}
req := e . futuresClient . NewCreateOrderService ( ) .
Symbol ( order . Symbol ) .
Type ( orderType ) .
2022-03-21 09:56:11 +00:00
Side ( futures . SideType ( order . Side ) ) .
ReduceOnly ( order . ReduceOnly )
2021-12-13 15:19:14 +00:00
2022-01-23 07:26:15 +00:00
clientOrderID := newFuturesClientOrderID ( order . ClientOrderID )
2021-12-13 15:19:14 +00:00
if len ( clientOrderID ) > 0 {
req . NewClientOrderID ( clientOrderID )
}
// use response result format
req . NewOrderResponseType ( futures . NewOrderRespTypeRESULT )
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-12-13 15:19:14 +00:00
req . Quantity ( order . Market . FormatQuantity ( order . Quantity ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . Quantity ( order . Quantity . FormatString ( 8 ) )
2021-12-13 15:19:14 +00:00
}
// set price field for limit orders
switch order . Type {
case types . OrderTypeStopLimit , types . OrderTypeLimit , types . OrderTypeLimitMaker :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-12-13 15:19:14 +00:00
req . Price ( order . Market . FormatPrice ( order . Price ) )
2022-01-10 17:36:19 +00:00
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . Price ( order . Price . FormatString ( 8 ) )
2021-12-13 15:19:14 +00:00
}
}
// set stop price
switch order . Type {
case types . OrderTypeStopLimit , types . OrderTypeStopMarket :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
req . StopPrice ( order . Market . FormatPrice ( order . StopPrice ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO report error
req . StopPrice ( order . StopPrice . FormatString ( 8 ) )
2021-12-13 15:19:14 +00:00
}
}
// could be IOC or FOK
if len ( order . TimeInForce ) > 0 {
// TODO: check the TimeInForce value
req . TimeInForce ( futures . TimeInForceType ( order . TimeInForce ) )
} else {
switch order . Type {
2022-08-11 03:17:40 +00:00
case types . OrderTypeLimit , types . OrderTypeLimitMaker , types . OrderTypeStopLimit :
2021-12-13 15:19:14 +00:00
req . TimeInForce ( futures . TimeInForceTypeGTC )
}
}
response , err := req . Do ( ctx )
if err != nil {
return nil , err
}
log . Infof ( "futures order creation response: %+v" , response )
createdOrder , err := toGlobalFuturesOrder ( & futures . Order {
Symbol : response . Symbol ,
OrderID : response . OrderID ,
ClientOrderID : response . ClientOrderID ,
Price : response . Price ,
OrigQuantity : response . OrigQuantity ,
ExecutedQuantity : response . ExecutedQuantity ,
2021-12-14 12:38:56 +00:00
Status : response . Status ,
TimeInForce : response . TimeInForce ,
Type : response . Type ,
Side : response . Side ,
2022-03-21 09:56:11 +00:00
ReduceOnly : response . ReduceOnly ,
2022-08-19 07:28:03 +00:00
} , false )
2021-12-13 15:19:14 +00:00
return createdOrder , err
}
2021-04-28 11:20:55 +00:00
// BBGO is a broker on Binance
const spotBrokerID = "NSUYEBKM"
func newSpotClientOrderID ( originalID string ) ( clientOrderID string ) {
2021-06-06 17:00:01 +00:00
if originalID == types . NoClientOrderID {
return ""
}
2021-04-28 11:20:55 +00:00
prefix := "x-" + spotBrokerID
prefixLen := len ( prefix )
if originalID != "" {
// try to keep the whole original client order ID if user specifies it.
2021-05-11 18:15:22 +00:00
if prefixLen + len ( originalID ) > 32 {
2021-04-28 11:20:55 +00:00
return originalID
}
clientOrderID = prefix + originalID
return clientOrderID
}
clientOrderID = uuid . New ( ) . String ( )
clientOrderID = prefix + clientOrderID
if len ( clientOrderID ) > 32 {
return clientOrderID [ 0 : 32 ]
}
return clientOrderID
}
2022-01-23 07:26:15 +00:00
// BBGO is a futures broker on Binance
const futuresBrokerID = "gBhMvywy"
func newFuturesClientOrderID ( originalID string ) ( clientOrderID string ) {
if originalID == types . NoClientOrderID {
return ""
}
prefix := "x-" + futuresBrokerID
prefixLen := len ( prefix )
if originalID != "" {
// try to keep the whole original client order ID if user specifies it.
if prefixLen + len ( originalID ) > 32 {
return originalID
}
clientOrderID = prefix + originalID
return clientOrderID
}
clientOrderID = uuid . New ( ) . String ( )
clientOrderID = prefix + clientOrderID
if len ( clientOrderID ) > 32 {
return clientOrderID [ 0 : 32 ]
}
return clientOrderID
}
2021-01-15 07:11:38 +00:00
func ( e * Exchange ) submitSpotOrder ( ctx context . Context , order types . SubmitOrder ) ( * types . Order , error ) {
orderType , err := toLocalOrderType ( order . Type )
if err != nil {
return nil , err
}
2020-10-25 10:56:07 +00:00
2022-04-26 07:58:12 +00:00
req := e . client . NewCreateOrderService ( ) .
2021-01-15 07:11:38 +00:00
Symbol ( order . Symbol ) .
Side ( binance . SideType ( order . Side ) ) .
Type ( orderType )
2020-07-11 05:02:53 +00:00
2021-06-06 17:06:38 +00:00
clientOrderID := newSpotClientOrderID ( order . ClientOrderID )
if len ( clientOrderID ) > 0 {
req . NewClientOrderID ( clientOrderID )
}
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-02-18 08:17:40 +00:00
req . Quantity ( order . Market . FormatQuantity ( order . Quantity ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO: report error
req . Quantity ( order . Quantity . FormatString ( 8 ) )
2021-02-18 08:17:40 +00:00
}
2020-07-11 05:02:53 +00:00
2021-02-18 08:17:40 +00:00
// set price field for limit orders
switch order . Type {
2021-10-17 16:41:41 +00:00
case types . OrderTypeStopLimit , types . OrderTypeLimit , types . OrderTypeLimitMaker :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
2021-02-18 08:17:40 +00:00
req . Price ( order . Market . FormatPrice ( order . Price ) )
2022-01-10 17:36:19 +00:00
} else {
2022-02-03 04:55:25 +00:00
// TODO: report error
req . Price ( order . Price . FormatString ( 8 ) )
2021-02-18 08:17:40 +00:00
}
2021-01-15 07:11:38 +00:00
}
2020-12-03 01:25:47 +00:00
2021-01-15 07:11:38 +00:00
switch order . Type {
case types . OrderTypeStopLimit , types . OrderTypeStopMarket :
2022-01-10 17:36:19 +00:00
if order . Market . Symbol != "" {
req . StopPrice ( order . Market . FormatPrice ( order . StopPrice ) )
} else {
2022-02-03 04:55:25 +00:00
// TODO: report error
req . StopPrice ( order . StopPrice . FormatString ( 8 ) )
2020-12-03 01:25:47 +00:00
}
2021-01-15 07:11:38 +00:00
}
2020-07-11 05:02:53 +00:00
2021-01-15 07:11:38 +00:00
if len ( order . TimeInForce ) > 0 {
// TODO: check the TimeInForce value
req . TimeInForce ( binance . TimeInForceType ( order . TimeInForce ) )
2021-04-13 15:18:40 +00:00
} else {
switch order . Type {
case types . OrderTypeLimit , types . OrderTypeStopLimit :
req . TimeInForce ( binance . TimeInForceTypeGTC )
}
2021-01-15 07:11:38 +00:00
}
2020-07-11 07:27:26 +00:00
2021-10-17 16:41:41 +00:00
req . NewOrderRespType ( binance . NewOrderRespTypeRESULT )
2021-01-15 07:11:38 +00:00
response , err := req . Do ( ctx )
if err != nil {
return nil , err
}
2020-12-29 09:26:22 +00:00
2021-05-28 16:26:39 +00:00
log . Infof ( "spot order creation response: %+v" , response )
2021-01-15 07:11:38 +00:00
2021-05-28 16:26:39 +00:00
createdOrder , err := toGlobalOrder ( & binance . Order {
2021-01-15 07:11:38 +00:00
Symbol : response . Symbol ,
OrderID : response . OrderID ,
ClientOrderID : response . ClientOrderID ,
Price : response . Price ,
OrigQuantity : response . OrigQuantity ,
ExecutedQuantity : response . ExecutedQuantity ,
CummulativeQuoteQuantity : response . CummulativeQuoteQuantity ,
Status : response . Status ,
TimeInForce : response . TimeInForce ,
Type : response . Type ,
Side : response . Side ,
UpdateTime : response . TransactTime ,
Time : response . TransactTime ,
2021-01-19 17:24:29 +00:00
IsIsolated : response . IsIsolated ,
} , false )
2021-01-15 07:11:38 +00:00
return createdOrder , err
}
2020-10-25 11:18:03 +00:00
2022-09-09 10:41:06 +00:00
func ( e * Exchange ) SubmitOrder ( ctx context . Context , order types . SubmitOrder ) ( createdOrder * types . Order , err error ) {
if err := orderLimiter . Wait ( ctx ) ; err != nil {
log . WithError ( err ) . Errorf ( "order rate limiter wait error" )
}
2020-10-25 11:18:03 +00:00
2022-09-09 10:41:06 +00:00
if e . IsMargin {
createdOrder , err = e . submitMarginOrder ( ctx , order )
} else if e . IsFutures {
createdOrder , err = e . submitFuturesOrder ( ctx , order )
} else {
createdOrder , err = e . submitSpotOrder ( ctx , order )
2020-07-11 07:27:26 +00:00
}
2022-09-09 10:41:06 +00:00
return createdOrder , err
2020-07-11 07:27:26 +00:00
}
2020-11-06 16:49:17 +00:00
// QueryKLines queries the Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.
2021-05-05 08:23:46 +00:00
// Binance uses inclusive start time query range, eg:
// https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&startTime=1620172860000
// the above query will return a kline with startTime = 1620172860000
// and,
// https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&startTime=1620172860000&endTime=1620172920000
// the above query will return a kline with startTime = 1620172860000, and a kline with endTime = 1620172860000
//
// the endTime of a binance kline, is the (startTime + interval time - 1 millisecond), e.g.,
// millisecond unix timestamp: 1620172860000 and 1620172919999
2020-11-06 13:40:48 +00:00
func ( e * Exchange ) QueryKLines ( ctx context . Context , symbol string , interval types . Interval , options types . KLineQueryOptions ) ( [ ] types . KLine , error ) {
2022-06-15 07:59:21 +00:00
if e . IsFutures {
return e . QueryFuturesKLines ( ctx , symbol , interval , options )
}
2020-10-10 04:02:06 +00:00
2021-05-02 09:46:08 +00:00
var limit = 1000
2020-07-15 04:20:44 +00:00
if options . Limit > 0 {
2021-05-02 09:46:08 +00:00
// default limit == 1000
2020-07-15 04:20:44 +00:00
limit = options . Limit
2020-07-11 12:40:19 +00:00
}
2020-08-11 00:36:36 +00:00
log . Infof ( "querying kline %s %s %v" , symbol , interval , options )
2020-07-11 05:02:53 +00:00
2022-04-26 07:58:12 +00:00
req := e . client . NewKlinesService ( ) .
2020-10-06 09:32:41 +00:00
Symbol ( symbol ) .
2020-11-06 13:40:48 +00:00
Interval ( string ( interval ) ) .
2020-07-15 04:20:44 +00:00
Limit ( limit )
if options . StartTime != nil {
2022-06-06 05:25:11 +00:00
req . StartTime ( options . StartTime . UnixMilli ( ) )
2020-07-15 04:20:44 +00:00
}
if options . EndTime != nil {
2022-06-06 05:25:11 +00:00
req . EndTime ( options . EndTime . UnixMilli ( ) )
2020-07-15 04:20:44 +00:00
}
resp , err := req . Do ( ctx )
2020-07-11 05:02:53 +00:00
if err != nil {
return nil , err
}
2022-06-15 07:59:21 +00:00
var kLines [ ] types . KLine
for _ , k := range resp {
kLines = append ( kLines , types . KLine {
Exchange : types . ExchangeBinance ,
Symbol : symbol ,
Interval : interval ,
StartTime : types . NewTimeFromUnix ( 0 , k . OpenTime * int64 ( time . Millisecond ) ) ,
EndTime : types . NewTimeFromUnix ( 0 , k . CloseTime * int64 ( time . Millisecond ) ) ,
Open : fixedpoint . MustNewFromString ( k . Open ) ,
Close : fixedpoint . MustNewFromString ( k . Close ) ,
High : fixedpoint . MustNewFromString ( k . High ) ,
Low : fixedpoint . MustNewFromString ( k . Low ) ,
Volume : fixedpoint . MustNewFromString ( k . Volume ) ,
QuoteVolume : fixedpoint . MustNewFromString ( k . QuoteAssetVolume ) ,
TakerBuyBaseAssetVolume : fixedpoint . MustNewFromString ( k . TakerBuyBaseAssetVolume ) ,
TakerBuyQuoteAssetVolume : fixedpoint . MustNewFromString ( k . TakerBuyQuoteAssetVolume ) ,
LastTradeID : 0 ,
NumberOfTrades : uint64 ( k . TradeNum ) ,
Closed : true ,
} )
}
kLines = types . SortKLinesAscending ( kLines )
return kLines , nil
}
func ( e * Exchange ) QueryFuturesKLines ( ctx context . Context , symbol string , interval types . Interval , options types . KLineQueryOptions ) ( [ ] types . KLine , error ) {
var limit = 1000
if options . Limit > 0 {
// default limit == 1000
limit = options . Limit
}
log . Infof ( "querying kline %s %s %v" , symbol , interval , options )
req := e . futuresClient . NewKlinesService ( ) .
Symbol ( symbol ) .
Interval ( string ( interval ) ) .
Limit ( limit )
if options . StartTime != nil {
req . StartTime ( options . StartTime . UnixMilli ( ) )
}
if options . EndTime != nil {
req . EndTime ( options . EndTime . UnixMilli ( ) )
}
resp , err := req . Do ( ctx )
if err != nil {
return nil , err
}
2020-07-11 05:02:53 +00:00
var kLines [ ] types . KLine
2020-09-16 04:28:15 +00:00
for _ , k := range resp {
2020-07-11 05:02:53 +00:00
kLines = append ( kLines , types . KLine {
2021-05-31 17:08:02 +00:00
Exchange : types . ExchangeBinance ,
Symbol : symbol ,
Interval : interval ,
2022-06-06 16:50:07 +00:00
StartTime : types . NewTimeFromUnix ( 0 , k . OpenTime * int64 ( time . Millisecond ) ) ,
2021-12-15 05:04:01 +00:00
EndTime : types . NewTimeFromUnix ( 0 , k . CloseTime * int64 ( time . Millisecond ) ) ,
2022-02-03 04:55:25 +00:00
Open : fixedpoint . MustNewFromString ( k . Open ) ,
Close : fixedpoint . MustNewFromString ( k . Close ) ,
High : fixedpoint . MustNewFromString ( k . High ) ,
Low : fixedpoint . MustNewFromString ( k . Low ) ,
Volume : fixedpoint . MustNewFromString ( k . Volume ) ,
QuoteVolume : fixedpoint . MustNewFromString ( k . QuoteAssetVolume ) ,
TakerBuyBaseAssetVolume : fixedpoint . MustNewFromString ( k . TakerBuyBaseAssetVolume ) ,
TakerBuyQuoteAssetVolume : fixedpoint . MustNewFromString ( k . TakerBuyQuoteAssetVolume ) ,
2021-05-31 17:08:02 +00:00
LastTradeID : 0 ,
NumberOfTrades : uint64 ( k . TradeNum ) ,
Closed : true ,
2020-07-11 05:02:53 +00:00
} )
}
2022-06-06 16:48:13 +00:00
kLines = types . SortKLinesAscending ( kLines )
2020-07-11 05:02:53 +00:00
return kLines , nil
}
2022-05-28 17:21:43 +00:00
func ( e * Exchange ) queryMarginTrades ( ctx context . Context , symbol string , options * types . TradeQueryOptions ) ( trades [ ] types . Trade , err error ) {
var remoteTrades [ ] * binance . TradeV3
req := e . client . NewListMarginTradesService ( ) .
IsIsolated ( e . IsIsolatedMargin ) .
Symbol ( symbol )
if options . Limit > 0 {
req . Limit ( int ( options . Limit ) )
} else {
req . Limit ( 1000 )
}
2020-07-22 04:26:27 +00:00
2022-12-09 09:09:03 +00:00
// BINANCE seems to have an API bug, we can't use both fromId and the start time/end time
2022-05-28 17:21:43 +00:00
// BINANCE uses inclusive last trade ID
if options . LastTradeID > 0 {
req . FromID ( int64 ( options . LastTradeID ) )
2022-12-09 09:09:03 +00:00
} else {
if options . StartTime != nil && options . EndTime != nil {
if options . EndTime . Sub ( * options . StartTime ) < 24 * time . Hour {
req . StartTime ( options . StartTime . UnixMilli ( ) )
req . EndTime ( options . EndTime . UnixMilli ( ) )
} else {
req . StartTime ( options . StartTime . UnixMilli ( ) )
}
} else if options . StartTime != nil {
2022-05-28 17:21:43 +00:00
req . StartTime ( options . StartTime . UnixMilli ( ) )
2022-12-09 09:09:03 +00:00
} else if options . EndTime != nil {
2022-05-28 17:21:43 +00:00
req . EndTime ( options . EndTime . UnixMilli ( ) )
2021-01-19 17:27:27 +00:00
}
2022-05-28 17:21:43 +00:00
}
2020-07-22 04:26:27 +00:00
2022-05-28 17:21:43 +00:00
remoteTrades , err = req . Do ( ctx )
if err != nil {
return nil , err
}
for _ , t := range remoteTrades {
localTrade , err := toGlobalTrade ( * t , e . IsMargin )
if err != nil {
log . WithError ( err ) . Errorf ( "can not convert binance trade: %+v" , t )
continue
2021-01-19 17:27:27 +00:00
}
2022-05-28 17:21:43 +00:00
trades = append ( trades , * localTrade )
}
trades = types . SortTradesAscending ( trades )
return trades , nil
}
func ( e * Exchange ) queryFuturesTrades ( ctx context . Context , symbol string , options * types . TradeQueryOptions ) ( trades [ ] types . Trade , err error ) {
var remoteTrades [ ] * futures . AccountTrade
req := e . futuresClient . NewListAccountTradeService ( ) .
Symbol ( symbol )
if options . Limit > 0 {
req . Limit ( int ( options . Limit ) )
} else {
req . Limit ( 1000 )
}
// BINANCE uses inclusive last trade ID
if options . LastTradeID > 0 {
req . FromID ( int64 ( options . LastTradeID ) )
}
// The parameter fromId cannot be sent with startTime or endTime.
// Mentioned in binance futures docs
if options . LastTradeID <= 0 {
2022-04-26 07:58:12 +00:00
if options . StartTime != nil && options . EndTime != nil {
2022-04-26 08:43:40 +00:00
if options . EndTime . Sub ( * options . StartTime ) < 24 * time . Hour {
2022-04-26 07:58:12 +00:00
req . StartTime ( options . StartTime . UnixMilli ( ) )
req . EndTime ( options . EndTime . UnixMilli ( ) )
} else {
req . StartTime ( options . StartTime . UnixMilli ( ) )
}
2022-04-26 08:43:40 +00:00
} else if options . EndTime != nil {
2022-04-26 07:58:12 +00:00
req . EndTime ( options . EndTime . UnixMilli ( ) )
}
2022-05-28 17:21:43 +00:00
}
2022-04-26 07:58:12 +00:00
2022-05-28 17:21:43 +00:00
remoteTrades , err = req . Do ( ctx )
if err != nil {
return nil , err
}
for _ , t := range remoteTrades {
localTrade , err := toGlobalFuturesTrade ( * t )
2021-01-19 17:27:27 +00:00
if err != nil {
2022-05-28 17:21:43 +00:00
log . WithError ( err ) . Errorf ( "can not convert binance futures trade: %+v" , t )
continue
2021-12-14 10:01:24 +00:00
}
2022-05-28 17:21:43 +00:00
trades = append ( trades , * localTrade )
}
2022-04-26 07:58:12 +00:00
2022-05-28 17:21:43 +00:00
trades = types . SortTradesAscending ( trades )
return trades , nil
}
2021-12-14 10:01:24 +00:00
2022-05-28 17:21:43 +00:00
func ( e * Exchange ) querySpotTrades ( ctx context . Context , symbol string , options * types . TradeQueryOptions ) ( trades [ ] types . Trade , err error ) {
2022-12-09 08:44:27 +00:00
req := e . client2 . NewGetMyTradesRequest ( )
req . Symbol ( symbol )
2021-01-19 17:27:27 +00:00
2022-05-28 17:21:43 +00:00
// BINANCE uses inclusive last trade ID
if options . LastTradeID > 0 {
2022-12-09 08:44:27 +00:00
req . FromID ( options . LastTradeID )
2022-12-09 09:28:06 +00:00
} else {
if options . StartTime != nil && options . EndTime != nil {
if options . EndTime . Sub ( * options . StartTime ) < 24 * time . Hour {
req . StartTime ( * options . StartTime )
req . EndTime ( * options . EndTime )
} else {
req . StartTime ( * options . StartTime )
}
} else if options . StartTime != nil {
2022-12-09 08:44:27 +00:00
req . StartTime ( * options . StartTime )
2022-12-09 09:28:06 +00:00
} else if options . EndTime != nil {
2022-12-09 08:44:27 +00:00
req . EndTime ( * options . EndTime )
2022-04-26 07:58:12 +00:00
}
2022-05-28 17:21:43 +00:00
}
2022-04-26 07:58:12 +00:00
2022-12-09 08:44:27 +00:00
if options . Limit > 0 {
req . Limit ( uint64 ( options . Limit ) )
} else {
req . Limit ( 1000 )
}
remoteTrades , err := req . Do ( ctx )
2022-05-28 17:21:43 +00:00
if err != nil {
return nil , err
}
2022-12-09 08:44:27 +00:00
2022-05-28 17:21:43 +00:00
for _ , t := range remoteTrades {
2022-12-09 08:44:27 +00:00
localTrade , err := toGlobalTrade ( t , e . IsMargin )
2021-01-19 17:27:27 +00:00
if err != nil {
2022-05-28 17:21:43 +00:00
log . WithError ( err ) . Errorf ( "can not convert binance trade: %+v" , t )
continue
2021-01-19 17:27:27 +00:00
}
2020-07-22 04:26:27 +00:00
2022-05-28 17:21:43 +00:00
trades = append ( trades , * localTrade )
}
trades = types . SortTradesAscending ( trades )
return trades , nil
}
2020-07-22 04:26:27 +00:00
2022-09-12 07:03:01 +00:00
func ( e * Exchange ) QueryTrades ( ctx context . Context , symbol string , options * types . TradeQueryOptions ) ( [ ] types . Trade , error ) {
if err := queryTradeLimiter . Wait ( ctx ) ; err != nil {
return nil , err
}
2022-05-28 17:21:43 +00:00
if e . IsMargin {
return e . queryMarginTrades ( ctx , symbol , options )
} else if e . IsFutures {
return e . queryFuturesTrades ( ctx , symbol , options )
2020-07-22 04:26:27 +00:00
}
2022-09-12 07:03:01 +00:00
return e . querySpotTrades ( ctx , symbol , options )
2020-07-22 04:26:27 +00:00
}
2022-06-02 19:24:34 +00:00
// DefaultFeeRates returns the Binance VIP 0 fee schedule
// See also https://www.binance.com/en/fee/schedule
func ( e * Exchange ) DefaultFeeRates ( ) types . ExchangeFee {
return types . ExchangeFee {
MakerFeeRate : fixedpoint . NewFromFloat ( 0.01 * 0.075 ) , // 0.075%
TakerFeeRate : fixedpoint . NewFromFloat ( 0.01 * 0.075 ) , // 0.075%
}
}
2022-04-21 07:29:59 +00:00
// QueryDepth query the order book depth of a symbol
2021-12-24 18:10:13 +00:00
func ( e * Exchange ) QueryDepth ( ctx context . Context , symbol string ) ( snapshot types . SliceOrderBook , finalUpdateID int64 , err error ) {
2022-05-12 10:40:48 +00:00
var response * binance . DepthResponse
if e . IsFutures {
res , err := e . futuresClient . NewDepthService ( ) . Symbol ( symbol ) . Do ( ctx )
if err != nil {
return snapshot , finalUpdateID , err
}
response = & binance . DepthResponse {
LastUpdateID : res . LastUpdateID ,
Bids : res . Bids ,
Asks : res . Asks ,
}
} else {
response , err = e . client . NewDepthService ( ) . Symbol ( symbol ) . Do ( ctx )
if err != nil {
return snapshot , finalUpdateID , err
}
2021-12-24 18:10:13 +00:00
}
snapshot . Symbol = symbol
finalUpdateID = response . LastUpdateID
for _ , entry := range response . Bids {
// entry.Price, Quantity: entry.Quantity
price , err := fixedpoint . NewFromString ( entry . Price )
if err != nil {
return snapshot , finalUpdateID , err
}
quantity , err := fixedpoint . NewFromString ( entry . Quantity )
if err != nil {
return snapshot , finalUpdateID , err
}
snapshot . Bids = append ( snapshot . Bids , types . PriceVolume { Price : price , Volume : quantity } )
}
for _ , entry := range response . Asks {
price , err := fixedpoint . NewFromString ( entry . Price )
if err != nil {
return snapshot , finalUpdateID , err
}
quantity , err := fixedpoint . NewFromString ( entry . Quantity )
if err != nil {
return snapshot , finalUpdateID , err
}
snapshot . Asks = append ( snapshot . Asks , types . PriceVolume { Price : price , Volume : quantity } )
}
return snapshot , finalUpdateID , nil
}
2022-05-28 17:21:43 +00:00
// QueryPremiumIndex is only for futures
2021-12-08 16:10:18 +00:00
func ( e * Exchange ) QueryPremiumIndex ( ctx context . Context , symbol string ) ( * types . PremiumIndex , error ) {
2021-10-20 06:01:19 +00:00
// when symbol is set, only one index will be returned.
2022-05-28 17:21:43 +00:00
indexes , err := e . futuresClient . NewPremiumIndexService ( ) . Symbol ( symbol ) . Do ( ctx )
2021-10-19 07:54:16 +00:00
if err != nil {
return nil , err
}
2021-10-20 06:01:19 +00:00
return convertPremiumIndex ( indexes [ 0 ] )
2021-10-19 07:54:16 +00:00
}
2021-12-08 16:10:18 +00:00
func ( e * Exchange ) QueryFundingRateHistory ( ctx context . Context , symbol string ) ( * types . FundingRate , error ) {
2022-05-28 17:21:43 +00:00
rates , err := e . futuresClient . NewFundingRateService ( ) .
2021-05-31 19:15:19 +00:00
Symbol ( symbol ) .
Limit ( 1 ) .
Do ( ctx )
if err != nil {
2021-10-14 15:01:10 +00:00
return nil , err
2021-05-31 19:15:19 +00:00
}
if len ( rates ) == 0 {
2021-10-14 15:01:10 +00:00
return nil , errors . New ( "empty funding rate data" )
}
rate := rates [ 0 ]
fundingRate , err := fixedpoint . NewFromString ( rate . FundingRate )
if err != nil {
return nil , err
2021-05-31 19:15:19 +00:00
}
2021-12-08 16:10:18 +00:00
return & types . FundingRate {
2021-10-14 15:01:10 +00:00
FundingRate : fundingRate ,
FundingTime : time . Unix ( 0 , rate . FundingTime * int64 ( time . Millisecond ) ) ,
Time : time . Unix ( 0 , rate . Time * int64 ( time . Millisecond ) ) ,
} , nil
2021-05-31 19:15:19 +00:00
}
2021-12-08 16:06:46 +00:00
2022-01-03 19:30:36 +00:00
func ( e * Exchange ) QueryPositionRisk ( ctx context . Context , symbol string ) ( * types . PositionRisk , error ) {
// when symbol is set, only one position risk will be returned.
2022-05-28 17:21:43 +00:00
risks , err := e . futuresClient . NewGetPositionRiskService ( ) . Symbol ( symbol ) . Do ( ctx )
2022-01-03 19:30:36 +00:00
if err != nil {
return nil , err
}
return convertPositionRisk ( risks [ 0 ] )
}
2022-10-03 16:24:16 +00:00
// in seconds
2022-07-26 07:42:34 +00:00
var SupportedIntervals = map [ types . Interval ] int {
2022-10-03 16:24:16 +00:00
types . Interval1s : 1 ,
types . Interval1m : 1 * 60 ,
types . Interval5m : 5 * 60 ,
types . Interval15m : 15 * 60 ,
types . Interval30m : 30 * 60 ,
types . Interval1h : 60 * 60 ,
types . Interval2h : 60 * 60 * 2 ,
types . Interval4h : 60 * 60 * 4 ,
types . Interval6h : 60 * 60 * 6 ,
types . Interval12h : 60 * 60 * 12 ,
types . Interval1d : 60 * 60 * 24 ,
types . Interval3d : 60 * 60 * 24 * 3 ,
types . Interval1w : 60 * 60 * 24 * 7 ,
2022-07-26 07:42:34 +00:00
}
func ( e * Exchange ) SupportedInterval ( ) map [ types . Interval ] int {
return SupportedIntervals
}
func ( e * Exchange ) IsSupportedInterval ( interval types . Interval ) bool {
_ , ok := SupportedIntervals [ interval ]
return ok
}
2021-12-08 16:06:46 +00:00
func getLaunchDate ( ) ( time . Time , error ) {
// binance launch date 12:00 July 14th, 2017
loc , err := time . LoadLocation ( "Asia/Shanghai" )
if err != nil {
return time . Time { } , err
}
return time . Date ( 2017 , time . July , 14 , 0 , 0 , 0 , 0 , loc ) , nil
2021-12-12 07:40:03 +00:00
}
2022-04-22 06:45:03 +00:00
// Margin tolerance ranges from 0.0 (liquidation) to 1.0 (safest level of margin).
func calculateMarginTolerance ( marginLevel fixedpoint . Value ) fixedpoint . Value {
if marginLevel . IsZero ( ) {
// Although margin level shouldn't be zero, that would indicate a significant problem.
// In that case, margin tolerance should return 0.0 to also reflect that problem.
return fixedpoint . Zero
}
// Formula created by operations team for our binance code. Liquidation occurs at 1.1,
// so when marginLevel equals 1.1, the formula becomes 1.0 - 1.0, or zero.
// = 1.0 - (1.1 / marginLevel)
return fixedpoint . One . Sub ( fixedpoint . NewFromFloat ( 1.1 ) . Div ( marginLevel ) )
}