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,84 +444,111 @@ 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.Session.UserDataStream.OnOrderUpdate(func(order types.Order) { s.sellPrice = 0
orderTagHistory[order.OrderID] = order.Tag s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) {
}) orderTagHistory[order.OrderID] = order.Tag
modify := func(p fixedpoint.Value) fixedpoint.Value { })
return p modify := func(p fixedpoint.Value) fixedpoint.Value {
} return p
if s.GraphPNLDeductFee {
fee := fixedpoint.NewFromFloat(0.0004) // taker fee % * 2, for upper bound
modify = func(p fixedpoint.Value) fixedpoint.Value {
return p.Mul(fixedpoint.One.Sub(fee))
}
}
s.Session.UserDataStream.OnTradeUpdate(func(trade types.Trade) {
tag, ok := orderTagHistory[trade.OrderID]
if !ok {
panic(fmt.Sprintf("cannot find order: %v", trade))
}
if tag == "close" {
if !buyPrice.IsZero() {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero
if !sellPrice.IsZero() {
panic("sellprice shouldn't be zero")
}
} else if !sellPrice.IsZero() {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero
if !buyPrice.IsZero() {
panic("buyprice shouldn't be zero")
}
} else {
panic("no price available")
}
} else if tag == "short" {
if buyPrice.IsZero() {
if !sellPrice.IsZero() {
panic("sellPrice not zero")
}
sellPrice = trade.Price
} else {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero
sellPrice = trade.Price
}
} else if tag == "long" {
if sellPrice.IsZero() {
if !buyPrice.IsZero() {
panic("buyPrice not zero")
}
buyPrice = trade.Price
} else {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero
buyPrice = trade.Price
}
} else if tag == "sl" {
if !buyPrice.IsZero() {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero
} else if !sellPrice.IsZero() {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero
} else {
panic("no position to sl")
}
}
})
} }
if s.GraphPNLDeductFee {
fee := fixedpoint.NewFromFloat(0.0004) // taker fee % * 2, for upper bound
modify = func(p fixedpoint.Value) fixedpoint.Value {
return p.Mul(fixedpoint.One.Sub(fee))
}
}
s.Session.UserDataStream.OnTradeUpdate(func(trade types.Trade) {
tag, ok := orderTagHistory[trade.OrderID]
if !ok {
panic(fmt.Sprintf("cannot find order: %v", trade))
}
if tag == "close" {
if !buyPrice.IsZero() {
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())
Volume = Volume.Sub(trade.Quantity)
if Volume.IsZero() {
buyPrice = fixedpoint.Zero
}
if !sellPrice.IsZero() {
panic("sellprice shouldn't be zero")
}
} else if !sellPrice.IsZero() {
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())
Volume = Volume.Add(trade.Quantity)
if Volume.IsZero() {
sellPrice = fixedpoint.Zero
}
if !buyPrice.IsZero() {
panic("buyprice shouldn't be zero")
}
} else {
panic("no price available")
}
} else if tag == "short" {
if buyPrice.IsZero() {
if !sellPrice.IsZero() {
sellPrice = sellPrice.Mul(Volume).Sub(trade.Price.Mul(trade.Quantity)).Div(Volume.Sub(trade.Quantity))
} else {
sellPrice = trade.Price
}
} else {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero
Volume = fixedpoint.Zero
sellPrice = trade.Price
}
Volume = Volume.Sub(trade.Quantity)
} else if tag == "long" {
if sellPrice.IsZero() {
if !buyPrice.IsZero() {
buyPrice = buyPrice.Mul(Volume).Add(trade.Price.Mul(trade.Quantity)).Div(Volume.Add(trade.Quantity))
} else {
buyPrice = trade.Price
}
} else {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero
buyPrice = trade.Price
Volume = fixedpoint.Zero
}
Volume = Volume.Add(trade.Quantity)
} else if tag == "sl" {
// TODO: not properly handled for single order, multiple trades
if !buyPrice.IsZero() {
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
buyPrice = fixedpoint.Zero
} else if !sellPrice.IsZero() {
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
cumProfit.Update(cumProfit.Last() * profit.Last())
sellPrice = fixedpoint.Zero
} else {
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