diff --git a/config/copytrader.yaml b/config/copytrader.yaml index d4119f6b0..d9fe53121 100644 --- a/config/copytrader.yaml +++ b/config/copytrader.yaml @@ -1,27 +1,39 @@ sessions: - binanceMaster: + binance-master: exchange: binance envVarPrefix: BINANCE - binanceSlave: + futures: true + binance-slave: exchange: binance envVarPrefix: BINANCE_SLAVE + futures: true -#sync: -# userDataStream: -# trades: true -# filledOrders: true -# sessions: -# - binanceMaster -# - binanceSlave -# symbols: -# - BTCUSDT +sync: + userDataStream: + trades: true + filledOrders: true + sessions: + - binance-master + - binance-slave + symbols: + - BTCUSDT crossExchangeStrategies: -- copytrader: - symbol: BTCUSDT - sourceExchange: binanceMaster - followerExchange: - - binanceMaster - - binanceSlave + +- on: binance-master + copytrader: + symbol: BTCUSDT + exchange: binance + +#- on: binance-slave +# copytrader: +# symbol: BTCUSDT +# exchange: binance + + +# sourceExchange: binanceMaster +# followerExchange: +# - binanceMaster +# - binanceSlave diff --git a/pkg/strategy/copytrader/strategy.go b/pkg/strategy/copytrader/strategy.go index 8c750322a..b9fbe9e21 100644 --- a/pkg/strategy/copytrader/strategy.go +++ b/pkg/strategy/copytrader/strategy.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" "github.com/sirupsen/logrus" ) @@ -26,14 +27,12 @@ type Strategy struct { Symbol string `json:"symbol"` - // SourceExchange session name - SourceExchange string `json:"sourceExchange"` + Exchange string `json:"exchange,omitempty"` - // FollowerExchange session name - FollowerExchange map[string]string `json:"makerExchange"` + SourceSession *bbgo.ExchangeSession + FollowerSession map[string]*bbgo.ExchangeSession - followerSession map[string]*bbgo.ExchangeSession - sourceSession *bbgo.ExchangeSession + FollowerMakerOrders map[string]*bbgo.LocalActiveOrderBook Market types.Market } @@ -46,48 +45,116 @@ func (s *Strategy) InstanceID() string { return fmt.Sprintf("%s:%s", ID, s.Symbol) } -//func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) { -// sourceSession, ok := sessions[s.SourceExchange] -// if !ok { -// panic(fmt.Errorf("source session %s is not defined", s.SourceExchange)) -// } -// -// sourceSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) -// sourceSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) -// -// for _, v := range s.FollowerExchange { -// makerSession, ok := sessions[v] -// if !ok { -// panic(fmt.Errorf("maker session %s is not defined", v)) -// } -// makerSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) -// } -// -//} +func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) { + //log.Info(sessions) + + //sourceSession, ok := sessions["binance-master"] + //if !ok { + // panic(fmt.Errorf("source session %s is not defined", sourceSession.Name)) + //} else { + // sourceSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // //sourceSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) + //} + + //s.SourceSession = sourceSession + // + ////s.FollowerSession = make(map[string]*bbgo.ExchangeSession, len(sessions)-1) + //s.FollowerMakerOrders = make(map[string]*bbgo.LocalActiveOrderBook, len(sessions)-1) + //for k, v := range sessions { + // // do not follower yourself + // if k != "binance-master" { + // followerSession, ok := sessions[k] + // if !ok { + // panic(fmt.Errorf("follower session %s is not defined", followerSession.Name)) + // } else { + // //followerSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // //followerSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) + // //s.FollowerSession[k] = followerSession + // + // s.FollowerMakerOrders[k] = bbgo.NewLocalActiveOrderBook(s.Symbol) + // s.FollowerMakerOrders[k].BindStream(sessions[k].UserDataStream) + // + // log.Infof("subscribe follower session %s: %s, from env var: %s", k, v.Name, v.EnvVarPrefix) + // } + // //if !ok { + // // panic(fmt.Errorf("follower session %s is not defined", v)) + // //} + // //s.FollowerSession[k].Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // } + //} + //for k, v := range sessions { + // // do not follower yourself + // if k != "binance-master" { + // s.FollowerSession[k] = sessions[k] + // log.Infof("subscribe follower session %s: %s, from env var: %s", k, v.Name, v.EnvVarPrefix) + // //if !ok { + // // panic(fmt.Errorf("follower session %s is not defined", v)) + // //} + // //s.FollowerSession[k].Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // } + //} + + //s.SourceSession = sessions["binance-master"] + ////if !ok { + //// panic(fmt.Errorf("source session %s is not defined", s.SourceExchange)) + ////} + // + //s.SourceSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) + //s.SourceSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // + //for k, v := range sessions { + // // do not follower yourself + // if k != "binance-master" { + // s.FollowerSession[k] = sessions[k] + // log.Infof("subscribe follower session %s: %s, from env var: %s", k, v.Name, v.EnvVarPrefix) + // //if !ok { + // // panic(fmt.Errorf("follower session %s is not defined", v)) + // //} + // s.FollowerSession[k].Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // } + //} + +} func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error { //_ = s.Persistence.Sync(s) // configure sessions - sourceSession, ok := sessions[s.SourceExchange] - if !ok { - return fmt.Errorf("source exchange session %s is not defined", s.SourceExchange) - } + //sourceSession, ok := sessions[s.SourceExchange] + //if !ok { + // return fmt.Errorf("source exchange session %s is not defined", s.SourceExchange) + //} - s.sourceSession = sourceSession + //s.sourceSession = sourceSession - for k, v := range s.FollowerExchange { - followerSession, ok := sessions[k] - if !ok { - panic(fmt.Errorf("maker exchange session %s is not defined", v)) - } - s.followerSession[k] = followerSession + //for k, v := range s.FollowerExchange { + // followerSession, ok := sessions[k] + // if !ok { + // panic(fmt.Errorf("maker exchange session %s is not defined", v)) + // } + // s.followerSession[k] = followerSession + // + //} - } + log.Info(sessions) - s.Market, ok = s.sourceSession.Market(s.Symbol) - if !ok { - return fmt.Errorf("source session market %s is not defined", s.Symbol) - } + //sourceSession, _ := sessions["binance-master"] + //s.SourceSession = sourceSession + // + //for k, v := range sessions { + // // do not follower yourself + // if k != "binance-master" { + // s.FollowerSession[k], _ = sessions[k] + // log.Infof("subscribe follower session %s: %s, from env var: %s", k, v.Name, v.EnvVarPrefix) + // //if !ok { + // // panic(fmt.Errorf("follower session %s is not defined", v)) + // //} + // //s.FollowerSession[k].Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) + // } + //} + + //if !ok { + // return fmt.Errorf("source session market %s is not defined", s.Symbol) + //} //s.followerMarket, ok = s.sourceSession.Market(s.Symbol) //if !ok { @@ -116,26 +183,86 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order // s.Position = types.NewPositionFromMarket(s.Market) //} - s.sourceSession.UserDataStream.OnOrderUpdate(func(order types.Order) { - log.Infof("source order: %v", order) + //log.Infof("===") + //log.Info(s.SourceSession) + //log.Infof("===") + //log.Info(s.FollowerSession) + sourceSession, ok := sessions["binance-master"] + if !ok { + panic(fmt.Errorf("source session %s is not defined", sourceSession.Name)) + } + s.Market, _ = sourceSession.Market(s.Symbol) - copyOrder := types.SubmitOrder{ - Symbol: order.Symbol, - Side: order.Side, - Type: order.Type, - Quantity: order.Quantity, - Market: order.Market, + s.FollowerMakerOrders = make(map[string]*bbgo.LocalActiveOrderBook) + for k, _ := range sessions { + s.FollowerMakerOrders[k] = bbgo.NewLocalActiveOrderBook(s.Symbol) + s.FollowerMakerOrders[k].BindStream(sessions[k].UserDataStream) + } + + //go func() { + //cnt := 0 + //log.Info(sourceSession) + + sourceSession.UserDataStream.OnOrderUpdate(func(order types.Order) { + utility := fixedpoint.Zero + account := sourceSession.GetAccount() + if quote, ok := account.Balance(s.Market.QuoteCurrency); ok { + utility = order.Quantity.Mul(order.Price).Div(quote.Available) } - log.Infof("copy order: %s", copyOrder) - for k, _ := range s.FollowerExchange { - createdOrders, err := s.followerSession[k].Exchange.SubmitOrders(ctx, copyOrder) - if err != nil { - log.WithError(err).Errorf("can not place order") - } else { - log.Infof("submitted order: %s for ...", createdOrders) + log.Infof("source order: %v", order) + log.Info(utility) + if order.Status == types.OrderStatusNew { + + for k, v := range sessions { + //log.Error(cnt) + if k != sourceSession.Name { + // cancel all follower's open orders from source + //if err := s.FollowerMakerOrders[k].GracefulCancel(context.Background(), + // sessions[k].Exchange); err != nil { + // log.WithError(err).Errorf("can not cancel %s orders", s.Symbol) + //} + //log.Infof("submitted order: %s for follower index key %s, %s", copyOrder, k, sessions[k].Name) + //cnt++ + followerAccount := v.GetAccount() + orderAmount := fixedpoint.Zero + if followerQuote, ok := followerAccount.Balance(s.Market.QuoteCurrency); ok { + orderAmount = followerQuote.Available.Mul(utility) + } + copyOrder := types.SubmitOrder{ + Symbol: order.Symbol, + Side: order.Side, + Price: order.Price, + Type: order.Type, + Quantity: orderAmount.Div(order.Price), + } + log.Infof("copy order: %s", copyOrder) + + //createdOrders, err := orderExecutionRouter.SubmitOrdersTo(ctx, sessions[k].Name, copyOrder) + //// createdOrders, err := v.Exchange.SubmitOrders(ctx, copyOrder) + //if err != nil { + // log.WithError(err).Errorf("can not place order") + //} else { + // s.FollowerMakerOrders[k].Add(createdOrders...) + // log.Infof("submit order success: %s for follower index key %s", createdOrders, k) + //} + } + } + } else if order.Status == types.OrderStatusCanceled { + for k, _ := range sessions { + //log.Error(cnt) + if k != sourceSession.Name { + // cancel all follower's open orders from source + if err := s.FollowerMakerOrders[k].GracefulCancel(context.Background(), + sessions[k].Exchange); err != nil { + log.WithError(err).Errorf("can not cancel %s orders", s.Symbol) + } else { + log.Infof("cancel order success: %d for follower index key %s", order.OrderID, k) + } + } } } }) + //}() //s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore)