Compare commits

...

13 Commits

Author SHA1 Message Date
bailantaotao
24bf561026
Merge 5e3b0238d8 into 2bf1072977 2024-09-02 08:53:26 -04:00
c9s
2bf1072977
Merge pull request #1725 from c9s/c9s/xmaker/stb-improvements
IMPROVE: [xmaker] improve hedge margin account leverage calculation
2024-09-02 15:29:53 +08:00
c9s
4d1c357c3d
xmaker: reuse makerMarket field 2024-09-01 17:55:00 +08:00
c9s
a4833524cf
xmaker: add more logs 2024-09-01 16:41:16 +08:00
c9s
ed073264f1
xmaker: add MaxHedgeAccountLeverage option 2024-09-01 15:42:36 +08:00
c9s
ad6056834e
xmaker: separate maximumValueInUsd in a new var 2024-09-01 01:34:25 +08:00
c9s
8b1306a6a6
xmaker: calculate maximum leveraged account value 2024-09-01 01:31:50 +08:00
c9s
d85da78e17
xmaker: improve hedge account credit calculation 2024-09-01 00:58:50 +08:00
edwin
5e3b0238d8 pkg/core: move convert manager to independent file 2024-08-23 17:30:30 +08:00
edwin
bc24e8dcaa pkg/core: add more tests for convert manager 2024-08-23 17:30:30 +08:00
edwin
4fefbaf0e7 pkg/core: add currency converter 2024-08-23 17:30:30 +08:00
edwin
b683550f44 rename converter.go to symbol_converter 2024-08-23 17:30:30 +08:00
edwin
26149103f0 pkg/core: support more function to symbol converter 2024-08-23 17:30:21 +08:00
9 changed files with 733 additions and 144 deletions

153
pkg/core/convert_manager.go Normal file
View File

@ -0,0 +1,153 @@
package core
import (
"github.com/c9s/bbgo/pkg/types"
"github.com/sirupsen/logrus"
)
type ConverterSetting struct {
SymbolConverter *SymbolConverter `json:"symbolConverter" yaml:"symbolConverter"`
CurrencyConverter *CurrencyConverter `json:"currencyConverter" yaml:"currencyConverter"`
}
func (s *ConverterSetting) getConverter() Converter {
if s.SymbolConverter != nil {
return s.SymbolConverter
}
if s.CurrencyConverter != nil {
return s.CurrencyConverter
}
return nil
}
func (s *ConverterSetting) InitializeConverter() (Converter, error) {
converter := s.getConverter()
if converter == nil {
return nil, nil
}
logrus.Infof("initializing converter %T ...", converter)
err := converter.Initialize()
return converter, err
}
// ConverterManager manages the converters for trade conversion
// It can be used to convert the trade symbol into the target symbol, or convert the price, volume into different units.
type ConverterManager struct {
ConverterSettings []ConverterSetting `json:"converters,omitempty" yaml:"converters,omitempty"`
converters []Converter
}
func (c *ConverterManager) Initialize() error {
for _, setting := range c.ConverterSettings {
converter, err := setting.InitializeConverter()
if err != nil {
return err
}
if converter != nil {
c.AddConverter(converter)
}
}
numConverters := len(c.converters)
logrus.Infof("%d converters loaded", numConverters)
return nil
}
func (c *ConverterManager) AddConverter(converter Converter) {
c.converters = append(c.converters, converter)
}
func (c *ConverterManager) ConvertOrder(order types.Order) types.Order {
if len(c.converters) == 0 {
return order
}
for _, converter := range c.converters {
convOrder, err := converter.ConvertOrder(order)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, order: %s", converter, order.String())
continue
}
order = convOrder
}
return order
}
func (c *ConverterManager) ConvertTrade(trade types.Trade) types.Trade {
if len(c.converters) == 0 {
return trade
}
for _, converter := range c.converters {
convTrade, err := converter.ConvertTrade(trade)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, trade: %s", converter, trade.String())
continue
}
trade = convTrade
}
return trade
}
func (c *ConverterManager) ConvertKLine(kline types.KLine) types.KLine {
if len(c.converters) == 0 {
return kline
}
for _, converter := range c.converters {
convKline, err := converter.ConvertKLine(kline)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, kline: %s", converter, kline.String())
continue
}
kline = convKline
}
return kline
}
func (c *ConverterManager) ConvertMarket(market types.Market) types.Market {
if len(c.converters) == 0 {
return market
}
for _, converter := range c.converters {
convMarket, err := converter.ConvertMarket(market)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, market: %+v", converter, market)
continue
}
market = convMarket
}
return market
}
func (c *ConverterManager) ConvertBalance(balance types.Balance) types.Balance {
if len(c.converters) == 0 {
return balance
}
for _, converter := range c.converters {
convBal, err := converter.ConvertBalance(balance)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, balance: %s", converter, balance.String())
continue
}
balance = convBal
}
return balance
}

View File

@ -0,0 +1,212 @@
package core
import (
"encoding/json"
"testing"
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert"
)
func TestInitializeConverter_ValidSymbolConverter(t *testing.T) {
setting := ConverterSetting{
SymbolConverter: &SymbolConverter{
FromSymbol: "MAXEXCHANGEUSDT",
ToSymbol: "MAXUSDT",
},
}
converter, err := setting.InitializeConverter()
assert.NoError(t, err)
assert.NotNil(t, converter)
}
func TestInitializeConverter_ValidCurrencyConverter(t *testing.T) {
setting := ConverterSetting{
CurrencyConverter: &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
},
}
converter, err := setting.InitializeConverter()
assert.NoError(t, err)
assert.NotNil(t, converter)
}
func TestInitializeConverter_NoConverter(t *testing.T) {
setting := ConverterSetting{}
converter, err := setting.InitializeConverter()
assert.NoError(t, err)
assert.Nil(t, converter)
}
func TestInitialize_ValidConverters(t *testing.T) {
manager := ConverterManager{
ConverterSettings: []ConverterSetting{
{SymbolConverter: &SymbolConverter{
FromSymbol: "MAXEXCHANGEUSDT",
ToSymbol: "MAXUSDT",
}},
{CurrencyConverter: &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
}},
},
}
err := manager.Initialize()
assert.NoError(t, err)
assert.Equal(t, 2, len(manager.converters))
}
func TestInitialize_NoConverters(t *testing.T) {
manager := ConverterManager{}
err := manager.Initialize()
assert.NoError(t, err)
assert.Equal(t, 0, len(manager.converters))
}
func TestConvertOrder_WithConverters(t *testing.T) {
jsonStr := `
{
"converters": [
{
"symbolConverter": {
"from": "MAXEXCHANGEUSDT",
"to": "MAXUSDT"
}
},
{
"currencyConverter": {
"from": "MAXEXCHANGE",
"to": "MAX"
}
}
]
}
`
manager := ConverterManager{}
err := json.Unmarshal([]byte(jsonStr), &manager)
assert.NoError(t, err)
order := types.Order{
SubmitOrder: types.SubmitOrder{
Symbol: "MAXEXCHANGEUSDT",
Market: types.Market{
Symbol: "MAXEXCHANGEUSDT",
QuoteCurrency: "USDT",
BaseCurrency: "MAXEXCHANGE",
},
},
}
err = manager.Initialize()
assert.NoError(t, err)
convertedOrder := manager.ConvertOrder(order)
assert.Equal(t, "MAXUSDT", convertedOrder.Symbol)
assert.Equal(t, "MAX", convertedOrder.Market.BaseCurrency)
assert.Equal(t, "USDT", convertedOrder.Market.QuoteCurrency)
assert.Equal(t, "MAXUSDT", convertedOrder.Market.Symbol)
}
func TestConvertOrder_NoConverters(t *testing.T) {
manager := ConverterManager{}
order := types.Order{}
err := manager.Initialize()
assert.NoError(t, err)
convertedOrder := manager.ConvertOrder(order)
assert.Equal(t, order, convertedOrder)
}
func TestConvertTrade_WithConverters(t *testing.T) {
manager := ConverterManager{}
converter := &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
}
err := manager.Initialize()
assert.NoError(t, err)
manager.AddConverter(converter)
trade := types.Trade{}
convertedTrade := manager.ConvertTrade(trade)
assert.Equal(t, trade, convertedTrade)
}
func TestConvertTrade_NoConverters(t *testing.T) {
manager := ConverterManager{}
trade := types.Trade{}
err := manager.Initialize()
assert.NoError(t, err)
convertedTrade := manager.ConvertTrade(trade)
assert.Equal(t, trade, convertedTrade)
}
func TestConvertKLine_WithConverters(t *testing.T) {
manager := ConverterManager{}
converter := &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
}
err := manager.Initialize()
assert.NoError(t, err)
manager.AddConverter(converter)
kline := types.KLine{}
convertedKline := manager.ConvertKLine(kline)
assert.Equal(t, kline, convertedKline)
}
func TestConvertKLine_NoConverters(t *testing.T) {
manager := ConverterManager{}
kline := types.KLine{}
err := manager.Initialize()
assert.NoError(t, err)
convertedKline := manager.ConvertKLine(kline)
assert.Equal(t, kline, convertedKline)
}
func TestConvertMarket_WithConverters(t *testing.T) {
manager := ConverterManager{}
converter := &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
}
err := manager.Initialize()
assert.NoError(t, err)
manager.AddConverter(converter)
market := types.Market{}
convertedMarket := manager.ConvertMarket(market)
assert.Equal(t, market, convertedMarket)
}
func TestConvertMarket_NoConverters(t *testing.T) {
manager := ConverterManager{}
market := types.Market{}
err := manager.Initialize()
assert.NoError(t, err)
convertedMarket := manager.ConvertMarket(market)
assert.Equal(t, market, convertedMarket)
}
func TestConvertBalance_WithConverters(t *testing.T) {
manager := ConverterManager{}
converter := &CurrencyConverter{
FromCurrency: "MAXEXCHANGE",
ToCurrency: "MAX",
}
err := manager.Initialize()
assert.NoError(t, err)
manager.AddConverter(converter)
balance := types.Balance{}
convertedBalance := manager.ConvertBalance(balance)
assert.Equal(t, balance, convertedBalance)
}
func TestConvertBalance_NoConverters(t *testing.T) {
manager := ConverterManager{}
balance := types.Balance{}
err := manager.Initialize()
assert.NoError(t, err)
convertedBalance := manager.ConvertBalance(balance)
assert.Equal(t, balance, convertedBalance)
}

View File

@ -1,31 +0,0 @@
package core
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
)
func TestSymbolConverter(t *testing.T) {
converter := NewSymbolConverter("MAXEXCHANGEUSDT", "MAXUSDT")
trade, err := converter.ConvertTrade(types.Trade{
Symbol: "MAXEXCHANGEUSDT",
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", trade.Symbol)
}
order, err := converter.ConvertOrder(types.Order{
SubmitOrder: types.SubmitOrder{
Symbol: "MAXEXCHANGEUSDT",
},
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", order.Symbol)
}
}

View File

@ -0,0 +1,68 @@
package core
import (
"errors"
"github.com/c9s/bbgo/pkg/types"
)
type CurrencyConverter struct {
FromCurrency string `json:"from"`
ToCurrency string `json:"to"`
}
func NewCurrencyConverter(fromSymbol, toSymbol string) *CurrencyConverter {
return &CurrencyConverter{FromCurrency: fromSymbol, ToCurrency: toSymbol}
}
func (c *CurrencyConverter) Initialize() error {
if c.FromCurrency == "" {
return errors.New("FromCurrency can not be empty")
}
if c.ToCurrency == "" {
return errors.New("ToCurrency can not be empty")
}
return nil
}
func (c *CurrencyConverter) ConvertOrder(order types.Order) (types.Order, error) {
if order.SubmitOrder.Market.QuoteCurrency == c.FromCurrency {
order.SubmitOrder.Market.QuoteCurrency = c.ToCurrency
}
if order.SubmitOrder.Market.BaseCurrency == c.FromCurrency {
order.SubmitOrder.Market.BaseCurrency = c.ToCurrency
}
return order, nil
}
func (c *CurrencyConverter) ConvertTrade(trade types.Trade) (types.Trade, error) {
if trade.FeeCurrency == c.FromCurrency {
trade.FeeCurrency = c.ToCurrency
}
return trade, nil
}
func (c *CurrencyConverter) ConvertKLine(kline types.KLine) (types.KLine, error) {
return kline, nil
}
func (c *CurrencyConverter) ConvertMarket(mkt types.Market) (types.Market, error) {
if mkt.QuoteCurrency == c.FromCurrency {
mkt.QuoteCurrency = c.ToCurrency
}
if mkt.BaseCurrency == c.FromCurrency {
mkt.BaseCurrency = c.ToCurrency
}
return mkt, nil
}
func (c *CurrencyConverter) ConvertBalance(balance types.Balance) (types.Balance, error) {
if balance.Currency == c.FromCurrency {
balance.Currency = c.ToCurrency
}
return balance, nil
}

View File

@ -0,0 +1,124 @@
package core
import (
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert"
"testing"
)
// pkg/core/tradecollector_test.go
func TestInitialize_ValidCurrencies(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
err := converter.Initialize()
assert.NoError(t, err)
}
func TestInitialize_EmptyFromCurrency(t *testing.T) {
converter := NewCurrencyConverter("", "MAX")
err := converter.Initialize()
assert.Error(t, err)
assert.Equal(t, "FromCurrency can not be empty", err.Error())
}
func TestInitialize_EmptyToCurrency(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "")
err := converter.Initialize()
assert.Error(t, err)
assert.Equal(t, "ToCurrency can not be empty", err.Error())
}
func TestConvertOrder_ValidConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
order := types.Order{
SubmitOrder: types.SubmitOrder{
Market: types.Market{
QuoteCurrency: "MAXEXCHANGE",
BaseCurrency: "MAXEXCHANGE",
},
},
}
convertedOrder, err := converter.ConvertOrder(order)
assert.NoError(t, err)
assert.Equal(t, "MAX", convertedOrder.SubmitOrder.Market.QuoteCurrency)
assert.Equal(t, "MAX", convertedOrder.SubmitOrder.Market.BaseCurrency)
}
func TestConvertOrder_NoConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
order := types.Order{
SubmitOrder: types.SubmitOrder{
Market: types.Market{
QuoteCurrency: "JPY",
BaseCurrency: "JPY",
},
},
}
convertedOrder, err := converter.ConvertOrder(order)
assert.NoError(t, err)
assert.Equal(t, "JPY", convertedOrder.SubmitOrder.Market.QuoteCurrency)
assert.Equal(t, "JPY", convertedOrder.SubmitOrder.Market.BaseCurrency)
}
func TestConvertTrade_ValidConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
trade := types.Trade{
FeeCurrency: "MAXEXCHANGE",
}
convertedTrade, err := converter.ConvertTrade(trade)
assert.NoError(t, err)
assert.Equal(t, "MAX", convertedTrade.FeeCurrency)
}
func TestConvertTrade_NoConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
trade := types.Trade{
FeeCurrency: "JPY",
}
convertedTrade, err := converter.ConvertTrade(trade)
assert.NoError(t, err)
assert.Equal(t, "JPY", convertedTrade.FeeCurrency)
}
func TestConvertMarket_ValidConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
market := types.Market{
QuoteCurrency: "MAXEXCHANGE",
BaseCurrency: "MAXEXCHANGE",
}
convertedMarket, err := converter.ConvertMarket(market)
assert.NoError(t, err)
assert.Equal(t, "MAX", convertedMarket.QuoteCurrency)
assert.Equal(t, "MAX", convertedMarket.BaseCurrency)
}
func TestConvertMarket_NoConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
market := types.Market{
QuoteCurrency: "JPY",
BaseCurrency: "JPY",
}
convertedMarket, err := converter.ConvertMarket(market)
assert.NoError(t, err)
assert.Equal(t, "JPY", convertedMarket.QuoteCurrency)
assert.Equal(t, "JPY", convertedMarket.BaseCurrency)
}
func TestConvertBalance_ValidConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
balance := types.Balance{
Currency: "MAXEXCHANGE",
}
convertedBalance, err := converter.ConvertBalance(balance)
assert.NoError(t, err)
assert.Equal(t, "MAX", convertedBalance.Currency)
}
func TestConvertBalance_NoConversion(t *testing.T) {
converter := NewCurrencyConverter("MAXEXCHANGE", "MAX")
balance := types.Balance{
Currency: "JPY",
}
convertedBalance, err := converter.ConvertBalance(balance)
assert.NoError(t, err)
assert.Equal(t, "JPY", convertedBalance.Currency)
}

