mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #786 from c9s/strategy/pivotshort
strategy: pivotshort: resistance short
This commit is contained in:
commit
c8a4af99bc
|
@ -39,18 +39,21 @@ exchangeStrategies:
|
|||
|
||||
# stopEMARange is the price range we allow short.
|
||||
# Short-allowed price range = [current price] > [EMA] * (1 - [stopEMARange])
|
||||
stopEMARange: 0%
|
||||
# Higher the stopEMARange than higher the chance to open a short
|
||||
stopEMARange: 2%
|
||||
stopEMA:
|
||||
interval: 1h
|
||||
window: 99
|
||||
|
||||
bounceShort:
|
||||
enabled: false
|
||||
resistanceShort:
|
||||
enabled: true
|
||||
interval: 1h
|
||||
window: 10
|
||||
window: 8
|
||||
|
||||
quantity: 10.0
|
||||
|
||||
# minDistance is used to ignore the place that is too near to the current price
|
||||
minDistance: 3%
|
||||
# stopLossPercentage: 1%
|
||||
|
||||
# ratio is the ratio of the resistance price,
|
||||
# higher the ratio, lower the price
|
||||
|
@ -86,12 +89,15 @@ exchangeStrategies:
|
|||
# you can grab a simple stats by the following SQL:
|
||||
# SELECT ((close - low) / close) AS shadow_ratio FROM binance_klines WHERE symbol = 'ETHUSDT' AND `interval` = '5m' AND start_time > '2022-01-01' ORDER BY shadow_ratio DESC LIMIT 20;
|
||||
- lowerShadowTakeProfit:
|
||||
interval: 30m
|
||||
window: 99
|
||||
ratio: 3%
|
||||
|
||||
# (5) cumulatedVolumeTakeProfit is used to take profit when the cumulated quote volume from the klines exceeded a threshold
|
||||
- cumulatedVolumeTakeProfit:
|
||||
minQuoteVolume: 100_000_000
|
||||
interval: 5m
|
||||
window: 2
|
||||
minQuoteVolume: 200_000_000
|
||||
|
||||
backtest:
|
||||
sessions:
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype"
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -387,7 +388,7 @@ func (c *Config) GetSignature() string {
|
|||
id := strategy.ID()
|
||||
ps = append(ps, id)
|
||||
|
||||
if symbol, ok := isSymbolBasedStrategy(reflect.ValueOf(strategy)); ok {
|
||||
if symbol, ok := dynamic.LookupSymbolField(reflect.ValueOf(strategy)); ok {
|
||||
ps = append(ps, symbol)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,23 @@ package bbgo
|
|||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
)
|
||||
|
||||
type ExitMethodSet []ExitMethod
|
||||
|
||||
func (s *ExitMethodSet) SetAndSubscribe(session *ExchangeSession, parent interface{}) {
|
||||
for i := range *s {
|
||||
m := (*s)[i]
|
||||
|
||||
// manually inherit configuration from strategy
|
||||
m.Inherit(parent)
|
||||
m.Subscribe(session)
|
||||
}
|
||||
}
|
||||
|
||||
type ExitMethod struct {
|
||||
RoiStopLoss *RoiStopLoss `json:"roiStopLoss"`
|
||||
ProtectiveStopLoss *ProtectiveStopLoss `json:"protectiveStopLoss"`
|
||||
|
@ -14,35 +28,50 @@ type ExitMethod struct {
|
|||
CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"`
|
||||
}
|
||||
|
||||
func (m *ExitMethod) Subscribe(session *ExchangeSession) {
|
||||
// TODO: pull out this implementation as a simple function to reflect.go
|
||||
rv := reflect.ValueOf(m)
|
||||
rt := reflect.TypeOf(m)
|
||||
|
||||
rv = rv.Elem()
|
||||
rt = rt.Elem()
|
||||
infType := reflect.TypeOf((*types.Subscriber)(nil)).Elem()
|
||||
|
||||
argValues := toReflectValues(session)
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
fieldType := rt.Field(i)
|
||||
if fieldType.Type.Implements(infType) {
|
||||
method := rv.Field(i).MethodByName("Subscribe")
|
||||
method.Call(argValues)
|
||||
// Inherit is used for inheriting properties from the given strategy struct
|
||||
// for example, some exit method requires the default interval and symbol name from the strategy param object
|
||||
func (m *ExitMethod) Inherit(parent interface{}) {
|
||||
// we need to pass some information from the strategy configuration to the exit methods, like symbol, interval and window
|
||||
rt := reflect.TypeOf(m).Elem()
|
||||
rv := reflect.ValueOf(m).Elem()
|
||||
for j := 0; j < rv.NumField(); j++ {
|
||||
if !rt.Field(j).IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := rv.Field(j)
|
||||
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
dynamic.InheritStructValues(fieldValue.Interface(), parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ExitMethod) Subscribe(session *ExchangeSession) {
|
||||
if err := dynamic.CallStructFieldsMethod(m, "Subscribe", session); err != nil {
|
||||
panic(errors.Wrap(err, "dynamic Subscribe call failed"))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ExitMethod) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) {
|
||||
if m.ProtectiveStopLoss != nil {
|
||||
m.ProtectiveStopLoss.Bind(session, orderExecutor)
|
||||
} else if m.RoiStopLoss != nil {
|
||||
}
|
||||
|
||||
if m.RoiStopLoss != nil {
|
||||
m.RoiStopLoss.Bind(session, orderExecutor)
|
||||
} else if m.RoiTakeProfit != nil {
|
||||
}
|
||||
|
||||
if m.RoiTakeProfit != nil {
|
||||
m.RoiTakeProfit.Bind(session, orderExecutor)
|
||||
} else if m.LowerShadowTakeProfit != nil {
|
||||
}
|
||||
|
||||
if m.LowerShadowTakeProfit != nil {
|
||||
m.LowerShadowTakeProfit.Bind(session, orderExecutor)
|
||||
} else if m.CumulatedVolumeTakeProfit != nil {
|
||||
}
|
||||
|
||||
if m.CumulatedVolumeTakeProfit != nil {
|
||||
m.CumulatedVolumeTakeProfit.Bind(session, orderExecutor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package bbgo
|
|||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -15,7 +17,10 @@ import (
|
|||
// > SELECT start_time, `interval`, quote_volume, open, close FROM binance_klines WHERE symbol = 'ETHUSDT' AND `interval` = '5m' ORDER BY quote_volume DESC LIMIT 20;
|
||||
//
|
||||
type CumulatedVolumeTakeProfit struct {
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
Ratio fixedpoint.Value `json:"ratio"`
|
||||
MinQuoteVolume fixedpoint.Value `json:"minQuoteVolume"`
|
||||
|
||||
|
@ -32,7 +37,7 @@ func (s *CumulatedVolumeTakeProfit) Bind(session *ExchangeSession, orderExecutor
|
|||
store, _ := session.MarketDataStore(position.Symbol)
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||
if kline.Symbol != position.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -46,7 +51,16 @@ func (s *CumulatedVolumeTakeProfit) Bind(session *ExchangeSession, orderExecutor
|
|||
return
|
||||
}
|
||||
|
||||
if klines, ok := store.KLinesOfInterval(s.Interval); ok {
|
||||
klines, ok := store.KLinesOfInterval(s.Interval)
|
||||
if !ok {
|
||||
log.Warnf("history kline not found")
|
||||
return
|
||||
}
|
||||
|
||||
if len(*klines) < s.Window {
|
||||
return
|
||||
}
|
||||
|
||||
var cbv = fixedpoint.Zero
|
||||
var cqv = fixedpoint.Zero
|
||||
for i := 0; i < s.Window; i++ {
|
||||
|
@ -65,6 +79,5 @@ func (s *CumulatedVolumeTakeProfit) Bind(session *ExchangeSession, orderExecutor
|
|||
_ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One, "cumulatedVolumeTakeProfit")
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,16 +8,29 @@ import (
|
|||
)
|
||||
|
||||
type LowerShadowTakeProfit struct {
|
||||
Ratio fixedpoint.Value `json:"ratio"`
|
||||
// inherit from the strategy
|
||||
types.IntervalWindow
|
||||
|
||||
// inherit from the strategy
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
Ratio fixedpoint.Value `json:"ratio"`
|
||||
session *ExchangeSession
|
||||
orderExecutor *GeneralOrderExecutor
|
||||
}
|
||||
|
||||
func (s *LowerShadowTakeProfit) Subscribe(session *ExchangeSession) {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||
}
|
||||
|
||||
func (s *LowerShadowTakeProfit) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
stdIndicatorSet, _ := session.StandardIndicatorSet(s.Symbol)
|
||||
ewma := stdIndicatorSet.EWMA(s.IntervalWindow)
|
||||
|
||||
|
||||
position := orderExecutor.Position()
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||
|
@ -38,6 +51,11 @@ func (s *LowerShadowTakeProfit) Bind(session *ExchangeSession, orderExecutor *Ge
|
|||
return
|
||||
}
|
||||
|
||||
// skip close price higher than the ewma
|
||||
if closePrice.Float64() > ewma.Last() {
|
||||
return
|
||||
}
|
||||
|
||||
if kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Ratio) > 0 {
|
||||
Notify("%s TakeProfit triggered by shadow ratio %f, price = %f",
|
||||
position.Symbol,
|
||||
|
|
|
@ -3,18 +3,40 @@ package bbgo
|
|||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var graceful = &Graceful{}
|
||||
|
||||
//go:generate callbackgen -type Graceful
|
||||
type Graceful struct {
|
||||
shutdownCallbacks []func(ctx context.Context, wg *sync.WaitGroup)
|
||||
}
|
||||
|
||||
// Shutdown is a blocking call to emit all shutdown callbacks at the same time.
|
||||
func (g *Graceful) Shutdown(ctx context.Context) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(g.shutdownCallbacks))
|
||||
|
||||
go g.EmitShutdown(ctx, &wg)
|
||||
// for each shutdown callback, we give them 10 second
|
||||
shtCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
|
||||
go g.EmitShutdown(shtCtx, &wg)
|
||||
|
||||
wg.Wait()
|
||||
cancel()
|
||||
}
|
||||
|
||||
func OnShutdown(f func(ctx context.Context, wg *sync.WaitGroup)) {
|
||||
graceful.OnShutdown(f)
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
logrus.Infof("shutting down...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
|
||||
graceful.Shutdown(ctx)
|
||||
cancel()
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -22,7 +23,7 @@ func Test_injectField(t *testing.T) {
|
|||
// get the value of the pointer, or it can not be set.
|
||||
var rv = reflect.ValueOf(tt).Elem()
|
||||
|
||||
_, ret := hasField(rv, "TradeService")
|
||||
_, ret := dynamic.HasField(rv, "TradeService")
|
||||
assert.True(t, ret)
|
||||
|
||||
ts := &service.TradeService{}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
)
|
||||
|
||||
|
@ -106,10 +107,10 @@ func Sync(obj interface{}) {
|
|||
}
|
||||
|
||||
func loadPersistenceFields(obj interface{}, id string, persistence service.PersistenceService) error {
|
||||
return iterateFieldsByTag(obj, "persistence", func(tag string, field reflect.StructField, value reflect.Value) error {
|
||||
return dynamic.IterateFieldsByTag(obj, "persistence", func(tag string, field reflect.StructField, value reflect.Value) error {
|
||||
log.Debugf("[loadPersistenceFields] loading value into field %v, tag = %s, original value = %v", field, tag, value)
|
||||
|
||||
newValueInf := newTypeValueInterface(value.Type())
|
||||
newValueInf := dynamic.NewTypeValueInterface(value.Type())
|
||||
// inf := value.Interface()
|
||||
store := persistence.NewStore("state", id, tag)
|
||||
if err := store.Load(&newValueInf); err != nil {
|
||||
|
@ -134,7 +135,7 @@ func loadPersistenceFields(obj interface{}, id string, persistence service.Persi
|
|||
}
|
||||
|
||||
func storePersistenceFields(obj interface{}, id string, persistence service.PersistenceService) error {
|
||||
return iterateFieldsByTag(obj, "persistence", func(tag string, ft reflect.StructField, fv reflect.Value) error {
|
||||
return dynamic.IterateFieldsByTag(obj, "persistence", func(tag string, ft reflect.StructField, fv reflect.Value) error {
|
||||
log.Debugf("[storePersistenceFields] storing value from field %v, tag = %s, original value = %v", ft, tag, fv)
|
||||
|
||||
inf := fv.Interface()
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -23,7 +24,6 @@ func (s *TestStructWithoutInstanceID) ID() string {
|
|||
|
||||
type TestStruct struct {
|
||||
*Environment
|
||||
*Graceful
|
||||
|
||||
Position *types.Position `persistence:"position"`
|
||||
Integer int64 `persistence:"integer"`
|
||||
|
@ -83,7 +83,7 @@ func Test_loadPersistenceFields(t *testing.T) {
|
|||
t.Run(psName+"/nil", func(t *testing.T) {
|
||||
var b *TestStruct = nil
|
||||
err := loadPersistenceFields(b, "test-nil", ps)
|
||||
assert.Equal(t, errCanNotIterateNilPointer, err)
|
||||
assert.Equal(t, dynamic.ErrCanNotIterateNilPointer, err)
|
||||
})
|
||||
|
||||
t.Run(psName+"/pointer-field", func(t *testing.T) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package bbgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
)
|
||||
|
||||
type InstanceIDProvider interface {
|
||||
|
@ -19,7 +19,7 @@ func callID(obj interface{}) string {
|
|||
return ret[0].String()
|
||||
}
|
||||
|
||||
if symbol, ok := isSymbolBasedStrategy(sv); ok {
|
||||
if symbol, ok := dynamic.LookupSymbolField(sv); ok {
|
||||
m := sv.MethodByName("ID")
|
||||
ret := m.Call(nil)
|
||||
return ret[0].String() + ":" + symbol
|
||||
|
@ -31,90 +31,3 @@ func callID(obj interface{}) string {
|
|||
return ret[0].String() + ":"
|
||||
}
|
||||
|
||||
func isSymbolBasedStrategy(rs reflect.Value) (string, bool) {
|
||||
if rs.Kind() == reflect.Ptr {
|
||||
rs = rs.Elem()
|
||||
}
|
||||
|
||||
field := rs.FieldByName("Symbol")
|
||||
if !field.IsValid() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if field.Kind() != reflect.String {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return field.String(), true
|
||||
}
|
||||
|
||||
func hasField(rs reflect.Value, fieldName string) (field reflect.Value, ok bool) {
|
||||
field = rs.FieldByName(fieldName)
|
||||
return field, field.IsValid()
|
||||
}
|
||||
|
||||
type StructFieldIterator func(tag string, ft reflect.StructField, fv reflect.Value) error
|
||||
|
||||
var errCanNotIterateNilPointer = errors.New("can not iterate struct on a nil pointer")
|
||||
|
||||
func iterateFieldsByTag(obj interface{}, tagName string, cb StructFieldIterator) error {
|
||||
sv := reflect.ValueOf(obj)
|
||||
st := reflect.TypeOf(obj)
|
||||
|
||||
if st.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("f should be a pointer of a struct, %s given", st)
|
||||
}
|
||||
|
||||
// for pointer, check if it's nil
|
||||
if sv.IsNil() {
|
||||
return errCanNotIterateNilPointer
|
||||
}
|
||||
|
||||
// solve the reference
|
||||
st = st.Elem()
|
||||
sv = sv.Elem()
|
||||
|
||||
if st.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("f should be a struct, %s given", st)
|
||||
}
|
||||
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
fv := sv.Field(i)
|
||||
ft := st.Field(i)
|
||||
|
||||
// skip unexported fields
|
||||
if !st.Field(i).IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
tag, ok := ft.Tag.Lookup(tagName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cb(tag, ft, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/xiaojun207/go-base-utils/blob/master/utils/Clone.go
|
||||
func newTypeValueInterface(typ reflect.Type) interface{} {
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
dst := reflect.New(typ).Elem()
|
||||
return dst.Addr().Interface()
|
||||
}
|
||||
dst := reflect.New(typ)
|
||||
return dst.Interface()
|
||||
}
|
||||
|
||||
func toReflectValues(args ...interface{}) (values []reflect.Value) {
|
||||
for _, arg := range args {
|
||||
values = append(values, reflect.ValueOf(arg))
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
|
2
pkg/bbgo/reflect_test.go
Normal file
2
pkg/bbgo/reflect_test.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
package bbgo
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/interact"
|
||||
)
|
||||
|
||||
|
@ -72,8 +73,6 @@ type Trader struct {
|
|||
exchangeStrategies map[string][]SingleExchangeStrategy
|
||||
|
||||
logger Logger
|
||||
|
||||
Graceful Graceful
|
||||
}
|
||||
|
||||
func NewTrader(environ *Environment) *Trader {
|
||||
|
@ -201,7 +200,7 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si
|
|||
return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy)
|
||||
}
|
||||
|
||||
if symbol, ok := isSymbolBasedStrategy(rs); ok {
|
||||
if symbol, ok := dynamic.LookupSymbolField(rs); ok {
|
||||
log.Infof("found symbol based strategy from %s", rs.Type())
|
||||
|
||||
market, ok := session.Market(symbol)
|
||||
|
@ -394,7 +393,7 @@ func (trader *Trader) injectCommonServices(s interface{}) error {
|
|||
// 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
|
||||
sv := reflect.ValueOf(s).Elem()
|
||||
if field, ok := hasField(sv, "Persistence"); ok {
|
||||
if field, ok := dynamic.HasField(sv, "Persistence"); ok {
|
||||
// the selector is set, but we need to update the facade pointer
|
||||
if !field.IsNil() {
|
||||
elem := field.Elem()
|
||||
|
@ -415,7 +414,6 @@ func (trader *Trader) injectCommonServices(s interface{}) error {
|
|||
}
|
||||
|
||||
return parseStructAndInject(s,
|
||||
&trader.Graceful,
|
||||
&trader.logger,
|
||||
Notification,
|
||||
trader.environment.TradeService,
|
||||
|
|
|
@ -443,9 +443,7 @@ var BacktestCmd = &cobra.Command{
|
|||
cmdutil.WaitForSignal(runCtx, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
log.Infof("shutting down trader...")
|
||||
shutdownCtx, cancelShutdown := context.WithDeadline(runCtx, time.Now().Add(10*time.Second))
|
||||
trader.Graceful.Shutdown(shutdownCtx)
|
||||
cancelShutdown()
|
||||
bbgo.Shutdown()
|
||||
|
||||
// put the logger back to print the pnl
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"path/filepath"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -78,12 +77,7 @@ func runSetup(baseCtx context.Context, userConfig *bbgo.Config, enableApiServer
|
|||
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
cancelTrading()
|
||||
|
||||
// graceful period = 15 second
|
||||
shutdownCtx, cancelShutdown := context.WithDeadline(ctx, time.Now().Add(15*time.Second))
|
||||
|
||||
log.Infof("shutting down...")
|
||||
trader.Graceful.Shutdown(shutdownCtx)
|
||||
cancelShutdown()
|
||||
bbgo.Shutdown()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -216,10 +210,7 @@ func runConfig(basectx context.Context, cmd *cobra.Command, userConfig *bbgo.Con
|
|||
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
cancelTrading()
|
||||
|
||||
log.Infof("shutting down...")
|
||||
shutdownCtx, cancelShutdown := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
|
||||
trader.Graceful.Shutdown(shutdownCtx)
|
||||
cancelShutdown()
|
||||
bbgo.Shutdown()
|
||||
|
||||
if err := trader.SaveState(); err != nil {
|
||||
log.WithError(err).Errorf("can not save strategy states")
|
||||
|
|
53
pkg/dynamic/call.go
Normal file
53
pkg/dynamic/call.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// CallStructFieldsMethod iterates field from the given struct object
|
||||
// check if the field object implements the interface, if it's implemented, then we call a specific method
|
||||
func CallStructFieldsMethod(m interface{}, method string, args ...interface{}) error {
|
||||
rv := reflect.ValueOf(m)
|
||||
rt := reflect.TypeOf(m)
|
||||
|
||||
if rt.Kind() != reflect.Ptr {
|
||||
return errors.New("the given object needs to be a pointer")
|
||||
}
|
||||
|
||||
rv = rv.Elem()
|
||||
rt = rt.Elem()
|
||||
|
||||
if rt.Kind() != reflect.Struct {
|
||||
return errors.New("the given object needs to be struct")
|
||||
}
|
||||
|
||||
argValues := ToReflectValues(args...)
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
fieldType := rt.Field(i)
|
||||
fieldValue := rv.Field(i)
|
||||
|
||||
// skip non-exported fields
|
||||
if !fieldType.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldType.Type.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
methodType, ok := fieldType.Type.MethodByName(method)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(argValues) < methodType.Type.NumIn() {
|
||||
// return fmt.Errorf("method %v require %d args, %d given", methodType, methodType.Type.NumIn(), len(argValues))
|
||||
}
|
||||
|
||||
refMethod := fieldValue.MethodByName(method)
|
||||
refMethod.Call(argValues)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
29
pkg/dynamic/call_test.go
Normal file
29
pkg/dynamic/call_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type callTest struct {
|
||||
ChildCall1 *childCall1
|
||||
ChildCall2 *childCall2
|
||||
}
|
||||
|
||||
type childCall1 struct{}
|
||||
|
||||
func (c *childCall1) Subscribe(a int) {}
|
||||
|
||||
type childCall2 struct{}
|
||||
|
||||
func (c *childCall2) Subscribe(a int) {}
|
||||
|
||||
func TestCallStructFieldsMethod(t *testing.T) {
|
||||
c := &callTest{
|
||||
ChildCall1: &childCall1{},
|
||||
ChildCall2: &childCall2{},
|
||||
}
|
||||
err := CallStructFieldsMethod(c, "Subscribe", 10)
|
||||
assert.NoError(t, err)
|
||||
}
|
26
pkg/dynamic/field.go
Normal file
26
pkg/dynamic/field.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package dynamic
|
||||
|
||||
import "reflect"
|
||||
|
||||
func HasField(rs reflect.Value, fieldName string) (field reflect.Value, ok bool) {
|
||||
field = rs.FieldByName(fieldName)
|
||||
return field, field.IsValid()
|
||||
}
|
||||
|
||||
func LookupSymbolField(rs reflect.Value) (string, bool) {
|
||||
if rs.Kind() == reflect.Ptr {
|
||||
rs = rs.Elem()
|
||||
}
|
||||
|
||||
field := rs.FieldByName("Symbol")
|
||||
if !field.IsValid() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if field.Kind() != reflect.String {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return field.String(), true
|
||||
}
|
||||
|
54
pkg/dynamic/iterate.go
Normal file
54
pkg/dynamic/iterate.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type StructFieldIterator func(tag string, ft reflect.StructField, fv reflect.Value) error
|
||||
|
||||
var ErrCanNotIterateNilPointer = errors.New("can not iterate struct on a nil pointer")
|
||||
|
||||
func IterateFieldsByTag(obj interface{}, tagName string, cb StructFieldIterator) error {
|
||||
sv := reflect.ValueOf(obj)
|
||||
st := reflect.TypeOf(obj)
|
||||
|
||||
if st.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("f should be a pointer of a struct, %s given", st)
|
||||
}
|
||||
|
||||
// for pointer, check if it's nil
|
||||
if sv.IsNil() {
|
||||
return ErrCanNotIterateNilPointer
|
||||
}
|
||||
|
||||
// solve the reference
|
||||
st = st.Elem()
|
||||
sv = sv.Elem()
|
||||
|
||||
if st.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("f should be a struct, %s given", st)
|
||||
}
|
||||
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
fv := sv.Field(i)
|
||||
ft := st.Field(i)
|
||||
|
||||
// skip unexported fields
|
||||
if !st.Field(i).IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
tag, ok := ft.Tag.Lookup(tagName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cb(tag, ft, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
41
pkg/dynamic/merge.go
Normal file
41
pkg/dynamic/merge.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package dynamic
|
||||
|
||||
import "reflect"
|
||||
|
||||
// InheritStructValues merges the field value from the source struct to the dest struct.
|
||||
// Only fields with the same type and the same name will be updated.
|
||||
func InheritStructValues(dst, src interface{}) {
|
||||
if dst == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rtA := reflect.TypeOf(dst)
|
||||
srcStructType := reflect.TypeOf(src)
|
||||
|
||||
rtA = rtA.Elem()
|
||||
srcStructType = srcStructType.Elem()
|
||||
|
||||
for i := 0; i < rtA.NumField(); i++ {
|
||||
fieldType := rtA.Field(i)
|
||||
fieldName := fieldType.Name
|
||||
|
||||
if !fieldType.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
// if there is a field with the same name
|
||||
fieldSrcType, found := srcStructType.FieldByName(fieldName)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure that the type is the same
|
||||
if fieldSrcType.Type == fieldType.Type {
|
||||
srcValue := reflect.ValueOf(src).Elem().FieldByName(fieldName)
|
||||
dstValue := reflect.ValueOf(dst).Elem().FieldByName(fieldName)
|
||||
if (fieldType.Type.Kind() == reflect.Ptr && dstValue.IsNil()) || dstValue.IsZero() {
|
||||
dstValue.Set(srcValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
82
pkg/dynamic/merge_test.go
Normal file
82
pkg/dynamic/merge_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type TestStrategy struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Interval string `json:"interval"`
|
||||
BaseQuantity fixedpoint.Value `json:"baseQuantity"`
|
||||
MaxAssetQuantity fixedpoint.Value `json:"maxAssetQuantity"`
|
||||
MinDropPercentage fixedpoint.Value `json:"minDropPercentage"`
|
||||
}
|
||||
|
||||
func Test_reflectMergeStructFields(t *testing.T) {
|
||||
t.Run("zero value", func(t *testing.T) {
|
||||
a := &TestStrategy{Symbol: "BTCUSDT"}
|
||||
b := &struct{ Symbol string }{Symbol: ""}
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "BTCUSDT", b.Symbol)
|
||||
})
|
||||
|
||||
t.Run("non-zero value", func(t *testing.T) {
|
||||
a := &TestStrategy{Symbol: "BTCUSDT"}
|
||||
b := &struct{ Symbol string }{Symbol: "ETHUSDT"}
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "ETHUSDT", b.Symbol, "should be the original value")
|
||||
})
|
||||
|
||||
t.Run("zero embedded struct", func(t *testing.T) {
|
||||
iw := types.IntervalWindow{Interval: types.Interval1h, Window: 30}
|
||||
a := &struct {
|
||||
types.IntervalWindow
|
||||
Symbol string
|
||||
}{
|
||||
IntervalWindow: iw,
|
||||
Symbol: "BTCUSDT",
|
||||
}
|
||||
b := &struct {
|
||||
Symbol string
|
||||
types.IntervalWindow
|
||||
}{}
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, iw, b.IntervalWindow)
|
||||
assert.Equal(t, "BTCUSDT", b.Symbol)
|
||||
})
|
||||
|
||||
t.Run("non-zero embedded struct", func(t *testing.T) {
|
||||
iw := types.IntervalWindow{Interval: types.Interval1h, Window: 30}
|
||||
a := &struct {
|
||||
types.IntervalWindow
|
||||
}{
|
||||
IntervalWindow: iw,
|
||||
}
|
||||
b := &struct {
|
||||
types.IntervalWindow
|
||||
}{
|
||||
IntervalWindow: types.IntervalWindow{Interval: types.Interval5m, Window: 9},
|
||||
}
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, types.IntervalWindow{Interval: types.Interval5m, Window: 9}, b.IntervalWindow)
|
||||
})
|
||||
|
||||
t.Run("skip different type but the same name", func(t *testing.T) {
|
||||
a := &struct {
|
||||
A float64
|
||||
}{
|
||||
A: 1.99,
|
||||
}
|
||||
b := &struct {
|
||||
A string
|
||||
}{}
|
||||
InheritStructValues(b, a)
|
||||
assert.Equal(t, "", b.A)
|
||||
assert.Equal(t, 1.99, a.A)
|
||||
})
|
||||
}
|
24
pkg/dynamic/typevalue.go
Normal file
24
pkg/dynamic/typevalue.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package dynamic
|
||||
|
||||
import "reflect"
|
||||
|
||||
// https://github.com/xiaojun207/go-base-utils/blob/master/utils/Clone.go
|
||||
func NewTypeValueInterface(typ reflect.Type) interface{} {
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
dst := reflect.New(typ).Elem()
|
||||
return dst.Addr().Interface()
|
||||
}
|
||||
dst := reflect.New(typ)
|
||||
return dst.Interface()
|
||||
}
|
||||
|
||||
// ToReflectValues convert the go objects into reflect.Value slice
|
||||
func ToReflectValues(args ...interface{}) (values []reflect.Value) {
|
||||
for i := range args {
|
||||
arg := args[i]
|
||||
values = append(values, reflect.ValueOf(arg))
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
|
@ -41,9 +41,6 @@ type Strategy struct {
|
|||
// This field will be injected automatically since we defined the Symbol field.
|
||||
*bbgo.StandardIndicatorSet
|
||||
|
||||
// Graceful let you define the graceful shutdown handler
|
||||
*bbgo.Graceful
|
||||
|
||||
// Market stores the configuration of the market, for example, VolumePrecision, PricePrecision, MinLotSize... etc
|
||||
// This field will be injected automatically since we defined the Symbol field.
|
||||
types.Market
|
||||
|
@ -350,7 +347,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.profitOrders.BindStream(session.UserDataStream)
|
||||
|
||||
// setup graceful shutting down handler
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
// call Done to notify the main process.
|
||||
defer wg.Done()
|
||||
log.Infof("canceling active orders...")
|
||||
|
|
|
@ -49,7 +49,6 @@ type BollingerSetting struct {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Environment *bbgo.Environment
|
||||
|
@ -616,7 +615,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
// s.book = types.NewStreamBook(s.Symbol)
|
||||
// s.book.BindStreamForBackground(session.MarketDataStream)
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
|
|
@ -47,7 +47,6 @@ func (b BudgetPeriod) Duration() time.Duration {
|
|||
|
||||
// Strategy is the Dollar-Cost-Average strategy
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
|
||||
Environment *bbgo.Environment
|
||||
Symbol string `json:"symbol"`
|
||||
|
|
|
@ -25,7 +25,6 @@ func init() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
|
||||
SourceExchangeName string `json:"sourceExchange"`
|
||||
|
||||
|
@ -217,7 +216,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.place(ctx, orderExecutor, session, indicator, closePrice)
|
||||
})
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
log.Infof("canceling trailingstop order...")
|
||||
s.clear(ctx, orderExecutor)
|
||||
|
@ -261,7 +260,7 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se
|
|||
s.place(ctx, &orderExecutor, session, indicator, closePrice)
|
||||
})
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
log.Infof("canceling trailingstop order...")
|
||||
s.clear(ctx, &orderExecutor)
|
||||
|
|
|
@ -51,7 +51,6 @@ type Strategy struct {
|
|||
KLineEndTime types.Time
|
||||
|
||||
*bbgo.Environment
|
||||
*bbgo.Graceful
|
||||
bbgo.StrategyController
|
||||
|
||||
activeMakerOrders *bbgo.ActiveOrderBook
|
||||
|
@ -1221,7 +1220,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}
|
||||
})
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
log.Infof("canceling active orders...")
|
||||
s.CancelAll(ctx)
|
||||
|
|
|
@ -49,10 +49,6 @@ type Strategy struct {
|
|||
// This field will be injected automatically since we defined the Symbol field.
|
||||
*bbgo.StandardIndicatorSet
|
||||
|
||||
// Graceful shutdown function
|
||||
*bbgo.Graceful
|
||||
// --------------------------
|
||||
|
||||
// ewma is the exponential weighted moving average indicator
|
||||
ewma *indicator.EWMA
|
||||
}
|
||||
|
@ -114,7 +110,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
log.Infof("canceling active orders...")
|
||||
|
|
|
@ -31,7 +31,6 @@ type IntervalWindowSetting struct {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Environment *bbgo.Environment
|
||||
|
|
|
@ -45,8 +45,6 @@ type State struct {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful `json:"-" yaml:"-"`
|
||||
|
||||
*bbgo.Persistence
|
||||
|
||||
// OrderExecutor is an interface for submitting order.
|
||||
|
@ -621,7 +619,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
})
|
||||
s.tradeCollector.BindStream(session.UserDataStream)
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := s.SaveState(); err != nil {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -47,8 +48,10 @@ type BreakLow struct {
|
|||
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
||||
}
|
||||
|
||||
type BounceShort struct {
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
|
@ -57,20 +60,129 @@ type BounceShort struct {
|
|||
LayerSpread fixedpoint.Value `json:"layerSpread"`
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
Ratio fixedpoint.Value `json:"ratio"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
orderExecutor *bbgo.GeneralOrderExecutor
|
||||
|
||||
resistancePivot *indicator.Pivot
|
||||
resistancePrices []float64
|
||||
nextResistancePrice fixedpoint.Value
|
||||
|
||||
resistanceOrders []types.Order
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
CatBounceRatio fixedpoint.Value `json:"catBounceRatio"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
TotalQuantity fixedpoint.Value `json:"totalQuantity"`
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
MarginSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
|
||||
position := orderExecutor.Position()
|
||||
symbol := position.Symbol
|
||||
store, _ := session.MarketDataStore(symbol)
|
||||
|
||||
s.resistancePivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
||||
s.resistancePivot.Bind(store)
|
||||
|
||||
// preload history kline data to the resistance pivot indicator
|
||||
// we use the last kline to find the higher lows
|
||||
lastKLine := preloadPivot(s.resistancePivot, store)
|
||||
|
||||
// use the last kline from the history before we get the next closed kline
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(closePrice) {
|
||||
return
|
||||
}
|
||||
|
||||
minDistance := s.MinDistance.Float64()
|
||||
lows := s.resistancePivot.Lows
|
||||
resistancePrices := findPossibleResistancePrices(closePrice.Float64(), minDistance, lows)
|
||||
|
||||
log.Infof("last price: %f, possible resistance prices: %+v", closePrice.Float64(), resistancePrices)
|
||||
|
||||
ctx := context.Background()
|
||||
if len(resistancePrices) > 0 {
|
||||
nextResistancePrice := fixedpoint.NewFromFloat(resistancePrices[0])
|
||||
if nextResistancePrice.Compare(s.nextResistancePrice) != 0 {
|
||||
s.nextResistancePrice = nextResistancePrice
|
||||
s.placeResistanceOrders(ctx, nextResistancePrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistancePrice fixedpoint.Value) {
|
||||
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
||||
_ = futuresMode
|
||||
|
||||
totalQuantity := s.Quantity
|
||||
numLayers := s.NumLayers
|
||||
if numLayers == 0 {
|
||||
numLayers = 1
|
||||
}
|
||||
|
||||
numLayersF := fixedpoint.NewFromInt(int64(numLayers))
|
||||
layerSpread := s.LayerSpread
|
||||
quantity := totalQuantity.Div(numLayersF)
|
||||
|
||||
if err := s.orderExecutor.CancelOrders(ctx, s.resistanceOrders...); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.resistanceOrders)
|
||||
}
|
||||
s.resistanceOrders = nil
|
||||
|
||||
log.Infof("placing resistance orders: resistance price = %f, layer quantity = %f, num of layers = %d", resistancePrice.Float64(), quantity.Float64(), numLayers)
|
||||
|
||||
var orderForms []types.SubmitOrder
|
||||
for i := 0; i < numLayers; i++ {
|
||||
balances := s.session.GetAccount().Balances()
|
||||
quoteBalance := balances[s.Market.QuoteCurrency]
|
||||
baseBalance := balances[s.Market.BaseCurrency]
|
||||
_ = quoteBalance
|
||||
_ = baseBalance
|
||||
|
||||
// price = (resistance_price * (1.0 + ratio)) * ((1.0 + layerSpread) * i)
|
||||
price := resistancePrice.Mul(fixedpoint.One.Add(s.Ratio))
|
||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||
price = price.Add(spread)
|
||||
log.Infof("price = %f", price.Float64())
|
||||
|
||||
log.Infof("placing bounce short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
||||
|
||||
orderForms = append(orderForms, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimitMaker,
|
||||
Price: price,
|
||||
Quantity: quantity,
|
||||
Tag: "resistanceShort",
|
||||
})
|
||||
|
||||
// TODO: fix futures mode later
|
||||
/*
|
||||
if futuresMode {
|
||||
if quantity.Mul(price).Compare(quoteBalance.Available) <= 0 {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
createdOrders, err := s.orderExecutor.SubmitOrders(ctx, orderForms...)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("can not place resistance order")
|
||||
}
|
||||
s.resistanceOrders = createdOrders
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
|
||||
Environment *bbgo.Environment
|
||||
Symbol string `json:"symbol"`
|
||||
Market types.Market
|
||||
|
@ -83,23 +195,22 @@ type Strategy struct {
|
|||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||
|
||||
// BreakLow is one of the entry method
|
||||
BreakLow BreakLow `json:"breakLow"`
|
||||
|
||||
BounceShort *BounceShort `json:"bounceShort"`
|
||||
// ResistanceShort is one of the entry method
|
||||
ResistanceShort *ResistanceShort `json:"resistanceShort"`
|
||||
|
||||
Entry Entry `json:"entry"`
|
||||
ExitMethods []bbgo.ExitMethod `json:"exits"`
|
||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
orderExecutor *bbgo.GeneralOrderExecutor
|
||||
|
||||
stopLossPrice fixedpoint.Value
|
||||
lastLow fixedpoint.Value
|
||||
pivot *indicator.Pivot
|
||||
resistancePivot *indicator.Pivot
|
||||
stopEWMA *indicator.EWMA
|
||||
pivotLowPrices []fixedpoint.Value
|
||||
resistancePrices []float64
|
||||
currentBounceShortPrice fixedpoint.Value
|
||||
|
||||
// StrategyController
|
||||
|
@ -114,55 +225,16 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||
|
||||
if s.BounceShort != nil && s.BounceShort.Enabled {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.BounceShort.Interval})
|
||||
if s.ResistanceShort != nil && s.ResistanceShort.Enabled {
|
||||
dynamic.InheritStructValues(s.ResistanceShort, s)
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.ResistanceShort.Interval})
|
||||
}
|
||||
|
||||
if !bbgo.IsBackTesting {
|
||||
session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value {
|
||||
balance, hasBalance := s.session.Account.Balance(s.Market.BaseCurrency)
|
||||
|
||||
if hasBalance {
|
||||
if quantity.IsZero() {
|
||||
bbgo.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
|
||||
quantity = balance.Available
|
||||
} else {
|
||||
quantity = fixedpoint.Min(quantity, balance.Available)
|
||||
}
|
||||
}
|
||||
|
||||
if quantity.IsZero() {
|
||||
log.Errorf("quantity is zero, can not submit sell order, please check settings")
|
||||
}
|
||||
|
||||
return quantity
|
||||
}
|
||||
|
||||
func (s *Strategy) placeLimitSell(ctx context.Context, price, quantity fixedpoint.Value, tag string) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Price: price,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: quantity,
|
||||
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||
Tag: tag,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Strategy) placeMarketSell(ctx context.Context, quantity fixedpoint.Value, tag string) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity,
|
||||
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||
Tag: tag,
|
||||
})
|
||||
s.ExitMethods.SetAndSubscribe(session, s)
|
||||
}
|
||||
|
||||
func (s *Strategy) InstanceID() string {
|
||||
|
@ -225,8 +297,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow}
|
||||
s.pivot.Bind(store)
|
||||
|
||||
lastKLine := s.preloadPivot(s.pivot, store)
|
||||
preloadPivot(s.pivot, store)
|
||||
|
||||
// update pivot low data
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
|
@ -247,11 +318,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow)
|
||||
})
|
||||
|
||||
if s.BounceShort != nil && s.BounceShort.Enabled {
|
||||
s.resistancePivot = &indicator.Pivot{IntervalWindow: s.BounceShort.IntervalWindow}
|
||||
s.resistancePivot.Bind(store)
|
||||
}
|
||||
|
||||
if s.BreakLow.StopEMA != nil {
|
||||
s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA)
|
||||
}
|
||||
|
@ -260,36 +326,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
method.Bind(session, s.orderExecutor)
|
||||
}
|
||||
|
||||
if s.BounceShort != nil && s.BounceShort.Enabled {
|
||||
if s.resistancePivot != nil {
|
||||
s.preloadPivot(s.resistancePivot, store)
|
||||
}
|
||||
|
||||
session.UserDataStream.OnStart(func() {
|
||||
if lastKLine == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.resistancePivot != nil {
|
||||
lows := s.resistancePivot.Lows
|
||||
minDistance := s.BounceShort.MinDistance.Float64()
|
||||
closePrice := lastKLine.Close.Float64()
|
||||
s.resistancePrices = findPossibleResistancePrices(closePrice, minDistance, lows)
|
||||
log.Infof("last price: %f, possible resistance prices: %+v", closePrice, s.resistancePrices)
|
||||
|
||||
if len(s.resistancePrices) > 0 {
|
||||
resistancePrice := fixedpoint.NewFromFloat(s.resistancePrices[0])
|
||||
if resistancePrice.Compare(s.currentBounceShortPrice) != 0 {
|
||||
log.Infof("updating resistance price... possible resistance prices: %+v", s.resistancePrices)
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
s.currentBounceShortPrice = resistancePrice
|
||||
s.placeBounceSellOrders(ctx, s.currentBounceShortPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if s.ResistanceShort != nil && s.ResistanceShort.Enabled {
|
||||
s.ResistanceShort.Bind(session, s.orderExecutor)
|
||||
}
|
||||
|
||||
// Always check whether you can open a short position or not
|
||||
|
@ -364,40 +402,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
})
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
// StrategyController
|
||||
if s.Status != types.StrategyStatusRunning {
|
||||
return
|
||||
}
|
||||
|
||||
if s.BounceShort == nil || !s.BounceShort.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.BounceShort.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
if s.resistancePivot != nil {
|
||||
closePrice := kline.Close.Float64()
|
||||
minDistance := s.BounceShort.MinDistance.Float64()
|
||||
lows := s.resistancePivot.Lows
|
||||
s.resistancePrices = findPossibleResistancePrices(closePrice, minDistance, lows)
|
||||
|
||||
if len(s.resistancePrices) > 0 {
|
||||
resistancePrice := fixedpoint.NewFromFloat(s.resistancePrices[0])
|
||||
if resistancePrice.Compare(s.currentBounceShortPrice) != 0 {
|
||||
log.Infof("updating resistance price... possible resistance prices: %+v", s.resistancePrices)
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
s.currentBounceShortPrice = resistancePrice
|
||||
s.placeBounceSellOrders(ctx, s.currentBounceShortPrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if !bbgo.IsBackTesting {
|
||||
// use market trade to submit short order
|
||||
session.MarketDataStream.OnMarketTrade(func(trade types.Trade) {
|
||||
|
@ -405,7 +409,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
})
|
||||
}
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||
wg.Done()
|
||||
})
|
||||
|
@ -423,46 +427,6 @@ func (s *Strategy) findHigherPivotLow(price fixedpoint.Value) (fixedpoint.Value,
|
|||
return price, false
|
||||
}
|
||||
|
||||
func (s *Strategy) placeBounceSellOrders(ctx context.Context, resistancePrice fixedpoint.Value) {
|
||||
futuresMode := s.session.Futures || s.session.IsolatedFutures
|
||||
totalQuantity := s.BounceShort.Quantity
|
||||
numLayers := s.BounceShort.NumLayers
|
||||
if numLayers == 0 {
|
||||
numLayers = 1
|
||||
}
|
||||
|
||||
numLayersF := fixedpoint.NewFromInt(int64(numLayers))
|
||||
|
||||
layerSpread := s.BounceShort.LayerSpread
|
||||
quantity := totalQuantity.Div(numLayersF)
|
||||
|
||||
log.Infof("placing bounce short orders: resistance price = %f, layer quantity = %f, num of layers = %d", resistancePrice.Float64(), quantity.Float64(), numLayers)
|
||||
|
||||
for i := 0; i < numLayers; i++ {
|
||||
balances := s.session.GetAccount().Balances()
|
||||
quoteBalance := balances[s.Market.QuoteCurrency]
|
||||
baseBalance := balances[s.Market.BaseCurrency]
|
||||
|
||||
// price = (resistance_price * (1.0 + ratio)) * ((1.0 + layerSpread) * i)
|
||||
price := resistancePrice.Mul(fixedpoint.One.Add(s.BounceShort.Ratio))
|
||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||
price = price.Add(spread)
|
||||
log.Infof("price = %f", price.Float64())
|
||||
|
||||
log.Infof("placing bounce short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
||||
|
||||
if futuresMode {
|
||||
if quantity.Mul(price).Compare(quoteBalance.Available) <= 0 {
|
||||
s.placeOrder(ctx, price, quantity)
|
||||
}
|
||||
} else {
|
||||
if quantity.Compare(baseBalance.Available) <= 0 {
|
||||
s.placeOrder(ctx, price, quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, quantity fixedpoint.Value) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
|
@ -473,26 +437,51 @@ func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, quant
|
|||
})
|
||||
}
|
||||
|
||||
func (s *Strategy) preloadPivot(pivot *indicator.Pivot, store *bbgo.MarketDataStore) *types.KLine {
|
||||
klines, ok := store.KLinesOfInterval(pivot.Interval)
|
||||
if !ok {
|
||||
return nil
|
||||
func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value {
|
||||
balance, hasBalance := s.session.Account.Balance(s.Market.BaseCurrency)
|
||||
|
||||
if hasBalance {
|
||||
if quantity.IsZero() {
|
||||
bbgo.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String())
|
||||
quantity = balance.Available
|
||||
} else {
|
||||
quantity = fixedpoint.Min(quantity, balance.Available)
|
||||
}
|
||||
}
|
||||
|
||||
last := (*klines)[len(*klines)-1]
|
||||
log.Debugf("updating pivot indicator: %d klines", len(*klines))
|
||||
|
||||
for i := pivot.Window; i < len(*klines); i++ {
|
||||
pivot.Update((*klines)[0 : i+1])
|
||||
if quantity.IsZero() {
|
||||
log.Errorf("quantity is zero, can not submit sell order, please check settings")
|
||||
}
|
||||
|
||||
log.Infof("found %s %v previous lows: %v", s.Symbol, pivot.IntervalWindow, pivot.Lows)
|
||||
log.Infof("found %s %v previous highs: %v", s.Symbol, pivot.IntervalWindow, pivot.Highs)
|
||||
return &last
|
||||
return quantity
|
||||
}
|
||||
|
||||
func (s *Strategy) placeLimitSell(ctx context.Context, price, quantity fixedpoint.Value, tag string) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Price: price,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimit,
|
||||
Quantity: quantity,
|
||||
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||
Tag: tag,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Strategy) placeMarketSell(ctx context.Context, quantity fixedpoint.Value, tag string) {
|
||||
_, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity,
|
||||
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||
Tag: tag,
|
||||
})
|
||||
}
|
||||
|
||||
func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 {
|
||||
// sort float64 in increasing order
|
||||
// lower to higher prices
|
||||
sort.Float64s(lows)
|
||||
|
||||
var resistancePrices []float64
|
||||
|
@ -514,3 +503,21 @@ func findPossibleResistancePrices(closePrice float64, minDistance float64, lows
|
|||
|
||||
return resistancePrices
|
||||
}
|
||||
|
||||
func preloadPivot(pivot *indicator.Pivot, store *bbgo.MarketDataStore) *types.KLine {
|
||||
klines, ok := store.KLinesOfInterval(pivot.Interval)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
last := (*klines)[len(*klines)-1]
|
||||
log.Debugf("updating pivot indicator: %d klines", len(*klines))
|
||||
|
||||
for i := pivot.Window; i < len(*klines); i++ {
|
||||
pivot.Update((*klines)[0 : i+1])
|
||||
}
|
||||
|
||||
log.Debugf("found %v previous lows: %v", pivot.IntervalWindow, pivot.Lows)
|
||||
log.Debugf("found %v previous highs: %v", pivot.IntervalWindow, pivot.Highs)
|
||||
return &last
|
||||
}
|
||||
|
|
|
@ -29,9 +29,6 @@ func init() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Notifiability
|
||||
|
||||
Environment *bbgo.Environment
|
||||
StandardIndicatorSet *bbgo.StandardIndicatorSet
|
||||
Market types.Market
|
||||
|
|
|
@ -30,7 +30,6 @@ func init() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Environment *bbgo.Environment
|
||||
|
@ -391,7 +390,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
})
|
||||
|
||||
// Graceful shutdown
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
close(s.stopC)
|
||||
|
||||
|
|
|
@ -134,7 +134,6 @@ func (control *TrailingStopControl) GenerateStopOrder(quantity fixedpoint.Value)
|
|||
type Strategy struct {
|
||||
*bbgo.Persistence `json:"-"`
|
||||
*bbgo.Environment `json:"-"`
|
||||
*bbgo.Graceful `json:"-"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
|
||||
|
@ -582,7 +581,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
})
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
// Cancel trailing stop order
|
||||
|
|
|
@ -30,7 +30,6 @@ func init() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Environment *bbgo.Environment
|
||||
|
@ -377,7 +376,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}()
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
close(s.stopC)
|
||||
|
||||
|
|
|
@ -135,8 +135,6 @@ func (a *Address) UnmarshalJSON(body []byte) error {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
|
||||
Interval types.Duration `json:"interval"`
|
||||
|
||||
Addresses map[string]Address `json:"addresses"`
|
||||
|
@ -342,7 +340,7 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se
|
|||
s.State = s.newDefaultState()
|
||||
}
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
})
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ func (s *State) Reset() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Symbol string `json:"symbol"`
|
||||
|
@ -193,7 +192,7 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se
|
|||
}
|
||||
}
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
close(s.stopC)
|
||||
|
|
|
@ -33,7 +33,6 @@ func init() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
Environment *bbgo.Environment
|
||||
|
||||
|
@ -879,7 +878,7 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
|
|||
}
|
||||
}()
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
close(s.stopC)
|
||||
|
|
|
@ -58,7 +58,6 @@ func (s *State) Reset() {
|
|||
}
|
||||
|
||||
type Strategy struct {
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
*bbgo.Environment
|
||||
|
||||
|
@ -180,7 +179,7 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se
|
|||
return err
|
||||
}
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
s.SaveState()
|
||||
|
|
|
@ -273,6 +273,10 @@ func (p *Position) IsClosed() bool {
|
|||
return p.Base.Sign() == 0
|
||||
}
|
||||
|
||||
func (p *Position) IsOpened(currentPrice fixedpoint.Value) bool {
|
||||
return !p.IsClosed() && !p.IsDust(currentPrice)
|
||||
}
|
||||
|
||||
func (p *Position) Type() PositionType {
|
||||
if p.Base.Sign() > 0 {
|
||||
return PositionLong
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package types
|
||||
|
||||
type Subscriber interface {
|
||||
Subscribe()
|
||||
}
|
Loading…
Reference in New Issue
Block a user