mirror: add copy trading strategy

This commit is contained in:
austin362667 2022-06-01 21:48:56 +08:00
parent c7349f5c99
commit bfa4c4f546
2 changed files with 211 additions and 72 deletions

View File

@ -1,27 +1,39 @@
sessions: sessions:
binanceMaster: binance-master:
exchange: binance exchange: binance
envVarPrefix: BINANCE envVarPrefix: BINANCE
binanceSlave: futures: true
binance-slave:
exchange: binance exchange: binance
envVarPrefix: BINANCE_SLAVE envVarPrefix: BINANCE_SLAVE
futures: true
#sync: sync:
# userDataStream: userDataStream:
# trades: true trades: true
# filledOrders: true filledOrders: true
# sessions: sessions:
# - binanceMaster - binance-master
# - binanceSlave - binance-slave
# symbols: symbols:
# - BTCUSDT - BTCUSDT
crossExchangeStrategies: crossExchangeStrategies:
- copytrader:
symbol: BTCUSDT - on: binance-master
sourceExchange: binanceMaster copytrader:
followerExchange: symbol: BTCUSDT
- binanceMaster exchange: binance
- binanceSlave
#- on: binance-slave
# copytrader:
# symbol: BTCUSDT
# exchange: binance
# sourceExchange: binanceMaster
# followerExchange:
# - binanceMaster
# - binanceSlave

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -26,14 +27,12 @@ type Strategy struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
// SourceExchange session name Exchange string `json:"exchange,omitempty"`
SourceExchange string `json:"sourceExchange"`
// FollowerExchange session name SourceSession *bbgo.ExchangeSession
FollowerExchange map[string]string `json:"makerExchange"` FollowerSession map[string]*bbgo.ExchangeSession
followerSession map[string]*bbgo.ExchangeSession FollowerMakerOrders map[string]*bbgo.LocalActiveOrderBook
sourceSession *bbgo.ExchangeSession
Market types.Market Market types.Market
} }
@ -46,48 +45,116 @@ func (s *Strategy) InstanceID() string {
return fmt.Sprintf("%s:%s", ID, s.Symbol) return fmt.Sprintf("%s:%s", ID, s.Symbol)
} }
//func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) { func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {
// sourceSession, ok := sessions[s.SourceExchange] //log.Info(sessions)
// if !ok {
// panic(fmt.Errorf("source session %s is not defined", s.SourceExchange)) //sourceSession, ok := sessions["binance-master"]
// } //if !ok {
// // panic(fmt.Errorf("source session %s is not defined", sourceSession.Name))
// sourceSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{}) //} else {
// sourceSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) // sourceSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
// // //sourceSession.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{})
// for _, v := range s.FollowerExchange { //}
// makerSession, ok := sessions[v]
// if !ok { //s.SourceSession = sourceSession
// panic(fmt.Errorf("maker session %s is not defined", v)) //
// } ////s.FollowerSession = make(map[string]*bbgo.ExchangeSession, len(sessions)-1)
// makerSession.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) //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 { func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error {
//_ = s.Persistence.Sync(s) //_ = s.Persistence.Sync(s)
// configure sessions // configure sessions
sourceSession, ok := sessions[s.SourceExchange] //sourceSession, ok := sessions[s.SourceExchange]
if !ok { //if !ok {
return fmt.Errorf("source exchange session %s is not defined", s.SourceExchange) // return fmt.Errorf("source exchange session %s is not defined", s.SourceExchange)
} //}
s.sourceSession = sourceSession //s.sourceSession = sourceSession
for k, v := range s.FollowerExchange { //for k, v := range s.FollowerExchange {
followerSession, ok := sessions[k] // followerSession, ok := sessions[k]
if !ok { // if !ok {
panic(fmt.Errorf("maker exchange session %s is not defined", v)) // panic(fmt.Errorf("maker exchange session %s is not defined", v))
} // }
s.followerSession[k] = followerSession // s.followerSession[k] = followerSession
//
//}
} log.Info(sessions)
s.Market, ok = s.sourceSession.Market(s.Symbol) //sourceSession, _ := sessions["binance-master"]
if !ok { //s.SourceSession = sourceSession
return fmt.Errorf("source session market %s is not defined", s.Symbol) //
} //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) //s.followerMarket, ok = s.sourceSession.Market(s.Symbol)
//if !ok { //if !ok {
@ -116,26 +183,86 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
// s.Position = types.NewPositionFromMarket(s.Market) // s.Position = types.NewPositionFromMarket(s.Market)
//} //}
s.sourceSession.UserDataStream.OnOrderUpdate(func(order types.Order) { //log.Infof("===")
log.Infof("source order: %v", order) //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{ s.FollowerMakerOrders = make(map[string]*bbgo.LocalActiveOrderBook)
Symbol: order.Symbol, for k, _ := range sessions {
Side: order.Side, s.FollowerMakerOrders[k] = bbgo.NewLocalActiveOrderBook(s.Symbol)
Type: order.Type, s.FollowerMakerOrders[k].BindStream(sessions[k].UserDataStream)
Quantity: order.Quantity, }
Market: order.Market,
//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) log.Infof("source order: %v", order)
for k, _ := range s.FollowerExchange { log.Info(utility)
createdOrders, err := s.followerSession[k].Exchange.SubmitOrders(ctx, copyOrder) if order.Status == types.OrderStatusNew {
if err != nil {
log.WithError(err).Errorf("can not place order") for k, v := range sessions {
} else { //log.Error(cnt)
log.Infof("submitted order: %s for ...", createdOrders) 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) //s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore)