From 26149103f0d5a8f820cf05bff2d93d477d49bd95 Mon Sep 17 00:00:00 2001 From: edwin Date: Fri, 23 Aug 2024 16:10:44 +0800 Subject: [PATCH 1/5] pkg/core: support more function to symbol converter --- pkg/core/converter.go | 63 ++++++++++++++++++++++++++++++++++++-- pkg/core/converter_test.go | 29 ++++++++++++++++++ pkg/core/tradecollector.go | 54 ++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) diff --git a/pkg/core/converter.go b/pkg/core/converter.go index a154e2758..baa5ecf7f 100644 --- a/pkg/core/converter.go +++ b/pkg/core/converter.go @@ -9,6 +9,9 @@ import ( type Converter interface { OrderConverter TradeConverter + KLineConverter + MarketConverter + BalanceConverter Initialize() error } @@ -22,12 +25,33 @@ type TradeConverter interface { 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 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 { - orderConverter OrderConvertFunc - tradeConverter TradeConvertFunc + orderConverter OrderConvertFunc + tradeConverter TradeConvertFunc + klineConverter KLineConvertFunc + marketConverter MarketConvertFunc + balanceConverter BalanceConvertFunc } 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) } +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 type SymbolConverter struct { FromSymbol string `json:"from"` @@ -73,6 +109,10 @@ func (c *SymbolConverter) ConvertOrder(order types.Order) (types.Order, error) { order.Symbol = c.ToSymbol } + if order.SubmitOrder.Market.Symbol == c.FromSymbol { + order.SubmitOrder.Market.Symbol = c.ToSymbol + } + return order, nil } @@ -83,3 +123,22 @@ func (c *SymbolConverter) ConvertTrade(trade types.Trade) (types.Trade, error) { 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 +} diff --git a/pkg/core/converter_test.go b/pkg/core/converter_test.go index 4718eca2b..7a922fd39 100644 --- a/pkg/core/converter_test.go +++ b/pkg/core/converter_test.go @@ -21,11 +21,40 @@ func TestSymbolConverter(t *testing.T) { 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) } } diff --git a/pkg/core/tradecollector.go b/pkg/core/tradecollector.go index ed581704f..d70ce922a 100644 --- a/pkg/core/tradecollector.go +++ b/pkg/core/tradecollector.go @@ -100,6 +100,60 @@ func (c *ConverterManager) ConvertTrade(trade types.Trade) types.Trade { 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 +} + //go:generate callbackgen -type TradeCollector type TradeCollector struct { Symbol string From b683550f442e62a5436eac16eaab7a46796c70fa Mon Sep 17 00:00:00 2001 From: edwin Date: Fri, 23 Aug 2024 16:13:39 +0800 Subject: [PATCH 2/5] rename converter.go to symbol_converter --- pkg/core/{converter.go => symbol_converter.go} | 0 pkg/core/{converter_test.go => symbol_converter_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename pkg/core/{converter.go => symbol_converter.go} (100%) rename pkg/core/{converter_test.go => symbol_converter_test.go} (100%) diff --git a/pkg/core/converter.go b/pkg/core/symbol_converter.go similarity index 100% rename from pkg/core/converter.go rename to pkg/core/symbol_converter.go diff --git a/pkg/core/converter_test.go b/pkg/core/symbol_converter_test.go similarity index 100% rename from pkg/core/converter_test.go rename to pkg/core/symbol_converter_test.go From 4fefbaf0e721730cd8a595d5dbea132a364305e4 Mon Sep 17 00:00:00 2001 From: edwin Date: Fri, 23 Aug 2024 17:05:52 +0800 Subject: [PATCH 3/5] pkg/core: add currency converter --- pkg/core/currency_converter.go | 68 +++++++++++++++ pkg/core/currency_converter_test.go | 124 ++++++++++++++++++++++++++++ pkg/core/tradecollector.go | 7 +- 3 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 pkg/core/currency_converter.go create mode 100644 pkg/core/currency_converter_test.go diff --git a/pkg/core/currency_converter.go b/pkg/core/currency_converter.go new file mode 100644 index 000000000..41390d862 --- /dev/null +++ b/pkg/core/currency_converter.go @@ -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 +} diff --git a/pkg/core/currency_converter_test.go b/pkg/core/currency_converter_test.go new file mode 100644 index 000000000..0e24b0e31 --- /dev/null +++ b/pkg/core/currency_converter_test.go @@ -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) +} diff --git a/pkg/core/tradecollector.go b/pkg/core/tradecollector.go index d70ce922a..17a5bcb27 100644 --- a/pkg/core/tradecollector.go +++ b/pkg/core/tradecollector.go @@ -13,7 +13,8 @@ import ( ) type ConverterSetting struct { - SymbolConverter *SymbolConverter `json:"symbolConverter" yaml:"symbolConverter"` + SymbolConverter *SymbolConverter `json:"symbolConverter" yaml:"symbolConverter"` + CurrencyConverter *CurrencyConverter `json:"currencyConverter" yaml:"currencyConverter"` } func (s *ConverterSetting) getConverter() Converter { @@ -21,6 +22,10 @@ func (s *ConverterSetting) getConverter() Converter { return s.SymbolConverter } + if s.CurrencyConverter != nil { + return s.CurrencyConverter + } + return nil } From bc24e8dcaa899aeaa8541d5d6d5996ff64174615 Mon Sep 17 00:00:00 2001 From: edwin Date: Fri, 23 Aug 2024 17:19:50 +0800 Subject: [PATCH 4/5] pkg/core: add more tests for convert manager --- pkg/core/tradecollector_test.go | 194 ++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/pkg/core/tradecollector_test.go b/pkg/core/tradecollector_test.go index 8f9ccf841..fa7c1fd37 100644 --- a/pkg/core/tradecollector_test.go +++ b/pkg/core/tradecollector_test.go @@ -9,6 +9,200 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +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) { + manager := ConverterManager{ + ConverterSettings: []ConverterSetting{ + {SymbolConverter: &SymbolConverter{ + FromSymbol: "MAXEXCHANGEUSDT", + ToSymbol: "MAXUSDT", + }}, + {CurrencyConverter: &CurrencyConverter{ + FromCurrency: "MAXEXCHANGE", + ToCurrency: "MAX", + }}, + }, + } + + 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) +} + func TestTradeCollector_NilConvertManager(t *testing.T) { symbol := "BTCUSDT" position := types.NewPosition(symbol, "BTC", "USDT") From 5e3b0238d8aaf41b69ebad29a9b0aa8d31637899 Mon Sep 17 00:00:00 2001 From: edwin Date: Fri, 23 Aug 2024 17:21:36 +0800 Subject: [PATCH 5/5] pkg/core: move convert manager to independent file --- pkg/core/convert_manager.go | 153 ++++++++++++++++++++++ pkg/core/convert_manager_test.go | 212 +++++++++++++++++++++++++++++++ pkg/core/tradecollector.go | 147 --------------------- pkg/core/tradecollector_test.go | 194 ---------------------------- 4 files changed, 365 insertions(+), 341 deletions(-) create mode 100644 pkg/core/convert_manager.go create mode 100644 pkg/core/convert_manager_test.go diff --git a/pkg/core/convert_manager.go b/pkg/core/convert_manager.go new file mode 100644 index 000000000..ec313da19 --- /dev/null +++ b/pkg/core/convert_manager.go @@ -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 +} diff --git a/pkg/core/convert_manager_test.go b/pkg/core/convert_manager_test.go new file mode 100644 index 000000000..5f1695851 --- /dev/null +++ b/pkg/core/convert_manager_test.go @@ -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) +} diff --git a/pkg/core/tradecollector.go b/pkg/core/tradecollector.go index 17a5bcb27..bd1ff628d 100644 --- a/pkg/core/tradecollector.go +++ b/pkg/core/tradecollector.go @@ -12,153 +12,6 @@ import ( "github.com/c9s/bbgo/pkg/types" ) -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 -} - //go:generate callbackgen -type TradeCollector type TradeCollector struct { Symbol string diff --git a/pkg/core/tradecollector_test.go b/pkg/core/tradecollector_test.go index fa7c1fd37..8f9ccf841 100644 --- a/pkg/core/tradecollector_test.go +++ b/pkg/core/tradecollector_test.go @@ -9,200 +9,6 @@ import ( "github.com/c9s/bbgo/pkg/types" ) -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) { - manager := ConverterManager{ - ConverterSettings: []ConverterSetting{ - {SymbolConverter: &SymbolConverter{ - FromSymbol: "MAXEXCHANGEUSDT", - ToSymbol: "MAXUSDT", - }}, - {CurrencyConverter: &CurrencyConverter{ - FromCurrency: "MAXEXCHANGE", - ToCurrency: "MAX", - }}, - }, - } - - 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) -} - func TestTradeCollector_NilConvertManager(t *testing.T) { symbol := "BTCUSDT" position := types.NewPosition(symbol, "BTC", "USDT")