Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: improve trade/order converter #1690

Merged
merged 8 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions pkg/core/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package core

import (
"errors"

"github.com/c9s/bbgo/pkg/types"
)

type Converter interface {
OrderConverter
TradeConverter
Initialize() error
}

// OrderConverter converts the order to another order
type OrderConverter interface {
ConvertOrder(order types.Order) (types.Order, error)
}

// TradeConverter converts the trade to another trade
type TradeConverter interface {
ConvertTrade(trade types.Trade) (types.Trade, error)
}

type OrderConvertFunc func(order types.Order) (types.Order, error)
type TradeConvertFunc func(trade types.Trade) (types.Trade, error)

type DynamicConverter struct {
orderConverter OrderConvertFunc
tradeConverter TradeConvertFunc
}

func NewDynamicConverter(orderConverter OrderConvertFunc, tradeConverter TradeConvertFunc) *DynamicConverter {
return &DynamicConverter{orderConverter: orderConverter, tradeConverter: tradeConverter}
}

func (c *DynamicConverter) Initialize() error {
return nil
}

func (c *DynamicConverter) ConvertOrder(order types.Order) (types.Order, error) {
return c.orderConverter(order)
}

func (c *DynamicConverter) ConvertTrade(trade types.Trade) (types.Trade, error) {
return c.tradeConverter(trade)
}

// SymbolConverter converts the symbol to another symbol
type SymbolConverter struct {
FromSymbol string `json:"from"`
ToSymbol string `json:"to"`
}

func NewSymbolConverter(fromSymbol, toSymbol string) *SymbolConverter {
return &SymbolConverter{FromSymbol: fromSymbol, ToSymbol: toSymbol}
}

func (c *SymbolConverter) Initialize() error {
if c.ToSymbol == "" {
return errors.New("toSymbol can not be empty")
}

if c.FromSymbol == "" {
return errors.New("fromSymbol can not be empty")
}

return nil
}

func (c *SymbolConverter) ConvertOrder(order types.Order) (types.Order, error) {
if order.Symbol == c.FromSymbol {
order.Symbol = c.ToSymbol
}

return order, nil
}

func (c *SymbolConverter) ConvertTrade(trade types.Trade) (types.Trade, error) {
if trade.Symbol == c.FromSymbol {
trade.Symbol = c.ToSymbol
}

return trade, nil
}
31 changes: 31 additions & 0 deletions pkg/core/converter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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)
}

}
117 changes: 87 additions & 30 deletions pkg/core/tradecollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,87 @@ import (
"github.com/c9s/bbgo/pkg/types"
)

type TradeConverter interface {
Convert(trade types.Trade) (types.Trade, error)
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 nil, err
}

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
}

c.AddConverter(converter)
}

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
Expand All @@ -29,14 +108,14 @@ type TradeCollector struct {

mu sync.Mutex

tradeConverters []TradeConverter

recoverCallbacks []func(trade types.Trade)

tradeCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)

positionUpdateCallbacks []func(position *types.Position)
profitCallbacks []func(trade types.Trade, profit *types.Profit)

ConverterManager
}

func NewTradeCollector(symbol string, position *types.Position, orderStore *OrderStore) *TradeCollector {
Expand All @@ -55,28 +134,6 @@ func NewTradeCollector(symbol string, position *types.Position, orderStore *Orde
}
}

func (c *TradeCollector) AddTradeConverter(converter TradeConverter) {
c.tradeConverters = append(c.tradeConverters, converter)
}

func (c *TradeCollector) convertTrade(trade types.Trade) types.Trade {
if len(c.tradeConverters) == 0 {
return trade
}

for _, converter := range c.tradeConverters {
convTrade, err := converter.Convert(trade)
if err != nil {
logrus.WithError(err).Errorf("trade %+v converter error, trade: %s", converter, trade.String())
continue
}

trade = convTrade
}

return trade
}

