mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
fix: drift bias on long entry position condition, make cancel faster
This commit is contained in:
parent
55704fdd21
commit
f2d37650a5
|
@ -16,6 +16,8 @@ exchangeStrategies:
|
||||||
interval: 15m
|
interval: 15m
|
||||||
window: 3
|
window: 3
|
||||||
stoploss: 2%
|
stoploss: 2%
|
||||||
|
source: hl2
|
||||||
|
predictOffset: 5
|
||||||
#exits:
|
#exits:
|
||||||
#- roiStopLoss:
|
#- roiStopLoss:
|
||||||
# percentage: 0.8%
|
# percentage: 0.8%
|
||||||
|
@ -48,7 +50,7 @@ sync:
|
||||||
- ETHUSDT
|
- ETHUSDT
|
||||||
|
|
||||||
backtest:
|
backtest:
|
||||||
startTime: "2022-04-01"
|
startTime: "2022-01-01"
|
||||||
endTime: "2022-06-18"
|
endTime: "2022-06-18"
|
||||||
symbols:
|
symbols:
|
||||||
- ETHUSDT
|
- ETHUSDT
|
||||||
|
|
|
@ -114,6 +114,9 @@ func (e *GeneralOrderExecutor) SubmitOrders(ctx context.Context, submitOrders ..
|
||||||
|
|
||||||
// GracefulCancelActiveOrderBook cancels the orders from the active orderbook.
|
// GracefulCancelActiveOrderBook cancels the orders from the active orderbook.
|
||||||
func (e *GeneralOrderExecutor) GracefulCancelActiveOrderBook(ctx context.Context, activeOrders *ActiveOrderBook) error {
|
func (e *GeneralOrderExecutor) GracefulCancelActiveOrderBook(ctx context.Context, activeOrders *ActiveOrderBook) error {
|
||||||
|
if activeOrders.NumOfOrders() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err := activeOrders.GracefulCancel(ctx, e.session.Exchange); err != nil {
|
if err := activeOrders.GracefulCancel(ctx, e.session.Exchange); err != nil {
|
||||||
return fmt.Errorf("graceful cancel order error: %w", err)
|
return fmt.Errorf("graceful cancel order error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Drift struct {
|
||||||
types.IntervalWindow
|
types.IntervalWindow
|
||||||
chng *types.Queue
|
chng *types.Queue
|
||||||
Values types.Float64Slice
|
Values types.Float64Slice
|
||||||
SMA *SMA
|
MA types.UpdatableSeriesExtend
|
||||||
LastValue float64
|
LastValue float64
|
||||||
|
|
||||||
UpdateCallbacks []func(value float64)
|
UpdateCallbacks []func(value float64)
|
||||||
|
@ -24,7 +24,9 @@ type Drift struct {
|
||||||
func (inc *Drift) Update(value float64) {
|
func (inc *Drift) Update(value float64) {
|
||||||
if inc.chng == nil {
|
if inc.chng == nil {
|
||||||
inc.SeriesBase.Series = inc
|
inc.SeriesBase.Series = inc
|
||||||
inc.SMA = &SMA{IntervalWindow: types.IntervalWindow{Interval: inc.Interval, Window: inc.Window}}
|
if inc.MA == nil {
|
||||||
|
inc.MA = &SMA{IntervalWindow: types.IntervalWindow{Interval: inc.Interval, Window: inc.Window}}
|
||||||
|
}
|
||||||
inc.chng = types.NewQueue(inc.Window)
|
inc.chng = types.NewQueue(inc.Window)
|
||||||
inc.LastValue = value
|
inc.LastValue = value
|
||||||
return
|
return
|
||||||
|
@ -36,11 +38,11 @@ func (inc *Drift) Update(value float64) {
|
||||||
chng = math.Log(value / inc.LastValue)
|
chng = math.Log(value / inc.LastValue)
|
||||||
inc.LastValue = value
|
inc.LastValue = value
|
||||||
}
|
}
|
||||||
inc.SMA.Update(chng)
|
inc.MA.Update(chng)
|
||||||
inc.chng.Update(chng)
|
inc.chng.Update(chng)
|
||||||
if inc.chng.Length() >= inc.Window {
|
if inc.chng.Length() >= inc.Window {
|
||||||
stdev := types.Stdev(inc.chng, inc.Window)
|
stdev := types.Stdev(inc.chng, inc.Window)
|
||||||
drift := inc.SMA.Last() - stdev*stdev*0.5
|
drift := inc.MA.Last() - stdev*stdev*0.5
|
||||||
inc.Values.Push(drift)
|
inc.Values.Push(drift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +52,7 @@ func (inc *Drift) Clone() (out *Drift) {
|
||||||
IntervalWindow: inc.IntervalWindow,
|
IntervalWindow: inc.IntervalWindow,
|
||||||
chng: inc.chng.Clone(),
|
chng: inc.chng.Clone(),
|
||||||
Values: inc.Values[:],
|
Values: inc.Values[:],
|
||||||
SMA: inc.SMA.Clone().(*SMA),
|
MA: types.Clone(inc.MA),
|
||||||
LastValue: inc.LastValue,
|
LastValue: inc.LastValue,
|
||||||
}
|
}
|
||||||
out.SeriesBase.Series = out
|
out.SeriesBase.Series = out
|
||||||
|
|
|
@ -2,10 +2,14 @@ package drift
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
"github.com/wcharczuk/go-chart/v2"
|
||||||
|
|
||||||
|
@ -19,11 +23,17 @@ import (
|
||||||
const ID = "drift"
|
const ID = "drift"
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
var Four fixedpoint.Value = fixedpoint.NewFromInt(4)
|
||||||
|
var Three fixedpoint.Value = fixedpoint.NewFromInt(3)
|
||||||
|
var Two fixedpoint.Value = fixedpoint.NewFromInt(2)
|
||||||
|
var Delta fixedpoint.Value = fixedpoint.NewFromFloat(0.01)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SourceFunc func(*types.KLine) fixedpoint.Value
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
|
|
||||||
|
@ -41,13 +51,30 @@ type Strategy struct {
|
||||||
midPrice fixedpoint.Value
|
midPrice fixedpoint.Value
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
Stoploss fixedpoint.Value `json:"stoploss"`
|
Source string `json:"source"`
|
||||||
CanvasPath string `json:"canvasPath"`
|
Stoploss fixedpoint.Value `json:"stoploss"`
|
||||||
|
CanvasPath string `json:"canvasPath"`
|
||||||
|
PredictOffset int `json:"predictOffset"`
|
||||||
|
|
||||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
Session *bbgo.ExchangeSession
|
Session *bbgo.ExchangeSession
|
||||||
*bbgo.GeneralOrderExecutor
|
*bbgo.GeneralOrderExecutor
|
||||||
*bbgo.ActiveOrderBook
|
|
||||||
|
getLastPrice func() fixedpoint.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) Print() {
|
||||||
|
b, _ := json.MarshalIndent(s.ExitMethods, " ", " ")
|
||||||
|
hiyellow := color.New(color.FgHiYellow).FprintfFunc()
|
||||||
|
hiyellow(os.Stderr, "------ %s Settings ------\n", s.InstanceID())
|
||||||
|
hiyellow(os.Stderr, "canvasPath: %s\n", s.CanvasPath)
|
||||||
|
hiyellow(os.Stderr, "source: %s\n", s.Source)
|
||||||
|
hiyellow(os.Stderr, "stoploss: %v\n", s.Stoploss)
|
||||||
|
hiyellow(os.Stderr, "predictOffset: %d\n", s.PredictOffset)
|
||||||
|
hiyellow(os.Stderr, "exits:\n %s\n", string(b))
|
||||||
|
hiyellow(os.Stderr, "symbol: %s\n", s.Symbol)
|
||||||
|
hiyellow(os.Stderr, "interval: %s\n", s.Interval)
|
||||||
|
hiyellow(os.Stderr, "window: %d\n", s.Window)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) ID() string {
|
func (s *Strategy) ID() string {
|
||||||
|
@ -72,35 +99,6 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
s.ExitMethods.SetAndSubscribe(session, s)
|
s.ExitMethods.SetAndSubscribe(session, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Three fixedpoint.Value = fixedpoint.NewFromInt(3)
|
|
||||||
var Two fixedpoint.Value = fixedpoint.NewFromInt(2)
|
|
||||||
|
|
||||||
func (s *Strategy) GetLastPrice() (lastPrice fixedpoint.Value) {
|
|
||||||
var ok bool
|
|
||||||
if s.Environment.IsBackTesting() {
|
|
||||||
lastPrice, ok = s.Session.LastPrice(s.Symbol)
|
|
||||||
if !ok {
|
|
||||||
log.Error("cannot get lastprice")
|
|
||||||
return lastPrice
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s.lock.RLock()
|
|
||||||
if s.midPrice.IsZero() {
|
|
||||||
lastPrice, ok = s.Session.LastPrice(s.Symbol)
|
|
||||||
if !ok {
|
|
||||||
log.Error("cannot get lastprice")
|
|
||||||
return lastPrice
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastPrice = s.midPrice
|
|
||||||
}
|
|
||||||
s.lock.RUnlock()
|
|
||||||
}
|
|
||||||
return lastPrice
|
|
||||||
}
|
|
||||||
|
|
||||||
var Delta fixedpoint.Value = fixedpoint.NewFromFloat(0.01)
|
|
||||||
|
|
||||||
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
||||||
order := s.Position.NewMarketCloseOrder(fixedpoint.One)
|
order := s.Position.NewMarketCloseOrder(fixedpoint.One)
|
||||||
if order == nil {
|
if order == nil {
|
||||||
|
@ -109,7 +107,7 @@ func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
||||||
order.TimeInForce = ""
|
order.TimeInForce = ""
|
||||||
balances := s.Session.GetAccount().Balances()
|
balances := s.Session.GetAccount().Balances()
|
||||||
baseBalance := balances[s.Market.BaseCurrency].Available
|
baseBalance := balances[s.Market.BaseCurrency].Available
|
||||||
price := s.GetLastPrice()
|
price := s.getLastPrice()
|
||||||
if order.Side == types.SideTypeBuy {
|
if order.Side == types.SideTypeBuy {
|
||||||
quoteAmount := balances[s.Market.QuoteCurrency].Available.Div(price)
|
quoteAmount := balances[s.Market.QuoteCurrency].Available.Div(price)
|
||||||
if order.Quantity.Compare(quoteAmount) > 0 {
|
if order.Quantity.Compare(quoteAmount) > 0 {
|
||||||
|
@ -131,6 +129,38 @@ func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) SourceFuncGenerator() SourceFunc {
|
||||||
|
switch strings.ToLower(s.Source) {
|
||||||
|
case "close":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value { return kline.Close }
|
||||||
|
case "high":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value { return kline.High }
|
||||||
|
case "low":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value { return kline.Low }
|
||||||
|
case "hl2":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value {
|
||||||
|
return kline.High.Add(kline.Low).Div(Two)
|
||||||
|
}
|
||||||
|
case "hlc3":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value {
|
||||||
|
return kline.High.Add(kline.Low).Add(kline.Close).Div(Three)
|
||||||
|
}
|
||||||
|
case "ohlc4":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value {
|
||||||
|
return kline.Open.Add(kline.High).Add(kline.Low).Add(kline.Close).Div(Four)
|
||||||
|
}
|
||||||
|
case "open":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value { return kline.Open }
|
||||||
|
case "":
|
||||||
|
return func(kline *types.KLine) fixedpoint.Value {
|
||||||
|
log.Infof("source not set, use hl2 by default")
|
||||||
|
return kline.High.Add(kline.Low).Div(Two)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unable to parse: %s", s.Source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
instanceID := s.InstanceID()
|
instanceID := s.InstanceID()
|
||||||
if s.Position == nil {
|
if s.Position == nil {
|
||||||
|
@ -168,18 +198,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
for _, method := range s.ExitMethods {
|
for _, method := range s.ExitMethods {
|
||||||
method.Bind(session, s.GeneralOrderExecutor)
|
method.Bind(session, s.GeneralOrderExecutor)
|
||||||
}
|
}
|
||||||
s.ActiveOrderBook = bbgo.NewActiveOrderBook(s.Symbol)
|
|
||||||
s.ActiveOrderBook.BindStream(session.UserDataStream)
|
|
||||||
|
|
||||||
store, _ := session.MarketDataStore(s.Symbol)
|
store, _ := session.MarketDataStore(s.Symbol)
|
||||||
|
|
||||||
getSource := func(kline *types.KLine) fixedpoint.Value {
|
getSource := s.SourceFuncGenerator()
|
||||||
//return kline.High.Add(kline.Low).Div(Two)
|
|
||||||
//return kline.Close
|
|
||||||
return kline.High.Add(kline.Low).Add(kline.Close).Div(Three)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.drift = &indicator.Drift{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.Window}}
|
s.drift = &indicator.Drift{
|
||||||
|
MA: &indicator.SMA{IntervalWindow: s.IntervalWindow},
|
||||||
|
IntervalWindow: s.IntervalWindow,
|
||||||
|
}
|
||||||
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}}
|
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}}
|
||||||
|
|
||||||
klines, ok := store.KLinesOfInterval(s.Interval)
|
klines, ok := store.KLinesOfInterval(s.Interval)
|
||||||
|
@ -187,33 +214,57 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
log.Errorf("klines not exists")
|
log.Errorf("klines not exists")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamicKLine := &types.KLine{}
|
||||||
for _, kline := range *klines {
|
for _, kline := range *klines {
|
||||||
source := getSource(&kline).Float64()
|
source := getSource(&kline).Float64()
|
||||||
s.drift.Update(source)
|
s.drift.Update(source)
|
||||||
s.atr.Update(kline.High.Float64(), kline.Low.Float64(), kline.Close.Float64())
|
s.atr.Update(kline.High.Float64(), kline.Low.Float64(), kline.Close.Float64())
|
||||||
}
|
}
|
||||||
|
|
||||||
session.MarketDataStream.OnBookTickerUpdate(func(ticker types.BookTicker) {
|
if s.Environment.IsBackTesting() {
|
||||||
if s.Environment.IsBackTesting() {
|
s.getLastPrice = func() fixedpoint.Value {
|
||||||
return
|
lastPrice, ok := s.Session.LastPrice(s.Symbol)
|
||||||
}
|
if !ok {
|
||||||
bestBid := ticker.Buy
|
log.Error("cannot get lastprice")
|
||||||
bestAsk := ticker.Sell
|
|
||||||
|
|
||||||
if util.TryLock(&s.lock) {
|
|
||||||
if !bestAsk.IsZero() && !bestBid.IsZero() {
|
|
||||||
s.midPrice = bestAsk.Add(bestBid).Div(types.Two)
|
|
||||||
} else if !bestAsk.IsZero() {
|
|
||||||
s.midPrice = bestAsk
|
|
||||||
} else {
|
|
||||||
s.midPrice = bestBid
|
|
||||||
}
|
}
|
||||||
s.lock.Unlock()
|
return lastPrice
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
|
session.MarketDataStream.OnBookTickerUpdate(func(ticker types.BookTicker) {
|
||||||
|
bestBid := ticker.Buy
|
||||||
|
bestAsk := ticker.Sell
|
||||||
|
|
||||||
|
if util.TryLock(&s.lock) {
|
||||||
|
if !bestAsk.IsZero() && !bestBid.IsZero() {
|
||||||
|
s.midPrice = bestAsk.Add(bestBid).Div(Two)
|
||||||
|
} else if !bestAsk.IsZero() {
|
||||||
|
s.midPrice = bestAsk
|
||||||
|
} else {
|
||||||
|
s.midPrice = bestBid
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.getLastPrice = func() (lastPrice fixedpoint.Value) {
|
||||||
|
var ok bool
|
||||||
|
s.lock.RLock()
|
||||||
|
if s.midPrice.IsZero() {
|
||||||
|
lastPrice, ok = s.Session.LastPrice(s.Symbol)
|
||||||
|
if !ok {
|
||||||
|
log.Error("cannot get lastprice")
|
||||||
|
return lastPrice
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastPrice = s.midPrice
|
||||||
|
}
|
||||||
|
s.lock.RUnlock()
|
||||||
|
return lastPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dynamicKLine := &types.KLine{}
|
|
||||||
priceLine := types.NewQueue(100)
|
priceLine := types.NewQueue(100)
|
||||||
|
stoploss := s.Stoploss.Float64()
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
if s.Status != types.StrategyStatusRunning {
|
if s.Status != types.StrategyStatusRunning {
|
||||||
|
@ -236,15 +287,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
source := getSource(dynamicKLine)
|
source := getSource(dynamicKLine)
|
||||||
sourcef := source.Float64()
|
sourcef := source.Float64()
|
||||||
priceLine.Update(sourcef)
|
priceLine.Update(sourcef)
|
||||||
dynamicKLine.Closed = false
|
|
||||||
s.drift.Update(sourcef)
|
s.drift.Update(sourcef)
|
||||||
drift = s.drift.Array(2)
|
drift = s.drift.Array(2)
|
||||||
driftPred = s.drift.Predict(3)
|
driftPred = s.drift.Predict(s.PredictOffset)
|
||||||
atr = s.atr.Last()
|
atr = s.atr.Last()
|
||||||
price := s.GetLastPrice()
|
price := s.getLastPrice()
|
||||||
pricef := price.Float64()
|
pricef := price.Float64()
|
||||||
avg := s.Position.AverageCost.Float64()
|
avg := s.Position.AverageCost.Float64()
|
||||||
stoploss := s.Stoploss.Float64()
|
|
||||||
|
|
||||||
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
||||||
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
||||||
|
@ -253,30 +302,17 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef) &&
|
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef) &&
|
||||||
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
||||||
|
|
||||||
if exitShortCondition {
|
if exitShortCondition || exitLongCondition {
|
||||||
if s.ActiveOrderBook.NumOfOrders() > 0 {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, _ = s.ClosePosition(ctx)
|
|
||||||
}
|
|
||||||
if exitLongCondition {
|
|
||||||
if s.ActiveOrderBook.NumOfOrders() > 0 {
|
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
|
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_, _ = s.ClosePosition(ctx)
|
_, _ = s.ClosePosition(ctx)
|
||||||
}
|
}
|
||||||
if shortCondition {
|
if shortCondition {
|
||||||
if s.ActiveOrderBook.NumOfOrders() > 0 {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency)
|
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -295,7 +331,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
Side: types.SideTypeSell,
|
Side: types.SideTypeSell,
|
||||||
Type: types.OrderTypeLimitMaker,
|
Type: types.OrderTypeLimitMaker,
|
||||||
Price: source,
|
Price: source,
|
||||||
StopPrice: fixedpoint.NewFromFloat(sourcef + atr/2),
|
StopPrice: fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss))),
|
||||||
Quantity: baseBalance.Available,
|
Quantity: baseBalance.Available,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -304,11 +340,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if longCondition {
|
if longCondition {
|
||||||
if s.ActiveOrderBook.NumOfOrders() > 0 {
|
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||||
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
|
log.WithError(err).Errorf("cannot cancel orders")
|
||||||
log.WithError(err).Errorf("cannot cancel orders")
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if source.Compare(price) > 0 {
|
if source.Compare(price) > 0 {
|
||||||
source = price
|
source = price
|
||||||
|
@ -322,15 +356,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
quoteBalance.Available.Div(source), source) {
|
quoteBalance.Available.Div(source), source) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.Position.IsClosed() && !s.Position.IsDust(source) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: s.Symbol,
|
Symbol: s.Symbol,
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
Type: types.OrderTypeLimitMaker,
|
Type: types.OrderTypeLimitMaker,
|
||||||
Price: source,
|
Price: source,
|
||||||
StopPrice: fixedpoint.NewFromFloat(sourcef - atr/2),
|
StopPrice: fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss))),
|
||||||
Quantity: quoteBalance.Available.Div(source),
|
Quantity: quoteBalance.Available.Div(source),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -342,8 +373,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||||
|
s.Print()
|
||||||
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
||||||
fmt.Println(dynamicKLine.StartTime, dynamicKLine.EndTime)
|
|
||||||
mean := priceLine.Mean(100)
|
mean := priceLine.Mean(100)
|
||||||
highestPrice := priceLine.Minus(mean).Highest(100)
|
highestPrice := priceLine.Minus(mean).Highest(100)
|
||||||
highestDrift := s.drift.Highest(100)
|
highestDrift := s.drift.Highest(100)
|
||||||
|
|
|
@ -3,11 +3,11 @@ package types
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gonum.org/v1/gonum/stat"
|
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
"github.com/wcharczuk/go-chart/v2"
|
||||||
|
"gonum.org/v1/gonum/stat"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Super basic Series type that simply holds the float64 data
|
// Super basic Series type that simply holds the float64 data
|
||||||
|
@ -46,8 +46,8 @@ func (inc *Queue) Length() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *Queue) Clone() *Queue {
|
func (inc *Queue) Clone() *Queue {
|
||||||
out := &Queue {
|
out := &Queue{
|
||||||
arr: inc.arr[:],
|
arr: inc.arr[:],
|
||||||
size: inc.size,
|
size: inc.size,
|
||||||
}
|
}
|
||||||
out.SeriesBase.Series = out
|
out.SeriesBase.Series = out
|
||||||
|
@ -213,7 +213,7 @@ func Abs(a Series) SeriesExtend {
|
||||||
|
|
||||||
var _ Series = &AbsResult{}
|
var _ Series = &AbsResult{}
|
||||||
|
|
||||||
func Predict(a Series, lookback int, offset ...int) float64 {
|
func LinearRegression(a Series, lookback int) (alpha float64, beta float64) {
|
||||||
if a.Length() < lookback {
|
if a.Length() < lookback {
|
||||||
lookback = a.Length()
|
lookback = a.Length()
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,12 @@ func Predict(a Series, lookback int, offset ...int) float64 {
|
||||||
x[i] = float64(i)
|
x[i] = float64(i)
|
||||||
y[i] = a.Index(i)
|
y[i] = a.Index(i)
|
||||||
}
|
}
|
||||||
alpha, beta := stat.LinearRegression(x, y, weights, false)
|
alpha, beta = stat.LinearRegression(x, y, weights, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Predict(a Series, lookback int, offset ...int) float64 {
|
||||||
|
alpha, beta := LinearRegression(a, lookback)
|
||||||
o := -1.0
|
o := -1.0
|
||||||
if len(offset) > 0 {
|
if len(offset) > 0 {
|
||||||
o = -float64(offset[0])
|
o = -float64(offset[0])
|
||||||
|
@ -1167,15 +1172,15 @@ type Canvas struct {
|
||||||
|
|
||||||
func NewCanvas(title string, interval Interval) *Canvas {
|
func NewCanvas(title string, interval Interval) *Canvas {
|
||||||
valueFormatter := chart.TimeValueFormatter
|
valueFormatter := chart.TimeValueFormatter
|
||||||
if interval.Minutes() > 24 * 60 {
|
if interval.Minutes() > 24*60 {
|
||||||
valueFormatter = chart.TimeDateValueFormatter
|
valueFormatter = chart.TimeDateValueFormatter
|
||||||
} else if interval.Minutes() > 60 {
|
} else if interval.Minutes() > 60 {
|
||||||
valueFormatter = chart.TimeHourValueFormatter
|
valueFormatter = chart.TimeHourValueFormatter
|
||||||
} else {
|
} else {
|
||||||
valueFormatter = chart.TimeMinuteValueFormatter
|
valueFormatter = chart.TimeMinuteValueFormatter
|
||||||
}
|
}
|
||||||
out := &Canvas {
|
out := &Canvas{
|
||||||
Chart: chart.Chart {
|
Chart: chart.Chart{
|
||||||
Title: title,
|
Title: title,
|
||||||
XAxis: chart.XAxis{
|
XAxis: chart.XAxis{
|
||||||
ValueFormatter: valueFormatter,
|
ValueFormatter: valueFormatter,
|
||||||
|
@ -1193,11 +1198,11 @@ func (canvas *Canvas) Plot(tag string, a Series, endTime Time, length int) {
|
||||||
var timeline []time.Time
|
var timeline []time.Time
|
||||||
e := endTime.Time()
|
e := endTime.Time()
|
||||||
for i := length - 1; i >= 0; i-- {
|
for i := length - 1; i >= 0; i-- {
|
||||||
shiftedT := e.Add(-time.Duration(i * canvas.Interval.Minutes()) * time.Minute)
|
shiftedT := e.Add(-time.Duration(i*canvas.Interval.Minutes()) * time.Minute)
|
||||||
timeline = append(timeline, shiftedT)
|
timeline = append(timeline, shiftedT)
|
||||||
}
|
}
|
||||||
canvas.Series = append(canvas.Series, chart.TimeSeries{
|
canvas.Series = append(canvas.Series, chart.TimeSeries{
|
||||||
Name: tag,
|
Name: tag,
|
||||||
YValues: Reverse(a, length),
|
YValues: Reverse(a, length),
|
||||||
XValues: timeline,
|
XValues: timeline,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user