mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
Merge pull request #974 from c9s/refactor/isolation
refactor persistence for isolation
This commit is contained in:
commit
04453c23ea
|
@ -283,7 +283,7 @@ func (environ *Environment) ConfigurePersistence(conf *PersistenceConfig) error
|
||||||
}
|
}
|
||||||
|
|
||||||
redisPersistence := service.NewRedisPersistenceService(conf.Redis)
|
redisPersistence := service.NewRedisPersistenceService(conf.Redis)
|
||||||
PersistenceServiceFacade.Redis = redisPersistence
|
persistenceServiceFacade.Redis = redisPersistence
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.Json != nil {
|
if conf.Json != nil {
|
||||||
|
@ -295,7 +295,7 @@ func (environ *Environment) ConfigurePersistence(conf *PersistenceConfig) error
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonPersistence := &service.JsonPersistenceService{Directory: conf.Json.Directory}
|
jsonPersistence := &service.JsonPersistenceService{Directory: conf.Json.Directory}
|
||||||
PersistenceServiceFacade.Json = jsonPersistence
|
persistenceServiceFacade.Json = jsonPersistence
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -630,7 +630,7 @@ func (environ *Environment) ConfigureNotificationSystem(userConfig *Config) erro
|
||||||
userConfig.Notifications = &NotificationConfig{}
|
userConfig.Notifications = &NotificationConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var persistence = PersistenceServiceFacade.Get()
|
var persistence = persistenceServiceFacade.Get()
|
||||||
|
|
||||||
err := environ.setupInteraction(persistence)
|
err := environ.setupInteraction(persistence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,22 +2,24 @@ package bbgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
const IsolationContextKey = "bbgo"
|
const IsolationContextKey = "bbgo"
|
||||||
|
|
||||||
var defaultIsolation *Isolation = nil
|
var defaultIsolation = NewIsolation()
|
||||||
|
|
||||||
func init() {
|
|
||||||
defaultIsolation = NewIsolation()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Isolation struct {
|
type Isolation struct {
|
||||||
gracefulShutdown GracefulShutdown
|
gracefulShutdown GracefulShutdown
|
||||||
|
persistenceServiceFacade *service.PersistenceServiceFacade
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIsolation() *Isolation {
|
func NewIsolation() *Isolation {
|
||||||
return &Isolation{}
|
return &Isolation{
|
||||||
|
gracefulShutdown: GracefulShutdown{},
|
||||||
|
persistenceServiceFacade: DefaultPersistenceServiceFacade,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIsolationFromContext(ctx context.Context) *Isolation {
|
func NewIsolationFromContext(ctx context.Context) *Isolation {
|
||||||
|
@ -28,3 +30,11 @@ func NewIsolationFromContext(ctx context.Context) *Isolation {
|
||||||
|
|
||||||
return defaultIsolation
|
return defaultIsolation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewContextWithIsolation(parent context.Context, isolation *Isolation) context.Context {
|
||||||
|
return context.WithValue(parent, IsolationContextKey, isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextWithDefaultIsolation(parent context.Context) context.Context {
|
||||||
|
return context.WithValue(parent, IsolationContextKey, defaultIsolation)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package bbgo
|
package bbgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -10,96 +10,21 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/service"
|
"github.com/c9s/bbgo/pkg/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PersistenceSelector struct {
|
|
||||||
// StoreID is the store you want to use.
|
|
||||||
StoreID string `json:"store" yaml:"store"`
|
|
||||||
|
|
||||||
// Type is the persistence type
|
|
||||||
Type string `json:"type" yaml:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultPersistenceServiceFacade = &service.PersistenceServiceFacade{
|
var DefaultPersistenceServiceFacade = &service.PersistenceServiceFacade{
|
||||||
Memory: service.NewMemoryService(),
|
Memory: service.NewMemoryService(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var PersistenceServiceFacade = DefaultPersistenceServiceFacade
|
var persistenceServiceFacade = DefaultPersistenceServiceFacade
|
||||||
|
|
||||||
// Persistence is used for strategy to inject the persistence.
|
|
||||||
type Persistence struct {
|
|
||||||
PersistenceSelector *PersistenceSelector `json:"persistence,omitempty" yaml:"persistence,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Persistence) backendService(t string) (service.PersistenceService, error) {
|
|
||||||
switch t {
|
|
||||||
case "json":
|
|
||||||
return PersistenceServiceFacade.Json, nil
|
|
||||||
|
|
||||||
case "redis":
|
|
||||||
if PersistenceServiceFacade.Redis == nil {
|
|
||||||
log.Warn("redis persistence is not available, fallback to memory backend")
|
|
||||||
return PersistenceServiceFacade.Memory, nil
|
|
||||||
}
|
|
||||||
return PersistenceServiceFacade.Redis, nil
|
|
||||||
|
|
||||||
case "memory":
|
|
||||||
return PersistenceServiceFacade.Memory, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported persistent type %s", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Persistence) Load(val interface{}, subIDs ...string) error {
|
|
||||||
ps, err := p.backendService(p.PersistenceSelector.Type)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("using persistence store %T for loading", ps)
|
|
||||||
|
|
||||||
if p.PersistenceSelector.StoreID == "" {
|
|
||||||
p.PersistenceSelector.StoreID = "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
store := ps.NewStore(p.PersistenceSelector.StoreID, subIDs...)
|
|
||||||
return store.Load(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Persistence) Save(val interface{}, subIDs ...string) error {
|
|
||||||
ps, err := p.backendService(p.PersistenceSelector.Type)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("using persistence store %T for storing", ps)
|
|
||||||
|
|
||||||
if p.PersistenceSelector.StoreID == "" {
|
|
||||||
p.PersistenceSelector.StoreID = "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
store := ps.NewStore(p.PersistenceSelector.StoreID, subIDs...)
|
|
||||||
return store.Save(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Persistence) Sync(obj interface{}) error {
|
|
||||||
id := dynamic.CallID(obj)
|
|
||||||
if len(id) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ps := PersistenceServiceFacade.Get()
|
|
||||||
return storePersistenceFields(obj, id, ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync syncs the object properties into the persistence layer
|
// Sync syncs the object properties into the persistence layer
|
||||||
func Sync(obj interface{}) {
|
func Sync(ctx context.Context, obj interface{}) {
|
||||||
id := dynamic.CallID(obj)
|
id := dynamic.CallID(obj)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
log.Warnf("InstanceID() is not provided, can not sync persistence")
|
log.Warnf("InstanceID() is not provided, can not sync persistence")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := PersistenceServiceFacade.Get()
|
ps := persistenceServiceFacade.Get()
|
||||||
err := storePersistenceFields(obj, id, ps)
|
err := storePersistenceFields(obj, id, ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Errorf("persistence sync failed")
|
log.WithError(err).Errorf("persistence sync failed")
|
||||||
|
|
|
@ -376,11 +376,11 @@ func (trader *Trader) LoadState() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if PersistenceServiceFacade == nil {
|
if persistenceServiceFacade == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := PersistenceServiceFacade.Get()
|
ps := persistenceServiceFacade.Get()
|
||||||
|
|
||||||
log.Infof("loading strategies states...")
|
log.Infof("loading strategies states...")
|
||||||
|
|
||||||
|
@ -413,11 +413,11 @@ func (trader *Trader) SaveState() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if PersistenceServiceFacade == nil {
|
if persistenceServiceFacade == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ps := PersistenceServiceFacade.Get()
|
ps := persistenceServiceFacade.Get()
|
||||||
|
|
||||||
log.Infof("saving strategies states...")
|
log.Infof("saving strategies states...")
|
||||||
return trader.IterateStrategies(func(strategy StrategyID) error {
|
return trader.IterateStrategies(func(strategy StrategyID) error {
|
||||||
|
@ -434,16 +434,7 @@ func (trader *Trader) Shutdown(ctx context.Context) {
|
||||||
trader.gracefulShutdown.Shutdown(ctx)
|
trader.gracefulShutdown.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultPersistenceSelector = &PersistenceSelector{
|
|
||||||
StoreID: "default",
|
|
||||||
Type: "memory",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (trader *Trader) injectCommonServices(s interface{}) error {
|
func (trader *Trader) injectCommonServices(s interface{}) error {
|
||||||
persistence := &Persistence{
|
|
||||||
PersistenceSelector: defaultPersistenceSelector,
|
|
||||||
}
|
|
||||||
|
|
||||||
// a special injection for persistence selector:
|
// a special injection for persistence selector:
|
||||||
// if user defined the selector, the facade pointer will be nil, hence we need to update the persistence facade pointer
|
// if user defined the selector, the facade pointer will be nil, hence we need to update the persistence facade pointer
|
||||||
sv := reflect.ValueOf(s).Elem()
|
sv := reflect.ValueOf(s).Elem()
|
||||||
|
@ -455,7 +446,7 @@ func (trader *Trader) injectCommonServices(s interface{}) error {
|
||||||
return fmt.Errorf("field Persistence is not a struct element, %s given", field)
|
return fmt.Errorf("field Persistence is not a struct element, %s given", field)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dynamic.InjectField(elem, "Facade", PersistenceServiceFacade, true); err != nil {
|
if err := dynamic.InjectField(elem, "Facade", persistenceServiceFacade, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +466,6 @@ func (trader *Trader) injectCommonServices(s interface{}) error {
|
||||||
trader.environment.DatabaseService,
|
trader.environment.DatabaseService,
|
||||||
trader.environment.AccountService,
|
trader.environment.AccountService,
|
||||||
trader.environment,
|
trader.environment,
|
||||||
persistence,
|
persistenceServiceFacade, // if the strategy use persistence facade separately
|
||||||
PersistenceServiceFacade, // if the strategy use persistence facade separately
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (t *LogHook) Fire(e *logrus.Entry) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var message = fmt.Sprintf("[%s] %s", e.Level.String(), e.Message)
|
var message = fmt.Sprintf("[%s] %s", e.Level.String(), e.Message)
|
||||||
if errData, ok := e.Data[logrus.ErrorKey]; ok {
|
if errData, ok := e.Data[logrus.ErrorKey]; ok && errData != nil {
|
||||||
if err, isErr := errData.(error); isErr {
|
if err, isErr := errData.(error); isErr {
|
||||||
message += " Error: " + err.Error()
|
message += " Error: " + err.Error()
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||||
|
|
|
@ -516,7 +516,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.ExitMethods.Bind(session, s.orderExecutor)
|
s.ExitMethods.Bind(session, s.orderExecutor)
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
s.OnSuspend(func() {
|
s.OnSuspend(func() {
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.OnEmergencyStop(func() {
|
s.OnEmergencyStop(func() {
|
||||||
|
|
|
@ -47,7 +47,6 @@ func (b BudgetPeriod) Duration() time.Duration {
|
||||||
|
|
||||||
// Strategy is the Dollar-Cost-Average strategy
|
// Strategy is the Dollar-Cost-Average strategy
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
@ -110,7 +109,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
||||||
s.orderExecutor.BindEnvironment(s.Environment)
|
s.orderExecutor.BindEnvironment(s.Environment)
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -795,7 +795,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.GeneralOrderExecutor.BindProfitStats(s.ProfitStats)
|
s.GeneralOrderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.GeneralOrderExecutor.BindTradeStats(s.TradeStats)
|
s.GeneralOrderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.GeneralOrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.GeneralOrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.GeneralOrderExecutor.Bind()
|
s.GeneralOrderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.GeneralOrderExecutor.BindProfitStats(s.ProfitStats)
|
s.GeneralOrderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.GeneralOrderExecutor.BindTradeStats(s.TradeStats)
|
s.GeneralOrderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.GeneralOrderExecutor.TradeCollector().OnPositionUpdate(func(p *types.Position) {
|
s.GeneralOrderExecutor.TradeCollector().OnPositionUpdate(func(p *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.GeneralOrderExecutor.Bind()
|
s.GeneralOrderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -708,7 +708,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
// s.orderExecutor.BindTradeStats(s.TradeStats)
|
// s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||||
|
|
|
@ -32,8 +32,6 @@ type IntervalWindowSetting struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
|
|
@ -19,7 +19,7 @@ const ID = "grid"
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
var NotionalModifier = fixedpoint.NewFromFloat(1.0001)
|
var notionalModifier = fixedpoint.NewFromFloat(1.0001)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Register the pointer of the strategy struct,
|
// Register the pointer of the strategy struct,
|
||||||
|
@ -40,13 +40,9 @@ type State struct {
|
||||||
// any created orders for tracking trades
|
// any created orders for tracking trades
|
||||||
// [source Order ID] -> arbitrage order
|
// [source Order ID] -> arbitrage order
|
||||||
ArbitrageOrders map[uint64]types.Order `json:"arbitrageOrders"`
|
ArbitrageOrders map[uint64]types.Order `json:"arbitrageOrders"`
|
||||||
|
|
||||||
ProfitStats types.ProfitStats `json:"profitStats,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
|
|
||||||
// OrderExecutor is an interface for submitting order.
|
// OrderExecutor is an interface for submitting order.
|
||||||
// This field will be injected automatically since it's a single exchange strategy.
|
// This field will be injected automatically since it's a single exchange strategy.
|
||||||
bbgo.OrderExecutor `json:"-" yaml:"-"`
|
bbgo.OrderExecutor `json:"-" yaml:"-"`
|
||||||
|
@ -88,7 +84,9 @@ type Strategy struct {
|
||||||
// Long means you want to hold more base asset than the quote asset.
|
// Long means you want to hold more base asset than the quote asset.
|
||||||
Long bool `json:"long,omitempty" yaml:"long,omitempty"`
|
Long bool `json:"long,omitempty" yaml:"long,omitempty"`
|
||||||
|
|
||||||
state *State
|
State *State `persistence:"state"`
|
||||||
|
|
||||||
|
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||||
|
|
||||||
// orderStore is used to store all the created orders, so that we can filter the trades.
|
// orderStore is used to store all the created orders, so that we can filter the trades.
|
||||||
orderStore *bbgo.OrderStore
|
orderStore *bbgo.OrderStore
|
||||||
|
@ -199,7 +197,7 @@ func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]type
|
||||||
baseBalance.Available.String())
|
baseBalance.Available.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, filled := s.state.FilledSellGrids[price]; filled {
|
if _, filled := s.State.FilledSellGrids[price]; filled {
|
||||||
log.Debugf("sell grid at price %s is already filled, skipping", price.String())
|
log.Debugf("sell grid at price %s is already filled, skipping", price.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -216,7 +214,7 @@ func (s *Strategy) generateGridSellOrders(session *bbgo.ExchangeSession) ([]type
|
||||||
})
|
})
|
||||||
baseBalance.Available = baseBalance.Available.Sub(quantity)
|
baseBalance.Available = baseBalance.Available.Sub(quantity)
|
||||||
|
|
||||||
s.state.FilledSellGrids[price] = struct{}{}
|
s.State.FilledSellGrids[price] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return orders, nil
|
return orders, nil
|
||||||
|
@ -300,7 +298,7 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types
|
||||||
quoteQuantity)
|
quoteQuantity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, filled := s.state.FilledBuyGrids[price]; filled {
|
if _, filled := s.State.FilledBuyGrids[price]; filled {
|
||||||
log.Debugf("buy grid at price %v is already filled, skipping", price)
|
log.Debugf("buy grid at price %v is already filled, skipping", price)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -317,7 +315,7 @@ func (s *Strategy) generateGridBuyOrders(session *bbgo.ExchangeSession) ([]types
|
||||||
})
|
})
|
||||||
balance.Available = balance.Available.Sub(quoteQuantity)
|
balance.Available = balance.Available.Sub(quoteQuantity)
|
||||||
|
|
||||||
s.state.FilledBuyGrids[price] = struct{}{}
|
s.State.FilledBuyGrids[price] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return orders, nil
|
return orders, nil
|
||||||
|
@ -416,7 +414,7 @@ func (s *Strategy) handleFilledOrder(filledOrder types.Order) {
|
||||||
|
|
||||||
if amount.Compare(s.Market.MinNotional) <= 0 {
|
if amount.Compare(s.Market.MinNotional) <= 0 {
|
||||||
quantity = bbgo.AdjustFloatQuantityByMinAmount(
|
quantity = bbgo.AdjustFloatQuantityByMinAmount(
|
||||||
quantity, price, s.Market.MinNotional.Mul(NotionalModifier))
|
quantity, price, s.Market.MinNotional.Mul(notionalModifier))
|
||||||
|
|
||||||
// update amount
|
// update amount
|
||||||
amount = quantity.Mul(price)
|
amount = quantity.Mul(price)
|
||||||
|
@ -438,7 +436,7 @@ func (s *Strategy) handleFilledOrder(filledOrder types.Order) {
|
||||||
|
|
||||||
// create one-way link from the newly created orders
|
// create one-way link from the newly created orders
|
||||||
for _, o := range createdOrders {
|
for _, o := range createdOrders {
|
||||||
s.state.ArbitrageOrders[o.OrderID] = filledOrder
|
s.State.ArbitrageOrders[o.OrderID] = filledOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
s.orderStore.Add(createdOrders...)
|
s.orderStore.Add(createdOrders...)
|
||||||
|
@ -454,53 +452,53 @@ func (s *Strategy) handleFilledOrder(filledOrder types.Order) {
|
||||||
if s.Long {
|
if s.Long {
|
||||||
switch filledOrder.Side {
|
switch filledOrder.Side {
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
if buyOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
|
if buyOrder, ok := s.State.ArbitrageOrders[filledOrder.OrderID]; ok {
|
||||||
// use base asset quantity here
|
// use base asset quantity here
|
||||||
baseProfit := buyOrder.Quantity.Sub(filledOrder.Quantity)
|
baseProfit := buyOrder.Quantity.Sub(filledOrder.Quantity)
|
||||||
s.state.AccumulativeArbitrageProfit = s.state.AccumulativeArbitrageProfit.
|
s.State.AccumulativeArbitrageProfit = s.State.AccumulativeArbitrageProfit.
|
||||||
Add(baseProfit)
|
Add(baseProfit)
|
||||||
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
||||||
s.Symbol,
|
s.Symbol,
|
||||||
baseProfit, s.Market.BaseCurrency,
|
baseProfit, s.Market.BaseCurrency,
|
||||||
s.state.AccumulativeArbitrageProfit, s.Market.BaseCurrency,
|
s.State.AccumulativeArbitrageProfit, s.Market.BaseCurrency,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.SideTypeBuy:
|
case types.SideTypeBuy:
|
||||||
if sellOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
|
if sellOrder, ok := s.State.ArbitrageOrders[filledOrder.OrderID]; ok {
|
||||||
// use base asset quantity here
|
// use base asset quantity here
|
||||||
baseProfit := filledOrder.Quantity.Sub(sellOrder.Quantity)
|
baseProfit := filledOrder.Quantity.Sub(sellOrder.Quantity)
|
||||||
s.state.AccumulativeArbitrageProfit = s.state.AccumulativeArbitrageProfit.Add(baseProfit)
|
s.State.AccumulativeArbitrageProfit = s.State.AccumulativeArbitrageProfit.Add(baseProfit)
|
||||||
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
||||||
s.Symbol,
|
s.Symbol,
|
||||||
baseProfit, s.Market.BaseCurrency,
|
baseProfit, s.Market.BaseCurrency,
|
||||||
s.state.AccumulativeArbitrageProfit, s.Market.BaseCurrency,
|
s.State.AccumulativeArbitrageProfit, s.Market.BaseCurrency,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !s.Long && s.Quantity.Sign() > 0 {
|
} else if !s.Long && s.Quantity.Sign() > 0 {
|
||||||
switch filledOrder.Side {
|
switch filledOrder.Side {
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
if buyOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
|
if buyOrder, ok := s.State.ArbitrageOrders[filledOrder.OrderID]; ok {
|
||||||
// use base asset quantity here
|
// use base asset quantity here
|
||||||
quoteProfit := filledOrder.Quantity.Mul(filledOrder.Price).Sub(
|
quoteProfit := filledOrder.Quantity.Mul(filledOrder.Price).Sub(
|
||||||
buyOrder.Quantity.Mul(buyOrder.Price))
|
buyOrder.Quantity.Mul(buyOrder.Price))
|
||||||
s.state.AccumulativeArbitrageProfit = s.state.AccumulativeArbitrageProfit.Add(quoteProfit)
|
s.State.AccumulativeArbitrageProfit = s.State.AccumulativeArbitrageProfit.Add(quoteProfit)
|
||||||
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s",
|
||||||
s.Symbol,
|
s.Symbol,
|
||||||
quoteProfit, s.Market.QuoteCurrency,
|
quoteProfit, s.Market.QuoteCurrency,
|
||||||
s.state.AccumulativeArbitrageProfit, s.Market.QuoteCurrency,
|
s.State.AccumulativeArbitrageProfit, s.Market.QuoteCurrency,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case types.SideTypeBuy:
|
case types.SideTypeBuy:
|
||||||
if sellOrder, ok := s.state.ArbitrageOrders[filledOrder.OrderID]; ok {
|
if sellOrder, ok := s.State.ArbitrageOrders[filledOrder.OrderID]; ok {
|
||||||
// use base asset quantity here
|
// use base asset quantity here
|
||||||
quoteProfit := sellOrder.Quantity.Mul(sellOrder.Price).
|
quoteProfit := sellOrder.Quantity.Mul(sellOrder.Price).
|
||||||
Sub(filledOrder.Quantity.Mul(filledOrder.Price))
|
Sub(filledOrder.Quantity.Mul(filledOrder.Price))
|
||||||
s.state.AccumulativeArbitrageProfit = s.state.AccumulativeArbitrageProfit.Add(quoteProfit)
|
s.State.AccumulativeArbitrageProfit = s.State.AccumulativeArbitrageProfit.Add(quoteProfit)
|
||||||
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s", s.Symbol,
|
bbgo.Notify("%s grid arbitrage profit %v %s, accumulative arbitrage profit %v %s", s.Symbol,
|
||||||
quoteProfit, s.Market.QuoteCurrency,
|
quoteProfit, s.Market.QuoteCurrency,
|
||||||
s.state.AccumulativeArbitrageProfit, s.Market.QuoteCurrency,
|
s.State.AccumulativeArbitrageProfit, s.Market.QuoteCurrency,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,58 +510,29 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) LoadState() error {
|
func (s *Strategy) LoadState() error {
|
||||||
instanceID := s.InstanceID()
|
if s.State == nil {
|
||||||
|
s.State = &State{
|
||||||
var state State
|
|
||||||
if s.Persistence != nil {
|
|
||||||
if err := s.Persistence.Load(&state, ID, instanceID); err != nil {
|
|
||||||
if err != service.ErrPersistenceNotExists {
|
|
||||||
return errors.Wrapf(err, "state load error")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.state = &State{
|
|
||||||
FilledBuyGrids: make(map[fixedpoint.Value]struct{}),
|
FilledBuyGrids: make(map[fixedpoint.Value]struct{}),
|
||||||
FilledSellGrids: make(map[fixedpoint.Value]struct{}),
|
FilledSellGrids: make(map[fixedpoint.Value]struct{}),
|
||||||
ArbitrageOrders: make(map[uint64]types.Order),
|
ArbitrageOrders: make(map[uint64]types.Order),
|
||||||
Position: types.NewPositionFromMarket(s.Market),
|
Position: types.NewPositionFromMarket(s.Market),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
s.state = &state
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// init profit stats
|
|
||||||
s.state.ProfitStats.Init(s.Market)
|
|
||||||
|
|
||||||
// field guards
|
// field guards
|
||||||
if s.state.ArbitrageOrders == nil {
|
if s.State.ArbitrageOrders == nil {
|
||||||
s.state.ArbitrageOrders = make(map[uint64]types.Order)
|
s.State.ArbitrageOrders = make(map[uint64]types.Order)
|
||||||
}
|
}
|
||||||
if s.state.FilledBuyGrids == nil {
|
if s.State.FilledBuyGrids == nil {
|
||||||
s.state.FilledBuyGrids = make(map[fixedpoint.Value]struct{})
|
s.State.FilledBuyGrids = make(map[fixedpoint.Value]struct{})
|
||||||
}
|
}
|
||||||
if s.state.FilledSellGrids == nil {
|
if s.State.FilledSellGrids == nil {
|
||||||
s.state.FilledSellGrids = make(map[fixedpoint.Value]struct{})
|
s.State.FilledSellGrids = make(map[fixedpoint.Value]struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) SaveState() error {
|
|
||||||
if s.Persistence != nil {
|
|
||||||
log.Infof("backing up grid state...")
|
|
||||||
|
|
||||||
instanceID := s.InstanceID()
|
|
||||||
submitOrders := s.activeOrders.Backup()
|
|
||||||
s.state.Orders = submitOrders
|
|
||||||
|
|
||||||
if err := s.Persistence.Save(s.state, ID, instanceID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstanceID returns the instance identifier from the current grid configuration parameters
|
// InstanceID returns the instance identifier from the current grid configuration parameters
|
||||||
func (s *Strategy) InstanceID() string {
|
func (s *Strategy) InstanceID() string {
|
||||||
return fmt.Sprintf("%s-%s-%d-%d-%d", ID, s.Symbol, s.GridNum, s.UpperPrice.Int(), s.LowerPrice.Int())
|
return fmt.Sprintf("%s-%s-%d-%d-%d", ID, s.Symbol, s.GridNum, s.UpperPrice.Int(), s.LowerPrice.Int())
|
||||||
|
@ -583,11 +552,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.groupID = util.FNV32(instanceID)
|
s.groupID = util.FNV32(instanceID)
|
||||||
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
||||||
|
|
||||||
|
if s.ProfitStats == nil {
|
||||||
|
s.ProfitStats = types.NewProfitStats(s.Market)
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.LoadState(); err != nil {
|
if err := s.LoadState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bbgo.Notify("grid %s position", s.Symbol, s.state.Position)
|
bbgo.Notify("grid %s position", s.Symbol, s.State.Position)
|
||||||
|
|
||||||
s.orderStore = bbgo.NewOrderStore(s.Symbol)
|
s.orderStore = bbgo.NewOrderStore(s.Symbol)
|
||||||
s.orderStore.BindStream(session.UserDataStream)
|
s.orderStore.BindStream(session.UserDataStream)
|
||||||
|
@ -597,11 +570,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.activeOrders.OnFilled(s.handleFilledOrder)
|
s.activeOrders.OnFilled(s.handleFilledOrder)
|
||||||
s.activeOrders.BindStream(session.UserDataStream)
|
s.activeOrders.BindStream(session.UserDataStream)
|
||||||
|
|
||||||
s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.state.Position, s.orderStore)
|
s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.State.Position, s.orderStore)
|
||||||
|
|
||||||
s.tradeCollector.OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) {
|
s.tradeCollector.OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) {
|
||||||
bbgo.Notify(trade)
|
bbgo.Notify(trade)
|
||||||
s.state.ProfitStats.AddTrade(trade)
|
s.ProfitStats.AddTrade(trade)
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -622,11 +595,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
if err := s.SaveState(); err != nil {
|
submitOrders := s.activeOrders.Backup()
|
||||||
log.WithError(err).Errorf("can not save state: %+v", s.state)
|
s.State.Orders = submitOrders
|
||||||
} else {
|
bbgo.Sync(ctx, s)
|
||||||
bbgo.Notify("%s: %s grid is saved", ID, s.Symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we can cancel the open orders
|
// now we can cancel the open orders
|
||||||
log.Infof("canceling active orders...")
|
log.Infof("canceling active orders...")
|
||||||
|
@ -637,10 +608,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
session.UserDataStream.OnStart(func() {
|
session.UserDataStream.OnStart(func() {
|
||||||
// if we have orders in the state data, we can restore them
|
// if we have orders in the state data, we can restore them
|
||||||
if len(s.state.Orders) > 0 {
|
if len(s.State.Orders) > 0 {
|
||||||
bbgo.Notify("restoring %s %d grid orders...", s.Symbol, len(s.state.Orders))
|
bbgo.Notify("restoring %s %d grid orders...", s.Symbol, len(s.State.Orders))
|
||||||
|
|
||||||
createdOrders, err := orderExecutor.SubmitOrders(ctx, s.state.Orders...)
|
createdOrders, err := orderExecutor.SubmitOrders(ctx, s.State.Orders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("active orders restore error")
|
log.WithError(err).Error("active orders restore error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
cumProfit.Update(s.CalcAssetValue(trade.Price).Float64())
|
cumProfit.Update(s.CalcAssetValue(trade.Price).Float64())
|
||||||
})
|
})
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||||
|
|
|
@ -165,7 +165,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ func (s *Strategy) Suspend(ctx context.Context) error {
|
||||||
log.WithError(err).Errorf("graceful cancel order error")
|
log.WithError(err).Errorf("graceful cancel order error")
|
||||||
}
|
}
|
||||||
|
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
instanceID := s.InstanceID()
|
instanceID := s.InstanceID()
|
||||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
// Update our counter and sync the changes to the persistence layer on time
|
// Update our counter and sync the changes to the persistence layer on time
|
||||||
// If you don't do this, BBGO will sync it automatically when BBGO shuts down.
|
// If you don't do this, BBGO will sync it automatically when BBGO shuts down.
|
||||||
s.State.Counter++
|
s.State.Counter++
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
|
|
||||||
// To check if we have the quote balance
|
// To check if we have the quote balance
|
||||||
// When symbol = "BTCUSDT", the quote currency is USDT
|
// When symbol = "BTCUSDT", the quote currency is USDT
|
||||||
|
|
|
@ -544,14 +544,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
// Sync position to redis on trade
|
// Sync position to redis on trade
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
// StrategyController
|
// StrategyController
|
||||||
s.Status = types.StrategyStatusRunning
|
s.Status = types.StrategyStatusRunning
|
||||||
s.OnSuspend(func() {
|
s.OnSuspend(func() {
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.OnEmergencyStop(func() {
|
s.OnEmergencyStop(func() {
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
|
|
|
@ -132,7 +132,6 @@ func (control *TrailingStopControl) GenerateStopOrder(quantity fixedpoint.Value)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence `json:"-"`
|
|
||||||
*bbgo.Environment `json:"-"`
|
*bbgo.Environment `json:"-"`
|
||||||
|
|
||||||
session *bbgo.ExchangeSession
|
session *bbgo.ExchangeSession
|
||||||
|
@ -176,8 +175,6 @@ type Strategy struct {
|
||||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||||
CurrentHighestPrice fixedpoint.Value `persistence:"current_highest_price"`
|
CurrentHighestPrice fixedpoint.Value `persistence:"current_highest_price"`
|
||||||
|
|
||||||
state *State
|
|
||||||
|
|
||||||
triggerEMA *indicator.EWMA
|
triggerEMA *indicator.EWMA
|
||||||
longTermEMA *indicator.EWMA
|
longTermEMA *indicator.EWMA
|
||||||
|
|
||||||
|
@ -349,7 +346,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.OnSuspend(func() {
|
s.OnSuspend(func() {
|
||||||
// Cancel all order
|
// Cancel all order
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.OnEmergencyStop(func() {
|
s.OnEmergencyStop(func() {
|
||||||
|
|
|
@ -112,7 +112,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||||
|
|
|
@ -30,8 +30,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
StandardIndicatorSet *bbgo.StandardIndicatorSet
|
StandardIndicatorSet *bbgo.StandardIndicatorSet
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
|
|
@ -279,7 +279,7 @@ func (s *Strategy) checkBalance(ctx context.Context, sessions map[string]*bbgo.E
|
||||||
|
|
||||||
s.State.DailyNumberOfTransfers += 1
|
s.State.DailyNumberOfTransfers += 1
|
||||||
s.State.DailyAmountOfTransfers = s.State.DailyAmountOfTransfers.Add(requiredAmount)
|
s.State.DailyAmountOfTransfers = s.State.DailyAmountOfTransfers.Add(requiredAmount)
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(ctx, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/service"
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -57,8 +56,6 @@ func (s *State) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
|
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
SourceExchange string `json:"sourceExchange"`
|
SourceExchange string `json:"sourceExchange"`
|
||||||
TradingExchange string `json:"tradingExchange"`
|
TradingExchange string `json:"tradingExchange"`
|
||||||
|
@ -73,7 +70,7 @@ type Strategy struct {
|
||||||
sourceSession, tradingSession *bbgo.ExchangeSession
|
sourceSession, tradingSession *bbgo.ExchangeSession
|
||||||
sourceMarket, tradingMarket types.Market
|
sourceMarket, tradingMarket types.Market
|
||||||
|
|
||||||
state *State
|
State *State `persistence:"state"`
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
lastSourceKLine, lastTradingKLine types.KLine
|
lastSourceKLine, lastTradingKLine types.KLine
|
||||||
|
@ -88,12 +85,12 @@ func (s *Strategy) isBudgetAllowed() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.state.AccumulatedFees == nil {
|
if s.State.AccumulatedFees == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
for asset, budget := range s.DailyFeeBudgets {
|
for asset, budget := range s.DailyFeeBudgets {
|
||||||
if fee, ok := s.state.AccumulatedFees[asset]; ok {
|
if fee, ok := s.State.AccumulatedFees[asset]; ok {
|
||||||
if fee.Compare(budget) >= 0 {
|
if fee.Compare(budget) >= 0 {
|
||||||
log.Warnf("accumulative fee %s exceeded the fee budget %s, skipping...", fee.String(), budget.String())
|
log.Warnf("accumulative fee %s exceeded the fee budget %s, skipping...", fee.String(), budget.String())
|
||||||
return false
|
return false
|
||||||
|
@ -111,18 +108,18 @@ func (s *Strategy) handleTradeUpdate(trade types.Trade) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.state.IsOver24Hours() {
|
if s.State.IsOver24Hours() {
|
||||||
s.state.Reset()
|
s.State.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// safe check
|
// safe check
|
||||||
if s.state.AccumulatedFees == nil {
|
if s.State.AccumulatedFees == nil {
|
||||||
s.state.AccumulatedFees = make(map[string]fixedpoint.Value)
|
s.State.AccumulatedFees = make(map[string]fixedpoint.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state.AccumulatedFees[trade.FeeCurrency] = s.state.AccumulatedFees[trade.FeeCurrency].Add(trade.Fee)
|
s.State.AccumulatedFees[trade.FeeCurrency] = s.State.AccumulatedFees[trade.FeeCurrency].Add(trade.Fee)
|
||||||
s.state.AccumulatedVolume = s.state.AccumulatedVolume.Add(trade.Quantity)
|
s.State.AccumulatedVolume = s.State.AccumulatedVolume.Add(trade.Quantity)
|
||||||
log.Infof("accumulated fee: %s %s", s.state.AccumulatedFees[trade.FeeCurrency].String(), trade.FeeCurrency)
|
log.Infof("accumulated fee: %s %s", s.State.AccumulatedFees[trade.FeeCurrency].String(), trade.FeeCurrency)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {
|
func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {
|
||||||
|
@ -172,36 +169,20 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se
|
||||||
|
|
||||||
s.stopC = make(chan struct{})
|
s.stopC = make(chan struct{})
|
||||||
|
|
||||||
var state State
|
if s.State == nil {
|
||||||
// load position
|
s.State = &State{}
|
||||||
if err := s.Persistence.Load(&state, ID, stateKey); err != nil {
|
s.State.Reset()
|
||||||
if err != service.ErrPersistenceNotExists {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = &State{}
|
if s.State.IsOver24Hours() {
|
||||||
s.state.Reset()
|
|
||||||
} else {
|
|
||||||
// loaded successfully
|
|
||||||
s.state = &state
|
|
||||||
log.Infof("state is restored: %+v", s.state)
|
|
||||||
|
|
||||||
if s.state.IsOver24Hours() {
|
|
||||||
log.Warn("state is over 24 hours, resetting to zero")
|
log.Warn("state is over 24 hours, resetting to zero")
|
||||||
s.state.Reset()
|
s.State.Reset()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
close(s.stopC)
|
close(s.stopC)
|
||||||
|
bbgo.Sync(context.Background(), s)
|
||||||
if err := s.Persistence.Save(&s.state, ID, stateKey); err != nil {
|
|
||||||
log.WithError(err).Errorf("can not save state: %+v", s.state)
|
|
||||||
} else {
|
|
||||||
log.Infof("state is saved => %+v", s.state)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// from here, set data binding
|
// from here, set data binding
|
||||||
|
|
|
@ -24,8 +24,6 @@ const priceUpdateTimeout = 30 * time.Second
|
||||||
|
|
||||||
const ID = "xmaker"
|
const ID = "xmaker"
|
||||||
|
|
||||||
const stateKey = "state-v1"
|
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -33,7 +31,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
|
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
|
@ -602,17 +599,6 @@ func (s *Strategy) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) LoadState() error {
|
|
||||||
var state State
|
|
||||||
|
|
||||||
// load position
|
|
||||||
if err := s.Persistence.Load(&state, ID, s.Symbol, stateKey); err == nil {
|
|
||||||
s.state = &state
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
if s.BollBandInterval == "" {
|
if s.BollBandInterval == "" {
|
||||||
s.BollBandInterval = types.Interval1m
|
s.BollBandInterval = types.Interval1m
|
||||||
|
@ -704,16 +690,8 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
|
||||||
s.groupID = util.FNV32(instanceID)
|
s.groupID = util.FNV32(instanceID)
|
||||||
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
||||||
|
|
||||||
if err := s.LoadState(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Position == nil {
|
if s.Position == nil {
|
||||||
if s.state != nil && s.state.Position != nil {
|
|
||||||
s.Position = s.state.Position
|
|
||||||
} else {
|
|
||||||
s.Position = types.NewPositionFromMarket(s.makerMarket)
|
s.Position = types.NewPositionFromMarket(s.makerMarket)
|
||||||
}
|
|
||||||
|
|
||||||
// force update for legacy code
|
// force update for legacy code
|
||||||
s.Position.Market = s.makerMarket
|
s.Position.Market = s.makerMarket
|
||||||
|
@ -722,16 +700,11 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
|
||||||
bbgo.Notify("xmaker: %s position is restored", s.Symbol, s.Position)
|
bbgo.Notify("xmaker: %s position is restored", s.Symbol, s.Position)
|
||||||
|
|
||||||
if s.ProfitStats == nil {
|
if s.ProfitStats == nil {
|
||||||
if s.state != nil {
|
|
||||||
p2 := s.state.ProfitStats
|
|
||||||
s.ProfitStats = &p2
|
|
||||||
} else {
|
|
||||||
s.ProfitStats = &ProfitStats{
|
s.ProfitStats = &ProfitStats{
|
||||||
ProfitStats: types.NewProfitStats(s.makerMarket),
|
ProfitStats: types.NewProfitStats(s.makerMarket),
|
||||||
MakerExchange: s.makerSession.ExchangeName,
|
MakerExchange: s.makerSession.ExchangeName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if s.CoveredPosition.IsZero() {
|
if s.CoveredPosition.IsZero() {
|
||||||
if s.state != nil && !s.CoveredPosition.IsZero() {
|
if s.state != nil && !s.CoveredPosition.IsZero() {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/service"
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -59,13 +58,13 @@ func (s *State) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
*bbgo.Environment
|
*bbgo.Environment
|
||||||
|
|
||||||
Interval types.Interval `json:"interval"`
|
Interval types.Interval `json:"interval"`
|
||||||
ReportOnStart bool `json:"reportOnStart"`
|
ReportOnStart bool `json:"reportOnStart"`
|
||||||
IgnoreDusts bool `json:"ignoreDusts"`
|
IgnoreDusts bool `json:"ignoreDusts"`
|
||||||
state *State
|
|
||||||
|
State *State `persistence:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) ID() string {
|
func (s *Strategy) ID() string {
|
||||||
|
@ -126,64 +125,28 @@ func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]
|
||||||
|
|
||||||
bbgo.Notify(displayAssets)
|
bbgo.Notify(displayAssets)
|
||||||
|
|
||||||
if s.state != nil {
|
if s.State != nil {
|
||||||
if s.state.IsOver24Hours() {
|
if s.State.IsOver24Hours() {
|
||||||
s.state.Reset()
|
s.State.Reset()
|
||||||
}
|
}
|
||||||
|
bbgo.Sync(ctx, s)
|
||||||
s.SaveState()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) SaveState() {
|
|
||||||
if err := s.Persistence.Save(s.state, ID, stateKey); err != nil {
|
|
||||||
log.WithError(err).Errorf("%s can not save state: %+v", ID, s.state)
|
|
||||||
} else {
|
|
||||||
log.Infof("%s state is saved: %+v", ID, s.state)
|
|
||||||
// s.Notifiability.Notify("%s %s state is saved", ID, s.Asset, s.state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Strategy) newDefaultState() *State {
|
|
||||||
return &State{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Strategy) LoadState() error {
|
|
||||||
var state State
|
|
||||||
if err := s.Persistence.Load(&state, ID, stateKey); err != nil {
|
|
||||||
if err != service.ErrPersistenceNotExists {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.state = s.newDefaultState()
|
|
||||||
s.state.Reset()
|
|
||||||
} else {
|
|
||||||
// we loaded it successfully
|
|
||||||
s.state = &state
|
|
||||||
|
|
||||||
// update Asset name for legacy caches
|
|
||||||
// s.state.Asset = s.Asset
|
|
||||||
|
|
||||||
log.Infof("%s state is restored: %+v", ID, s.state)
|
|
||||||
bbgo.Notify("%s state is restored", ID, s.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error {
|
func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error {
|
||||||
if s.Interval == "" {
|
if s.Interval == "" {
|
||||||
return errors.New("interval can not be empty")
|
return errors.New("interval can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.LoadState(); err != nil {
|
if s.State == nil {
|
||||||
return err
|
s.State = &State{}
|
||||||
|
s.State.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
s.SaveState()
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
|
|
||||||
if s.ReportOnStart {
|
if s.ReportOnStart {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user