mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
Merge pull request #418 from austin362667/refactor/futures-account
binance: add futures exchange api queries
This commit is contained in:
commit
0e0525be99
38
config/funding.yaml
Normal file
38
config/funding.yaml
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
notifications:
|
||||
slack:
|
||||
defaultChannel: "dev-bbgo"
|
||||
errorChannel: "bbgo-error"
|
||||
|
||||
# if you want to route channel by symbol
|
||||
symbolChannels:
|
||||
"^BTC": "btc"
|
||||
"^ETH": "eth"
|
||||
|
||||
# object routing rules
|
||||
routing:
|
||||
trade: "$symbol"
|
||||
order: "$symbol"
|
||||
submitOrder: "$session" # not supported yet
|
||||
pnL: "bbgo-pnl"
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
futures: true
|
||||
|
||||
exchangeStrategies:
|
||||
- on: binance
|
||||
funding:
|
||||
symbol: ETHUSDT
|
||||
quantity: 0.0001
|
||||
fundingRate:
|
||||
high: 0.01%
|
||||
supportDetection:
|
||||
- interval: 1m
|
||||
movingAverageType: EMA
|
||||
movingAverageIntervalWindow:
|
||||
interval: 15m
|
||||
window: 60
|
||||
minVolume: 8_000
|
|
@ -30,6 +30,7 @@ package backtest
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/cache"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -68,7 +69,7 @@ type Exchange struct {
|
|||
func NewExchange(sourceName types.ExchangeName, sourceExchange types.Exchange, srv *service.BacktestService, config *bbgo.Backtest) (*Exchange, error) {
|
||||
ex := sourceExchange
|
||||
|
||||
markets, err := bbgo.LoadExchangeMarketsWithCache(context.Background(), ex)
|
||||
markets, err := cache.LoadExchangeMarketsWithCache(context.Background(), ex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package bbgo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/cache"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -295,7 +296,7 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment)
|
|||
if util.SetEnvVarBool("DISABLE_MARKETS_CACHE", &disableMarketsCache); disableMarketsCache {
|
||||
markets, err = session.Exchange.QueryMarkets(ctx)
|
||||
} else {
|
||||
markets, err = LoadExchangeMarketsWithCache(ctx, session.Exchange)
|
||||
markets, err = cache.LoadExchangeMarketsWithCache(ctx, session.Exchange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
2
pkg/bbgo/cache.go → pkg/cache/cache.go
vendored
2
pkg/bbgo/cache.go → pkg/cache/cache.go
vendored
|
@ -1,4 +1,4 @@
|
|||
package bbgo
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
2
pkg/bbgo/home.go → pkg/cache/home.go
vendored
2
pkg/bbgo/home.go → pkg/cache/home.go
vendored
|
@ -1,4 +1,4 @@
|
|||
package bbgo
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
|
@ -7,7 +7,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/emastop"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/etf"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/flashcrash"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xgap"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/funding"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/grid"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/kline"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/pricealert"
|
||||
|
@ -18,6 +18,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/swing"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/techsignal"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xbalance"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xgap"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
||||
|
|
|
@ -50,6 +50,42 @@ func toGlobalMarket(symbol binance.Symbol) types.Market {
|
|||
return market
|
||||
}
|
||||
|
||||
// TODO: Cuz it returns types.Market as well, merge following to the above function
|
||||
func toGlobalFuturesMarket(symbol futures.Symbol) types.Market {
|
||||
market := types.Market{
|
||||
Symbol: symbol.Symbol,
|
||||
LocalSymbol: symbol.Symbol,
|
||||
PricePrecision: symbol.QuotePrecision,
|
||||
VolumePrecision: symbol.BaseAssetPrecision,
|
||||
QuoteCurrency: symbol.QuoteAsset,
|
||||
BaseCurrency: symbol.BaseAsset,
|
||||
}
|
||||
|
||||
if f := symbol.MinNotionalFilter(); f != nil {
|
||||
market.MinNotional = util.MustParseFloat(f.Notional)
|
||||
market.MinAmount = util.MustParseFloat(f.Notional)
|
||||
}
|
||||
|
||||
// The LOT_SIZE filter defines the quantity (aka "lots" in auction terms) rules for a symbol.
|
||||
// There are 3 parts:
|
||||
// minQty defines the minimum quantity/icebergQty allowed.
|
||||
// maxQty defines the maximum quantity/icebergQty allowed.
|
||||
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
|
||||
if f := symbol.LotSizeFilter(); f != nil {
|
||||
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
|
||||
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
|
||||
market.StepSize = util.MustParseFloat(f.StepSize)
|
||||
}
|
||||
|
||||
if f := symbol.PriceFilter(); f != nil {
|
||||
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
|
||||
market.MinPrice = util.MustParseFloat(f.MinPrice)
|
||||
market.TickSize = util.MustParseFloat(f.TickSize)
|
||||
}
|
||||
|
||||
return market
|
||||
}
|
||||
|
||||
func toGlobalIsolatedUserAsset(userAsset binance.IsolatedUserAsset) types.IsolatedUserAsset {
|
||||
return types.IsolatedUserAsset{
|
||||
Asset: userAsset.Asset,
|
||||
|
@ -81,40 +117,42 @@ func toGlobalIsolatedMarginAsset(asset binance.IsolatedMarginAsset) types.Isolat
|
|||
}
|
||||
}
|
||||
|
||||
func toGlobalIsolatedMarginAssets(assets []binance.IsolatedMarginAsset) (retAssets []types.IsolatedMarginAsset) {
|
||||
for _, asset := range assets {
|
||||
retAssets = append(retAssets, toGlobalIsolatedMarginAsset(asset))
|
||||
func toGlobalIsolatedMarginAssets(assets []binance.IsolatedMarginAsset) (retAssets types.IsolatedMarginAssetMap) {
|
||||
retMarginAssets := make(types.IsolatedMarginAssetMap)
|
||||
for _, marginAsset := range assets {
|
||||
retMarginAssets[marginAsset.Symbol] = toGlobalIsolatedMarginAsset(marginAsset)
|
||||
}
|
||||
|
||||
return retAssets
|
||||
return retMarginAssets
|
||||
}
|
||||
|
||||
func toGlobalIsolatedMarginAccount(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccount {
|
||||
return &types.IsolatedMarginAccount{
|
||||
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
||||
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
||||
}
|
||||
}
|
||||
//func toGlobalIsolatedMarginAccount(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccount {
|
||||
// return &types.IsolatedMarginAccount{
|
||||
// TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
// TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
||||
// TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
// Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
||||
// }
|
||||
//}
|
||||
|
||||
func toGlobalMarginUserAssets(userAssets []binance.UserAsset) (retAssets []types.MarginUserAsset) {
|
||||
for _, asset := range userAssets {
|
||||
retAssets = append(retAssets, types.MarginUserAsset{
|
||||
Asset: asset.Asset,
|
||||
Borrowed: fixedpoint.MustNewFromString(asset.Borrowed),
|
||||
Free: fixedpoint.MustNewFromString(asset.Free),
|
||||
Interest: fixedpoint.MustNewFromString(asset.Interest),
|
||||
Locked: fixedpoint.MustNewFromString(asset.Locked),
|
||||
NetAsset: fixedpoint.MustNewFromString(asset.NetAsset),
|
||||
})
|
||||
func toGlobalMarginUserAssets(assets []binance.UserAsset) types.MarginAssetMap {
|
||||
retMarginAssets := make(types.MarginAssetMap)
|
||||
for _, marginAsset := range assets {
|
||||
retMarginAssets[marginAsset.Asset] = types.MarginUserAsset{
|
||||
Asset: marginAsset.Asset,
|
||||
Borrowed: fixedpoint.MustNewFromString(marginAsset.Borrowed),
|
||||
Free: fixedpoint.MustNewFromString(marginAsset.Free),
|
||||
Interest: fixedpoint.MustNewFromString(marginAsset.Interest),
|
||||
Locked: fixedpoint.MustNewFromString(marginAsset.Locked),
|
||||
NetAsset: fixedpoint.MustNewFromString(marginAsset.NetAsset),
|
||||
}
|
||||
}
|
||||
|
||||
return retAssets
|
||||
return retMarginAssets
|
||||
}
|
||||
|
||||
func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount {
|
||||
return &types.MarginAccount{
|
||||
func toGlobalMarginAccountInfo(account *binance.MarginAccount) *types.MarginAccountInfo {
|
||||
return &types.MarginAccountInfo{
|
||||
BorrowEnabled: account.BorrowEnabled,
|
||||
MarginLevel: fixedpoint.MustNewFromString(account.MarginLevel),
|
||||
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
||||
|
@ -122,15 +160,22 @@ func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount
|
|||
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
TradeEnabled: account.TradeEnabled,
|
||||
TransferEnabled: account.TransferEnabled,
|
||||
UserAssets: toGlobalMarginUserAssets(account.UserAssets),
|
||||
Assets: toGlobalMarginUserAssets(account.UserAssets),
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalIsolatedMarginAccountInfo(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccountInfo {
|
||||
return &types.IsolatedMarginAccountInfo{
|
||||
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
||||
TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
||||
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||
Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalFuturesAccountInfo(account *futures.Account) *types.FuturesAccountInfo {
|
||||
return &types.FuturesAccountInfo{
|
||||
Assets: toGlobalFuturesUserAssets(account.Assets),
|
||||
FeeTier: account.FeeTier,
|
||||
MaxWithdrawAmount: fixedpoint.MustNewFromString(account.MaxWithdrawAmount),
|
||||
Positions: toGlobalFuturesPositions(account.Positions),
|
||||
TotalInitialMargin: fixedpoint.MustNewFromString(account.TotalInitialMargin),
|
||||
TotalMaintMargin: fixedpoint.MustNewFromString(account.TotalMaintMargin),
|
||||
|
@ -170,23 +215,23 @@ func toGlobalFuturesPositions(futuresPositions []*futures.AccountPosition) types
|
|||
return retFuturesPositions
|
||||
}
|
||||
|
||||
func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets map[types.Asset]types.FuturesUserAsset) {
|
||||
for _, asset := range assets {
|
||||
// TODO: or modify to type FuturesAssetMap map[string]FuturesAssetMap
|
||||
retAssets[types.Asset{Currency: asset.Asset}] = types.FuturesUserAsset{
|
||||
Asset: asset.Asset,
|
||||
InitialMargin: fixedpoint.MustNewFromString(asset.InitialMargin),
|
||||
MaintMargin: fixedpoint.MustNewFromString(asset.MaintMargin),
|
||||
MarginBalance: fixedpoint.MustNewFromString(asset.MarginBalance),
|
||||
MaxWithdrawAmount: fixedpoint.MustNewFromString(asset.MaxWithdrawAmount),
|
||||
OpenOrderInitialMargin: fixedpoint.MustNewFromString(asset.OpenOrderInitialMargin),
|
||||
PositionInitialMargin: fixedpoint.MustNewFromString(asset.PositionInitialMargin),
|
||||
UnrealizedProfit: fixedpoint.MustNewFromString(asset.UnrealizedProfit),
|
||||
WalletBalance: fixedpoint.MustNewFromString(asset.WalletBalance),
|
||||
func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets types.FuturesAssetMap) {
|
||||
retFuturesAssets := make(types.FuturesAssetMap)
|
||||
for _, futuresAsset := range assets {
|
||||
retFuturesAssets[futuresAsset.Asset] = types.FuturesUserAsset{
|
||||
Asset: futuresAsset.Asset,
|
||||
InitialMargin: fixedpoint.MustNewFromString(futuresAsset.InitialMargin),
|
||||
MaintMargin: fixedpoint.MustNewFromString(futuresAsset.MaintMargin),
|
||||
MarginBalance: fixedpoint.MustNewFromString(futuresAsset.MarginBalance),
|
||||
MaxWithdrawAmount: fixedpoint.MustNewFromString(futuresAsset.MaxWithdrawAmount),
|
||||
OpenOrderInitialMargin: fixedpoint.MustNewFromString(futuresAsset.OpenOrderInitialMargin),
|
||||
PositionInitialMargin: fixedpoint.MustNewFromString(futuresAsset.PositionInitialMargin),
|
||||
UnrealizedProfit: fixedpoint.MustNewFromString(futuresAsset.UnrealizedProfit),
|
||||
WalletBalance: fixedpoint.MustNewFromString(futuresAsset.WalletBalance),
|
||||
}
|
||||
}
|
||||
|
||||
return retAssets
|
||||
return retFuturesAssets
|
||||
}
|
||||
|
||||
func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) {
|
||||
|
|
|
@ -169,6 +169,21 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[stri
|
|||
}
|
||||
|
||||
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
exchangeInfo, err := e.Client.NewExchangeInfoService().Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -198,16 +213,21 @@ func (e *Exchange) NewStream() types.Stream {
|
|||
return stream
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryMarginAccount(ctx context.Context) (*types.MarginAccount, error) {
|
||||
func (e *Exchange) QueryMarginAccount(ctx context.Context) (*types.Account, error) {
|
||||
account, err := e.Client.NewGetMarginAccountService().Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toGlobalMarginAccount(account), nil
|
||||
a := &types.Account{
|
||||
AccountType: types.AccountTypeMargin,
|
||||
MarginInfo: toGlobalMarginAccountInfo(account), // In binance GO api, Account define account info which mantain []*AccountAsset and []*AccountPosition.
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...string) (*types.IsolatedMarginAccount, error) {
|
||||
func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...string) (*types.Account, error) {
|
||||
req := e.Client.NewGetIsolatedMarginAccountService()
|
||||
if len(symbols) > 0 {
|
||||
req.Symbols(symbols...)
|
||||
|
@ -218,7 +238,12 @@ func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...st
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return toGlobalIsolatedMarginAccount(account), nil
|
||||
a := &types.Account{
|
||||
AccountType: types.AccountTypeMargin,
|
||||
IsolatedMarginInfo: toGlobalIsolatedMarginAccountInfo(account), // In binance GO api, Account define account info which mantain []*AccountAsset and []*AccountPosition.
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) Withdrawal(ctx context.Context, asset string, amount fixedpoint.Value, address string, options *types.WithdrawalOptions) error {
|
||||
|
@ -416,7 +441,7 @@ func (e *Exchange) PlatformFeeCurrency() string {
|
|||
return BNB
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||
func (e *Exchange) QuerySpotAccount(ctx context.Context) (*types.Account, error) {
|
||||
account, err := e.Client.NewGetAccountService().Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -426,20 +451,68 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
|||
for _, b := range account.Balances {
|
||||
balances[b.Asset] = types.Balance{
|
||||
Currency: b.Asset,
|
||||
Available: fixedpoint.Must(fixedpoint.NewFromString(b.Free)),
|
||||
Locked: fixedpoint.Must(fixedpoint.NewFromString(b.Locked)),
|
||||
Available: fixedpoint.MustNewFromString(b.Free),
|
||||
Locked: fixedpoint.MustNewFromString(b.Locked),
|
||||
}
|
||||
}
|
||||
|
||||
// binance use 15 -> 0.15%, so we convert it to 0.0015
|
||||
a := &types.Account{
|
||||
AccountType: types.AccountTypeSpot,
|
||||
MakerCommission: fixedpoint.NewFromFloat(float64(account.MakerCommission) * 0.0001),
|
||||
TakerCommission: fixedpoint.NewFromFloat(float64(account.TakerCommission) * 0.0001),
|
||||
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) 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) {
|
||||
var account *types.Account
|
||||
var err error
|
||||
if e.IsFutures {
|
||||
account, err = e.QueryFuturesAccount(ctx)
|
||||
} else if e.IsIsolatedMargin {
|
||||
account, err = e.QueryIsolatedMarginAccount(ctx)
|
||||
} else if e.IsMargin {
|
||||
account, err = e.QueryMarginAccount(ctx)
|
||||
} else {
|
||||
account, err = e.QuerySpotAccount(ctx)
|
||||
}
|
||||
|
||||
return account, err
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||
if e.IsMargin {
|
||||
req := e.Client.NewListMarginOpenOrdersService().Symbol(symbol)
|
||||
|
@ -516,7 +589,6 @@ func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since,
|
|||
if err != nil {
|
||||
return orders, err
|
||||
}
|
||||
|
||||
return toGlobalFuturesOrders(binanceOrders)
|
||||
}
|
||||
|
||||
|
@ -1032,9 +1104,8 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
|||
return trades, nil
|
||||
} else if e.IsFutures {
|
||||
var remoteTrades []*futures.AccountTrade
|
||||
req := e.futuresClient.NewListAccountTradeService(). // IsIsolated(e.IsIsolatedFutures).
|
||||
Symbol(symbol)
|
||||
|
||||
req := e.futuresClient.NewListAccountTradeService().
|
||||
Symbol(symbol)
|
||||
if options.Limit > 0 {
|
||||
req.Limit(int(options.Limit))
|
||||
} else {
|
||||
|
@ -1053,7 +1124,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
|||
for _, t := range remoteTrades {
|
||||
localTrade, err := toGlobalFuturesTrade(*t)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("can not convert binance trade: %+v", t)
|
||||
log.WithError(err).Errorf("can not convert binance futures trade: %+v", t)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1207,6 +1278,18 @@ func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (
|
|||
}, nil
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
func getLaunchDate() (time.Time, error) {
|
||||
// binance launch date 12:00 July 14th, 2017
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
|
|
|
@ -719,6 +719,29 @@ func (e *OrderTradeUpdateEvent) OrderFutures() (*types.Order, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (e *OrderTradeUpdateEvent) TradeFutures() (*types.Trade, error) {
|
||||
if e.OrderTrade.CurrentExecutionType != "TRADE" {
|
||||
return nil, errors.New("execution report is not a futures trade")
|
||||
}
|
||||
|
||||
tt := time.Unix(0, e.OrderTrade.OrderTradeTime*int64(time.Millisecond))
|
||||
return &types.Trade{
|
||||
ID: uint64(e.OrderTrade.TradeId),
|
||||
Exchange: types.ExchangeBinance,
|
||||
Symbol: e.OrderTrade.Symbol,
|
||||
OrderID: uint64(e.OrderTrade.OrderId),
|
||||
Side: toGlobalSideType(binance.SideType(e.OrderTrade.Side)),
|
||||
Price: float64(e.OrderTrade.LastFilledPrice),
|
||||
Quantity: float64(e.OrderTrade.OrderLastFilledQuantity),
|
||||
QuoteQuantity: float64(e.OrderTrade.OrderFilledAccumulatedQuantity),
|
||||
IsBuyer: e.OrderTrade.Side == "BUY",
|
||||
IsMaker: e.OrderTrade.IsMaker,
|
||||
Time: types.Time(tt),
|
||||
Fee: float64(e.OrderTrade.CommissionAmount),
|
||||
FeeCurrency: e.OrderTrade.CommissionAsset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type AccountUpdate struct {
|
||||
EventReasonType string `json:"m"`
|
||||
Balances []*futures.Balance `json:"B,omitempty"`
|
||||
|
|
|
@ -247,33 +247,21 @@ func (s *Stream) handleOrderTradeUpdateEvent(e *OrderTradeUpdateEvent) {
|
|||
case "NEW", "CANCELED", "EXPIRED":
|
||||
order, err := e.OrderFutures()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("order convert error")
|
||||
log.WithError(err).Error("futures order convert error")
|
||||
return
|
||||
}
|
||||
|
||||
s.EmitOrderUpdate(*order)
|
||||
|
||||
case "TRADE":
|
||||
// TODO
|
||||
trade, err := e.TradeFutures()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("futures trade convert error")
|
||||
return
|
||||
}
|
||||
|
||||
// trade, err := e.Trade()
|
||||
// if err != nil {
|
||||
// log.WithError(err).Error("trade convert error")
|
||||
// return
|
||||
// }
|
||||
s.EmitTradeUpdate(*trade)
|
||||
|
||||
// stream.EmitTradeUpdate(*trade)
|
||||
|
||||
// order, err := e.OrderFutures()
|
||||
// if err != nil {
|
||||
// log.WithError(err).Error("order convert error")
|
||||
// return
|
||||
// }
|
||||
|
||||
// Update Order with FILLED event
|
||||
// if order.Status == types.OrderStatusFilled {
|
||||
// stream.EmitOrderUpdate(*order)
|
||||
// }
|
||||
case "CALCULATED - Liquidation Execution":
|
||||
log.Infof("CALCULATED - Liquidation Execution not support yet.")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/c9s/bbgo/pkg/cache"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -25,17 +26,22 @@ type SyncService struct {
|
|||
func (s *SyncService) SyncSessionSymbols(ctx context.Context, exchange types.Exchange, startTime time.Time, symbols ...string) error {
|
||||
for _, symbol := range symbols {
|
||||
log.Infof("syncing %s %s trades...", exchange.Name(), symbol)
|
||||
if err := s.TradeService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||
markets, err := cache.LoadExchangeMarketsWithCache(ctx, exchange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("syncing %s %s orders...", exchange.Name(), symbol)
|
||||
if err := s.OrderService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||
return err
|
||||
if _, ok := markets[symbol]; ok {
|
||||
if err := s.TradeService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("syncing %s %s orders...", exchange.Name(), symbol)
|
||||
if err := s.OrderService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.Infof("syncing %s deposit records...", exchange.Name())
|
||||
if err := s.DepositService.Sync(ctx, exchange); err != nil {
|
||||
if err != ErrNotImplemented {
|
||||
|
|
211
pkg/strategy/funding/strategy.go
Normal file
211
pkg/strategy/funding/strategy.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package funding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/exchange/binance"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/sirupsen/logrus"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
const ID = "funding"
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
|
||||
func init() {
|
||||
// Register the pointer of the strategy struct,
|
||||
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
|
||||
// Note: built-in strategies need to imported manually in the bbgo cmd package.
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Notifiability
|
||||
// These fields will be filled from the config file (it translates YAML to JSON)
|
||||
Symbol string `json:"symbol"`
|
||||
Market types.Market `json:"-"`
|
||||
Quantity fixedpoint.Value `json:"quantity,omitempty"`
|
||||
MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"`
|
||||
//Interval types.Interval `json:"interval"`
|
||||
|
||||
FundingRate *struct {
|
||||
High fixedpoint.Value `json:"high"`
|
||||
Neutral fixedpoint.Value `json:"neutral"`
|
||||
DiffThreshold fixedpoint.Value `json:"diffThreshold"`
|
||||
} `json:"fundingRate"`
|
||||
|
||||
SupportDetection []struct {
|
||||
Interval types.Interval `json:"interval"`
|
||||
// MovingAverageType is the moving average indicator type that we want to use,
|
||||
// it could be SMA or EWMA
|
||||
MovingAverageType string `json:"movingAverageType"`
|
||||
|
||||
// MovingAverageInterval is the interval of k-lines for the moving average indicator to calculate,
|
||||
// it could be "1m", "5m", "1h" and so on. note that, the moving averages are calculated from
|
||||
// the k-line data we subscribed
|
||||
//MovingAverageInterval types.Interval `json:"movingAverageInterval"`
|
||||
//
|
||||
//// MovingAverageWindow is the number of the window size of the moving average indicator.
|
||||
//// The number of k-lines in the window. generally used window sizes are 7, 25 and 99 in the TradingView.
|
||||
//MovingAverageWindow int `json:"movingAverageWindow"`
|
||||
|
||||
MovingAverageIntervalWindow types.IntervalWindow `json:"movingAverageIntervalWindow"`
|
||||
|
||||
MinVolume fixedpoint.Value `json:"minVolume"`
|
||||
|
||||
MinQuoteVolume fixedpoint.Value `json:"minQuoteVolume"`
|
||||
} `json:"supportDetection"`
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
// session.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{})
|
||||
|
||||
//session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
||||
// Interval: string(s.Interval),
|
||||
//})
|
||||
|
||||
for _, detection := range s.SupportDetection {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
||||
Interval: string(detection.Interval),
|
||||
})
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
|
||||
Interval: string(detection.MovingAverageIntervalWindow.Interval),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) Validate() error {
|
||||
if len(s.Symbol) == 0 {
|
||||
return errors.New("symbol is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
|
||||
standardIndicatorSet, ok := session.StandardIndicatorSet(s.Symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("standardIndicatorSet is nil, symbol %s", s.Symbol)
|
||||
}
|
||||
//binanceExchange, ok := session.Exchange.(*binance.Exchange)
|
||||
//if !ok {
|
||||
// log.Error("exchange failed")
|
||||
//}
|
||||
if !session.Futures {
|
||||
log.Error("futures not enabled in config for this strategy")
|
||||
return nil
|
||||
}
|
||||
|
||||
//if s.FundingRate != nil {
|
||||
// go s.listenToFundingRate(ctx, binanceExchange)
|
||||
//}
|
||||
premiumIndex, err := session.Exchange.(*binance.Exchange).QueryPremiumIndex(ctx, s.Symbol)
|
||||
if err != nil {
|
||||
log.Error("exchange does not support funding rate api")
|
||||
}
|
||||
|
||||
var ma types.Float64Indicator
|
||||
for _, detection := range s.SupportDetection {
|
||||
|
||||
switch strings.ToLower(detection.MovingAverageType) {
|
||||
case "sma":
|
||||
ma = standardIndicatorSet.SMA(types.IntervalWindow{
|
||||
Interval: detection.MovingAverageIntervalWindow.Interval,
|
||||
Window: detection.MovingAverageIntervalWindow.Window,
|
||||
})
|
||||
case "ema", "ewma":
|
||||
ma = standardIndicatorSet.EWMA(types.IntervalWindow{
|
||||
Interval: detection.MovingAverageIntervalWindow.Interval,
|
||||
Window: detection.MovingAverageIntervalWindow.Window,
|
||||
})
|
||||
default:
|
||||
ma = standardIndicatorSet.EWMA(types.IntervalWindow{
|
||||
Interval: detection.MovingAverageIntervalWindow.Interval,
|
||||
Window: detection.MovingAverageIntervalWindow.Window,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
// skip k-lines from other symbols
|
||||
if kline.Symbol != s.Symbol {
|
||||
return
|
||||
}
|
||||
for _, detection := range s.SupportDetection {
|
||||
var lastMA = ma.Last()
|
||||
|
||||
closePriceF := kline.GetClose()
|
||||
closePrice := fixedpoint.NewFromFloat(closePriceF)
|
||||
// skip if the closed price is under the moving average
|
||||
if closePrice.Float64() < lastMA {
|
||||
log.Infof("skip %s closed price %f < last ma %f", s.Symbol, closePrice.Float64(), lastMA)
|
||||
return
|
||||
}
|
||||
|
||||
fundingRate := premiumIndex.LastFundingRate
|
||||
|
||||
if fundingRate >= s.FundingRate.High {
|
||||
s.Notifiability.Notify("%s funding rate %s is too high! threshold %s",
|
||||
s.Symbol,
|
||||
fundingRate.Percentage(),
|
||||
s.FundingRate.High.Percentage(),
|
||||
)
|
||||
} else {
|
||||
log.Infof("skip funding rate is too low")
|
||||
return
|
||||
}
|
||||
|
||||
prettyBaseVolume := s.Market.BaseCurrencyFormatter()
|
||||
prettyQuoteVolume := s.Market.QuoteCurrencyFormatter()
|
||||
|
||||
if detection.MinVolume > 0 && kline.Volume > detection.MinVolume.Float64() {
|
||||
s.Notifiability.Notify("Detected %s %s resistance base volume %s > min base volume %s, quote volume %s",
|
||||
s.Symbol, detection.Interval.String(),
|
||||
prettyBaseVolume.FormatMoney(math.Round(kline.Volume)),
|
||||
prettyBaseVolume.FormatMoney(math.Round(detection.MinVolume.Float64())),
|
||||
prettyQuoteVolume.FormatMoney(math.Round(kline.QuoteVolume)),
|
||||
)
|
||||
s.Notifiability.Notify(kline)
|
||||
|
||||
baseBalance, ok := session.Account.Balance(s.Market.BaseCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if baseBalance.Available > 0 && baseBalance.Total() < s.MaxExposurePosition {
|
||||
log.Infof("opening a short position")
|
||||
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: kline.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: s.Quantity.Float64(),
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("submit order error")
|
||||
}
|
||||
}
|
||||
} else if detection.MinQuoteVolume > 0 && kline.QuoteVolume > detection.MinQuoteVolume.Float64() {
|
||||
s.Notifiability.Notify("Detected %s %s resistance quote volume %s > min quote volume %s, base volume %s",
|
||||
s.Symbol, detection.Interval.String(),
|
||||
prettyQuoteVolume.FormatMoney(math.Round(kline.QuoteVolume)),
|
||||
prettyQuoteVolume.FormatMoney(math.Round(detection.MinQuoteVolume.Float64())),
|
||||
prettyBaseVolume.FormatMoney(math.Round(kline.Volume)),
|
||||
)
|
||||
s.Notifiability.Notify(kline)
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
|
@ -121,6 +121,9 @@ func (m AssetMap) SlackAttachment() slack.Attachment {
|
|||
|
||||
type BalanceMap map[string]Balance
|
||||
type PositionMap map[string]Position
|
||||
type IsolatedMarginAssetMap map[string]IsolatedMarginAsset
|
||||
type MarginAssetMap map[string]MarginUserAsset
|
||||
type FuturesAssetMap map[string]FuturesUserAsset
|
||||
type FuturesPositionMap map[string]FuturesPosition
|
||||
|
||||
func (m BalanceMap) String() string {
|
||||
|
@ -200,14 +203,17 @@ type AccountType string
|
|||
|
||||
const (
|
||||
AccountTypeFutures = AccountType("futures")
|
||||
AccountTypeMargin = AccountType("margin")
|
||||
AccountTypeSpot = AccountType("spot")
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
sync.Mutex `json:"-"`
|
||||
|
||||
AccountType AccountType `json:"accountType,omitempty"`
|
||||
FuturesInfo *FuturesAccountInfo
|
||||
AccountType AccountType `json:"accountType,omitempty"`
|
||||
FuturesInfo *FuturesAccountInfo
|
||||
MarginInfo *MarginAccountInfo
|
||||
IsolatedMarginInfo *IsolatedMarginAccountInfo
|
||||
|
||||
MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty"`
|
||||
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
||||
|
@ -227,18 +233,35 @@ type Account struct {
|
|||
|
||||
type FuturesAccountInfo struct {
|
||||
// Futures fields
|
||||
Assets map[Asset]FuturesUserAsset `json:"assets"`
|
||||
FeeTier int `json:"feeTier"`
|
||||
MaxWithdrawAmount fixedpoint.Value `json:"maxWithdrawAmount"`
|
||||
Positions FuturesPositionMap `json:"positions"`
|
||||
TotalInitialMargin fixedpoint.Value `json:"totalInitialMargin"`
|
||||
TotalMaintMargin fixedpoint.Value `json:"totalMaintMargin"`
|
||||
TotalMarginBalance fixedpoint.Value `json:"totalMarginBalance"`
|
||||
TotalOpenOrderInitialMargin fixedpoint.Value `json:"totalOpenOrderInitialMargin"`
|
||||
TotalPositionInitialMargin fixedpoint.Value `json:"totalPositionInitialMargin"`
|
||||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit"`
|
||||
TotalWalletBalance fixedpoint.Value `json:"totalWalletBalance"`
|
||||
UpdateTime int64 `json:"updateTime"`
|
||||
Assets FuturesAssetMap `json:"assets"`
|
||||
Positions FuturesPositionMap `json:"positions"`
|
||||
TotalInitialMargin fixedpoint.Value `json:"totalInitialMargin"`
|
||||
TotalMaintMargin fixedpoint.Value `json:"totalMaintMargin"`
|
||||
TotalMarginBalance fixedpoint.Value `json:"totalMarginBalance"`
|
||||
TotalOpenOrderInitialMargin fixedpoint.Value `json:"totalOpenOrderInitialMargin"`
|
||||
TotalPositionInitialMargin fixedpoint.Value `json:"totalPositionInitialMargin"`
|
||||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit"`
|
||||
TotalWalletBalance fixedpoint.Value `json:"totalWalletBalance"`
|
||||
UpdateTime int64 `json:"updateTime"`
|
||||
}
|
||||
|
||||
type MarginAccountInfo struct {
|
||||
// Margin fields
|
||||
BorrowEnabled bool `json:"borrowEnabled"`
|
||||
MarginLevel fixedpoint.Value `json:"marginLevel"`
|
||||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||||
TradeEnabled bool `json:"tradeEnabled"`
|
||||
TransferEnabled bool `json:"transferEnabled"`
|
||||
Assets MarginAssetMap `json:"userAssets"`
|
||||
}
|
||||
|
||||
type IsolatedMarginAccountInfo struct {
|
||||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||||
Assets IsolatedMarginAssetMap `json:"userAssets"`
|
||||
}
|
||||
|
||||
func NewAccount() *Account {
|
||||
|
|
|
@ -92,10 +92,10 @@ type MarginUserAsset struct {
|
|||
|
||||
// IsolatedMarginAccount defines isolated user assets of margin account
|
||||
type IsolatedMarginAccount struct {
|
||||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||||
Assets []IsolatedMarginAsset `json:"assets"`
|
||||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||||
Assets IsolatedMarginAssetMap `json:"assets"`
|
||||
}
|
||||
|
||||
// IsolatedMarginAsset defines isolated margin asset information, like margin level, liquidation price... etc
|
||||
|
|
Loading…
Reference in New Issue
Block a user