mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
fix: drift exit condition, trade_stats serialization in redis
This commit is contained in:
parent
a5039de6aa
commit
a8fe20ae3a
|
@ -1,8 +1,14 @@
|
|||
---
|
||||
persistence:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
db: 0
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
futures: true
|
||||
futures: false
|
||||
envVarPrefix: binance
|
||||
heikinAshi: false
|
||||
|
||||
|
@ -15,13 +21,15 @@ exchangeStrategies:
|
|||
# kline interval for indicators
|
||||
interval: 15m
|
||||
window: 2
|
||||
stoploss: 3%
|
||||
stoploss: 2%
|
||||
source: close
|
||||
predictOffset: 14
|
||||
predictOffset: 3
|
||||
# position avg +- takeProfitFactor * atr as take profit price
|
||||
takeProfitFactor: 1
|
||||
noStopPrice: true
|
||||
noTrailingStopLoss: false
|
||||
# stddev on high/low-source
|
||||
hlVarianceMultiplier: 0.35
|
||||
hlVarianceMultiplier: 0.34
|
||||
|
||||
generateGraph: true
|
||||
graphPNLDeductFee: false
|
||||
|
|
|
@ -57,6 +57,7 @@ type Strategy struct {
|
|||
lock sync.RWMutex
|
||||
|
||||
Source string `json:"source"`
|
||||
TakeProfitFactor float64 `json:"takeProfitFactor"`
|
||||
StopLoss fixedpoint.Value `json:"stoploss"`
|
||||
CanvasPath string `json:"canvasPath"`
|
||||
PredictOffset int `json:"predictOffset"`
|
||||
|
@ -72,7 +73,7 @@ type Strategy struct {
|
|||
// Whether to generate graph when shutdown
|
||||
GenerateGraph bool `json:"generateGraph"`
|
||||
|
||||
StopOrders map[uint64]types.SubmitOrder
|
||||
StopOrders map[uint64]*types.SubmitOrder
|
||||
|
||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||
Session *bbgo.ExchangeSession
|
||||
|
@ -91,6 +92,7 @@ func (s *Strategy) Print(o *os.File) {
|
|||
hiyellow(f, "canvasPath: %s\n", s.CanvasPath)
|
||||
hiyellow(f, "source: %s\n", s.Source)
|
||||
hiyellow(f, "stoploss: %v\n", s.StopLoss)
|
||||
hiyellow(f, "takeProfitFactor: %f\n", s.TakeProfitFactor)
|
||||
hiyellow(f, "predictOffset: %d\n", s.PredictOffset)
|
||||
hiyellow(f, "exits:\n %s\n", string(b))
|
||||
hiyellow(f, "symbol: %s\n", s.Symbol)
|
||||
|
@ -124,12 +126,23 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
s.ExitMethods.SetAndSubscribe(session, s)
|
||||
}
|
||||
|
||||
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
||||
// Cleanup pending StopOrders
|
||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
||||
order := s.Position.NewMarketCloseOrder(fixedpoint.One)
|
||||
func (s *Strategy) CurrentPosition() *types.Position {
|
||||
return s.Position
|
||||
}
|
||||
|
||||
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
|
||||
order := s.Position.NewMarketCloseOrder(percentage)
|
||||
if order == nil {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
if percentage.Compare(fixedpoint.One) == 0 {
|
||||
// Cleanup pending StopOrders
|
||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||
} else {
|
||||
// Should only have one stop order
|
||||
for _, o := range s.StopOrders {
|
||||
o.Quantity = o.Quantity.Mul(fixedpoint.One.Sub(percentage))
|
||||
}
|
||||
}
|
||||
order.Tag = "close"
|
||||
order.TimeInForce = ""
|
||||
|
@ -146,14 +159,14 @@ func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
|
|||
}
|
||||
for {
|
||||
if s.Market.IsDustQuantity(order.Quantity, price) {
|
||||
return nil, true
|
||||
return nil
|
||||
}
|
||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order)
|
||||
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *order)
|
||||
if err != nil {
|
||||
order.Quantity = order.Quantity.Mul(fixedpoint.One.Sub(Delta))
|
||||
continue
|
||||
}
|
||||
return &createdOrders[0], true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +203,7 @@ func (s *Strategy) SourceFuncGenerator() SourceFunc {
|
|||
}
|
||||
|
||||
func (s *Strategy) BindStopLoss(ctx context.Context) {
|
||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||
s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
||||
if len(s.StopOrders) == 0 {
|
||||
return
|
||||
|
@ -222,7 +235,7 @@ func (s *Strategy) BindStopLoss(ctx context.Context) {
|
|||
}
|
||||
o.Quantity = baseBalance.Available
|
||||
}
|
||||
if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, o); err != nil {
|
||||
if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *o); err != nil {
|
||||
log.WithError(err).Errorf("cannot send stop order: %v", order)
|
||||
}
|
||||
}
|
||||
|
@ -295,16 +308,16 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
|
|||
atr = s.atr.Last()
|
||||
avg = s.Position.AverageCost.Float64()
|
||||
stoploss = s.StopLoss.Float64()
|
||||
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
||||
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
||||
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) &&
|
||||
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||
exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef) &&
|
||||
(!s.Position.IsLong() && !s.Position.IsDust(price))
|
||||
if exitShortCondition || exitLongCondition {
|
||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
return
|
||||
}
|
||||
_, _ = s.ClosePosition(ctx)
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -360,11 +373,12 @@ func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit ty
|
|||
}
|
||||
f, err = os.Create(s.GraphPNLPath)
|
||||
if err != nil {
|
||||
panic("open pnl")
|
||||
log.WithError(err).Errorf("open pnl")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||
panic("render pnl")
|
||||
log.WithError(err).Errorf("render pnl")
|
||||
}
|
||||
|
||||
canvas = types.NewCanvas(s.InstanceID())
|
||||
|
@ -375,11 +389,12 @@ func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit ty
|
|||
}
|
||||
f, err = os.Create(s.GraphCumPNLPath)
|
||||
if err != nil {
|
||||
panic("open cumpnl")
|
||||
log.WithError(err).Errorf("open cumpnl")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||
panic("render cumpnl")
|
||||
log.WithError(err).Errorf("render cumpnl")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,7 +425,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
s.OnEmergencyStop(func() {
|
||||
_ = s.GeneralOrderExecutor.GracefulCancel(ctx)
|
||||
_, _ = s.ClosePosition(ctx)
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
})
|
||||
|
||||
s.GeneralOrderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||
|
@ -544,16 +559,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
highf := math.Max(kline.High.Float64(), pricef)
|
||||
avg := s.Position.AverageCost.Float64()
|
||||
|
||||
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
||||
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(price))
|
||||
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
||||
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||
exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
||||
(s.Position.IsLong() && !s.Position.IsDust(price))
|
||||
if exitShortCondition || exitLongCondition {
|
||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
return
|
||||
}
|
||||
_, _ = s.ClosePosition(ctx)
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -578,19 +593,26 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.stdevHigh.Update(highdiff)
|
||||
avg := s.Position.AverageCost.Float64()
|
||||
|
||||
if !s.IsBackTesting() {
|
||||
balances := s.Session.GetAccount().Balances()
|
||||
log.Infof("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
|
||||
sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
|
||||
log.Infof("balances: [Base] %v [Quote] %v", balances[s.Market.BaseCurrency], balances[s.Market.QuoteCurrency])
|
||||
}
|
||||
|
||||
shortCondition := (driftPred <= 0 && drift[0] <= 0)
|
||||
longCondition := (driftPred >= 0 && drift[0] >= 0)
|
||||
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition
|
||||
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf) &&
|
||||
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
||||
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
|
||||
(s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition
|
||||
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) &&
|
||||
(s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition
|
||||
|
||||
if exitShortCondition || exitLongCondition {
|
||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
log.WithError(err).Errorf("cannot cancel orders")
|
||||
return
|
||||
}
|
||||
_, _ = s.ClosePosition(ctx)
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
if shortCondition {
|
||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
|
@ -611,7 +633,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
// Cleanup pending StopOrders
|
||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||
quantity := baseBalance.Available
|
||||
stopPrice := fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss)))
|
||||
stopOrder := types.SubmitOrder{
|
||||
|
@ -642,7 +664,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
||||
return
|
||||
}
|
||||
s.StopOrders[createdOrders[0].OrderID] = stopOrder
|
||||
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
||||
}
|
||||
if longCondition {
|
||||
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
|
@ -663,7 +685,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
// Cleanup pending StopOrders
|
||||
s.StopOrders = make(map[uint64]types.SubmitOrder)
|
||||
s.StopOrders = make(map[uint64]*types.SubmitOrder)
|
||||
quantity := quoteBalance.Available.Div(source)
|
||||
stopPrice := fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss)))
|
||||
stopOrder := types.SubmitOrder{
|
||||
|
@ -695,7 +717,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder)
|
||||
return
|
||||
}
|
||||
s.StopOrders[createdOrders[0].OrderID] = stopOrder
|
||||
s.StopOrders[createdOrders[0].OrderID] = &stopOrder
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
@ -10,9 +9,9 @@ import (
|
|||
)
|
||||
|
||||
type IntervalProfitCollector struct {
|
||||
Interval Interval
|
||||
Profits *Float64Slice
|
||||
tmpTime time.Time
|
||||
Interval Interval `json:"interval"`
|
||||
Profits *Float64Slice `json:"profits"`
|
||||
tmpTime time.Time `json:"tmpTime"`
|
||||
}
|
||||
|
||||
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
|
||||
|
@ -92,16 +91,6 @@ func (s IntervalProfitCollector) MarshalYAML() (interface{}, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (s *IntervalProfitCollector) MarshalJSON() ([]byte, error) {
|
||||
result := make(map[string]interface{})
|
||||
result["Sharpe Ratio"] = s.GetSharpe()
|
||||
result["Omega Ratio"] = s.GetOmega()
|
||||
result["Profitable Count"] = s.GetNumOfProfitableIntervals()
|
||||
result["NonProfitable Count"] = s.GetNumOfNonProfitableIntervals()
|
||||
return json.Marshal(result)
|
||||
|
||||
}
|
||||
|
||||
// TODO: Add more stats from the reference:
|
||||
// See https://www.metatrader5.com/en/terminal/help/algotrading/testing_report
|
||||
type TradeStats struct {
|
||||
|
@ -117,7 +106,7 @@ type TradeStats struct {
|
|||
MostLossTrade fixedpoint.Value `json:"mostLossTrade" yaml:"mostLossTrade"`
|
||||
ProfitFactor fixedpoint.Value `json:"profitFactor" yaml:"profitFactor"`
|
||||
TotalNetProfit fixedpoint.Value `json:"totalNetProfit" yaml:"totalNetProfit"`
|
||||
IntervalProfits map[Interval]*IntervalProfitCollector `json:"intervalProfits,omitempty" yaml: "intervalProfits,omitempty"`
|
||||
IntervalProfits map[Interval]*IntervalProfitCollector `jons:"intervalProfits,omitempty" yaml: "intervalProfits,omitempty"`
|
||||
}
|
||||
|
||||
func NewTradeStats(symbol string) *TradeStats {
|
||||
|
|
Loading…
Reference in New Issue
Block a user