fix: buyPrice/sellPrice calculation on one order multiple trades

This commit is contained in:
zenix 2022-07-22 13:32:37 +09:00
parent d2dee44647
commit 553a55811c
2 changed files with 124 additions and 83 deletions

View File

@ -125,6 +125,10 @@ func New(key, secret string) *Exchange {
ex.setServerTimeOffset(ctx) ex.setServerTimeOffset(ctx)
} }
} }
if err = client2.SetTimeOffsetFromServer(context.Background()); err != nil {
log.WithError(err).Error("can not set server time")
}
}) })
} }

View File

@ -65,6 +65,9 @@ type Strategy struct {
NoStopPrice bool `json:"noStopPrice"` NoStopPrice bool `json:"noStopPrice"`
NoTrailingStopLoss bool `json:"noTrailingStopLoss"` NoTrailingStopLoss bool `json:"noTrailingStopLoss"`
buyPrice float64
sellPrice float64
// This is not related to trade but for statistics graph generation // This is not related to trade but for statistics graph generation
// Will deduct fee in percentage from every trade // Will deduct fee in percentage from every trade
GraphPNLDeductFee bool `json:"graphPNLDeductFee"` GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
@ -306,7 +309,7 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) {
return return
} }
atr = s.atr.Last() atr = s.atr.Last()
avg = s.Position.AverageCost.Float64() avg = s.buyPrice + s.sellPrice
stoploss = s.StopLoss.Float64() stoploss = s.StopLoss.Float64()
exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) && exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) &&
(s.Position.IsShort() && !s.Position.IsDust(price)) (s.Position.IsShort() && !s.Position.IsDust(price))
@ -349,12 +352,10 @@ func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit ty
mean := priceLine.Mean(Length) mean := priceLine.Mean(Length)
highestPrice := priceLine.Minus(mean).Abs().Highest(Length) highestPrice := priceLine.Minus(mean).Abs().Highest(Length)
highestDrift := s.drift.Abs().Highest(Length) highestDrift := s.drift.Abs().Highest(Length)
meanDrift := s.drift.Mean(Length)
ratio := highestDrift / highestPrice ratio := highestDrift / highestPrice
canvas.Plot("drift", s.drift, time, Length) canvas.Plot("drift", s.drift, time, Length)
canvas.Plot("zero", types.NumberSeries(0), time, Length) canvas.Plot("zero", types.NumberSeries(0), time, Length)
canvas.Plot("price", priceLine.Minus(mean).Mul(ratio), time, Length) canvas.Plot("price", priceLine.Minus(mean).Mul(ratio), time, Length)
canvas.Plot("0", types.NumberSeries(meanDrift), time, Length)
f, err := os.Create(s.CanvasPath) f, err := os.Create(s.CanvasPath)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath) log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
@ -443,10 +444,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
buyPrice := fixedpoint.Zero buyPrice := fixedpoint.Zero
sellPrice := fixedpoint.Zero sellPrice := fixedpoint.Zero
Volume := fixedpoint.Zero
profit := types.Float64Slice{} profit := types.Float64Slice{}
cumProfit := types.Float64Slice{1.} cumProfit := types.Float64Slice{1.}
orderTagHistory := make(map[uint64]string) orderTagHistory := make(map[uint64]string)
if s.GenerateGraph { s.buyPrice = 0
s.sellPrice = 0
s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) { s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
orderTagHistory[order.OrderID] = order.Tag orderTagHistory[order.OrderID] = order.Tag
}) })
@ -466,16 +469,33 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
if tag == "close" { if tag == "close" {
if !buyPrice.IsZero() { if !buyPrice.IsZero() {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64()) profit.Update(modify(trade.Price.Div(buyPrice)).
Sub(fixedpoint.One).
Mul(trade.Quantity).
Div(Volume).
Add(fixedpoint.One).
Float64())
cumProfit.Update(cumProfit.Last() * profit.Last()) cumProfit.Update(cumProfit.Last() * profit.Last())
Volume = Volume.Sub(trade.Quantity)
if Volume.IsZero() {
buyPrice = fixedpoint.Zero buyPrice = fixedpoint.Zero
}
if !sellPrice.IsZero() { if !sellPrice.IsZero() {
panic("sellprice shouldn't be zero") panic("sellprice shouldn't be zero")
} }
} else if !sellPrice.IsZero() { } else if !sellPrice.IsZero() {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64()) profit.Update(modify(sellPrice.Div(trade.Price)).
Sub(fixedpoint.One).
Mul(trade.Quantity).
Div(Volume).
Neg().
Add(fixedpoint.One).
Float64())
cumProfit.Update(cumProfit.Last() * profit.Last()) cumProfit.Update(cumProfit.Last() * profit.Last())
Volume = Volume.Add(trade.Quantity)
if Volume.IsZero() {
sellPrice = fixedpoint.Zero sellPrice = fixedpoint.Zero
}
if !buyPrice.IsZero() { if !buyPrice.IsZero() {
panic("buyprice shouldn't be zero") panic("buyprice shouldn't be zero")
} }
@ -485,28 +505,35 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} else if tag == "short" { } else if tag == "short" {
if buyPrice.IsZero() { if buyPrice.IsZero() {
if !sellPrice.IsZero() { if !sellPrice.IsZero() {
panic("sellPrice not zero") sellPrice = sellPrice.Mul(Volume).Sub(trade.Price.Mul(trade.Quantity)).Div(Volume.Sub(trade.Quantity))
} } else {
sellPrice = trade.Price sellPrice = trade.Price
}
} else { } else {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64()) profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last()) cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero buyPrice = fixedpoint.Zero
Volume = fixedpoint.Zero
sellPrice = trade.Price sellPrice = trade.Price
} }
Volume = Volume.Sub(trade.Quantity)
} else if tag == "long" { } else if tag == "long" {
if sellPrice.IsZero() { if sellPrice.IsZero() {
if !buyPrice.IsZero() { if !buyPrice.IsZero() {
panic("buyPrice not zero") buyPrice = buyPrice.Mul(Volume).Add(trade.Price.Mul(trade.Quantity)).Div(Volume.Add(trade.Quantity))
} } else {
buyPrice = trade.Price buyPrice = trade.Price
}
} else { } else {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64()) profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last()) cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero sellPrice = fixedpoint.Zero
buyPrice = trade.Price buyPrice = trade.Price
Volume = fixedpoint.Zero
} }
Volume = Volume.Add(trade.Quantity)
} else if tag == "sl" { } else if tag == "sl" {
// TODO: not properly handled for single order, multiple trades
if !buyPrice.IsZero() { if !buyPrice.IsZero() {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64()) profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last()) cumProfit.Update(cumProfit.Last() * profit.Last())
@ -519,8 +546,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
panic("no position to sl") panic("no position to sl")
} }
} }
s.buyPrice = buyPrice.Float64()
s.sellPrice = sellPrice.Float64()
}) })
}
s.BindStopLoss(ctx) s.BindStopLoss(ctx)
@ -557,7 +585,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
pricef := price.Float64() pricef := price.Float64()
lowf := math.Min(kline.Low.Float64(), pricef) lowf := math.Min(kline.Low.Float64(), pricef)
highf := math.Max(kline.High.Float64(), pricef) highf := math.Max(kline.High.Float64(), pricef)
avg := s.Position.AverageCost.Float64() avg := s.buyPrice + s.sellPrice
exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) && exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) &&
(s.Position.IsShort() && !s.Position.IsDust(price)) (s.Position.IsShort() && !s.Position.IsDust(price))
@ -591,13 +619,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.stdevLow.Update(lowdiff) s.stdevLow.Update(lowdiff)
highdiff := highf - s.ma.Last() highdiff := highf - s.ma.Last()
s.stdevHigh.Update(highdiff) s.stdevHigh.Update(highdiff)
avg := s.Position.AverageCost.Float64() avg := s.buyPrice + s.sellPrice
if !s.IsBackTesting() { if !s.IsBackTesting() {
balances := s.Session.GetAccount().Balances() balances := s.Session.GetAccount().Balances()
log.Infof("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f", bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
sourcef, pricef, driftPred, drift[0], drift[1], atr, avg) sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
log.Infof("balances: [Base] %v [Quote] %v", balances[s.Market.BaseCurrency], balances[s.Market.QuoteCurrency]) // Notify will parse args to strings and process separately
bbgo.Notify("balances: [Base] %s [Quote] %s", balances[s.Market.BaseCurrency].String(), balances[s.Market.QuoteCurrency].String())
} }
shortCondition := (driftPred <= 0 && drift[0] <= 0) shortCondition := (driftPred <= 0 && drift[0] <= 0)
@ -628,6 +657,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if source.Compare(price) < 0 { if source.Compare(price) < 0 {
source = price source = price
} }
sourcef = source.Float64()
if s.Market.IsDustQuantity(baseBalance.Available, source) { if s.Market.IsDustQuantity(baseBalance.Available, source) {
return return
@ -657,11 +687,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.WithError(err).Errorf("cannot place sell order") log.WithError(err).Errorf("cannot place sell order")
return return
} }
orderTagHistory[createdOrders[0].OrderID] = "short"
if s.NoStopPrice { if s.NoStopPrice {
return return
} }
if createdOrders[0].Status == types.OrderStatusFilled { if createdOrders[0].Status == types.OrderStatusFilled {
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder) if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil {
orderTagHistory[o[0].OrderID] = "sl"
}
return return
} }
s.StopOrders[createdOrders[0].OrderID] = &stopOrder s.StopOrders[createdOrders[0].OrderID] = &stopOrder
@ -675,6 +708,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if source.Compare(price) > 0 { if source.Compare(price) > 0 {
source = price source = price
} }
sourcef = source.Float64()
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency) quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
if !ok { if !ok {
log.Errorf("unable to get quoteCurrency") log.Errorf("unable to get quoteCurrency")
@ -710,11 +744,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.WithError(err).Errorf("cannot place buy order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
orderTagHistory[createdOrders[0].OrderID] = "long"
if s.NoStopPrice { if s.NoStopPrice {
return return
} }
if createdOrders[0].Status == types.OrderStatusFilled { if createdOrders[0].Status == types.OrderStatusFilled {
s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder) if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil {
orderTagHistory[o[0].OrderID] = "sl"
}
return return
} }
s.StopOrders[createdOrders[0].OrderID] = &stopOrder s.StopOrders[createdOrders[0].OrderID] = &stopOrder