mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #789 from c9s/strategy/pivotshort
strategy: pivotshort: add more improvements for margin
This commit is contained in:
commit
f98cf53e4e
|
@ -8,24 +8,26 @@ import (
|
|||
)
|
||||
|
||||
type RoiStopLoss struct {
|
||||
Symbol string
|
||||
Percentage fixedpoint.Value `json:"percentage"`
|
||||
|
||||
session *ExchangeSession
|
||||
orderExecutor *GeneralOrderExecutor
|
||||
}
|
||||
|
||||
func (s *RoiStopLoss) Subscribe(session *ExchangeSession) {
|
||||
// use 1m kline to handle roi stop
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||
}
|
||||
|
||||
func (s *RoiStopLoss) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
|
||||
position := orderExecutor.Position()
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||
return
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(kline types.KLine) {
|
||||
s.checkStopPrice(kline.Close, position)
|
||||
})
|
||||
}))
|
||||
|
||||
if !IsBackTesting {
|
||||
session.MarketDataStream.OnMarketTrade(func(trade types.Trade) {
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package bbgo
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/dynamic"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func Test_injectField(t *testing.T) {
|
||||
type TT struct {
|
||||
TradeService *service.TradeService
|
||||
}
|
||||
|
||||
// only pointer object can be set.
|
||||
var tt = &TT{}
|
||||
|
||||
// get the value of the pointer, or it can not be set.
|
||||
var rv = reflect.ValueOf(tt).Elem()
|
||||
|
||||
_, ret := dynamic.HasField(rv, "TradeService")
|
||||
assert.True(t, ret)
|
||||
|
||||
ts := &service.TradeService{}
|
||||
|
||||
err := injectField(rv, "TradeService", ts, true)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_parseStructAndInject(t *testing.T) {
|
||||
t.Run("skip nil", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *Environment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := parseStructAndInject(&ss, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, ss.Env)
|
||||
})
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *Environment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := parseStructAndInject(&ss, &Environment{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Env)
|
||||
})
|
||||
|
||||
t.Run("composition", func(t *testing.T) {
|
||||
type TT struct {
|
||||
*service.TradeService
|
||||
}
|
||||
ss := TT{}
|
||||
err := parseStructAndInject(&ss, &service.TradeService{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.TradeService)
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env Environment
|
||||
}{
|
||||
a: 1,
|
||||
}
|
||||
err := parseStructAndInject(&ss, Environment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, time.Time{}, ss.Env.startTime)
|
||||
})
|
||||
t.Run("interface/any", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Any interface{} // anything
|
||||
}{
|
||||
Any: nil,
|
||||
}
|
||||
err := parseStructAndInject(&ss, &Environment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Any)
|
||||
})
|
||||
t.Run("interface/stringer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Stringer types.Stringer // stringer interface
|
||||
}{
|
||||
Stringer: nil,
|
||||
}
|
||||
err := parseStructAndInject(&ss, &types.Trade{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Stringer)
|
||||
})
|
||||
}
|
|
@ -196,7 +196,7 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si
|
|||
return err
|
||||
}
|
||||
|
||||
if err := injectField(rs, "OrderExecutor", orderExecutor, false); err != nil {
|
||||
if err := dynamic.InjectField(rs, "OrderExecutor", orderExecutor, false); err != nil {
|
||||
return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy)
|
||||
}
|
||||
|
||||
|
@ -218,7 +218,7 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si
|
|||
return fmt.Errorf("marketDataStore of symbol %s not found", symbol)
|
||||
}
|
||||
|
||||
if err := parseStructAndInject(strategy,
|
||||
if err := dynamic.ParseStructAndInject(strategy,
|
||||
market,
|
||||
indicatorSet,
|
||||
store,
|
||||
|
@ -401,19 +401,19 @@ func (trader *Trader) injectCommonServices(s interface{}) error {
|
|||
return fmt.Errorf("field Persistence is not a struct element, %s given", field)
|
||||
}
|
||||
|
||||
if err := injectField(elem, "Facade", PersistenceServiceFacade, true); err != nil {
|
||||
if err := dynamic.InjectField(elem, "Facade", PersistenceServiceFacade, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
if err := parseStructAndInject(field.Interface(), persistenceFacade); err != nil {
|
||||
if err := ParseStructAndInject(field.Interface(), persistenceFacade); err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
return parseStructAndInject(s,
|
||||
return dynamic.ParseStructAndInject(s,
|
||||
&trader.logger,
|
||||
Notification,
|
||||
trader.environment.TradeService,
|
||||
|
|
|
@ -51,3 +51,105 @@ func CallStructFieldsMethod(m interface{}, method string, args ...interface{}) e
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallMatch calls the function with the matched argument automatically
|
||||
// you can define multiple parameter factory function to inject the return value as the function argument.
|
||||
// e.g.,
|
||||
// CallMatch(targetFunction, 1, 10, true, func() *ParamType { .... })
|
||||
//
|
||||
func CallMatch(f interface{}, objects ...interface{}) ([]reflect.Value, error) {
|
||||
fv := reflect.ValueOf(f)
|
||||
ft := reflect.TypeOf(f)
|
||||
|
||||
var startIndex = 0
|
||||
var fArgs []reflect.Value
|
||||
|
||||
var factoryParams = findFactoryParams(objects...)
|
||||
|
||||
nextDynamicInputArg:
|
||||
for i := 0; i < ft.NumIn(); i++ {
|
||||
at := ft.In(i)
|
||||
|
||||
// uat == underlying argument type
|
||||
uat := at
|
||||
if at.Kind() == reflect.Ptr {
|
||||
uat = at.Elem()
|
||||
}
|
||||
|
||||
for oi := startIndex; oi < len(objects); oi++ {
|
||||
var obj = objects[oi]
|
||||
var objT = reflect.TypeOf(obj)
|
||||
if objT == at {
|
||||
fArgs = append(fArgs, reflect.ValueOf(obj))
|
||||
startIndex = oi + 1
|
||||
continue nextDynamicInputArg
|
||||
}
|
||||
|
||||
// get the kind of argument
|
||||
switch k := uat.Kind(); k {
|
||||
|
||||
case reflect.Interface:
|
||||
if objT.Implements(at) {
|
||||
fArgs = append(fArgs, reflect.ValueOf(obj))
|
||||
startIndex = oi + 1
|
||||
continue nextDynamicInputArg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// factory param can be reused
|
||||
for _, fp := range factoryParams {
|
||||
fpt := fp.Type()
|
||||
outType := fpt.Out(0)
|
||||
if outType == at {
|
||||
fOut := fp.Call(nil)
|
||||
fArgs = append(fArgs, fOut[0])
|
||||
continue nextDynamicInputArg
|
||||
}
|
||||
}
|
||||
|
||||
fArgs = append(fArgs, reflect.Zero(at))
|
||||
}
|
||||
|
||||
out := fv.Call(fArgs)
|
||||
if ft.NumOut() == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// try to get the error object from the return value (if any)
|
||||
var err error
|
||||
for i := 0; i < ft.NumOut(); i++ {
|
||||
outType := ft.Out(i)
|
||||
switch outType.Kind() {
|
||||
case reflect.Interface:
|
||||
o := out[i].Interface()
|
||||
switch ov := o.(type) {
|
||||
case error:
|
||||
err = ov
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func findFactoryParams(objs ...interface{}) (fs []reflect.Value) {
|
||||
for i := range objs {
|
||||
obj := objs[i]
|
||||
|
||||
objT := reflect.TypeOf(obj)
|
||||
|
||||
if objT.Kind() != reflect.Func {
|
||||
continue
|
||||
}
|
||||
|
||||
if objT.NumOut() == 0 || objT.NumIn() > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fs = append(fs, reflect.ValueOf(obj))
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
|
|
@ -27,3 +27,89 @@ func TestCallStructFieldsMethod(t *testing.T) {
|
|||
err := CallStructFieldsMethod(c, "Subscribe", 10)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
type S struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (s *S) String() string { return s.ID }
|
||||
|
||||
func TestCallMatch(t *testing.T) {
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
f := func(a int, b int) {
|
||||
assert.Equal(t, 1, a)
|
||||
assert.Equal(t, 2, b)
|
||||
}
|
||||
_, err := CallMatch(f, 1, 2)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("interface", func(t *testing.T) {
|
||||
type A interface {
|
||||
String() string
|
||||
}
|
||||
f := func(foo int, a A) {
|
||||
assert.Equal(t, "foo", a.String())
|
||||
}
|
||||
_, err := CallMatch(f, 10, &S{ID: "foo"})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("nil interface", func(t *testing.T) {
|
||||
type A interface {
|
||||
String() string
|
||||
}
|
||||
f := func(foo int, a A) {
|
||||
assert.Equal(t, 10, foo)
|
||||
assert.Nil(t, a)
|
||||
}
|
||||
_, err := CallMatch(f, 10)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("struct pointer", func(t *testing.T) {
|
||||
f := func(foo int, s *S) {
|
||||
assert.Equal(t, 10, foo)
|
||||
assert.NotNil(t, s)
|
||||
}
|
||||
_, err := CallMatch(f, 10, &S{})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("struct pointer x 2", func(t *testing.T) {
|
||||
f := func(foo int, s1, s2 *S) {
|
||||
assert.Equal(t, 10, foo)
|
||||
assert.Equal(t, "s1", s1.String())
|
||||
assert.Equal(t, "s2", s2.String())
|
||||
}
|
||||
_, err := CallMatch(f, 10, &S{ID: "s1"}, &S{ID: "s2"})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("func factory", func(t *testing.T) {
|
||||
f := func(s *S) {
|
||||
assert.Equal(t, "factory", s.String())
|
||||
}
|
||||
_, err := CallMatch(f, func() *S {
|
||||
return &S{ID: "factory"}
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
f := func(s *S) {
|
||||
assert.Nil(t, s)
|
||||
}
|
||||
_, err := CallMatch(f)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("zero struct", func(t *testing.T) {
|
||||
f := func(s S) {
|
||||
assert.Equal(t, S{}, s)
|
||||
}
|
||||
_, err := CallMatch(f)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
package bbgo
|
||||
package dynamic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func injectField(rs reflect.Value, fieldName string, obj interface{}, pointerOnly bool) error {
|
||||
type testEnvironment struct {
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func InjectField(rs reflect.Value, fieldName string, obj interface{}, pointerOnly bool) error {
|
||||
field := rs.FieldByName(fieldName)
|
||||
if !field.IsValid() {
|
||||
return nil
|
||||
|
@ -38,10 +48,10 @@ func injectField(rs reflect.Value, fieldName string, obj interface{}, pointerOnl
|
|||
return nil
|
||||
}
|
||||
|
||||
// parseStructAndInject parses the struct fields and injects the objects into the corresponding fields by its type.
|
||||
// ParseStructAndInject parses the struct fields and injects the objects into the corresponding fields by its type.
|
||||
// if the given object is a reference of an object, the type of the target field MUST BE a pointer field.
|
||||
// if the given object is a struct value, the type of the target field CAN BE a pointer field or a struct value field.
|
||||
func parseStructAndInject(f interface{}, objects ...interface{}) error {
|
||||
func ParseStructAndInject(f interface{}, objects ...interface{}) error {
|
||||
sv := reflect.ValueOf(f)
|
||||
st := reflect.TypeOf(f)
|
||||
|
||||
|
@ -121,3 +131,96 @@ func parseStructAndInject(f interface{}, objects ...interface{}) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_injectField(t *testing.T) {
|
||||
type TT struct {
|
||||
TradeService *service.TradeService
|
||||
}
|
||||
|
||||
// only pointer object can be set.
|
||||
var tt = &TT{}
|
||||
|
||||
// get the value of the pointer, or it can not be set.
|
||||
var rv = reflect.ValueOf(tt).Elem()
|
||||
|
||||
_, ret := HasField(rv, "TradeService")
|
||||
assert.True(t, ret)
|
||||
|
||||
ts := &service.TradeService{}
|
||||
|
||||
err := InjectField(rv, "TradeService", ts, true)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_parseStructAndInject(t *testing.T) {
|
||||
t.Run("skip nil", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, ss.Env)
|
||||
})
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Env)
|
||||
})
|
||||
|
||||
t.Run("composition", func(t *testing.T) {
|
||||
type TT struct {
|
||||
*service.TradeService
|
||||
}
|
||||
ss := TT{}
|
||||
err := ParseStructAndInject(&ss, &service.TradeService{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.TradeService)
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, time.Time{}, ss.Env.startTime)
|
||||
})
|
||||
t.Run("interface/any", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Any interface{} // anything
|
||||
}{
|
||||
Any: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Any)
|
||||
})
|
||||
t.Run("interface/stringer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Stringer types.Stringer // stringer interface
|
||||
}{
|
||||
Stringer: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &types.Trade{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Stringer)
|
||||
})
|
||||
}
|
|
@ -112,7 +112,7 @@ func (it *Interact) handleResponse(session Session, text string, ctxObjects ...i
|
|||
}
|
||||
|
||||
ctxObjects = append(ctxObjects, session)
|
||||
_, err := parseFuncArgsAndCall(f, args, ctxObjects...)
|
||||
_, err := ParseFuncArgsAndCall(f, args, ctxObjects...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func (it *Interact) runCommand(session Session, command string, args []string, c
|
|||
|
||||
ctxObjects = append(ctxObjects, session)
|
||||
session.SetState(cmd.initState)
|
||||
if _, err := parseFuncArgsAndCall(cmd.F, args, ctxObjects...); err != nil {
|
||||
if _, err := ParseFuncArgsAndCall(cmd.F, args, ctxObjects...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ func Test_parseFuncArgsAndCall_NoErrorFunction(t *testing.T) {
|
|||
return nil
|
||||
}
|
||||
|
||||
_, err := parseFuncArgsAndCall(noErrorFunc, []string{"BTCUSDT", "0.123", "true"})
|
||||
_, err := ParseFuncArgsAndCall(noErrorFunc, []string{"BTCUSDT", "0.123", "true"})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ func Test_parseFuncArgsAndCall_ErrorFunction(t *testing.T) {
|
|||
return errors.New("error")
|
||||
}
|
||||
|
||||
_, err := parseFuncArgsAndCall(errorFunc, []string{"BTCUSDT", "0.123"})
|
||||
_, err := ParseFuncArgsAndCall(errorFunc, []string{"BTCUSDT", "0.123"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ func Test_parseFuncArgsAndCall_InterfaceInjection(t *testing.T) {
|
|||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err := parseFuncArgsAndCall(f, []string{"BTCUSDT", "0.123"}, buf)
|
||||
_, err := ParseFuncArgsAndCall(f, []string{"BTCUSDT", "0.123"}, buf)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "123", buf.String())
|
||||
}
|
||||
|
|
|
@ -10,21 +10,20 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{}) (State, error) {
|
||||
func ParseFuncArgsAndCall(f interface{}, args []string, objects ...interface{}) (State, error) {
|
||||
fv := reflect.ValueOf(f)
|
||||
ft := reflect.TypeOf(f)
|
||||
|
||||
argIndex := 0
|
||||
|
||||
var rArgs []reflect.Value
|
||||
for i := 0; i < ft.NumIn(); i++ {
|
||||
at := ft.In(i)
|
||||
|
||||
// get the kind of argument
|
||||
switch k := at.Kind(); k {
|
||||
|
||||
case reflect.Interface:
|
||||
found := false
|
||||
|
||||
for oi := 0; oi < len(objects); oi++ {
|
||||
obj := objects[oi]
|
||||
objT := reflect.TypeOf(obj)
|
||||
|
@ -90,8 +89,8 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
|||
}
|
||||
|
||||
// try to get the error object from the return value
|
||||
var state State
|
||||
var err error
|
||||
var state State
|
||||
for i := 0; i < ft.NumOut(); i++ {
|
||||
outType := ft.Out(i)
|
||||
switch outType.Kind() {
|
||||
|
@ -107,7 +106,6 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
|||
err = ov
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return state, err
|
||||
|
|
146
pkg/strategy/pivotshort/resistance.go
Normal file
146
pkg/strategy/pivotshort/resistance.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package pivotshort
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
MinDistance fixedpoint.Value `json:"minDistance"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
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
|
||||
|
||||
activeOrders *bbgo.ActiveOrderBook
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
store, _ := session.MarketDataStore(s.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
|
||||
if lastKLine != nil {
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(kline.Close) {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
|
||||
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 {
|
||||
bbgo.Notify("Found next resistance price: %f", nextResistancePrice.Float64())
|
||||
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 s.activeOrders.NumOfOrders() > 0 {
|
||||
if err := s.orderExecutor.GracefulCancelActiveOrderBook(ctx, s.activeOrders); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.activeOrders.Orders())
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
MarginSideEffect: types.SideEffectTypeMarginBuy,
|
||||
})
|
||||
|
||||
// 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.activeOrders.Add(createdOrders...)
|
||||
}
|
|
@ -48,143 +48,6 @@ type BreakLow struct {
|
|||
StopEMA *types.IntervalWindow `json:"stopEMA"`
|
||||
}
|
||||
|
||||
type ResistanceShort struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Symbol string `json:"-"`
|
||||
Market types.Market `json:"-"`
|
||||
|
||||
types.IntervalWindow
|
||||
|
||||
MinDistance fixedpoint.Value `json:"minDistance"`
|
||||
NumLayers int `json:"numLayers"`
|
||||
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
|
||||
|
||||
activeOrders *bbgo.ActiveOrderBook
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||
s.session = session
|
||||
s.orderExecutor = orderExecutor
|
||||
s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol)
|
||||
s.activeOrders.BindStream(session.UserDataStream)
|
||||
|
||||
store, _ := session.MarketDataStore(s.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
|
||||
if lastKLine != nil {
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
position := s.orderExecutor.Position()
|
||||
if position.IsOpened(kline.Close) {
|
||||
return
|
||||
}
|
||||
|
||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
|
||||
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 {
|
||||
bbgo.Notify("Found next resistance price: %f", nextResistancePrice.Float64())
|
||||
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.activeOrders.Orders()...); err != nil {
|
||||
log.WithError(err).Errorf("can not cancel resistance orders: %+v", s.activeOrders.Orders())
|
||||
}
|
||||
|
||||
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.activeOrders.Add(createdOrders...)
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Environment *bbgo.Environment
|
||||
Symbol string `json:"symbol"`
|
||||
|
@ -302,11 +165,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
preloadPivot(s.pivot, store)
|
||||
|
||||
// update pivot low data
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||
lastLow := fixedpoint.NewFromFloat(s.pivot.LastLow())
|
||||
if lastLow.IsZero() {
|
||||
return
|
||||
|
@ -318,7 +177,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
s.lastLow = lastLow
|
||||
s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow)
|
||||
})
|
||||
}))
|
||||
|
||||
if s.BreakLow.StopEMA != nil {
|
||||
s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA)
|
||||
|
@ -332,17 +191,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.ResistanceShort.Bind(session, s.orderExecutor)
|
||||
}
|
||||
|
||||
// Always check whether you can open a short position or not
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(kline types.KLine) {
|
||||
if s.Status != types.StrategyStatusRunning {
|
||||
return
|
||||
}
|
||||
|
||||
if kline.Symbol != s.Symbol || kline.Interval != types.Interval1m {
|
||||
return
|
||||
}
|
||||
|
||||
if !s.Position.IsClosed() && !s.Position.IsDust(kline.Close) {
|
||||
if s.Position.IsOpened(kline.Close) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -368,8 +222,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
|
||||
// we need the price cross the break line
|
||||
// or we do nothing
|
||||
// we need the price cross the break line or we do nothing
|
||||
if !(openPrice.Compare(breakPrice) > 0 && closePrice.Compare(breakPrice) < 0) {
|
||||
return
|
||||
}
|
||||
|
@ -390,6 +243,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}
|
||||
|
||||
// graceful cancel all active orders
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
|
||||
quantity := s.useQuantityOrBaseBalance(s.BreakLow.Quantity)
|
||||
|
@ -402,7 +256,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
bbgo.Notify("%s price %f breaks the previous low %f with ratio %f, submitting limit sell @ %f", s.Symbol, kline.Close.Float64(), previousLow.Float64(), s.BreakLow.Ratio.Float64(), sellPrice.Float64())
|
||||
s.placeLimitSell(ctx, sellPrice, quantity, "breakLowLimit")
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
if !bbgo.IsBackTesting {
|
||||
// use market trade to submit short order
|
||||
|
@ -440,6 +294,10 @@ func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, quant
|
|||
}
|
||||
|
||||
func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value {
|
||||
if s.session.Margin || s.session.IsolatedMargin || s.session.Futures || s.session.IsolatedFutures {
|
||||
return quantity
|
||||
}
|
||||
|
||||
balance, hasBalance := s.session.Account.Balance(s.Market.BaseCurrency)
|
||||
|
||||
if hasBalance {
|
||||
|
|
|
@ -604,3 +604,14 @@ func (k *KLineSeries) Length() int {
|
|||
}
|
||||
|
||||
var _ Series = &KLineSeries{}
|
||||
|
||||
type KLineCallBack func(k KLine)
|
||||
|
||||
func KLineWith(symbol string, interval Interval, callback KLineCallBack) KLineCallBack {
|
||||
return func(k KLine) {
|
||||
if k.Symbol != symbol || k.Interval != interval {
|
||||
return
|
||||
}
|
||||
callback(k)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user