Merge pull request #322 from c9s/refactor/exchange-structure

refactor: refactor binance exchange structure
This commit is contained in:
Yo-An Lin 2021-12-09 01:17:10 +08:00 committed by GitHub
commit e67942ca69
5 changed files with 118 additions and 94 deletions

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/adshao/go-binance/v2"
"github.com/adshao/go-binance/v2/futures"
"github.com/pkg/errors"
"github.com/c9s/bbgo/pkg/fixedpoint"
@ -14,6 +15,42 @@ import (
"github.com/c9s/bbgo/pkg/util"
)
func toGlobalMarket(symbol binance.Symbol) types.Market {
market := types.Market{
Symbol: symbol.Symbol,
LocalSymbol: symbol.Symbol,
PricePrecision: symbol.QuotePrecision,
VolumePrecision: symbol.BaseAssetPrecision,
QuoteCurrency: symbol.QuoteAsset,
BaseCurrency: symbol.BaseAsset,
}
if f := symbol.MinNotionalFilter(); f != nil {
market.MinNotional = util.MustParseFloat(f.MinNotional)
market.MinAmount = util.MustParseFloat(f.MinNotional)
}
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
// There are 3 parts:
// minQty defines the minimum quantity/icebergQty allowed.
// maxQty defines the maximum quantity/icebergQty allowed.
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
if f := symbol.LotSizeFilter(); f != nil {
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
market.StepSize = util.MustParseFloat(f.StepSize)
}
if f := symbol.PriceFilter(); f != nil {
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
market.MinPrice = util.MustParseFloat(f.MinPrice)
market.TickSize = util.MustParseFloat(f.TickSize)
}
return market
}
func toGlobalIsolatedUserAsset(userAsset binance.IsolatedUserAsset) types.IsolatedUserAsset {
return types.IsolatedUserAsset{
Asset: userAsset.Asset,
@ -90,8 +127,8 @@ func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount
}
}
func toGlobalTicker(stats *binance.PriceChangeStats) types.Ticker {
return types.Ticker{
func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) {
return &types.Ticker{
Volume: util.MustParseFloat(stats.Volume),
Last: util.MustParseFloat(stats.LastPrice),
Open: util.MustParseFloat(stats.OpenPrice),
@ -100,8 +137,9 @@ func toGlobalTicker(stats *binance.PriceChangeStats) types.Ticker {
Buy: util.MustParseFloat(stats.BidPrice),
Sell: util.MustParseFloat(stats.AskPrice),
Time: time.Unix(0, stats.CloseTime*int64(time.Millisecond)),
},nil
}
}
func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
switch orderType {
@ -303,3 +341,27 @@ func convertSubscription(s types.Subscription) string {
return fmt.Sprintf("%s@%s", strings.ToLower(s.Symbol), s.Channel)
}
func convertPremiumIndex(index *futures.PremiumIndex) (*types.PremiumIndex, error) {
markPrice, err := fixedpoint.NewFromString(index.MarkPrice)
if err != nil {
return nil, err
}
lastFundingRate, err := fixedpoint.NewFromString(index.LastFundingRate)
if err != nil {
return nil, err
}
nextFundingTime := time.Unix(0, index.NextFundingTime*int64(time.Millisecond))
t := time.Unix(0, index.Time*int64(time.Millisecond))
return &types.PremiumIndex{
Symbol: index.Symbol,
MarkPrice: markPrice,
NextFundingTime: nextFundingTime,
LastFundingRate: lastFundingRate,
Time: t,
}, nil
}

View File

@ -3,14 +3,14 @@ package binance
import (
"context"
"fmt"
"github.com/adshao/go-binance/v2/futures"
"golang.org/x/time/rate"
"net/http"
"os"
"strconv"
"strings"
"time"
"golang.org/x/time/rate"
"github.com/adshao/go-binance/v2"
"github.com/google/uuid"
"github.com/pkg/errors"
@ -36,6 +36,7 @@ func init() {
_ = types.Exchange(&Exchange{})
_ = types.MarginExchange(&Exchange{})
// FIXME: this is not effected since dotenv is loaded in the rootCmd, not in the init function
if ok, _ := strconv.ParseBool(os.Getenv("DEBUG_BINANCE_STREAM")); ok {
log.Level = logrus.DebugLevel
}
@ -74,8 +75,7 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke
return nil, err
}
ticker := toGlobalTicker(stats[0])
return &ticker, nil
return toGlobalTicker(stats[0])
}
func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[string]types.Ticker, error) {
@ -136,38 +136,7 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
markets := types.MarketMap{}
for _, symbol := range exchangeInfo.Symbols {
market := types.Market{
Symbol: symbol.Symbol,
LocalSymbol: symbol.Symbol,
PricePrecision: symbol.QuotePrecision,
VolumePrecision: symbol.BaseAssetPrecision,
QuoteCurrency: symbol.QuoteAsset,
BaseCurrency: symbol.BaseAsset,
}
if f := symbol.MinNotionalFilter(); f != nil {
market.MinNotional = util.MustParseFloat(f.MinNotional)
market.MinAmount = util.MustParseFloat(f.MinNotional)
}
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
// There are 3 parts:
// minQty defines the minimum quantity/icebergQty allowed.
// maxQty defines the maximum quantity/icebergQty allowed.
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
if f := symbol.LotSizeFilter(); f != nil {
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
market.StepSize = util.MustParseFloat(f.StepSize)
}
if f := symbol.PriceFilter(); f != nil {
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
market.MinPrice = util.MustParseFloat(f.MinPrice)
market.TickSize = util.MustParseFloat(f.TickSize)
}
markets[symbol.Symbol] = market
markets[symbol.Symbol] = toGlobalMarket(symbol)
}
return markets, nil
@ -211,15 +180,6 @@ func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...st
return toGlobalIsolatedMarginAccount(account), nil
}
func (e *Exchange) 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
}
func (e *Exchange) Withdrawal(ctx context.Context, asset string, amount fixedpoint.Value, address string, options *types.WithdrawalOptions) error {
req := e.Client.NewCreateWithdrawService()
@ -250,7 +210,7 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
var emptyTime = time.Time{}
if startTime == emptyTime {
startTime, err = e.getLaunchDate()
startTime, err = getLaunchDate()
if err != nil {
return nil, err
}
@ -338,7 +298,7 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
var emptyTime = time.Time{}
if startTime == emptyTime {
startTime, err = e.getLaunchDate()
startTime, err = getLaunchDate()
if err != nil {
return nil, err
}
@ -925,44 +885,7 @@ func (e *Exchange) BatchQueryKLines(ctx context.Context, symbol string, interval
return allKLines, nil
}
type FundingRate struct {
FundingRate fixedpoint.Value
FundingTime time.Time
Time time.Time
}
type PremiumIndex struct {
Symbol string `json:"symbol"`
MarkPrice fixedpoint.Value `json:"markPrice"`
LastFundingRate fixedpoint.Value `json:"lastFundingRate"`
NextFundingTime time.Time `json:"nextFundingTime"`
Time time.Time `json:"time"`
}
func convertPremiumIndex(index *futures.PremiumIndex) (*PremiumIndex, error) {
markPrice, err := fixedpoint.NewFromString(index.MarkPrice)
if err != nil {
return nil, err
}
lastFundingRate, err := fixedpoint.NewFromString(index.LastFundingRate)
if err != nil {
return nil, err
}
nextFundingTime := time.Unix(0, index.NextFundingTime*int64(time.Millisecond))
t := time.Unix(0, index.Time*int64(time.Millisecond))
return &PremiumIndex{
Symbol: index.Symbol,
MarkPrice: markPrice,
NextFundingTime: nextFundingTime,
LastFundingRate: lastFundingRate,
Time: t,
}, nil
}
func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*PremiumIndex, error) {
func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*types.PremiumIndex, error) {
futuresClient := binance.NewFuturesClient(e.key, e.secret)
// when symbol is set, only one index will be returned.
@ -974,7 +897,7 @@ func (e *Exchange) QueryPremiumIndex(ctx context.Context, symbol string) (*Premi
return convertPremiumIndex(indexes[0])
}
func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (*FundingRate, error) {
func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (*types.FundingRate, error) {
futuresClient := binance.NewFuturesClient(e.key, e.secret)
rates, err := futuresClient.NewFundingRateService().
Symbol(symbol).
@ -994,9 +917,19 @@ func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (
return nil, err
}
return &FundingRate{
return &types.FundingRate{
FundingRate: fundingRate,
FundingTime: time.Unix(0, rate.FundingTime*int64(time.Millisecond)),
Time: time.Unix(0, rate.Time*int64(time.Millisecond)),
}, nil
}
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
}

View File

@ -4,13 +4,14 @@ import (
"context"
"errors"
"fmt"
"github.com/c9s/bbgo/pkg/exchange/binance"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/sirupsen/logrus"
"math"
"strings"
"time"
"github.com/c9s/bbgo/pkg/exchange/binance"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
)
@ -87,7 +88,7 @@ func (s *Strategy) Validate() error {
}
func (s *Strategy) listenToFundingRate(ctx context.Context, exchange *binance.Exchange) {
var previousIndex, fundingRate24HoursLowIndex *binance.PremiumIndex
var previousIndex, fundingRate24HoursLowIndex *types.PremiumIndex
fundingRateTicker := time.NewTicker(1 * time.Hour)
defer fundingRateTicker.Stop()

13
pkg/types/fundingrate.go Normal file
View File

@ -0,0 +1,13 @@
package types
import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type FundingRate struct {
FundingRate fixedpoint.Value
FundingTime time.Time
Time time.Time
}

15
pkg/types/premiumindex.go Normal file
View File

@ -0,0 +1,15 @@
package types
import (
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type PremiumIndex struct {
Symbol string `json:"symbol"`
MarkPrice fixedpoint.Value `json:"markPrice"`
LastFundingRate fixedpoint.Value `json:"lastFundingRate"`
NextFundingTime time.Time `json:"nextFundingTime"`
Time time.Time `json:"time"`
}