2020-07-11 05:02:53 +00:00
package binance
import (
"context"
2020-07-11 07:27:26 +00:00
"fmt"
2021-11-23 02:54:31 +00:00
"net/http"
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"
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"
2021-01-11 05:36:49 +00:00
"github.com/adshao/go-binance/v2"
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"
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-01-14 16:52:54 +00:00
// 5 per second and a 2 initial bucket
var orderLimiter = rate . NewLimiter ( 5 , 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 { } )
2020-12-21 09:48:30 +00:00
2021-12-08 16:01:33 +00:00
// FIXME: this is not effected since dotenv is loaded in the rootCmd, not in the init function
2020-12-21 09:48:30 +00:00
if ok , _ := strconv . ParseBool ( os . Getenv ( "DEBUG_BINANCE_STREAM" ) ) ; ok {
log . Level = logrus . DebugLevel
}
}
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
2021-12-12 07:40:03 +00:00
key , secret string
Client * binance . Client // Spot & Margin
futuresClient * futures . Client // USDT-M Futures
// deliveryClient *delivery.Client // Coin-M Futures
2020-07-11 05:02:53 +00:00
}
2022-01-01 18:14:04 +00:00
var timeSetter sync . Once
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 )
2021-11-23 02:54:31 +00:00
client . HTTPClient = & http . Client { Timeout : 15 * time . Second }
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 )
futuresClient . HTTPClient = & http . Client { Timeout : 15 * time . Second }
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
}
2021-12-12 07:40:03 +00:00
var err error
2021-12-21 17:27:25 +00:00
if len ( key ) > 0 && len ( secret ) > 0 {
2022-01-01 18:14:04 +00:00
timeSetter . Do ( func ( ) {
_ , err = client . NewSetServerTimeService ( ) . Do ( context . Background ( ) )
if err != nil {
log . WithError ( err ) . Error ( "can not set server time" )
}
2021-12-12 07:40:03 +00:00
2022-01-01 18:14:04 +00:00
_ , err = futuresClient . NewSetServerTimeService ( ) . Do ( context . Background ( ) )
if err != nil {
log . WithError ( err ) . Error ( "can not set server time" )
}
} )
2021-12-12 07:40:03 +00:00
}
return & Exchange {
key : key ,
secret : secret ,
Client : client ,
futuresClient : futuresClient ,
// deliveryClient: deliveryClient,
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 ) {
req := e . Client . NewListPriceChangeStatsService ( )
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-18 09:37:49 +00:00
var req = e . Client . NewListPriceChangeStatsService ( )
changeStats , err := req . Do ( ctx )
2021-02-06 17:35:23 +00:00
if err != nil {
return nil , err
}
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
}
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
}
2020-10-14 02:16:59 +00:00
exchangeInfo , err := e . Client . NewExchangeInfoService ( ) . Do ( ctx )
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 ) {
2020-07-11 05:02:53 +00:00
resp , err := e . Client . NewAveragePriceService ( ) . Symbol ( symbol ) . Do ( ctx )
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 {
2021-12-24 18:10:13 +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-21 07:53:52 +00:00
func ( e * Exchange ) queryCrossMarginAccount ( ctx context . Context ) ( * types . Account , error ) {
marginAccount , err := e . Client . NewGetMarginAccountService ( ) . 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 {
AccountType : types . AccountTypeMargin ,
2022-04-21 07:53:52 +00:00
MarginInfo : toGlobalMarginAccountInfo ( marginAccount ) , // In binance GO api, Account define marginAccount info which mantain []*AccountAsset and []*AccountPosition.
MarginLevel : fixedpoint . MustNewFromString ( marginAccount . MarginLevel ) ,
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-04-21 07:53:52 +00:00
func ( e * Exchange ) queryIsolatedMarginAccount ( ctx context . Context ) ( * types . Account , error ) {
2020-12-21 09:48:30 +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 {
AccountType : types . AccountTypeMargin ,
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-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 ]
a . MarginLevel = fixedpoint . MustNewFromString ( userAsset . MarginLevel )
// 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
}
2021-05-26 15:24:05 +00:00
func ( e * Exchange ) Withdrawal ( ctx context . Context , asset string , amount fixedpoint . Value , address string , options * types . WithdrawalOptions ) error {
2021-09-01 16:21:56 +00:00
req := e . Client . NewCreateWithdrawService ( )
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
}
2021-03-13 12:49:51 +00:00
func ( e * Exchange ) QueryWithdrawHistory ( ctx context . Context , asset string , since , until time . Time ) ( allWithdraws [ ] types . Withdraw , err error ) {
2020-08-13 02:11:27 +00:00
startTime := since
2021-03-13 12:49:51 +00:00
var emptyTime = time . Time { }
if startTime == emptyTime {
2021-12-08 16:06:46 +00:00
startTime , err = getLaunchDate ( )
2021-03-13 12:49:51 +00:00
if err != nil {
return nil , err
}
}
2020-08-13 02:11:27 +00:00
txIDs := map [ string ] struct { } { }
for startTime . Before ( until ) {
// startTime ~ endTime must be in 90 days
endTime := startTime . AddDate ( 0 , 0 , 60 )
if endTime . After ( until ) {
endTime = until
}
2020-10-12 09:15:33 +00:00
req := e . Client . NewListWithdrawsService ( )
if len ( asset ) > 0 {
2021-09-01 16:27:57 +00:00
req . Coin ( asset )
2020-10-12 09:15:33 +00:00
}
withdraws , err := req .
2020-08-13 02:11:27 +00:00
StartTime ( startTime . UnixNano ( ) / int64 ( time . Millisecond ) ) .
EndTime ( endTime . UnixNano ( ) / int64 ( time . Millisecond ) ) .
Do ( ctx )
if err != nil {
2020-10-11 12:08:54 +00:00
return allWithdraws , err
2020-08-13 02:11:27 +00:00
}
for _ , d := range withdraws {
if _ , ok := txIDs [ d . TxID ] ; ok {
continue
}
status := ""
switch d . Status {
case 0 :
status = "email_sent"
case 1 :
status = "cancelled"
case 2 :
status = "awaiting_approval"
case 3 :
status = "rejected"
case 4 :
status = "processing"
case 5 :
status = "failure"
case 6 :
status = "completed"
2020-10-11 09:35:59 +00:00
default :
status = fmt . Sprintf ( "unsupported code: %d" , d . Status )
2020-08-13 02:11:27 +00:00
}
txIDs [ d . TxID ] = struct { } { }
2021-09-01 16:27:57 +00:00
2021-09-03 06:21:59 +00:00
// 2006-01-02 15:04:05
applyTime , err := time . Parse ( "2006-01-02 15:04:05" , d . ApplyTime )
2021-09-01 16:27:57 +00:00
if err != nil {
return nil , err
}
2020-10-11 12:08:54 +00:00
allWithdraws = append ( allWithdraws , types . Withdraw {
2021-03-14 03:01:40 +00:00
Exchange : types . ExchangeBinance ,
2021-09-01 16:27:57 +00:00
ApplyTime : types . Time ( applyTime ) ,
Asset : d . Coin ,
2022-02-03 04:55:25 +00:00
Amount : fixedpoint . MustNewFromString ( d . Amount ) ,
2020-08-31 04:32:51 +00:00
Address : d . Address ,
TransactionID : d . TxID ,
2022-02-03 04:55:25 +00:00
TransactionFee : fixedpoint . MustNewFromString ( d . TransactionFee ) ,
2020-08-13 02:11:27 +00:00
WithdrawOrderID : d . WithdrawOrderID ,
2020-08-31 04:32:51 +00:00
Network : d . Network ,
Status : status ,
2020-08-13 02:11:27 +00:00
} )
}
startTime = endTime
}
return allWithdraws , nil
}
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 ) {
2020-08-13 02:11:27 +00:00
startTime := since
2021-04-21 07:06:53 +00:00
var emptyTime = time . Time { }
if startTime == emptyTime {
2021-12-08 16:06:46 +00:00
startTime , err = getLaunchDate ( )
2021-04-21 07:06:53 +00:00
if err != nil {
return nil , err
}
}
2020-08-13 02:11:27 +00:00
txIDs := map [ string ] struct { } { }
for startTime . Before ( until ) {
// startTime ~ endTime must be in 90 days
endTime := startTime . AddDate ( 0 , 0 , 60 )
if endTime . After ( until ) {
endTime = until
}
2020-10-12 09:15:33 +00:00
req := e . Client . NewListDepositsService ( )
if len ( asset ) > 0 {
2021-09-01 16:27:57 +00:00
req . Coin ( asset )
2020-10-12 09:15:33 +00:00
}
deposits , err := req .
2020-08-13 02:11:27 +00:00
StartTime ( startTime . UnixNano ( ) / int64 ( time . Millisecond ) ) .
EndTime ( endTime . UnixNano ( ) / int64 ( time . Millisecond ) ) .
Do ( ctx )
if err != nil {
return nil , err
}
for _ , d := range deposits {
if _ , ok := txIDs [ d . TxID ] ; ok {
continue
}
// 0(0:pending,6: credited but cannot withdraw, 1:success)
2020-10-11 09:35:59 +00:00
status := types . DepositStatus ( fmt . Sprintf ( "code: %d" , d . Status ) )
2020-08-13 02:11:27 +00:00
switch d . Status {
case 0 :
2020-10-11 09:35:59 +00:00
status = types . DepositPending
2020-08-13 02:11:27 +00:00
case 6 :
2020-10-11 09:35:59 +00:00
// https://www.binance.com/en/support/faq/115003736451
status = types . DepositCredited
2020-08-13 02:11:27 +00:00
case 1 :
2020-10-11 09:35:59 +00:00
status = types . DepositSuccess
2020-08-13 02:11:27 +00:00
}
txIDs [ d . TxID ] = struct { } { }
2020-10-11 09:35:59 +00:00
allDeposits = append ( allDeposits , types . Deposit {
2021-03-14 03:01:40 +00:00
Exchange : types . ExchangeBinance ,
2021-05-19 17:32:26 +00:00
Time : types . Time ( time . Unix ( 0 , d . InsertTime * int64 ( time . Millisecond ) ) ) ,
2021-09-01 16:27:57 +00:00
Asset : d . Coin ,
2022-02-03 04:55:25 +00:00
Amount : fixedpoint . MustNewFromString ( d . Amount ) ,
2020-08-13 02:11:27 +00:00
Address : d . Address ,
AddressTag : d . AddressTag ,
TransactionID : d . TxID ,
Status : status ,
} )
}
startTime = endTime
}
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 ) {
2020-07-13 04:28:40 +00:00
account , err := e . Client . NewGetAccountService ( ) . Do ( ctx )
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-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 {
balances [ b . Asset ] = types . Balance {
Currency : b . Asset ,
Available : fixedpoint . Must ( fixedpoint . NewFromString ( b . AvailableBalance ) ) ,
}
}
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-04-21 07:53:52 +00:00
account , err = e . queryIsolatedMarginAccount ( ctx )
2022-01-13 11:31:33 +00:00
} else if e . IsMargin {
2022-04-21 07:53:52 +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 {
2021-01-18 13:56:58 +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
}
2021-05-28 16:26:39 +00:00
return toGlobalOrders ( binanceOrders )
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
}
return toGlobalFuturesOrders ( binanceOrders )
}
2021-01-18 11:35:40 +00:00
binanceOrders , err := e . Client . NewListOpenOrdersService ( ) . Symbol ( symbol ) . Do ( ctx )
if err != nil {
return orders , err
2020-10-25 10:26:10 +00:00
}
2021-05-28 16:26:39 +00:00
return toGlobalOrders ( binanceOrders )
2020-10-25 10:26:10 +00:00
}
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 {
order , err = e . Client . NewGetMarginOrderService ( ) . Symbol ( q . Symbol ) . OrderID ( orderID ) . Do ( ctx )
} else {
order , err = e . Client . NewGetOrderService ( ) . Symbol ( q . Symbol ) . OrderID ( orderID ) . Do ( ctx )
}
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 {
2021-01-18 13:56:58 +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
}
2021-05-28 16:26:39 +00:00
return toGlobalOrders ( binanceOrders )
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
}
return toGlobalFuturesOrders ( binanceOrders )
}
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.
2020-11-05 03:00:51 +00:00
req := e . Client . NewListOrdersService ( ) .
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
2021-05-28 16:26:39 +00:00
return toGlobalOrders ( binanceOrders )
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 {
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
2021-12-29 09:35:27 +00:00
var req = e . Client . NewCancelOrderService ( )
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
}
req := e . Client . NewCreateMarginOrderService ( ) .
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 {
case types . OrderTypeLimit , types . OrderTypeStopLimit :
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 ,
2021-12-13 15:19:14 +00:00
} , true )
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
2021-01-15 07:11:38 +00:00
req := e . Client . NewCreateOrderService ( ) .
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
2021-01-15 07:11:38 +00:00
func ( e * Exchange ) SubmitOrders ( ctx context . Context , orders ... types . SubmitOrder ) ( createdOrders types . OrderSlice , err error ) {
for _ , order := range orders {
2021-12-12 07:40:03 +00:00
if err := orderLimiter . Wait ( ctx ) ; err != nil {
2021-11-04 04:49:33 +00:00
log . WithError ( err ) . Errorf ( "order rate limiter wait error" )
}
2021-01-15 07:46:54 +00:00
2021-11-04 04:49:33 +00:00
var createdOrder * types . Order
2021-01-19 18:09:12 +00:00
if e . IsMargin {
2021-01-15 07:46:54 +00:00
createdOrder , err = e . submitMarginOrder ( ctx , order )
2021-12-13 15:19:14 +00:00
} else if e . IsFutures {
createdOrder , err = e . submitFuturesOrder ( ctx , order )
2021-01-15 07:46:54 +00:00
} else {
createdOrder , err = e . submitSpotOrder ( ctx , order )
}
2020-10-25 11:18:03 +00:00
if err != nil {
return createdOrders , err
}
if createdOrder == nil {
return createdOrders , errors . New ( "nil converted order" )
}
createdOrders = append ( createdOrders , * createdOrder )
2020-07-11 07:27:26 +00:00
}
2020-10-25 11:18:03 +00:00
return createdOrders , 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 ) {
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
2020-10-06 09:32:41 +00:00
req := e . Client . NewKlinesService ( ) .
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 {
req . StartTime ( options . StartTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
if options . EndTime != nil {
req . EndTime ( options . EndTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
resp , err := req . Do ( ctx )
2020-07-11 05:02:53 +00:00
if err != nil {
return nil , err
}
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 ,
2021-12-15 05:04:01 +00:00
StartTime : types . NewTimeFromUnix ( 0 , k . OpenTime * int64 ( time . Millisecond ) ) ,
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
} )
}
return kLines , nil
}
2020-09-18 10:15:45 +00:00
func ( e * Exchange ) QueryTrades ( ctx context . Context , symbol string , options * types . TradeQueryOptions ) ( trades [ ] types . Trade , err error ) {
2021-01-19 18:09:12 +00:00
if e . IsMargin {
2021-12-14 10:01:24 +00:00
var remoteTrades [ ] * binance . TradeV3
2021-01-19 17:27:27 +00:00
req := e . Client . NewListMarginTradesService ( ) .
2021-01-19 18:09:12 +00:00
IsIsolated ( e . IsIsolatedMargin ) .
2021-01-19 17:27:27 +00:00
Symbol ( symbol )
2020-07-22 04:26:27 +00:00
2021-01-19 17:27:27 +00:00
if options . Limit > 0 {
req . Limit ( int ( options . Limit ) )
2021-02-18 09:37:49 +00:00
} else {
req . Limit ( 1000 )
2021-01-19 17:27:27 +00:00
}
2020-07-22 04:26:27 +00:00
2021-01-19 17:27:27 +00:00
if options . StartTime != nil {
req . StartTime ( options . StartTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2021-02-18 09:37:49 +00:00
2021-01-19 17:27:27 +00:00
if options . EndTime != nil {
req . EndTime ( options . EndTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2021-02-18 08:40:47 +00:00
2021-02-18 10:20:18 +00:00
// BINANCE uses inclusive last trade ID
2021-01-19 17:27:27 +00:00
if options . LastTradeID > 0 {
2021-12-23 05:15:27 +00:00
req . FromID ( int64 ( options . LastTradeID ) )
2021-01-19 17:27:27 +00:00
}
remoteTrades , err = req . Do ( ctx )
if err != nil {
return nil , err
}
2021-12-14 10:01:24 +00:00
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
}
trades = append ( trades , * localTrade )
}
2022-04-21 07:29:59 +00:00
trades = types . SortTradesAscending ( trades )
2021-12-14 10:01:24 +00:00
return trades , nil
} else if e . IsFutures {
var remoteTrades [ ] * futures . AccountTrade
2022-01-13 11:31:33 +00:00
req := e . futuresClient . NewListAccountTradeService ( ) .
Symbol ( symbol )
2021-12-14 10:01:24 +00:00
if options . Limit > 0 {
req . Limit ( int ( options . Limit ) )
} else {
req . Limit ( 1000 )
}
// BINANCE uses inclusive last trade ID
if options . LastTradeID > 0 {
2021-12-23 05:15:27 +00:00
req . FromID ( int64 ( options . LastTradeID ) )
2021-12-14 10:01:24 +00:00
}
remoteTrades , err = req . Do ( ctx )
if err != nil {
return nil , err
}
for _ , t := range remoteTrades {
localTrade , err := toGlobalFuturesTrade ( * t )
if err != nil {
2022-01-03 19:31:26 +00:00
log . WithError ( err ) . Errorf ( "can not convert binance futures trade: %+v" , t )
2021-12-14 10:01:24 +00:00
continue
}
trades = append ( trades , * localTrade )
}
2022-04-21 07:29:59 +00:00
trades = types . SortTradesAscending ( trades )
2021-12-14 10:01:24 +00:00
return trades , nil
2021-01-19 17:27:27 +00:00
} else {
2021-12-14 10:01:24 +00:00
var remoteTrades [ ] * binance . TradeV3
2021-01-19 17:27:27 +00:00
req := e . Client . NewListTradesService ( ) .
Symbol ( symbol )
if options . Limit > 0 {
req . Limit ( int ( options . Limit ) )
2021-02-18 09:37:49 +00:00
} else {
req . Limit ( 1000 )
2021-01-19 17:27:27 +00:00
}
if options . StartTime != nil {
req . StartTime ( options . StartTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
if options . EndTime != nil {
req . EndTime ( options . EndTime . UnixNano ( ) / int64 ( time . Millisecond ) )
}
2021-02-18 09:37:49 +00:00
2021-02-18 10:20:18 +00:00
// BINANCE uses inclusive last trade ID
2021-01-19 17:27:27 +00:00
if options . LastTradeID > 0 {
2021-12-23 05:15:27 +00:00
req . FromID ( int64 ( options . LastTradeID ) )
2021-01-19 17:27:27 +00:00
}
remoteTrades , err = req . Do ( ctx )
if err != nil {
return nil , err
}
2021-12-14 10:01:24 +00:00
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
}
2020-07-22 04:26:27 +00:00
2021-12-14 10:01:24 +00:00
trades = append ( trades , * localTrade )
2020-07-22 04:26:27 +00:00
}
2022-04-21 07:29:59 +00:00
trades = types . SortTradesAscending ( trades )
2021-12-14 10:01:24 +00:00
return trades , nil
2020-07-22 04:26:27 +00:00
}
}
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 ) {
response , err := e . Client . NewDepthService ( ) . Symbol ( symbol ) . Do ( ctx )
if err != nil {
return snapshot , finalUpdateID , err
}
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
}
2020-11-06 13:40:48 +00:00
func ( e * Exchange ) BatchQueryKLines ( ctx context . Context , symbol string , interval types . Interval , startTime , endTime time . Time ) ( [ ] types . KLine , error ) {
2020-08-14 05:08:09 +00:00
var allKLines [ ] types . KLine
for startTime . Before ( endTime ) {
klines , err := e . QueryKLines ( ctx , symbol , interval , types . KLineQueryOptions {
StartTime : & startTime ,
Limit : 1000 ,
} )
if err != nil {
return nil , err
}
for _ , kline := range klines {
2020-09-16 04:28:15 +00:00
if kline . EndTime . After ( endTime ) {
2020-08-14 05:08:09 +00:00
return allKLines , nil
}
allKLines = append ( allKLines , kline )
2021-12-15 05:04:01 +00:00
startTime = kline . EndTime . Time ( )
2020-08-14 05:08:09 +00:00
}
}
return allKLines , nil
2020-08-14 05:47:55 +00:00
}
2021-05-31 19:15:19 +00:00
2021-12-08 16:10:18 +00:00
func ( e * Exchange ) QueryPremiumIndex ( ctx context . Context , symbol string ) ( * types . PremiumIndex , error ) {
2021-10-19 07:54:16 +00:00
futuresClient := binance . NewFuturesClient ( e . key , e . secret )
2021-10-20 06:01:19 +00:00
// when symbol is set, only one index will be returned.
2021-10-19 07:54:16 +00:00
indexes , err := futuresClient . NewPremiumIndexService ( ) . Symbol ( symbol ) . Do ( ctx )
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 ) {
2021-05-31 19:15:19 +00:00
futuresClient := binance . NewFuturesClient ( e . key , e . secret )
rates , err := futuresClient . NewFundingRateService ( ) .
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 ) {
futuresClient := binance . NewFuturesClient ( e . key , e . secret )
// when symbol is set, only one position risk will be returned.
risks , err := futuresClient . NewGetPositionRiskService ( ) . Symbol ( symbol ) . Do ( ctx )
if err != nil {
return nil , err
}
return convertPositionRisk ( risks [ 0 ] )
}
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
}