View File

@ -9,6 +9,9 @@ import (
type Converter interface { type Converter interface {
OrderConverter OrderConverter
TradeConverter TradeConverter
KLineConverter
MarketConverter
BalanceConverter
Initialize() error Initialize() error
} }
@ -22,12 +25,33 @@ type TradeConverter interface {
ConvertTrade(trade types.Trade) (types.Trade, error) ConvertTrade(trade types.Trade) (types.Trade, error)
} }
// KLineConverter converts the kline to another kline
type KLineConverter interface {
ConvertKLine(kline types.KLine) (types.KLine, error)
}
// MarketConverter converts the market to another market
type MarketConverter interface {
ConvertMarket(market types.Market) (types.Market, error)
}
// BalanceConverter converts the balance to another balance
type BalanceConverter interface {
ConvertBalance(balance types.Balance) (types.Balance, error)
}
type OrderConvertFunc func(order types.Order) (types.Order, error) type OrderConvertFunc func(order types.Order) (types.Order, error)
type TradeConvertFunc func(trade types.Trade) (types.Trade, error) type TradeConvertFunc func(trade types.Trade) (types.Trade, error)
type KLineConvertFunc func(kline types.KLine) (types.KLine, error)
type MarketConvertFunc func(market types.Market) (types.Market, error)
type BalanceConvertFunc func(balance types.Balance) (types.Balance, error)
type DynamicConverter struct { type DynamicConverter struct {
orderConverter OrderConvertFunc orderConverter OrderConvertFunc
tradeConverter TradeConvertFunc tradeConverter TradeConvertFunc
klineConverter KLineConvertFunc
marketConverter MarketConvertFunc
balanceConverter BalanceConvertFunc
} }
func NewDynamicConverter(orderConverter OrderConvertFunc, tradeConverter TradeConvertFunc) *DynamicConverter { func NewDynamicConverter(orderConverter OrderConvertFunc, tradeConverter TradeConvertFunc) *DynamicConverter {
@ -46,6 +70,18 @@ func (c *DynamicConverter) ConvertTrade(trade types.Trade) (types.Trade, error)
return c.tradeConverter(trade) return c.tradeConverter(trade)
} }
func (c *DynamicConverter) ConvertKLine(kline types.KLine) (types.KLine, error) {
return c.klineConverter(kline)
}
func (c *DynamicConverter) ConvertMarket(market types.Market) (types.Market, error) {
return c.marketConverter(market)
}
func (c *DynamicConverter) ConvertBalance(balance types.Balance) (types.Balance, error) {
return c.balanceConverter(balance)
}
// SymbolConverter converts the symbol to another symbol // SymbolConverter converts the symbol to another symbol
type SymbolConverter struct { type SymbolConverter struct {
FromSymbol string `json:"from"` FromSymbol string `json:"from"`
@ -73,6 +109,10 @@ func (c *SymbolConverter) ConvertOrder(order types.Order) (types.Order, error) {
order.Symbol = c.ToSymbol order.Symbol = c.ToSymbol
} }
if order.SubmitOrder.Market.Symbol == c.FromSymbol {
order.SubmitOrder.Market.Symbol = c.ToSymbol
}
return order, nil return order, nil
} }
@ -83,3 +123,22 @@ func (c *SymbolConverter) ConvertTrade(trade types.Trade) (types.Trade, error) {
return trade, nil return trade, nil
} }
func (c *SymbolConverter) ConvertKLine(kline types.KLine) (types.KLine, error) {
if kline.Symbol == c.FromSymbol {
kline.Symbol = c.ToSymbol
}
return kline, nil
}
func (s *SymbolConverter) ConvertMarket(mkt types.Market) (types.Market, error) {
if mkt.Symbol == s.FromSymbol {
mkt.Symbol = s.ToSymbol
}
return mkt, nil
}
func (c *SymbolConverter) ConvertBalance(balance types.Balance) (types.Balance, error) {
return balance, nil
}

