fix all the fixedpoint use other than strategy

This commit is contained in:
zenix 2022-02-03 20:19:56 +09:00
parent b8bf2af14d
commit d9450e823e
14 changed files with 295 additions and 289 deletions

View File

@ -16,7 +16,7 @@ import (
// BINANCE uses 0.1% for both maker and taker
// for BNB holders, it's 0.075% for both maker and taker
// MAX uses 0.050% for maker and 0.15% for taker
const DefaultFeeRate = 0.075 * 0.01
var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01)
var orderID uint64 = 1
var tradeID uint64 = 1
@ -92,12 +92,12 @@ func (m *SimplePriceMatching) CancelOrder(o types.Order) (types.Order, error) {
switch o.Side {
case types.SideTypeBuy:
if err := m.Account.UnlockBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(o.Price*o.Quantity)); err != nil {
if err := m.Account.UnlockBalance(m.Market.QuoteCurrency, o.Price.Mul(o.Quantity)); err != nil {
return o, err
}
case types.SideTypeSell:
if err := m.Account.UnlockBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(o.Quantity)); err != nil {
if err := m.Account.UnlockBalance(m.Market.BaseCurrency, o.Quantity); err != nil {
return o, err
}
}
@ -114,32 +114,32 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (closedOrders *typ
switch o.Type {
case types.OrderTypeMarket:
if m.LastPrice == 0 {
if m.LastPrice.IsZero() {
panic("unexpected: last price can not be zero")
}
price = m.LastPrice.Float64()
price = m.LastPrice
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
price = o.Price
}
if o.Quantity < m.Market.MinQuantity {
return nil, nil, fmt.Errorf("order quantity %f is less than minQuantity %f, order: %+v", o.Quantity, m.Market.MinQuantity, o)
if o.Quantity.Compare(m.Market.MinQuantity) < 0 {
return nil, nil, fmt.Errorf("order quantity %s is less than minQuantity %s, order: %+v", o.Quantity.String(), m.Market.MinQuantity.String(), o)
}
quoteQuantity := o.Quantity * price
if quoteQuantity < m.Market.MinNotional {
return nil, nil, fmt.Errorf("order amount %f is less than minNotional %f, order: %+v", quoteQuantity, m.Market.MinNotional, o)
quoteQuantity := o.Quantity.Mul(price)
if quoteQuantity.Compare(m.Market.MinNotional) < 0 {
return nil, nil, fmt.Errorf("order amount %s is less than minNotional %s, order: %+v", quoteQuantity.String(), m.Market.MinNotional.String(), o)
}
switch o.Side {
case types.SideTypeBuy:
if err := m.Account.LockBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(quoteQuantity)); err != nil {
if err := m.Account.LockBalance(m.Market.QuoteCurrency, quoteQuantity); err != nil {
return nil, nil, err
}
case types.SideTypeSell:
if err := m.Account.LockBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(o.Quantity)); err != nil {
if err := m.Account.LockBalance(m.Market.BaseCurrency, o.Quantity); err != nil {
return nil, nil, err
}
}
@ -190,13 +190,13 @@ func (m *SimplePriceMatching) executeTrade(trade types.Trade) {
var err error
// execute trade, update account balances
if trade.IsBuyer {
err = m.Account.UseLockedBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(trade.Price*trade.Quantity))
err = m.Account.UseLockedBalance(m.Market.QuoteCurrency, trade.Price.Mul(trade.Quantity))
m.Account.AddBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(trade.Quantity))
m.Account.AddBalance(m.Market.BaseCurrency, trade.Quantity)
} else {
err = m.Account.UseLockedBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(trade.Quantity))
err = m.Account.UseLockedBalance(m.Market.BaseCurrency, trade.Quantity)
m.Account.AddBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(trade.Quantity*trade.Price))
m.Account.AddBalance(m.Market.QuoteCurrency, trade.Quantity.Mul(trade.Price))
}
if err != nil {
@ -213,37 +213,37 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool)
// MAX uses 0.050% for maker and 0.15% for taker
var feeRate = DefaultFeeRate
if isMaker {
if m.MakerFeeRate > 0 {
feeRate = m.MakerFeeRate.Float64()
if m.MakerFeeRate.Sign() > 0 {
feeRate = m.MakerFeeRate
}
} else {
if m.TakerFeeRate > 0 {
feeRate = m.TakerFeeRate.Float64()
if m.TakerFeeRate.Sign() > 0 {
feeRate = m.TakerFeeRate
}
}
price := order.Price
switch order.Type {
case types.OrderTypeMarket, types.OrderTypeStopMarket:
if m.LastPrice == 0 {
if m.LastPrice.IsZero() {
panic("unexpected: last price can not be zero")
}
price = m.LastPrice.Float64()
price = m.LastPrice
}
var fee float64
var fee fixedpoint.Value
var feeCurrency string
switch order.Side {
case types.SideTypeBuy:
fee = order.Quantity * feeRate
fee = order.Quantity.Mul(feeRate)
feeCurrency = m.Market.BaseCurrency
case types.SideTypeSell:
fee = order.Quantity * price * feeRate
fee = order.Quantity.Mul(price).Mul(feeRate)
feeCurrency = m.Market.QuoteCurrency
}
@ -255,7 +255,7 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool)
Exchange: "backtest",
Price: price,
Quantity: order.Quantity,
QuoteQuantity: order.Quantity * price,
QuoteQuantity: order.Quantity.Mul(price),
Symbol: order.Symbol,
Side: order.Side,
IsBuyer: order.Side == types.SideTypeBuy,
@ -267,7 +267,6 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool)
}
func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders []types.Order, trades []types.Trade) {
var priceF = price.Float64()
var askOrders []types.Order
for _, o := range m.askOrders {
@ -275,7 +274,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
case types.OrderTypeStopMarket:
// should we trigger the order
if priceF <= o.StopPrice {
if price.Compare(o.StopPrice) <= 0 {
// not triggering it, put it back
askOrders = append(askOrders, o)
break
@ -283,7 +282,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
o.Type = types.OrderTypeMarket
o.ExecutedQuantity = o.Quantity
o.Price = priceF
o.Price = price
o.Status = types.OrderStatusFilled
closedOrders = append(closedOrders, o)
@ -296,7 +295,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
case types.OrderTypeStopLimit:
// should we trigger the order?
if priceF <= o.StopPrice {
if price.Compare(o.StopPrice) <= 0 {
askOrders = append(askOrders, o)
break
}
@ -304,7 +303,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
o.Type = types.OrderTypeLimit
// is it a taker order?
if priceF >= o.Price {
if price.Compare(o.Price) >= 0 {
o.ExecutedQuantity = o.Quantity
o.Status = types.OrderStatusFilled
closedOrders = append(closedOrders, o)
@ -321,7 +320,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
}
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
if priceF >= o.Price {
if price.Compare(o.Price) >= 0 {
o.ExecutedQuantity = o.Quantity
o.Status = types.OrderStatusFilled
closedOrders = append(closedOrders, o)
@ -349,14 +348,14 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
}
func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders []types.Order, trades []types.Trade) {
var sellPrice = price.Float64()
var sellPrice = price
var bidOrders []types.Order
for _, o := range m.bidOrders {
switch o.Type {
case types.OrderTypeStopMarket:
// should we trigger the order
if sellPrice <= o.StopPrice {
if sellPrice.Compare(o.StopPrice) <= 0 {
o.ExecutedQuantity = o.Quantity
o.Price = sellPrice
o.Status = types.OrderStatusFilled
@ -374,10 +373,10 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders
case types.OrderTypeStopLimit:
// should we trigger the order
if sellPrice <= o.StopPrice {
if sellPrice.Compare(o.StopPrice) <= 0 {
o.Type = types.OrderTypeLimit
if sellPrice <= o.Price {
if sellPrice.Compare(o.Price) <= 0 {
o.ExecutedQuantity = o.Quantity
o.Status = types.OrderStatusFilled
closedOrders = append(closedOrders, o)
@ -396,7 +395,7 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders
}
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
if sellPrice <= o.Price {
if sellPrice.Compare(o.Price) <= 0 {
o.ExecutedQuantity = o.Quantity
o.Status = types.OrderStatusFilled
closedOrders = append(closedOrders, o)
@ -428,31 +427,31 @@ func (m *SimplePriceMatching) processKLine(kline types.KLine) {
switch kline.Direction() {
case types.DirectionDown:
if kline.High >= kline.Open {
m.BuyToPrice(fixedpoint.NewFromFloat(kline.High))
if kline.High.Compare(kline.Open) >= 0 {
m.BuyToPrice(kline.High)
}
if kline.Low > kline.Close {
m.SellToPrice(fixedpoint.NewFromFloat(kline.Low))
m.BuyToPrice(fixedpoint.NewFromFloat(kline.Close))
if kline.Low.Compare(kline.Close) > 0 {
m.SellToPrice(kline.Low)
m.BuyToPrice(kline.Close)
} else {
m.SellToPrice(fixedpoint.NewFromFloat(kline.Close))
m.SellToPrice(kline.Close)
}
case types.DirectionUp:
if kline.Low <= kline.Open {
m.SellToPrice(fixedpoint.NewFromFloat(kline.Low))
if kline.Low.Compare(kline.Open) <= 0 {
m.SellToPrice(kline.Low)
}
if kline.High > kline.Close {
m.BuyToPrice(fixedpoint.NewFromFloat(kline.High))
m.SellToPrice(fixedpoint.NewFromFloat(kline.Close))
if kline.High.Compare(kline.Close) > 0 {
m.BuyToPrice(kline.High)
m.SellToPrice(kline.Close)
} else {
m.BuyToPrice(fixedpoint.NewFromFloat(kline.Close))
m.BuyToPrice(kline.Close)
}
default: // no trade up or down
if m.LastPrice == 0 {
m.BuyToPrice(fixedpoint.NewFromFloat(kline.Close))
if m.LastPrice.IsZero() {
m.BuyToPrice(kline.Close)
}
}
@ -464,7 +463,7 @@ func (m *SimplePriceMatching) newOrder(o types.SubmitOrder, orderID uint64) type
SubmitOrder: o,
Exchange: types.ExchangeBacktest,
Status: types.OrderStatusNew,
ExecutedQuantity: 0,
ExecutedQuantity: fixedpoint.Zero,
IsWorking: true,
CreationTime: types.Time(m.CurrentTime),
UpdateTime: types.Time(m.CurrentTime),

View File

@ -15,7 +15,7 @@ type PriceOrder struct {
type PriceOrderSlice []PriceOrder
func (slice PriceOrderSlice) Len() int { return len(slice) }
func (slice PriceOrderSlice) Less(i, j int) bool { return slice[i].Price < slice[j].Price }
func (slice PriceOrderSlice) Less(i, j int) bool { return slice[i].Price.Compare(slice[j].Price) < 0 }
func (slice PriceOrderSlice) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] }
func (slice PriceOrderSlice) InsertAt(idx int, po PriceOrder) PriceOrderSlice {
@ -47,9 +47,9 @@ func (slice PriceOrderSlice) First() (PriceOrder, bool) {
func (slice PriceOrderSlice) Find(price fixedpoint.Value, descending bool) (pv PriceOrder, idx int) {
idx = sort.Search(len(slice), func(i int) bool {
if descending {
return slice[i].Price <= price
return slice[i].Price.Compare(price) <= 0
}
return slice[i].Price >= price
return slice[i].Price.Compare(price) >= 0
})
if idx >= len(slice) || slice[idx].Price != price {

View File

@ -463,7 +463,7 @@ func (environ *Environment) BindSync(userConfig *Config) {
orderWriter := func(order types.Order) {
switch order.Status {
case types.OrderStatusFilled, types.OrderStatusCanceled:
if order.ExecutedQuantity > 0.0 {
if order.ExecutedQuantity.Sign() > 0 {
if err := environ.OrderService.Insert(order); err != nil {
log.WithError(err).Errorf("order insert error: %+v", order)
}

View File

@ -10,10 +10,11 @@ import (
"github.com/c9s/bbgo/pkg/interact"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
type PositionCloser interface {
ClosePosition(ctx context.Context, percentage float64) error
ClosePosition(ctx context.Context, percentage fixedpoint.Value) error
}
type PositionReader interface {
@ -23,7 +24,7 @@ type PositionReader interface {
type closePositionContext struct {
signature string
closer PositionCloser
percentage float64
percentage fixedpoint.Value
}
type CoreInteraction struct {
@ -75,7 +76,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
message := "Your balances\n"
balances := session.Account.Balances()
for _, balance := range balances {
if balance.Total() == 0 {
if balance.Total().IsZero() {
continue
}
@ -121,7 +122,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
reply.Send("Your current position:")
reply.Send(position.PlainText())
if position.Base == 0 {
if position.Base.IsZero() {
reply.Message(fmt.Sprintf("Strategy %q has no opened position", signature))
return fmt.Errorf("strategy %T has no opened position", strategy)
}
@ -173,7 +174,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
reply.Send("Your current position:")
reply.Send(position.PlainText())
if position.Base == 0 {
if position.Base.IsZero() {
reply.Message("No opened position")
if kc, ok := reply.(interact.KeyboardController) ; ok {
kc.RemoveKeyboard()
@ -190,7 +191,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
return nil
}).Next(func(percentageStr string, reply interact.Reply) error {
percentage, err := parseFloatPercent(percentageStr, 64)
percentage, err := fixedpoint.NewFromString(percentageStr)
if err != nil {
reply.Message(fmt.Sprintf("%q is not a valid percentage string", percentageStr))
return err

View File

@ -3,7 +3,6 @@ package bbgo
import (
"context"
"fmt"
"math"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -122,8 +121,10 @@ func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ...
errs = append(errs, err)
}
accumulativeQuoteAmount := 0.0
accumulativeBaseSellQuantity := 0.0
accumulativeQuoteAmount := fixedpoint.Zero
accumulativeBaseSellQuantity := fixedpoint.Zero
increaseFactor := fixedpoint.NewFromFloat(1.01)
for _, order := range orders {
lastPrice, ok := session.LastPrice(order.Symbol)
if !ok {
@ -146,6 +147,7 @@ func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ...
switch order.Side {
case types.SideTypeBuy:
minAmount := market.MinAmount.Mul(increaseFactor)
// Critical conditions for placing buy orders
quoteBalance, ok := balances[market.QuoteCurrency]
if !ok {
@ -153,67 +155,70 @@ func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ...
continue
}
if quoteBalance.Available < c.MinQuoteBalance {
if quoteBalance.Available.Compare(c.MinQuoteBalance) < 0 {
addError(errors.Wrapf(ErrQuoteBalanceLevelTooLow, "can not place buy order, quote balance level is too low: %s < %s, order: %s",
types.USD.FormatMoneyFloat64(quoteBalance.Available.Float64()),
types.USD.FormatMoneyFloat64(c.MinQuoteBalance.Float64()), order.String()))
types.USD.FormatMoney(quoteBalance.Available),
types.USD.FormatMoney(c.MinQuoteBalance), order.String()))
continue
}
// Increase the quantity if the amount is not enough,
// this is the only increase op, later we will decrease the quantity if it meets the criteria
quantity = AdjustFloatQuantityByMinAmount(quantity, price, market.MinAmount*1.01)
quantity = AdjustFloatQuantityByMinAmount(quantity, price, minAmount)
if c.MaxOrderAmount > 0 {
quantity = AdjustFloatQuantityByMaxAmount(quantity, price, c.MaxOrderAmount.Float64())
if c.MaxOrderAmount.Sign() > 0 {
quantity = AdjustFloatQuantityByMaxAmount(quantity, price, c.MaxOrderAmount)
}
quoteAssetQuota := math.Max(0.0, quoteBalance.Available.Float64()-c.MinQuoteBalance.Float64())
if quoteAssetQuota < market.MinAmount {
quoteAssetQuota := fixedpoint.Max(
fixedpoint.Zero, quoteBalance.Available.Sub(c.MinQuoteBalance))
if quoteAssetQuota.Compare(market.MinAmount) < 0 {
addError(
errors.Wrapf(
ErrInsufficientQuoteBalance,
"can not place buy order, insufficient quote balance: quota %f < min amount %f, order: %s",
quoteAssetQuota, market.MinAmount, order.String()))
"can not place buy order, insufficient quote balance: quota %s < min amount %s, order: %s",
quoteAssetQuota.String(), market.MinAmount.String(), order.String()))
continue
}
quantity = AdjustFloatQuantityByMaxAmount(quantity, price, quoteAssetQuota)
// if MaxBaseAssetBalance is enabled, we should check the current base asset balance
if baseBalance, hasBaseAsset := balances[market.BaseCurrency]; hasBaseAsset && c.MaxBaseAssetBalance > 0 {
if baseBalance.Available > c.MaxBaseAssetBalance {
if baseBalance, hasBaseAsset := balances[market.BaseCurrency]; hasBaseAsset && c.MaxBaseAssetBalance.Sign() > 0 {
if baseBalance.Available.Compare(c.MaxBaseAssetBalance) > 0 {
addError(
errors.Wrapf(
ErrAssetBalanceLevelTooHigh,
"should not place buy order, asset balance level is too high: %f > %f, order: %s",
baseBalance.Available.Float64(),
c.MaxBaseAssetBalance.Float64(),
"should not place buy order, asset balance level is too high: %s > %s, order: %s",
baseBalance.Available.String(),
c.MaxBaseAssetBalance.String(),
order.String()))
continue
}
baseAssetQuota := math.Max(0.0, c.MaxBaseAssetBalance.Float64()-baseBalance.Available.Float64())
if quantity > baseAssetQuota {
baseAssetQuota := fixedpoint.Max(fixedpoint.Zero, c.MaxBaseAssetBalance.Sub(baseBalance.Available))
if quantity.Compare(baseAssetQuota) > 0 {
quantity = baseAssetQuota
}
}
// if the amount is still too small, we should skip it.
notional := quantity * lastPrice
if notional < market.MinAmount {
notional := quantity.Mul(lastPrice)
if notional.Compare(market.MinAmount) < 0 {
addError(
fmt.Errorf(
"can not place buy order, quote amount too small: notional %f < min amount %f, order: %s",
notional,
market.MinAmount,
"can not place buy order, quote amount too small: notional %s < min amount %s, order: %s",
notional.String(),
market.MinAmount.String(),
order.String()))
continue
}
accumulativeQuoteAmount += notional
accumulativeQuoteAmount = accumulativeQuoteAmount.Add(notional)
case types.SideTypeSell:
minNotion := market.MinNotional.Mul(increaseFactor)
// Critical conditions for placing SELL orders
baseAssetBalance, ok := balances[market.BaseCurrency]
if !ok {
@ -226,58 +231,58 @@ func (c *BasicRiskController) ProcessOrders(session *ExchangeSession, orders ...
}
// if the amount is too small, we should increase it.
quantity = AdjustFloatQuantityByMinAmount(quantity, price, market.MinNotional*1.01)
quantity = AdjustFloatQuantityByMinAmount(quantity, price, minNotion)
// we should not SELL too much
quantity = math.Min(quantity, baseAssetBalance.Available.Float64())
quantity = fixedpoint.Min(quantity, baseAssetBalance.Available)
if c.MinBaseAssetBalance > 0 {
if baseAssetBalance.Available < c.MinBaseAssetBalance {
if c.MinBaseAssetBalance.Sign() > 0 {
if baseAssetBalance.Available.Compare(c.MinBaseAssetBalance) < 0 {
addError(
errors.Wrapf(
ErrAssetBalanceLevelTooLow,
"asset balance level is too low: %f > %f", baseAssetBalance.Available.Float64(), c.MinBaseAssetBalance.Float64()))
"asset balance level is too low: %s > %s", baseAssetBalance.Available.String(), c.MinBaseAssetBalance.String()))
continue
}
quantity = math.Min(quantity, baseAssetBalance.Available.Float64()-c.MinBaseAssetBalance.Float64())
if quantity < market.MinQuantity {
quantity = fixedpoint.Min(quantity, baseAssetBalance.Available.Sub(c.MinBaseAssetBalance))
if quantity.Compare(market.MinQuantity) < 0 {
addError(
errors.Wrapf(
ErrInsufficientAssetBalance,
"insufficient asset balance: %f > minimal quantity %f",
baseAssetBalance.Available.Float64(),
market.MinQuantity))
"insufficient asset balance: %s > minimal quantity %s",
baseAssetBalance.Available.String(),
market.MinQuantity.String()))
continue
}
}
if c.MaxOrderAmount > 0 {
quantity = AdjustFloatQuantityByMaxAmount(quantity, price, c.MaxOrderAmount.Float64())
if c.MaxOrderAmount.Sign() > 0 {
quantity = AdjustFloatQuantityByMaxAmount(quantity, price, c.MaxOrderAmount)
}
notional := quantity * lastPrice
if notional < market.MinNotional {
notional := quantity.Mul(lastPrice)
if notional.Compare(market.MinNotional) < 0 {
addError(
fmt.Errorf(
"can not place sell order, notional %f < min notional: %f, order: %s",
notional,
market.MinNotional,
"can not place sell order, notional %s < min notional: %s, order: %s",
notional.String(),
market.MinNotional.String(),
order.String()))
continue
}
if quantity < market.MinQuantity {
if quantity.Compare(market.MinQuantity) < 0 {
addError(
fmt.Errorf(
"can not place sell order, quantity %f is less than the minimal lot %f, order: %s",
quantity,
market.MinQuantity,
"can not place sell order, quantity %s is less than the minimal lot %s, order: %s",
quantity.String(),
market.MinQuantity.String(),
order.String()))
continue
}
accumulativeBaseSellQuantity += quantity
accumulativeBaseSellQuantity = accumulativeBaseSellQuantity.Add(quantity)
}
// update quantity and format the order

View File

@ -18,7 +18,7 @@ var (
func AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixedpoint.Value) fixedpoint.Value {
// modify quantity for the min amount
amount := currentPrice.Mul(quantity)
if amount < maxAmount {
if amount.Compare(maxAmount) < 0 {
return quantity
}
@ -30,7 +30,7 @@ func AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixedpoint.Valu
func AdjustQuantityByMinAmount(quantity, currentPrice, minAmount fixedpoint.Value) fixedpoint.Value {
// modify quantity for the min amount
amount := currentPrice.Mul(quantity)
if amount < minAmount {
if amount.Compare(minAmount) < 0 {
ratio := minAmount.Div(amount)
quantity = quantity.Mul(ratio)
}
@ -39,22 +39,22 @@ func AdjustQuantityByMinAmount(quantity, currentPrice, minAmount fixedpoint.Valu
}
// AdjustFloatQuantityByMinAmount adjusts the quantity to make the amount greater than the given minAmount
func AdjustFloatQuantityByMinAmount(quantity, currentPrice, minAmount float64) float64 {
func AdjustFloatQuantityByMinAmount(quantity, currentPrice, minAmount fixedpoint.Value) fixedpoint.Value {
// modify quantity for the min amount
amount := currentPrice * quantity
if amount < minAmount {
ratio := minAmount / amount
quantity *= ratio
amount := currentPrice.Mul(quantity)
if amount.Compare(minAmount) < 0 {
ratio := minAmount.Div(amount)
return quantity.Mul(ratio)
}
return quantity
}
func AdjustFloatQuantityByMaxAmount(quantity float64, price float64, maxAmount float64) float64 {
amount := price * quantity
if amount > maxAmount {
ratio := maxAmount / amount
quantity *= ratio
func AdjustFloatQuantityByMaxAmount(quantity fixedpoint.Value, price fixedpoint.Value, maxAmount fixedpoint.Value) fixedpoint.Value {
amount := price.Mul(quantity)
if amount.Compare(maxAmount) > 0 {
ratio := maxAmount.Div(amount)
return quantity.Mul(ratio)
}
return quantity

View File

@ -140,7 +140,7 @@ func (s *OrderStore) handleOrderUpdate(order types.Order) {
case types.OrderStatusCanceled:
if s.RemoveCancelled {
s.Remove(order)
} else if order.ExecutedQuantity == 0.0 {
} else if order.ExecutedQuantity.IsZero() {
s.Remove(order)
}

View File

@ -48,7 +48,7 @@ func (p *Profit) SlackAttachment() slack.Attachment {
var fields []slack.AttachmentField
if p.NetProfit != 0 {
if !p.NetProfit.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Net Profit",
Value: pnlSignString(p.NetProfit) + " " + p.QuoteCurrency,
@ -56,7 +56,7 @@ func (p *Profit) SlackAttachment() slack.Attachment {
})
}
if p.ProfitMargin != 0 {
if !p.ProfitMargin.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Profit Margin",
Value: p.ProfitMargin.Percentage(),
@ -64,7 +64,7 @@ func (p *Profit) SlackAttachment() slack.Attachment {
})
}
if p.NetProfitMargin != 0 {
if !p.NetProfitMargin.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Net Profit Margin",
Value: p.NetProfitMargin.Percentage(),
@ -72,7 +72,7 @@ func (p *Profit) SlackAttachment() slack.Attachment {
})
}
if p.TradeAmount != 0.0 {
if !p.TradeAmount.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Trade Amount",
Value: p.TradeAmount.String() + " " + p.QuoteCurrency,
@ -80,7 +80,7 @@ func (p *Profit) SlackAttachment() slack.Attachment {
})
}
if p.FeeInUSD != 0 {
if !p.FeeInUSD.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Fee In USD",
Value: p.FeeInUSD.String() + " USD",
@ -106,19 +106,19 @@ func (p *Profit) SlackAttachment() slack.Attachment {
func (p *Profit) PlainText() string {
var emoji string
if p.ProfitMargin != 0 {
if !p.ProfitMargin.IsZero() {
emoji = pnlEmojiMargin(p.Profit, p.ProfitMargin, defaultPnlLevelResolution)
} else {
emoji = pnlEmojiSimple(p.Profit)
}
return fmt.Sprintf("%s trade profit %s %f %s (%.2f%%), net profit =~ %f %s (%.2f%%)",
return fmt.Sprintf("%s trade profit %s %s %s (%s), net profit =~ %s %s (%s)",
p.Symbol,
emoji,
p.Profit.Float64(), p.QuoteCurrency,
p.ProfitMargin.Float64()*100.0,
p.NetProfit.Float64(), p.QuoteCurrency,
p.NetProfitMargin.Float64()*100.0,
p.Profit.String(), p.QuoteCurrency,
p.ProfitMargin.Percentage(),
p.NetProfit.String(), p.QuoteCurrency,
p.NetProfitMargin.Percentage(),
)
}
@ -127,25 +127,25 @@ var profitEmoji = "💰"
var defaultPnlLevelResolution = fixedpoint.NewFromFloat(0.001)
func pnlColor(pnl fixedpoint.Value) string {
if pnl > 0 {
if pnl.Sign() > 0 {
return types.GreenColor
}
return types.RedColor
}
func pnlSignString(pnl fixedpoint.Value) string {
if pnl > 0 {
if pnl.Sign() > 0 {
return "+" + pnl.String()
}
return pnl.String()
}
func pnlEmojiSimple(pnl fixedpoint.Value) string {
if pnl < 0 {
if pnl.Sign() < 0 {
return lossEmoji
}
if pnl == 0 {
if pnl.IsZero() {
return ""
}
@ -153,26 +153,26 @@ func pnlEmojiSimple(pnl fixedpoint.Value) string {
}
func pnlEmojiMargin(pnl, margin, resolution fixedpoint.Value) (out string) {
if margin == 0 {
if margin.IsZero() {
return pnlEmojiSimple(pnl)
}
if pnl < 0 {
if pnl.Sign() < 0 {
out = lossEmoji
level := (-margin).Div(resolution).Floor()
for i := 1; i < level.Int(); i++ {
level := (margin.Neg()).Div(resolution).Int()
for i := 1; i < level; i++ {
out += lossEmoji
}
return out
}
if pnl == 0 {
if pnl.IsZero() {
return out
}
out = profitEmoji
level := margin.Div(resolution).Floor()
for i := 1; i < level.Int(); i++ {
level := margin.Div(resolution).Int()
for i := 1; i < level; i++ {
out += profitEmoji
}
return out
@ -207,17 +207,17 @@ func (s *ProfitStats) Init(market types.Market) {
}
func (s *ProfitStats) AddProfit(profit Profit) {
s.AccumulatedPnL += profit.Profit
s.AccumulatedNetProfit += profit.NetProfit
s.TodayPnL += profit.Profit
s.TodayNetProfit += profit.NetProfit
s.AccumulatedPnL = s.AccumulatedPnL.Add(profit.Profit)
s.AccumulatedNetProfit = s.AccumulatedNetProfit.Add(profit.NetProfit)
s.TodayPnL = s.TodayPnL.Add(profit.Profit)
s.TodayNetProfit = s.TodayNetProfit.Add(profit.NetProfit)
if profit.Profit < 0 {
s.AccumulatedLoss += profit.Profit
s.TodayLoss += profit.Profit
} else if profit.Profit > 0 {
s.AccumulatedProfit += profit.Profit
s.TodayProfit += profit.Profit
if profit.Profit.Sign() < 0 {
s.AccumulatedLoss = s.AccumulatedLoss.Add(profit.Profit)
s.TodayLoss = s.TodayLoss.Add(profit.Profit)
} else if profit.Profit.Sign() > 0 {
s.AccumulatedProfit = s.AccumulatedLoss.Add(profit.Profit)
s.TodayProfit = s.TodayProfit.Add(profit.Profit)
}
}
@ -226,7 +226,7 @@ func (s *ProfitStats) AddTrade(trade types.Trade) {
s.ResetToday()
}
s.AccumulatedVolume += fixedpoint.NewFromFloat(trade.Quantity)
s.AccumulatedVolume = s.AccumulatedVolume.Add(trade.Quantity)
}
func (s *ProfitStats) IsOver24Hours() bool {
@ -234,10 +234,10 @@ func (s *ProfitStats) IsOver24Hours() bool {
}
func (s *ProfitStats) ResetToday() {
s.TodayPnL = 0
s.TodayNetProfit = 0
s.TodayProfit = 0
s.TodayLoss = 0
s.TodayPnL = fixedpoint.Zero
s.TodayNetProfit = fixedpoint.Zero
s.TodayProfit = fixedpoint.Zero
s.TodayLoss = fixedpoint.Zero
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
s.TodaySince = beginningOfTheDay.Unix()
@ -246,21 +246,21 @@ func (s *ProfitStats) ResetToday() {
func (s *ProfitStats) PlainText() string {
since := time.Unix(s.AccumulatedSince, 0).Local()
return fmt.Sprintf("%s Profit Today\n"+
"Profit %f %s\n"+
"Net profit %f %s\n"+
"Trade Loss %f %s\n"+
"Profit %s %s\n"+
"Net profit %s %s\n"+
"Trade Loss %s %s\n"+
"Summary:\n"+
"Accumulated Profit %f %s\n"+
"Accumulated Net Profit %f %s\n"+
"Accumulated Trade Loss %f %s\n"+
"Accumulated Profit %s %s\n"+
"Accumulated Net Profit %s %s\n"+
"Accumulated Trade Loss %s %s\n"+
"Since %s",
s.Symbol,
s.TodayPnL.Float64(), s.QuoteCurrency,
s.TodayNetProfit.Float64(), s.QuoteCurrency,
s.TodayLoss.Float64(), s.QuoteCurrency,
s.AccumulatedPnL.Float64(), s.QuoteCurrency,
s.AccumulatedNetProfit.Float64(), s.QuoteCurrency,
s.AccumulatedLoss.Float64(), s.QuoteCurrency,
s.TodayPnL.String(), s.QuoteCurrency,
s.TodayNetProfit.String(), s.QuoteCurrency,
s.TodayLoss.String(), s.QuoteCurrency,
s.AccumulatedPnL.String(), s.QuoteCurrency,
s.AccumulatedNetProfit.String(), s.QuoteCurrency,
s.AccumulatedLoss.String(), s.QuoteCurrency,
since.Format(time.RFC822),
)
}
@ -274,7 +274,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
var fields []slack.AttachmentField
if s.TodayPnL != 0 {
if !s.TodayPnL.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "P&L Today",
Value: pnlSignString(s.TodayPnL) + " " + s.QuoteCurrency,
@ -282,7 +282,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
})
}
if s.TodayProfit != 0 {
if !s.TodayProfit.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Profit Today",
Value: pnlSignString(s.TodayProfit) + " " + s.QuoteCurrency,
@ -290,7 +290,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
})
}
if s.TodayNetProfit != 0 {
if !s.TodayNetProfit.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Net Profit Today",
Value: pnlSignString(s.TodayNetProfit) + " " + s.QuoteCurrency,
@ -298,7 +298,7 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
})
}
if s.TodayLoss != 0 {
if !s.TodayLoss.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Loss Today",
Value: pnlSignString(s.TodayLoss) + " " + s.QuoteCurrency,
@ -306,28 +306,28 @@ func (s *ProfitStats) SlackAttachment() slack.Attachment {
})
}
if s.AccumulatedPnL != 0 {
if !s.AccumulatedPnL.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Accumulated P&L",
Value: pnlSignString(s.AccumulatedPnL) + " " + s.QuoteCurrency,
})
}
if s.AccumulatedProfit != 0 {
if !s.AccumulatedProfit.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Accumulated Profit",
Value: pnlSignString(s.AccumulatedProfit) + " " + s.QuoteCurrency,
})
}
if s.AccumulatedNetProfit != 0 {
if !s.AccumulatedNetProfit.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Accumulated Net Profit",
Value: pnlSignString(s.AccumulatedNetProfit) + " " + s.QuoteCurrency,
})
}
if s.AccumulatedLoss != 0 {
if !s.AccumulatedLoss.IsZero() {
fields = append(fields, slack.AttachmentField{
Title: "Accumulated Loss",
Value: pnlSignString(s.AccumulatedLoss) + " " + s.QuoteCurrency,

View File

@ -18,11 +18,11 @@ type QuantityOrAmount struct {
}
func (qa *QuantityOrAmount) IsSet() bool {
return qa.Quantity > 0 || qa.Amount > 0
return qa.Quantity.Sign() > 0 || qa.Amount.Sign() > 0
}
func (qa *QuantityOrAmount) Validate() error {
if qa.Quantity == 0 && qa.Amount == 0 {
if qa.Quantity.IsZero() && qa.Amount.IsZero() {
return errors.New("either quantity or amount can not be empty")
}
return nil
@ -31,7 +31,7 @@ func (qa *QuantityOrAmount) Validate() error {
// CalculateQuantity calculates the equivalent quantity of the given price when amount is set
// it returns the quantity if the quantity is set
func (qa *QuantityOrAmount) CalculateQuantity(currentPrice fixedpoint.Value) fixedpoint.Value {
if qa.Amount > 0 {
if qa.Amount.Sign() > 0 {
quantity := qa.Amount.Div(currentPrice)
return quantity
}

View File

@ -14,18 +14,18 @@ type Quota struct {
func (q *Quota) Add(fund fixedpoint.Value) {
q.mu.Lock()
q.Available += fund
q.Available = q.Available.Add(fund)
q.mu.Unlock()
}
func (q *Quota) Lock(fund fixedpoint.Value) bool {
if fund > q.Available {
if fund.Compare(q.Available) > 0 {
return false
}
q.mu.Lock()
q.Available -= fund
q.Locked += fund
q.Available = q.Available.Sub(fund)
q.Locked = q.Locked.Add(fund)
q.mu.Unlock()
return true
@ -33,14 +33,14 @@ func (q *Quota) Lock(fund fixedpoint.Value) bool {
func (q *Quota) Commit() {
q.mu.Lock()
q.Locked = 0
q.Locked = fixedpoint.Zero
q.mu.Unlock()
}
func (q *Quota) Rollback() {
q.mu.Lock()
q.Available += q.Locked
q.Locked = 0
q.Available = q.Available.Add(q.Locked)
q.Locked = fixedpoint.Zero
q.mu.Unlock()
}

View File

@ -93,10 +93,10 @@ func NewStandardIndicatorSet(symbol string, store *MarketDataStore) *StandardInd
// BOLL returns the bollinger band indicator of the given interval and the window,
// Please note that the K for std dev is fixed and defaults to 2.0
func (set *StandardIndicatorSet) BOLL(iw types.IntervalWindow, bandWidth float64) *indicator.BOLL {
func (set *StandardIndicatorSet) BOLL(iw types.IntervalWindow, bandWidth fixedpoint.Value) *indicator.BOLL {
inc, ok := set.boll[iw]
if !ok {
inc = &indicator.BOLL{IntervalWindow: iw, K: bandWidth}
inc = &indicator.BOLL{IntervalWindow: iw, K: bandWidth.Float64()}
inc.Bind(set.store)
set.boll[iw] = inc
}
@ -545,17 +545,17 @@ func (session *ExchangeSession) OrderBook(symbol string) (s *types.StreamOrderBo
return s, ok
}
func (session *ExchangeSession) StartPrice(symbol string) (price float64, ok bool) {
func (session *ExchangeSession) StartPrice(symbol string) (price fixedpoint.Value, ok bool) {
price, ok = session.startPrices[symbol]
return price, ok
}
func (session *ExchangeSession) LastPrice(symbol string) (price float64, ok bool) {
func (session *ExchangeSession) LastPrice(symbol string) (price fixedpoint.Value, ok bool) {
price, ok = session.lastPrices[symbol]
return price, ok
}
func (session *ExchangeSession) LastPrices() map[string]float64 {
func (session *ExchangeSession) LastPrices() map[string]fixedpoint.Value {
return session.lastPrices
}
@ -644,7 +644,7 @@ func (session *ExchangeSession) FindPossibleSymbols() (symbols []string, err err
var fiatAssets []string
for _, currency := range types.FiatCurrencies {
if balance, ok := balances[currency]; ok && balance.Total() > 0 {
if balance, ok := balances[currency]; ok && balance.Total().Sign() > 0 {
fiatAssets = append(fiatAssets, currency)
}
}
@ -659,7 +659,7 @@ func (session *ExchangeSession) FindPossibleSymbols() (symbols []string, err err
// ignore the asset that we don't have in the balance sheet
balance, hasAsset := balances[market.BaseCurrency]
if !hasAsset || balance.Total() == 0 {
if !hasAsset || balance.Total().IsZero() {
continue
}
@ -740,8 +740,8 @@ func (session *ExchangeSession) InitExchange(name string, exchange types.Exchang
session.orderBooks = make(map[string]*types.StreamOrderBook)
session.markets = make(map[string]types.Market)
session.lastPrices = make(map[string]float64)
session.startPrices = make(map[string]float64)
session.lastPrices = make(map[string]fixedpoint.Value)
session.startPrices = make(map[string]fixedpoint.Value)
session.marketDataStores = make(map[string]*MarketDataStore)
session.positions = make(map[string]*types.Position)
session.standardIndicatorSets = make(map[string]*StandardIndicatorSet)
@ -812,7 +812,7 @@ func (session *ExchangeSession) metricsTradeUpdater(trade types.Trade) {
"symbol": trade.Symbol,
"liquidity": trade.Liquidity(),
}
metricsTradingVolume.With(labels).Add(trade.Quantity * trade.Price)
metricsTradingVolume.With(labels).Add(trade.Quantity.Mul(trade.Price).Float64())
metricsTradesTotal.With(labels).Inc()
metricsLastUpdateTimeBalance.With(prometheus.Labels{
"exchange": session.ExchangeName.String(),

View File

@ -3,7 +3,6 @@ package bbgo
import (
"context"
"errors"
"math"
log "github.com/sirupsen/logrus"
@ -37,7 +36,7 @@ type TrailingStopController struct {
Symbol string
position *types.Position
latestHigh float64
latestHigh fixedpoint.Value
averageCost fixedpoint.Value
// activated: when the price reaches the min profit price, we set the activated to true to enable trailing stop
@ -74,25 +73,25 @@ func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSessi
}
// if average cost is zero, we don't need trailing stop
if c.averageCost == 0 || c.position == nil {
if c.averageCost.IsZero() || c.position == nil {
return
}
closePrice := kline.Close
// if we don't hold position, we just skip dust position
if c.position.Base.Abs().Float64() < c.position.Market.MinQuantity || c.position.Base.Abs().Float64()*closePrice < c.position.Market.MinNotional {
if c.position.Base.Abs().Compare(c.position.Market.MinQuantity) < 0 || c.position.Base.Abs().Mul(closePrice).Compare(c.position.Market.MinNotional) < 0 {
return
}
if c.MinProfit <= 0 {
if c.MinProfit.Sign() <= 0 {
// when minProfit is not set, we should always activate the trailing stop order
c.activated = true
} else if closePrice > c.averageCost.Float64() ||
changeRate(closePrice, c.averageCost.Float64()) > c.MinProfit.Float64() {
} else if closePrice.Compare(c.averageCost) > 0 ||
changeRate(closePrice, c.averageCost).Compare(c.MinProfit) > 0 {
if !c.activated {
log.Infof("%s trailing stop activated at price %f", c.Symbol, closePrice)
log.Infof("%s trailing stop activated at price %s", c.Symbol, closePrice.String())
c.activated = true
}
} else {
@ -105,37 +104,37 @@ func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSessi
// if the trailing stop order is activated, we should update the latest high
// update the latest high
c.latestHigh = math.Max(closePrice, c.latestHigh)
c.latestHigh = fixedpoint.Max(closePrice, c.latestHigh)
// if it's in the callback rate, we don't want to trigger stop
if closePrice < c.latestHigh && changeRate(closePrice, c.latestHigh) < c.CallbackRate.Float64() {
if closePrice.Compare(c.latestHigh) < 0 && changeRate(closePrice, c.latestHigh).Compare(c.CallbackRate) < 0 {
return
}
if c.Virtual {
// if the profit rate is defined, and it is less than our minimum profit rate, we skip stop
if c.MinProfit > 0 &&
(closePrice < c.averageCost.Float64() ||
changeRate(closePrice, c.averageCost.Float64()) < c.MinProfit.Float64()) {
if c.MinProfit.Sign() > 0 &&
closePrice.Compare(c.averageCost) < 0 ||
changeRate(closePrice, c.averageCost).Compare(c.MinProfit) < 0 {
return
}
log.Infof("%s trailing stop emitted, latest high: %f, closed price: %f, average cost: %f, profit spread: %f",
log.Infof("%s trailing stop emitted, latest high: %s, closed price: %s, average cost: %s, profit spread: %s",
c.Symbol,
c.latestHigh,
closePrice,
c.averageCost.Float64(),
closePrice-c.averageCost.Float64())
c.latestHigh.String(),
closePrice.String(),
c.averageCost.String(),
closePrice.Sub(c.averageCost).String())
log.Infof("current %s position: %s", c.Symbol, c.position.String())
marketOrder := c.position.NewClosePositionOrder(c.ClosePosition.Float64())
marketOrder := c.position.NewClosePositionOrder(c.ClosePosition)
if marketOrder != nil {
log.Infof("submitting %s market order to stop: %+v", c.Symbol, marketOrder)
// skip dust order
if marketOrder.Quantity*closePrice < c.position.Market.MinNotional {
log.Warnf("%s market order quote quantity %f < min notional %f, skip placing order", c.Symbol, marketOrder.Quantity*closePrice, c.position.Market.MinNotional)
if marketOrder.Quantity.Mul(closePrice).Compare(c.position.Market.MinNotional) < 0 {
log.Warnf("%s market order quote quantity %s < min notional %s, skip placing order", c.Symbol, marketOrder.Quantity.Mul(closePrice).String(), c.position.Market.MinNotional.String())
return
}
@ -148,16 +147,16 @@ func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSessi
tradeCollector.Process()
// reset the state
c.latestHigh = 0.0
c.latestHigh = fixedpoint.Zero
c.activated = false
}
} else {
// place stop order only when the closed price is greater than the current average cost
if c.MinProfit > 0 && closePrice > c.averageCost.Float64() &&
changeRate(closePrice, c.averageCost.Float64()) >= c.MinProfit.Float64() {
if c.MinProfit.Sign() > 0 && closePrice.Compare(c.averageCost) > 0 &&
changeRate(closePrice, c.averageCost).Compare(c.MinProfit) >= 0 {
stopPrice := c.averageCost.MulFloat64(1.0 + c.MinProfit.Float64())
orderForm := c.GenerateStopOrder(stopPrice.Float64(), c.averageCost.Float64())
stopPrice := c.averageCost.Mul(fixedpoint.One.Add(c.MinProfit))
orderForm := c.GenerateStopOrder(stopPrice, c.averageCost)
if orderForm != nil {
log.Infof("updating %s stop limit order to simulate trailing stop order...", c.Symbol)
@ -175,26 +174,27 @@ func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSessi
})
}
func (c *TrailingStopController) GenerateStopOrder(stopPrice, price float64) *types.SubmitOrder {
func (c *TrailingStopController) GenerateStopOrder(stopPrice, price fixedpoint.Value) *types.SubmitOrder {
base := c.position.GetBase()
if base == 0 {
if base.IsZero() {
return nil
}
quantity := math.Abs(base.Float64())
quoteQuantity := price * quantity
quantity := base.Abs()
quoteQuantity := price.Mul(quantity)
if c.ClosePosition > 0 {
quantity = quantity * c.ClosePosition.Float64()
if c.ClosePosition.Sign() > 0 {
quantity = quantity.Mul(c.ClosePosition)
}
// skip dust orders
if quantity < c.position.Market.MinQuantity || quoteQuantity < c.position.Market.MinNotional {
if quantity.Compare(c.position.Market.MinQuantity) < 0 ||
quoteQuantity.Compare(c.position.Market.MinNotional) < 0 {
return nil
}
side := types.SideTypeSell
if base < 0 {
if base.Sign() < 0 {
side = types.SideTypeBuy
}
@ -282,6 +282,6 @@ func (s *SmartStops) RunStopControllers(ctx context.Context, session *ExchangeSe
}
}
func changeRate(a, b float64) float64 {
return math.Abs(a-b) / b
func changeRate(a, b fixedpoint.Value) fixedpoint.Value {
return a.Sub(b).Div(b).Abs()
}

View File

@ -93,56 +93,56 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
// if number of ticks = 2, than the tickSpread is 0.02
// tickSpread = min(0.02 - 0.01, 0.02)
// price = first bid price 28.00 + tickSpread (0.01) = 28.01
tickSize := fixedpoint.NewFromFloat(e.market.TickSize)
tickSpread := tickSize.MulInt(e.NumOfTicks)
if spread > tickSize {
tickSize := e.market.TickSize
tickSpread := tickSize.Mul(fixedpoint.NewFromInt(int64(e.NumOfTicks)))
if spread.Compare(tickSize) > 0 {
// there is a gap in the spread
tickSpread = fixedpoint.Min(tickSpread, spread-tickSize)
tickSpread = fixedpoint.Min(tickSpread, spread.Sub(tickSize))
switch e.Side {
case types.SideTypeSell:
newPrice -= tickSpread
newPrice = newPrice.Sub(tickSpread)
case types.SideTypeBuy:
newPrice += tickSpread
newPrice = newPrice.Add(tickSpread)
}
}
if e.StopPrice > 0 {
if e.StopPrice.Sign() > 0 {
switch e.Side {
case types.SideTypeSell:
if newPrice < e.StopPrice {
log.Infof("%s order price %f is lower than the stop sell price %f, setting order price to the stop sell price %f",
if newPrice.Compare(e.StopPrice) < 0 {
log.Infof("%s order price %s is lower than the stop sell price %s, setting order price to the stop sell price %s",
e.Symbol,
newPrice.Float64(),
e.StopPrice.Float64(),
e.StopPrice.Float64())
newPrice.String(),
e.StopPrice.String(),
e.StopPrice.String())
newPrice = e.StopPrice
}
case types.SideTypeBuy:
if newPrice > e.StopPrice {
log.Infof("%s order price %f is higher than the stop buy price %f, setting order price to the stop buy price %f",
if newPrice.Compare(e.StopPrice) > 0 {
log.Infof("%s order price %s is higher than the stop buy price %s, setting order price to the stop buy price %s",
e.Symbol,
newPrice.Float64(),
e.StopPrice.Float64(),
e.StopPrice.Float64())
newPrice.String(),
e.StopPrice.String(),
e.StopPrice.String())
newPrice = e.StopPrice
}
}
}
minQuantity := fixedpoint.NewFromFloat(e.market.MinQuantity)
minQuantity := e.market.MinQuantity
base := e.position.GetBase()
restQuantity := e.TargetQuantity - fixedpoint.Abs(base)
restQuantity := e.TargetQuantity.Sub(base.Abs())
if restQuantity <= 0 {
if restQuantity.Sign() <= 0 {
if e.cancelContextIfTargetQuantityFilled() {
return
}
}
if restQuantity < minQuantity {
return orderForm, fmt.Errorf("can not continue placing orders, rest quantity %f is less than the min quantity %f", restQuantity.Float64(), minQuantity.Float64())
if restQuantity.Compare(minQuantity) < 0 {
return orderForm, fmt.Errorf("can not continue placing orders, rest quantity %s is less than the min quantity %s", restQuantity.String(), minQuantity.String())
}
// when slice = 1000, if we only have 998, we should adjust our quantity to 998
@ -150,12 +150,12 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
// if the rest quantity in the next round is not enough, we should merge the rest quantity into this round
// if there are rest slices
nextRestQuantity := restQuantity - e.SliceQuantity
if nextRestQuantity > 0 && nextRestQuantity < minQuantity {
nextRestQuantity := restQuantity.Sub(e.SliceQuantity)
if nextRestQuantity.Sign() > 0 && nextRestQuantity.Compare(minQuantity) < 0 {
orderQuantity = restQuantity
}
minNotional := fixedpoint.NewFromFloat(e.market.MinNotional)
minNotional := e.market.MinNotional
orderQuantity = AdjustQuantityByMinAmount(orderQuantity, newPrice, minNotional)
switch e.Side {
@ -179,7 +179,7 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
Symbol: e.Symbol,
Side: e.Side,
Type: types.OrderTypeMarket,
Quantity: restQuantity.Float64(),
Quantity: restQuantity,
Market: e.market,
}
return orderForm, nil
@ -191,8 +191,8 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
Symbol: e.Symbol,
Side: e.Side,
Type: types.OrderTypeLimitMaker,
Quantity: orderQuantity.Float64(),
Price: newPrice.Float64(),
Quantity: orderQuantity,
Price: newPrice,
Market: e.market,
TimeInForce: "GTC",
}
@ -214,8 +214,9 @@ func (e *TwapExecution) updateOrder(ctx context.Context) error {
return fmt.Errorf("no secoond price on the %s order book %s, can not update", e.Symbol, e.Side)
}
tickSize := fixedpoint.NewFromFloat(e.market.TickSize)
tickSpread := tickSize.MulInt(e.NumOfTicks)
tickSize := e.market.TickSize
numOfTicks := fixedpoint.NewFromInt(int64(e.NumOfTicks))
tickSpread := tickSize.Mul(numOfTicks)
// check and see if we need to cancel the existing active orders
for e.activeMakerOrders.NumOfOrders() > 0 {
@ -227,12 +228,12 @@ func (e *TwapExecution) updateOrder(ctx context.Context) error {
// get the first order
order := orders[0]
orderPrice := fixedpoint.NewFromFloat(order.Price)
orderPrice := order.Price
// quantity := fixedpoint.NewFromFloat(order.Quantity)
remainingQuantity := order.Quantity - order.ExecutedQuantity
if remainingQuantity <= e.market.MinQuantity {
log.Infof("order remaining quantity %f is less than the market minimal quantity %f, skip updating order", remainingQuantity, e.market.MinQuantity)
remainingQuantity := order.Quantity.Sub(order.ExecutedQuantity)
if remainingQuantity.Compare(e.market.MinQuantity) <= 0 {
log.Infof("order remaining quantity %s is less than the market minimal quantity %s, skip updating order", remainingQuantity.String(), e.market.MinQuantity.String())
return nil
}
@ -241,24 +242,24 @@ func (e *TwapExecution) updateOrder(ctx context.Context) error {
// DO NOT UPDATE IF:
// tickSpread > 0 AND current order price == second price + tickSpread
// current order price == first price
log.Infof("orderPrice = %f first.Price = %f second.Price = %f tickSpread = %f", orderPrice.Float64(), first.Price.Float64(), second.Price.Float64(), tickSpread.Float64())
log.Infof("orderPrice = %s first.Price = %s second.Price = %s tickSpread = %s", orderPrice.String(), first.Price.String(), second.Price.String(), tickSpread.String())
switch e.Side {
case types.SideTypeBuy:
if tickSpread > 0 && orderPrice == second.Price+tickSpread {
log.Infof("the current order is already on the best ask price %f", orderPrice.Float64())
if tickSpread.Sign() > 0 && orderPrice == second.Price.Add(tickSpread) {
log.Infof("the current order is already on the best ask price %s", orderPrice.String())
return nil
} else if orderPrice == first.Price {
log.Infof("the current order is already on the best bid price %f", orderPrice.Float64())
log.Infof("the current order is already on the best bid price %s", orderPrice.String())
return nil
}
case types.SideTypeSell:
if tickSpread > 0 && orderPrice == second.Price-tickSpread {
log.Infof("the current order is already on the best ask price %f", orderPrice.Float64())
if tickSpread.Sign() > 0 && orderPrice == second.Price.Sub(tickSpread) {
log.Infof("the current order is already on the best ask price %s", orderPrice.String())
return nil
} else if orderPrice == first.Price {
log.Infof("the current order is already on the best ask price %f", orderPrice.Float64())
log.Infof("the current order is already on the best ask price %s", orderPrice.String())
return nil
}
}
@ -340,7 +341,7 @@ func (e *TwapExecution) orderUpdater(ctx context.Context) {
func (e *TwapExecution) cancelContextIfTargetQuantityFilled() bool {
base := e.position.GetBase()
if fixedpoint.Abs(base) >= e.TargetQuantity {
if base.Abs().Compare(e.TargetQuantity) >= 0 {
log.Infof("filled target quantity, canceling the order execution context")
e.cancelExecution()
return true

View File

@ -418,17 +418,17 @@ func genFakeAssets() types.AssetMap {
"MAX": types.Balance{Currency: "MAX", Available: fixedpoint.NewFromFloat(200000.0 * rand.Float64())},
"COMP": types.Balance{Currency: "COMP", Available: fixedpoint.NewFromFloat(100.0 * rand.Float64())},
}
assets := balances.Assets(map[string]float64{
"BTCUSDT": 38000.0,
"BCHUSDT": 478.0,
"LTCUSDT": 150.0,
"COMPUSDT": 450.0,
"ETHUSDT": 1700.0,
"BNBUSDT": 70.0,
"GRTUSDT": 0.89,
"DOTUSDT": 20.0,
"SANDUSDT": 0.13,
"MAXUSDT": 0.122,
assets := balances.Assets(map[string]fixedpoint.Value{
"BTCUSDT": fixedpoint.NewFromFloat(38000.0),
"BCHUSDT": fixedpoint.NewFromFloat(478.0),
"LTCUSDT": fixedpoint.NewFromFloat(150.0),
"COMPUSDT": fixedpoint.NewFromFloat(450.0),
"ETHUSDT": fixedpoint.NewFromFloat(1700.0),
"BNBUSDT": fixedpoint.NewFromFloat(70.0),
"GRTUSDT": fixedpoint.NewFromFloat(0.89),
"DOTUSDT": fixedpoint.NewFromFloat(20.0),
"SANDUSDT": fixedpoint.NewFromFloat(0.13),
"MAXUSDT": fixedpoint.NewFromFloat(0.122),
})
for currency, asset := range assets {
totalAssets[currency] = asset