// OrderStore returns the order store used by the trade collector
func (c *TradeCollector) OrderStore() *OrderStore {
return c.orderStore
Expand Down Expand Up @@ -108,7 +165,7 @@ func (c *TradeCollector) BindStreamForBackground(stream types.Stream) {

func (c *TradeCollector) BindStream(stream types.Stream) {
stream.OnTradeUpdate(func(trade types.Trade) {
c.processTrade(trade)
c.ProcessTrade(trade)
})
}

Expand Down Expand Up @@ -144,7 +201,7 @@ func (c *TradeCollector) Recover(
}

func (c *TradeCollector) RecoverTrade(td types.Trade) bool {
td = c.convertTrade(td)
td = c.ConvertTrade(td)

logrus.Debugf("checking trade: %s", td.String())
if c.processTrade(td) {
Expand Down Expand Up @@ -260,7 +317,7 @@ func (c *TradeCollector) processTrade(trade types.Trade) bool {
// return true when the given trade is added
// return false when the given trade is not added
func (c *TradeCollector) ProcessTrade(trade types.Trade) bool {
return c.processTrade(c.convertTrade(trade))
return c.processTrade(c.ConvertTrade(trade))
}

// Run is a goroutine executed in the background
Expand All @@ -279,7 +336,7 @@ func (c *TradeCollector) Run(ctx context.Context) {
c.Process()

case trade := <-c.tradeC:
c.processTrade(c.convertTrade(trade))
c.processTrade(c.ConvertTrade(trade))

}
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/strategy/common/profit_fixer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"golang.org/x/sync/errgroup"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/core"
"github.com/c9s/bbgo/pkg/exchange/batch"
"github.com/c9s/bbgo/pkg/types"
)
Expand All @@ -22,6 +23,8 @@ type ProfitFixerConfig struct {
// ProfitFixer implements a trade-history-based profit fixer
type ProfitFixer struct {
sessions map[string]types.ExchangeTradeHistoryService

core.ConverterManager
}

func NewProfitFixer() *ProfitFixer {
Expand Down Expand Up @@ -106,6 +109,8 @@ func (f *ProfitFixer) Fix(

func (f *ProfitFixer) FixFromTrades(allTrades []types.Trade, stats *types.ProfitStats, position *types.Position) error {
for _, trade := range allTrades {
trade = f.ConverterManager.ConvertTrade(trade)

profit, netProfit, madeProfit := position.AddTrade(trade)
if madeProfit {
p := position.NewProfit(trade, profit, netProfit)
Expand Down
21 changes: 20 additions & 1 deletion pkg/strategy/xdepthmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"golang.org/x/time/rate"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/core"
"github.com/c9s/bbgo/pkg/exchange/retry"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/strategy/common"
Expand Down Expand Up @@ -48,7 +49,10 @@ type CrossExchangeMarketMakingStrategy struct {
Position *types.Position `json:"position,omitempty" persistence:"position"`
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`
CoveredPosition fixedpoint.Value `json:"coveredPosition,omitempty" persistence:"covered_position"`
mu sync.Mutex

core.ConverterManager

mu sync.Mutex

MakerOrderExecutor, HedgeOrderExecutor *bbgo.GeneralOrderExecutor
}
Expand Down Expand Up @@ -78,6 +82,10 @@ func (s *CrossExchangeMarketMakingStrategy) Initialize(
return fmt.Errorf("maker session market %s is not defined", symbol)
}

if err := s.ConverterManager.Initialize(); err != nil {
return err
}

if s.ProfitStats == nil {
s.ProfitStats = types.NewProfitStats(s.makerMarket)
}
Expand Down Expand Up @@ -106,6 +114,10 @@ func (s *CrossExchangeMarketMakingStrategy) Initialize(
s.makerMarket.Symbol,
strategyID, instanceID,
s.Position)

// update converter manager
s.MakerOrderExecutor.TradeCollector().ConverterManager = s.ConverterManager

s.MakerOrderExecutor.BindEnvironment(environ)
s.MakerOrderExecutor.BindProfitStats(s.ProfitStats)
s.MakerOrderExecutor.Bind()
Expand All @@ -121,6 +133,9 @@ func (s *CrossExchangeMarketMakingStrategy) Initialize(
s.HedgeOrderExecutor.BindEnvironment(environ)
s.HedgeOrderExecutor.BindProfitStats(s.ProfitStats)
s.HedgeOrderExecutor.Bind()

s.HedgeOrderExecutor.TradeCollector().ConverterManager = s.ConverterManager

s.HedgeOrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
// bbgo.Sync(ctx, s)
})
Expand Down Expand Up @@ -149,6 +164,7 @@ type Strategy struct {

Environment *bbgo.Environment

// Symbol is the maker exchange symbol
Symbol string `json:"symbol"`

// HedgeSymbol is the symbol for the hedge exchange
Expand Down Expand Up @@ -251,6 +267,7 @@ func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {
})

hedgeSession.Subscribe(types.KLineChannel, s.HedgeSymbol, types.SubscribeOptions{Interval: "1m"})

makerSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
}

Expand Down Expand Up @@ -344,6 +361,8 @@ func (s *Strategy) CrossRun(
s.CrossExchangeMarketMakingStrategy.ProfitStats = types.NewProfitStats(makerMarket)

fixer := common.NewProfitFixer()
fixer.ConverterManager = s.ConverterManager

if ss, ok := makerSession.Exchange.(types.ExchangeTradeHistoryService); ok {
log.Infof("adding makerSession %s to profitFixer", makerSession.Name)
fixer.AddExchange(makerSession.Name, ss)
Expand Down
Loading
Loading