mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-27 09:15:15 +00:00
mirror: add copy trading strategy
This commit is contained in:
parent
c7349f5c99
commit
bfa4c4f546
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user