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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/c9s/bbgo/pkg/cache"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ type Exchange struct {
|
||||||
func NewExchange(sourceName types.ExchangeName, sourceExchange types.Exchange, srv *service.BacktestService, config *bbgo.Backtest) (*Exchange, error) {
|
func NewExchange(sourceName types.ExchangeName, sourceExchange types.Exchange, srv *service.BacktestService, config *bbgo.Backtest) (*Exchange, error) {
|
||||||
ex := sourceExchange
|
ex := sourceExchange
|
||||||
|
|
||||||
markets, err := bbgo.LoadExchangeMarketsWithCache(context.Background(), ex)
|
markets, err := cache.LoadExchangeMarketsWithCache(context.Background(), ex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package bbgo
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/c9s/bbgo/pkg/cache"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -295,7 +296,7 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment)
|
||||||
if util.SetEnvVarBool("DISABLE_MARKETS_CACHE", &disableMarketsCache); disableMarketsCache {
|
if util.SetEnvVarBool("DISABLE_MARKETS_CACHE", &disableMarketsCache); disableMarketsCache {
|
||||||
markets, err = session.Exchange.QueryMarkets(ctx)
|
markets, err = session.Exchange.QueryMarkets(ctx)
|
||||||
} else {
|
} else {
|
||||||
markets, err = LoadExchangeMarketsWithCache(ctx, session.Exchange)
|
markets, err = cache.LoadExchangeMarketsWithCache(ctx, session.Exchange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 (
|
import (
|
||||||
"context"
|
"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 (
|
import (
|
||||||
"os"
|
"os"
|
|
@ -7,7 +7,7 @@ import (
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/emastop"
|
_ "github.com/c9s/bbgo/pkg/strategy/emastop"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/etf"
|
_ "github.com/c9s/bbgo/pkg/strategy/etf"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/flashcrash"
|
_ "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/grid"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/kline"
|
_ "github.com/c9s/bbgo/pkg/strategy/kline"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/pricealert"
|
_ "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/swing"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/techsignal"
|
_ "github.com/c9s/bbgo/pkg/strategy/techsignal"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/xbalance"
|
_ "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/xmaker"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
|
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
|
||||||
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
||||||
|
|
|
@ -50,6 +50,42 @@ func toGlobalMarket(symbol binance.Symbol) types.Market {
|
||||||
return 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 {
|
func toGlobalIsolatedUserAsset(userAsset binance.IsolatedUserAsset) types.IsolatedUserAsset {
|
||||||
return types.IsolatedUserAsset{
|
return types.IsolatedUserAsset{
|
||||||
Asset: userAsset.Asset,
|
Asset: userAsset.Asset,
|
||||||
|
@ -81,40 +117,42 @@ func toGlobalIsolatedMarginAsset(asset binance.IsolatedMarginAsset) types.Isolat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalIsolatedMarginAssets(assets []binance.IsolatedMarginAsset) (retAssets []types.IsolatedMarginAsset) {
|
func toGlobalIsolatedMarginAssets(assets []binance.IsolatedMarginAsset) (retAssets types.IsolatedMarginAssetMap) {
|
||||||
for _, asset := range assets {
|
retMarginAssets := make(types.IsolatedMarginAssetMap)
|
||||||
retAssets = append(retAssets, toGlobalIsolatedMarginAsset(asset))
|
for _, marginAsset := range assets {
|
||||||
|
retMarginAssets[marginAsset.Symbol] = toGlobalIsolatedMarginAsset(marginAsset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return retAssets
|
return retMarginAssets
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalIsolatedMarginAccount(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccount {
|
//func toGlobalIsolatedMarginAccount(account *binance.IsolatedMarginAccount) *types.IsolatedMarginAccount {
|
||||||
return &types.IsolatedMarginAccount{
|
// return &types.IsolatedMarginAccount{
|
||||||
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
// TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||||
TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
// TotalLiabilityOfBTC: fixedpoint.MustNewFromString(account.TotalLiabilityOfBTC),
|
||||||
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
// TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||||
Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
// Assets: toGlobalIsolatedMarginAssets(account.Assets),
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func toGlobalMarginUserAssets(userAssets []binance.UserAsset) (retAssets []types.MarginUserAsset) {
|
func toGlobalMarginUserAssets(assets []binance.UserAsset) types.MarginAssetMap {
|
||||||
for _, asset := range userAssets {
|
retMarginAssets := make(types.MarginAssetMap)
|
||||||
retAssets = append(retAssets, types.MarginUserAsset{
|
for _, marginAsset := range assets {
|
||||||
Asset: asset.Asset,
|
retMarginAssets[marginAsset.Asset] = types.MarginUserAsset{
|
||||||
Borrowed: fixedpoint.MustNewFromString(asset.Borrowed),
|
Asset: marginAsset.Asset,
|
||||||
Free: fixedpoint.MustNewFromString(asset.Free),
|
Borrowed: fixedpoint.MustNewFromString(marginAsset.Borrowed),
|
||||||
Interest: fixedpoint.MustNewFromString(asset.Interest),
|
Free: fixedpoint.MustNewFromString(marginAsset.Free),
|
||||||
Locked: fixedpoint.MustNewFromString(asset.Locked),
|
Interest: fixedpoint.MustNewFromString(marginAsset.Interest),
|
||||||
NetAsset: fixedpoint.MustNewFromString(asset.NetAsset),
|
Locked: fixedpoint.MustNewFromString(marginAsset.Locked),
|
||||||
})
|
NetAsset: fixedpoint.MustNewFromString(marginAsset.NetAsset),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retAssets
|
return retMarginAssets
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount {
|
func toGlobalMarginAccountInfo(account *binance.MarginAccount) *types.MarginAccountInfo {
|
||||||
return &types.MarginAccount{
|
return &types.MarginAccountInfo{
|
||||||
BorrowEnabled: account.BorrowEnabled,
|
BorrowEnabled: account.BorrowEnabled,
|
||||||
MarginLevel: fixedpoint.MustNewFromString(account.MarginLevel),
|
MarginLevel: fixedpoint.MustNewFromString(account.MarginLevel),
|
||||||
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
TotalAssetOfBTC: fixedpoint.MustNewFromString(account.TotalAssetOfBTC),
|
||||||
|
@ -122,15 +160,22 @@ func toGlobalMarginAccount(account *binance.MarginAccount) *types.MarginAccount
|
||||||
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
TotalNetAssetOfBTC: fixedpoint.MustNewFromString(account.TotalNetAssetOfBTC),
|
||||||
TradeEnabled: account.TradeEnabled,
|
TradeEnabled: account.TradeEnabled,
|
||||||
TransferEnabled: account.TransferEnabled,
|
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 {
|
func toGlobalFuturesAccountInfo(account *futures.Account) *types.FuturesAccountInfo {
|
||||||
return &types.FuturesAccountInfo{
|
return &types.FuturesAccountInfo{
|
||||||
Assets: toGlobalFuturesUserAssets(account.Assets),
|
Assets: toGlobalFuturesUserAssets(account.Assets),
|
||||||
FeeTier: account.FeeTier,
|
|
||||||
MaxWithdrawAmount: fixedpoint.MustNewFromString(account.MaxWithdrawAmount),
|
|
||||||
Positions: toGlobalFuturesPositions(account.Positions),
|
Positions: toGlobalFuturesPositions(account.Positions),
|
||||||
TotalInitialMargin: fixedpoint.MustNewFromString(account.TotalInitialMargin),
|
TotalInitialMargin: fixedpoint.MustNewFromString(account.TotalInitialMargin),
|
||||||
TotalMaintMargin: fixedpoint.MustNewFromString(account.TotalMaintMargin),
|
TotalMaintMargin: fixedpoint.MustNewFromString(account.TotalMaintMargin),
|
||||||
|
@ -170,23 +215,23 @@ func toGlobalFuturesPositions(futuresPositions []*futures.AccountPosition) types
|
||||||
return retFuturesPositions
|
return retFuturesPositions
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets map[types.Asset]types.FuturesUserAsset) {
|
func toGlobalFuturesUserAssets(assets []*futures.AccountAsset) (retAssets types.FuturesAssetMap) {
|
||||||
for _, asset := range assets {
|
retFuturesAssets := make(types.FuturesAssetMap)
|
||||||
// TODO: or modify to type FuturesAssetMap map[string]FuturesAssetMap
|
for _, futuresAsset := range assets {
|
||||||
retAssets[types.Asset{Currency: asset.Asset}] = types.FuturesUserAsset{
|
retFuturesAssets[futuresAsset.Asset] = types.FuturesUserAsset{
|
||||||
Asset: asset.Asset,
|
Asset: futuresAsset.Asset,
|
||||||
InitialMargin: fixedpoint.MustNewFromString(asset.InitialMargin),
|
InitialMargin: fixedpoint.MustNewFromString(futuresAsset.InitialMargin),
|
||||||
MaintMargin: fixedpoint.MustNewFromString(asset.MaintMargin),
|
MaintMargin: fixedpoint.MustNewFromString(futuresAsset.MaintMargin),
|
||||||
MarginBalance: fixedpoint.MustNewFromString(asset.MarginBalance),
|
MarginBalance: fixedpoint.MustNewFromString(futuresAsset.MarginBalance),
|
||||||
MaxWithdrawAmount: fixedpoint.MustNewFromString(asset.MaxWithdrawAmount),
|
MaxWithdrawAmount: fixedpoint.MustNewFromString(futuresAsset.MaxWithdrawAmount),
|
||||||
OpenOrderInitialMargin: fixedpoint.MustNewFromString(asset.OpenOrderInitialMargin),
|
OpenOrderInitialMargin: fixedpoint.MustNewFromString(futuresAsset.OpenOrderInitialMargin),
|
||||||
PositionInitialMargin: fixedpoint.MustNewFromString(asset.PositionInitialMargin),
|
PositionInitialMargin: fixedpoint.MustNewFromString(futuresAsset.PositionInitialMargin),
|
||||||
UnrealizedProfit: fixedpoint.MustNewFromString(asset.UnrealizedProfit),
|
UnrealizedProfit: fixedpoint.MustNewFromString(futuresAsset.UnrealizedProfit),
|
||||||
WalletBalance: fixedpoint.MustNewFromString(asset.WalletBalance),
|
WalletBalance: fixedpoint.MustNewFromString(futuresAsset.WalletBalance),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retAssets
|
return retFuturesAssets
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalTicker(stats *binance.PriceChangeStats) (*types.Ticker, error) {
|
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) {
|
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)
|
exchangeInfo, err := e.Client.NewExchangeInfoService().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -198,16 +213,21 @@ func (e *Exchange) NewStream() types.Stream {
|
||||||
return 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)
|
account, err := e.Client.NewGetMarginAccountService().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
req := e.Client.NewGetIsolatedMarginAccountService()
|
||||||
if len(symbols) > 0 {
|
if len(symbols) > 0 {
|
||||||
req.Symbols(symbols...)
|
req.Symbols(symbols...)
|
||||||
|
@ -218,7 +238,12 @@ func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...st
|
||||||
return nil, err
|
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 {
|
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
|
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)
|
account, err := e.Client.NewGetAccountService().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -426,20 +451,68 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||||
for _, b := range account.Balances {
|
for _, b := range account.Balances {
|
||||||
balances[b.Asset] = types.Balance{
|
balances[b.Asset] = types.Balance{
|
||||||
Currency: b.Asset,
|
Currency: b.Asset,
|
||||||
Available: fixedpoint.Must(fixedpoint.NewFromString(b.Free)),
|
Available: fixedpoint.MustNewFromString(b.Free),
|
||||||
Locked: fixedpoint.Must(fixedpoint.NewFromString(b.Locked)),
|
Locked: fixedpoint.MustNewFromString(b.Locked),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// binance use 15 -> 0.15%, so we convert it to 0.0015
|
|
||||||
a := &types.Account{
|
a := &types.Account{
|
||||||
|
AccountType: types.AccountTypeSpot,
|
||||||
MakerCommission: fixedpoint.NewFromFloat(float64(account.MakerCommission) * 0.0001),
|
MakerCommission: fixedpoint.NewFromFloat(float64(account.MakerCommission) * 0.0001),
|
||||||
TakerCommission: fixedpoint.NewFromFloat(float64(account.TakerCommission) * 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)
|
a.UpdateBalances(balances)
|
||||||
return a, nil
|
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) {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
if e.IsMargin {
|
if e.IsMargin {
|
||||||
req := e.Client.NewListMarginOpenOrdersService().Symbol(symbol)
|
req := e.Client.NewListMarginOpenOrdersService().Symbol(symbol)
|
||||||
|
@ -516,7 +589,6 @@ func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return orders, err
|
return orders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return toGlobalFuturesOrders(binanceOrders)
|
return toGlobalFuturesOrders(binanceOrders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1032,9 +1104,8 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
return trades, nil
|
return trades, nil
|
||||||
} else if e.IsFutures {
|
} else if e.IsFutures {
|
||||||
var remoteTrades []*futures.AccountTrade
|
var remoteTrades []*futures.AccountTrade
|
||||||
req := e.futuresClient.NewListAccountTradeService(). // IsIsolated(e.IsIsolatedFutures).
|
req := e.futuresClient.NewListAccountTradeService().
|
||||||
Symbol(symbol)
|
Symbol(symbol)
|
||||||
|
|
||||||
if options.Limit > 0 {
|
if options.Limit > 0 {
|
||||||
req.Limit(int(options.Limit))
|
req.Limit(int(options.Limit))
|
||||||
} else {
|
} else {
|
||||||
|
@ -1053,7 +1124,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
for _, t := range remoteTrades {
|
for _, t := range remoteTrades {
|
||||||
localTrade, err := toGlobalFuturesTrade(*t)
|
localTrade, err := toGlobalFuturesTrade(*t)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1207,6 +1278,18 @@ func (e *Exchange) QueryFundingRateHistory(ctx context.Context, symbol string) (
|
||||||
}, nil
|
}, 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) {
|
func getLaunchDate() (time.Time, error) {
|
||||||
// binance launch date 12:00 July 14th, 2017
|
// binance launch date 12:00 July 14th, 2017
|
||||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
|
|
@ -719,6 +719,29 @@ func (e *OrderTradeUpdateEvent) OrderFutures() (*types.Order, error) {
|
||||||
}, nil
|
}, 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 {
|
type AccountUpdate struct {
|
||||||
EventReasonType string `json:"m"`
|
EventReasonType string `json:"m"`
|
||||||
Balances []*futures.Balance `json:"B,omitempty"`
|
Balances []*futures.Balance `json:"B,omitempty"`
|
||||||
|
|
|
@ -247,33 +247,21 @@ func (s *Stream) handleOrderTradeUpdateEvent(e *OrderTradeUpdateEvent) {
|
||||||
case "NEW", "CANCELED", "EXPIRED":
|
case "NEW", "CANCELED", "EXPIRED":
|
||||||
order, err := e.OrderFutures()
|
order, err := e.OrderFutures()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("order convert error")
|
log.WithError(err).Error("futures order convert error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.EmitOrderUpdate(*order)
|
s.EmitOrderUpdate(*order)
|
||||||
|
|
||||||
case "TRADE":
|
case "TRADE":
|
||||||
// TODO
|
trade, err := e.TradeFutures()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("futures trade convert error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// trade, err := e.Trade()
|
s.EmitTradeUpdate(*trade)
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("trade convert error")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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":
|
case "CALCULATED - Liquidation Execution":
|
||||||
log.Infof("CALCULATED - Liquidation Execution not support yet.")
|
log.Infof("CALCULATED - Liquidation Execution not support yet.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/c9s/bbgo/pkg/cache"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
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 {
|
func (s *SyncService) SyncSessionSymbols(ctx context.Context, exchange types.Exchange, startTime time.Time, symbols ...string) error {
|
||||||
for _, symbol := range symbols {
|
for _, symbol := range symbols {
|
||||||
log.Infof("syncing %s %s trades...", exchange.Name(), symbol)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("syncing %s %s orders...", exchange.Name(), symbol)
|
if _, ok := markets[symbol]; ok {
|
||||||
if err := s.OrderService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
if err := s.TradeService.Sync(ctx, exchange, symbol, startTime); err != nil {
|
||||||
return err
|
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())
|
log.Infof("syncing %s deposit records...", exchange.Name())
|
||||||
if err := s.DepositService.Sync(ctx, exchange); err != nil {
|
if err := s.DepositService.Sync(ctx, exchange); err != nil {
|
||||||
if err != ErrNotImplemented {
|
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 BalanceMap map[string]Balance
|
||||||
type PositionMap map[string]Position
|
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
|
type FuturesPositionMap map[string]FuturesPosition
|
||||||
|
|
||||||
func (m BalanceMap) String() string {
|
func (m BalanceMap) String() string {
|
||||||
|
@ -200,14 +203,17 @@ type AccountType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AccountTypeFutures = AccountType("futures")
|
AccountTypeFutures = AccountType("futures")
|
||||||
|
AccountTypeMargin = AccountType("margin")
|
||||||
AccountTypeSpot = AccountType("spot")
|
AccountTypeSpot = AccountType("spot")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
sync.Mutex `json:"-"`
|
sync.Mutex `json:"-"`
|
||||||
|
|
||||||
AccountType AccountType `json:"accountType,omitempty"`
|
AccountType AccountType `json:"accountType,omitempty"`
|
||||||
FuturesInfo *FuturesAccountInfo
|
FuturesInfo *FuturesAccountInfo
|
||||||
|
MarginInfo *MarginAccountInfo
|
||||||
|
IsolatedMarginInfo *IsolatedMarginAccountInfo
|
||||||
|
|
||||||
MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty"`
|
MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty"`
|
||||||
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
||||||
|
@ -227,18 +233,35 @@ type Account struct {
|
||||||
|
|
||||||
type FuturesAccountInfo struct {
|
type FuturesAccountInfo struct {
|
||||||
// Futures fields
|
// Futures fields
|
||||||
Assets map[Asset]FuturesUserAsset `json:"assets"`
|
Assets FuturesAssetMap `json:"assets"`
|
||||||
FeeTier int `json:"feeTier"`
|
Positions FuturesPositionMap `json:"positions"`
|
||||||
MaxWithdrawAmount fixedpoint.Value `json:"maxWithdrawAmount"`
|
TotalInitialMargin fixedpoint.Value `json:"totalInitialMargin"`
|
||||||
Positions FuturesPositionMap `json:"positions"`
|
TotalMaintMargin fixedpoint.Value `json:"totalMaintMargin"`
|
||||||
TotalInitialMargin fixedpoint.Value `json:"totalInitialMargin"`
|
TotalMarginBalance fixedpoint.Value `json:"totalMarginBalance"`
|
||||||
TotalMaintMargin fixedpoint.Value `json:"totalMaintMargin"`
|
TotalOpenOrderInitialMargin fixedpoint.Value `json:"totalOpenOrderInitialMargin"`
|
||||||
TotalMarginBalance fixedpoint.Value `json:"totalMarginBalance"`
|
TotalPositionInitialMargin fixedpoint.Value `json:"totalPositionInitialMargin"`
|
||||||
TotalOpenOrderInitialMargin fixedpoint.Value `json:"totalOpenOrderInitialMargin"`
|
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit"`
|
||||||
TotalPositionInitialMargin fixedpoint.Value `json:"totalPositionInitialMargin"`
|
TotalWalletBalance fixedpoint.Value `json:"totalWalletBalance"`
|
||||||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit"`
|
UpdateTime int64 `json:"updateTime"`
|
||||||
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 {
|
func NewAccount() *Account {
|
||||||
|
|
|
@ -92,10 +92,10 @@ type MarginUserAsset struct {
|
||||||
|
|
||||||
// IsolatedMarginAccount defines isolated user assets of margin account
|
// IsolatedMarginAccount defines isolated user assets of margin account
|
||||||
type IsolatedMarginAccount struct {
|
type IsolatedMarginAccount struct {
|
||||||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||||||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||||||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||||||
Assets []IsolatedMarginAsset `json:"assets"`
|
Assets IsolatedMarginAssetMap `json:"assets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsolatedMarginAsset defines isolated margin asset information, like margin level, liquidation price... etc
|
// IsolatedMarginAsset defines isolated margin asset information, like margin level, liquidation price... etc
|
||||||
|
|
Loading…
Reference in New Issue
Block a user