View File

@ -0,0 +1,60 @@
package core
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/types"
)
func TestSymbolConverter(t *testing.T) {
converter := NewSymbolConverter("MAXEXCHANGEUSDT", "MAXUSDT")
trade, err := converter.ConvertTrade(types.Trade{
Symbol: "MAXEXCHANGEUSDT",
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", trade.Symbol)
}
order, err := converter.ConvertOrder(types.Order{
SubmitOrder: types.SubmitOrder{
Symbol: "MAXEXCHANGEUSDT",
Market: types.Market{
Symbol: "MAXEXCHANGEUSDT",
},
},
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", order.Symbol)
assert.Equal(t, "MAXUSDT", order.SubmitOrder.Symbol)
assert.Equal(t, "MAXUSDT", order.SubmitOrder.Market.Symbol)
}
kline, err := converter.ConvertKLine(types.KLine{
Symbol: "MAXEXCHANGEUSDT",
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", kline.Symbol)
}
market, err := converter.ConvertMarket(types.Market{
Symbol: "MAXEXCHANGEUSDT",
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXUSDT", market.Symbol)
}
balance, err := converter.ConvertBalance(types.Balance{
Currency: "MAXEXCHANGE",
})
if assert.NoError(t, err) {
assert.Equal(t, "MAXEXCHANGE", balance.Currency)
}
}

View File

@ -12,94 +12,6 @@ import (
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
type ConverterSetting struct {
SymbolConverter *SymbolConverter `json:"symbolConverter" yaml:"symbolConverter"`
}
func (s *ConverterSetting) getConverter() Converter {
if s.SymbolConverter != nil {
return s.SymbolConverter
}
return nil
}
func (s *ConverterSetting) InitializeConverter() (Converter, error) {
converter := s.getConverter()
if converter == nil {
return nil, nil
}
logrus.Infof("initializing converter %T ...", converter)
err := converter.Initialize()
return converter, err
}
// ConverterManager manages the converters for trade conversion
// It can be used to convert the trade symbol into the target symbol, or convert the price, volume into different units.
type ConverterManager struct {
ConverterSettings []ConverterSetting `json:"converters,omitempty" yaml:"converters,omitempty"`
converters []Converter
}
func (c *ConverterManager) Initialize() error {
for _, setting := range c.ConverterSettings {
converter, err := setting.InitializeConverter()
if err != nil {
return err
}
if converter != nil {
c.AddConverter(converter)
}
}
numConverters := len(c.converters)
logrus.Infof("%d converters loaded", numConverters)
return nil
}
func (c *ConverterManager) AddConverter(converter Converter) {
c.converters = append(c.converters, converter)
}
func (c *ConverterManager) ConvertOrder(order types.Order) types.Order {
if len(c.converters) == 0 {
return order
}
for _, converter := range c.converters {
convOrder, err := converter.ConvertOrder(order)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, order: %s", converter, order.String())
continue
}
order = convOrder
}
return order
}
func (c *ConverterManager) ConvertTrade(trade types.Trade) types.Trade {
if len(c.converters) == 0 {
return trade
}
for _, converter := range c.converters {
convTrade, err := converter.ConvertTrade(trade)
if err != nil {
logrus.WithError(err).Errorf("converter %+v error, trade: %s", converter, trade.String())
continue
}
trade = convTrade
}
return trade
}
//go:generate callbackgen -type TradeCollector //go:generate callbackgen -type TradeCollector
type TradeCollector struct { type TradeCollector struct {
Symbol string Symbol string

View File

@ -119,6 +119,8 @@ type Strategy struct {
// MaxExposurePosition defines the unhedged quantity of stop // MaxExposurePosition defines the unhedged quantity of stop
MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"` MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"`
MaxHedgeAccountLeverage fixedpoint.Value `json:"maxHedgeAccountLeverage"`
DisableHedge bool `json:"disableHedge"` DisableHedge bool `json:"disableHedge"`
NotifyTrade bool `json:"notifyTrade"` NotifyTrade bool `json:"notifyTrade"`
@ -479,6 +481,7 @@ func (s *Strategy) updateQuote(ctx context.Context) {
makerQuota.BaseAsset.Add(b.Available) makerQuota.BaseAsset.Add(b.Available)
} else { } else {
disableMakerAsk = true disableMakerAsk = true
s.logger.Infof("%s maker ask disabled: insufficient base balance %s", s.Symbol, b.String())
} }
} }
@ -487,6 +490,7 @@ func (s *Strategy) updateQuote(ctx context.Context) {
makerQuota.QuoteAsset.Add(b.Available) makerQuota.QuoteAsset.Add(b.Available)
} else { } else {
disableMakerBid = true disableMakerBid = true
s.logger.Infof("%s maker bid disabled: insufficient quote balance %s", s.Symbol, b.String())
} }
} }
@ -503,6 +507,10 @@ func (s *Strategy) updateQuote(ctx context.Context) {
!hedgeAccount.MarginLevel.IsZero() { !hedgeAccount.MarginLevel.IsZero() {
if hedgeAccount.MarginLevel.Compare(s.MinMarginLevel) < 0 { if hedgeAccount.MarginLevel.Compare(s.MinMarginLevel) < 0 {
s.logger.Infof("hedge account margin level %s is less then the min margin level %s, calculating the borrowed positions",
hedgeAccount.MarginLevel.String(),
s.MinMarginLevel.String())
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok { if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
quoteDebt := quote.Debt() quoteDebt := quote.Debt()
if quoteDebt.Sign() > 0 { if quoteDebt.Sign() > 0 {
@ -517,24 +525,46 @@ func (s *Strategy) updateQuote(ctx context.Context) {
} }
} }
} else { } else {
// credit buffer s.logger.Infof("hedge account margin level %s is greater than the min margin level %s, calculating the net value",
creditBufferRatio := fixedpoint.NewFromFloat(1.2) hedgeAccount.MarginLevel.String(),
s.MinMarginLevel.String())
netValueInUsd, calcErr := s.accountValueCalculator.NetValue(ctx)
if calcErr != nil {
s.logger.WithError(calcErr).Errorf("unable to calculate the net value")
} else {
// calculate credit buffer
s.logger.Infof("hedge account net value in usd: %f", netValueInUsd.Float64())
maximumValueInUsd := netValueInUsd.Mul(s.MaxHedgeAccountLeverage)
s.logger.Infof("hedge account maximum leveraged value in usd: %f (%f x)", maximumValueInUsd.Float64(), s.MaxHedgeAccountLeverage.Float64())
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok { if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
netQuote := quote.Net() debt := quote.Debt()
if netQuote.Sign() > 0 { quota := maximumValueInUsd.Sub(debt)
hedgeQuota.QuoteAsset.Add(netQuote.Mul(creditBufferRatio))
} s.logger.Infof("hedge account quote balance: %s, debt: %s, quota: %s",
quote.String(),
debt.String(),
quota.String())
hedgeQuota.QuoteAsset.Add(quota)
} }
if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok { if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
netBase := base.Net() debt := base.Debt()
if netBase.Sign() > 0 { quota := maximumValueInUsd.Div(bestAsk.Price).Sub(debt)
hedgeQuota.BaseAsset.Add(netBase.Mul(creditBufferRatio))
}
}
// netValueInUsd, err := s.accountValueCalculator.NetValue(ctx)
}
s.logger.Infof("hedge account base balance: %s, debt: %s, quota: %s",
base.String(),
debt.String(),
quota.String())
hedgeQuota.BaseAsset.Add(quota)
}
}
}
} else { } else {
if b, ok := hedgeBalances[s.sourceMarket.BaseCurrency]; ok { if b, ok := hedgeBalances[s.sourceMarket.BaseCurrency]; ok {
// to make bid orders, we need enough base asset in the foreign exchange, // to make bid orders, we need enough base asset in the foreign exchange,
@ -544,13 +574,13 @@ func (s *Strategy) updateQuote(ctx context.Context) {
if b.Available.Compare(minAvailable) > 0 { if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.BaseAsset.Add(b.Available.Sub(minAvailable)) hedgeQuota.BaseAsset.Add(b.Available.Sub(minAvailable))
} else { } else {
s.logger.Warnf("%s maker bid disabled: insufficient base balance %s", s.Symbol, b.String()) s.logger.Warnf("%s maker bid disabled: insufficient hedge base balance %s", s.Symbol, b.String())
disableMakerBid = true disableMakerBid = true
} }
} else if b.Available.Compare(s.sourceMarket.MinQuantity) > 0 { } else if b.Available.Compare(s.sourceMarket.MinQuantity) > 0 {
hedgeQuota.BaseAsset.Add(b.Available) hedgeQuota.BaseAsset.Add(b.Available)
} else { } else {
s.logger.Warnf("%s maker bid disabled: insufficient base balance %s", s.Symbol, b.String()) s.logger.Warnf("%s maker bid disabled: insufficient hedge base balance %s", s.Symbol, b.String())
disableMakerBid = true disableMakerBid = true
} }
} }
@ -563,17 +593,16 @@ func (s *Strategy) updateQuote(ctx context.Context) {
if b.Available.Compare(minAvailable) > 0 { if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available.Sub(minAvailable)) hedgeQuota.QuoteAsset.Add(b.Available.Sub(minAvailable))
} else { } else {
s.logger.Warnf("%s maker ask disabled: insufficient quote balance %s", s.Symbol, b.String()) s.logger.Warnf("%s maker ask disabled: insufficient hedge quote balance %s", s.Symbol, b.String())
disableMakerAsk = true disableMakerAsk = true
} }
} else if b.Available.Compare(s.sourceMarket.MinNotional) > 0 { } else if b.Available.Compare(s.sourceMarket.MinNotional) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available) hedgeQuota.QuoteAsset.Add(b.Available)
} else { } else {
s.logger.Warnf("%s maker ask disabled: insufficient quote balance %s", s.Symbol, b.String()) s.logger.Warnf("%s maker ask disabled: insufficient hedge quote balance %s", s.Symbol, b.String())
disableMakerAsk = true disableMakerAsk = true
} }
} }
} }
// if max exposure position is configured, we should not: // if max exposure position is configured, we should not:
@ -1009,6 +1038,10 @@ func (s *Strategy) Defaults() error {
s.MinMarginLevel = fixedpoint.NewFromFloat(3.0) s.MinMarginLevel = fixedpoint.NewFromFloat(3.0)
} }
if s.MaxHedgeAccountLeverage.IsZero() {
s.MaxHedgeAccountLeverage = fixedpoint.NewFromFloat(1.2)
}
if s.BidMargin.IsZero() { if s.BidMargin.IsZero() {
if !s.Margin.IsZero() { if !s.Margin.IsZero() {
s.BidMargin = s.Margin s.BidMargin = s.Margin
@ -1267,9 +1300,8 @@ func (s *Strategy) CrossRun(
return errors.New("tradesSince time can not be zero") return errors.New("tradesSince time can not be zero")
} }
makerMarket, _ := makerSession.Market(s.Symbol) position := types.NewPositionFromMarket(s.makerMarket)
position := types.NewPositionFromMarket(makerMarket) profitStats := types.NewProfitStats(s.makerMarket)
profitStats := types.NewProfitStats(makerMarket)
fixer := common.NewProfitFixer() fixer := common.NewProfitFixer()
// fixer.ConverterManager = s.ConverterManager // fixer.ConverterManager = s.ConverterManager
@ -1284,7 +1316,7 @@ func (s *Strategy) CrossRun(
fixer.AddExchange(sourceSession.Name, ss) fixer.AddExchange(sourceSession.Name, ss)
} }
if err2 := fixer.Fix(ctx, makerMarket.Symbol, if err2 := fixer.Fix(ctx, s.makerMarket.Symbol,
s.ProfitFixerConfig.TradesSince.Time(), s.ProfitFixerConfig.TradesSince.Time(),
time.Now(), time.Now(),
profitStats, profitStats,