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

View File

@ -15,7 +15,7 @@ type PriceOrder struct {
type PriceOrderSlice []PriceOrder type PriceOrderSlice []PriceOrder
func (slice PriceOrderSlice) Len() int { return len(slice) } 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) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] }
func (slice PriceOrderSlice) InsertAt(idx int, po PriceOrder) PriceOrderSlice { 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) { func (slice PriceOrderSlice) Find(price fixedpoint.Value, descending bool) (pv PriceOrder, idx int) {
idx = sort.Search(len(slice), func(i int) bool { idx = sort.Search(len(slice), func(i int) bool {
if descending { 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 { 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) { orderWriter := func(order types.Order) {
switch order.Status { switch order.Status {
case types.OrderStatusFilled, types.OrderStatusCanceled: case types.OrderStatusFilled, types.OrderStatusCanceled:
if order.ExecutedQuantity > 0.0 { if order.ExecutedQuantity.Sign() > 0 {
if err := environ.OrderService.Insert(order); err != nil { if err := environ.OrderService.Insert(order); err != nil {
log.WithError(err).Errorf("order insert error: %+v", order) 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/interact"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/fixedpoint"
) )
type PositionCloser interface { type PositionCloser interface {
ClosePosition(ctx context.Context, percentage float64) error ClosePosition(ctx context.Context, percentage fixedpoint.Value) error
} }
type PositionReader interface { type PositionReader interface {
@ -23,7 +24,7 @@ type PositionReader interface {
type closePositionContext struct { type closePositionContext struct {
signature string signature string
closer PositionCloser closer PositionCloser
percentage float64 percentage fixedpoint.Value
} }
type CoreInteraction struct { type CoreInteraction struct {
@ -75,7 +76,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
message := "Your balances\n" message := "Your balances\n"
balances := session.Account.Balances() balances := session.Account.Balances()
for _, balance := range balances { for _, balance := range balances {
if balance.Total() == 0 { if balance.Total().IsZero() {
continue continue
} }
@ -121,7 +122,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
reply.Send("Your current position:") reply.Send("Your current position:")
reply.Send(position.PlainText()) reply.Send(position.PlainText())
if position.Base == 0 { if position.Base.IsZero() {
reply.Message(fmt.Sprintf("Strategy %q has no opened position", signature)) reply.Message(fmt.Sprintf("Strategy %q has no opened position", signature))
return fmt.Errorf("strategy %T has no opened position", strategy) 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("Your current position:")
reply.Send(position.PlainText()) reply.Send(position.PlainText())
if position.Base == 0 { if position.Base.IsZero() {
reply.Message("No opened position") reply.Message("No opened position")
if kc, ok := reply.(interact.KeyboardController) ; ok { if kc, ok := reply.(interact.KeyboardController) ; ok {
kc.RemoveKeyboard() kc.RemoveKeyboard()
@ -190,7 +191,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
return nil return nil
}).Next(func(percentageStr string, reply interact.Reply) error { }).Next(func(percentageStr string, reply interact.Reply) error {
percentage, err := parseFloatPercent(percentageStr, 64) percentage, err := fixedpoint.NewFromString(percentageStr)
if err != nil { if err != nil {
reply.Message(fmt.Sprintf("%q is not a valid percentage string", percentageStr)) reply.Message(fmt.Sprintf("%q is not a valid percentage string", percentageStr))
return err return err

View File

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

View File

@ -18,7 +18,7 @@ var (
func AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixedpoint.Value) fixedpoint.Value { func AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixedpoint.Value) fixedpoint.Value {
// modify quantity for the min amount // modify quantity for the min amount
amount := currentPrice.Mul(quantity) amount := currentPrice.Mul(quantity)
if amount < maxAmount { if amount.Compare(maxAmount) < 0 {
return quantity return quantity
} }
@ -30,7 +30,7 @@ func AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixedpoint.Valu
func AdjustQuantityByMinAmount(quantity, currentPrice, minAmount fixedpoint.Value) fixedpoint.Value { func AdjustQuantityByMinAmount(quantity, currentPrice, minAmount fixedpoint.Value) fixedpoint.Value {
// modify quantity for the min amount // modify quantity for the min amount
amount := currentPrice.Mul(quantity) amount := currentPrice.Mul(quantity)
if amount < minAmount { if amount.Compare(minAmount) < 0 {
ratio := minAmount.Div(amount) ratio := minAmount.Div(amount)
quantity = quantity.Mul(ratio) 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 // 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 // modify quantity for the min amount
amount := currentPrice * quantity amount := currentPrice.Mul(quantity)
if amount < minAmount { if amount.Compare(minAmount) < 0 {
ratio := minAmount / amount ratio := minAmount.Div(amount)
quantity *= ratio return quantity.Mul(ratio)
} }
return quantity return quantity
} }
func AdjustFloatQuantityByMaxAmount(quantity float64, price float64, maxAmount float64) float64 { func AdjustFloatQuantityByMaxAmount(quantity fixedpoint.Value, price fixedpoint.Value, maxAmount fixedpoint.Value) fixedpoint.Value {
amount := price * quantity amount := price.Mul(quantity)
if amount > maxAmount { if amount.Compare(maxAmount) > 0 {
ratio := maxAmount / amount ratio := maxAmount.Div(amount)
quantity *= ratio return quantity.Mul(ratio)
} }
return quantity return quantity

View File

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

View File

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

View File

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

View File

@ -14,18 +14,18 @@ type Quota struct {
func (q *Quota) Add(fund fixedpoint.Value) { func (q *Quota) Add(fund fixedpoint.Value) {
q.mu.Lock() q.mu.Lock()
q.Available += fund q.Available = q.Available.Add(fund)
q.mu.Unlock() q.mu.Unlock()
} }
func (q *Quota) Lock(fund fixedpoint.Value) bool { func (q *Quota) Lock(fund fixedpoint.Value) bool {
if fund > q.Available { if fund.Compare(q.Available) > 0 {
return false return false
} }
q.mu.Lock() q.mu.Lock()
q.Available -= fund q.Available = q.Available.Sub(fund)
q.Locked += fund q.Locked = q.Locked.Add(fund)
q.mu.Unlock() q.mu.Unlock()
return true return true
@ -33,14 +33,14 @@ func (q *Quota) Lock(fund fixedpoint.Value) bool {
func (q *Quota) Commit() { func (q *Quota) Commit() {
q.mu.Lock() q.mu.Lock()
q.Locked = 0 q.Locked = fixedpoint.Zero
q.mu.Unlock() q.mu.Unlock()
} }
func (q *Quota) Rollback() { func (q *Quota) Rollback() {
q.mu.Lock() q.mu.Lock()
q.Available += q.Locked q.Available = q.Available.Add(q.Locked)
q.Locked = 0 q.Locked = fixedpoint.Zero
q.mu.Unlock() 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, // 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 // 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] inc, ok := set.boll[iw]
if !ok { if !ok {
inc = &indicator.BOLL{IntervalWindow: iw, K: bandWidth} inc = &indicator.BOLL{IntervalWindow: iw, K: bandWidth.Float64()}
inc.Bind(set.store) inc.Bind(set.store)
set.boll[iw] = inc set.boll[iw] = inc
} }
@ -545,17 +545,17 @@ func (session *ExchangeSession) OrderBook(symbol string) (s *types.StreamOrderBo
return s, ok 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] price, ok = session.startPrices[symbol]
return price, ok 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] price, ok = session.lastPrices[symbol]
return price, ok return price, ok
} }
func (session *ExchangeSession) LastPrices() map[string]float64 { func (session *ExchangeSession) LastPrices() map[string]fixedpoint.Value {
return session.lastPrices return session.lastPrices
} }
@ -644,7 +644,7 @@ func (session *ExchangeSession) FindPossibleSymbols() (symbols []string, err err
var fiatAssets []string var fiatAssets []string
for _, currency := range types.FiatCurrencies { 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) 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 // ignore the asset that we don't have in the balance sheet
balance, hasAsset := balances[market.BaseCurrency] balance, hasAsset := balances[market.BaseCurrency]
if !hasAsset || balance.Total() == 0 { if !hasAsset || balance.Total().IsZero() {
continue continue
} }
@ -740,8 +740,8 @@ func (session *ExchangeSession) InitExchange(name string, exchange types.Exchang
session.orderBooks = make(map[string]*types.StreamOrderBook) session.orderBooks = make(map[string]*types.StreamOrderBook)
session.markets = make(map[string]types.Market) session.markets = make(map[string]types.Market)
session.lastPrices = make(map[string]float64) session.lastPrices = make(map[string]fixedpoint.Value)
session.startPrices = make(map[string]float64) session.startPrices = make(map[string]fixedpoint.Value)
session.marketDataStores = make(map[string]*MarketDataStore) session.marketDataStores = make(map[string]*MarketDataStore)
session.positions = make(map[string]*types.Position) session.positions = make(map[string]*types.Position)
session.standardIndicatorSets = make(map[string]*StandardIndicatorSet) session.standardIndicatorSets = make(map[string]*StandardIndicatorSet)
@ -812,7 +812,7 @@ func (session *ExchangeSession) metricsTradeUpdater(trade types.Trade) {
"symbol": trade.Symbol, "symbol": trade.Symbol,
"liquidity": trade.Liquidity(), "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() metricsTradesTotal.With(labels).Inc()
metricsLastUpdateTimeBalance.With(prometheus.Labels{ metricsLastUpdateTimeBalance.With(prometheus.Labels{
"exchange": session.ExchangeName.String(), "exchange": session.ExchangeName.String(),

View File

@ -3,7 +3,6 @@ package bbgo
import ( import (
"context" "context"
"errors" "errors"
"math"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -37,7 +36,7 @@ type TrailingStopController struct {
Symbol string Symbol string
position *types.Position position *types.Position
latestHigh float64 latestHigh fixedpoint.Value
averageCost fixedpoint.Value averageCost fixedpoint.Value
// activated: when the price reaches the min profit price, we set the activated to true to enable trailing stop // 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 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 return
} }
closePrice := kline.Close closePrice := kline.Close
// if we don't hold position, we just skip dust position // 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 return
} }
if c.MinProfit <= 0 { if c.MinProfit.Sign() <= 0 {
// when minProfit is not set, we should always activate the trailing stop order // when minProfit is not set, we should always activate the trailing stop order
c.activated = true c.activated = true
} else if closePrice > c.averageCost.Float64() || } else if closePrice.Compare(c.averageCost) > 0 ||
changeRate(closePrice, c.averageCost.Float64()) > c.MinProfit.Float64() { changeRate(closePrice, c.averageCost).Compare(c.MinProfit) > 0 {
if !c.activated { 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 c.activated = true
} }
} else { } 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 // if the trailing stop order is activated, we should update the latest high
// 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 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 return
} }
if c.Virtual { if c.Virtual {
// if the profit rate is defined, and it is less than our minimum profit rate, we skip stop // if the profit rate is defined, and it is less than our minimum profit rate, we skip stop
if c.MinProfit > 0 && if c.MinProfit.Sign() > 0 &&
(closePrice < c.averageCost.Float64() || closePrice.Compare(c.averageCost) < 0 ||
changeRate(closePrice, c.averageCost.Float64()) < c.MinProfit.Float64()) { changeRate(closePrice, c.averageCost).Compare(c.MinProfit) < 0 {
return 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.Symbol,
c.latestHigh, c.latestHigh.String(),
closePrice, closePrice.String(),
c.averageCost.Float64(), c.averageCost.String(),
closePrice-c.averageCost.Float64()) closePrice.Sub(c.averageCost).String())
log.Infof("current %s position: %s", c.Symbol, c.position.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 { if marketOrder != nil {
log.Infof("submitting %s market order to stop: %+v", c.Symbol, marketOrder) log.Infof("submitting %s market order to stop: %+v", c.Symbol, marketOrder)
// skip dust order // skip dust order
if 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 %f < min notional %f, skip placing order", c.Symbol, marketOrder.Quantity*closePrice, c.position.Market.MinNotional) 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 return
} }
@ -148,16 +147,16 @@ func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSessi
tradeCollector.Process() tradeCollector.Process()
// reset the state // reset the state
c.latestHigh = 0.0 c.latestHigh = fixedpoint.Zero
c.activated = false c.activated = false
} }
} else { } else {
// place stop order only when the closed price is greater than the current average cost // place stop order only when the closed price is greater than the current average cost
if c.MinProfit > 0 && closePrice > c.averageCost.Float64() && if c.MinProfit.Sign() > 0 && closePrice.Compare(c.averageCost) > 0 &&
changeRate(closePrice, c.averageCost.Float64()) >= c.MinProfit.Float64() { changeRate(closePrice, c.averageCost).Compare(c.MinProfit) >= 0 {
stopPrice := c.averageCost.MulFloat64(1.0 + c.MinProfit.Float64()) stopPrice := c.averageCost.Mul(fixedpoint.One.Add(c.MinProfit))
orderForm := c.GenerateStopOrder(stopPrice.Float64(), c.averageCost.Float64()) orderForm := c.GenerateStopOrder(stopPrice, c.averageCost)
if orderForm != nil { if orderForm != nil {
log.Infof("updating %s stop limit order to simulate trailing stop order...", c.Symbol) 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() base := c.position.GetBase()
if base == 0 { if base.IsZero() {
return nil return nil
} }
quantity := math.Abs(base.Float64()) quantity := base.Abs()
quoteQuantity := price * quantity quoteQuantity := price.Mul(quantity)
if c.ClosePosition > 0 { if c.ClosePosition.Sign() > 0 {
quantity = quantity * c.ClosePosition.Float64() quantity = quantity.Mul(c.ClosePosition)
} }
// skip dust orders // 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 return nil
} }
side := types.SideTypeSell side := types.SideTypeSell
if base < 0 { if base.Sign() < 0 {
side = types.SideTypeBuy side = types.SideTypeBuy
} }
@ -282,6 +282,6 @@ func (s *SmartStops) RunStopControllers(ctx context.Context, session *ExchangeSe
} }
} }
func changeRate(a, b float64) float64 { func changeRate(a, b fixedpoint.Value) fixedpoint.Value {
return math.Abs(a-b) / b 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 // if number of ticks = 2, than the tickSpread is 0.02
// tickSpread = min(0.02 - 0.01, 0.02) // tickSpread = min(0.02 - 0.01, 0.02)
// price = first bid price 28.00 + tickSpread (0.01) = 28.01 // price = first bid price 28.00 + tickSpread (0.01) = 28.01
tickSize := fixedpoint.NewFromFloat(e.market.TickSize) tickSize := e.market.TickSize
tickSpread := tickSize.MulInt(e.NumOfTicks) tickSpread := tickSize.Mul(fixedpoint.NewFromInt(int64(e.NumOfTicks)))
if spread > tickSize { if spread.Compare(tickSize) > 0 {
// there is a gap in the spread // there is a gap in the spread
tickSpread = fixedpoint.Min(tickSpread, spread-tickSize) tickSpread = fixedpoint.Min(tickSpread, spread.Sub(tickSize))
switch e.Side { switch e.Side {
case types.SideTypeSell: case types.SideTypeSell:
newPrice -= tickSpread newPrice = newPrice.Sub(tickSpread)
case types.SideTypeBuy: case types.SideTypeBuy:
newPrice += tickSpread newPrice = newPrice.Add(tickSpread)
} }
} }
if e.StopPrice > 0 { if e.StopPrice.Sign() > 0 {
switch e.Side { switch e.Side {
case types.SideTypeSell: case types.SideTypeSell:
if newPrice < e.StopPrice { if newPrice.Compare(e.StopPrice) < 0 {
log.Infof("%s order price %f is lower than the stop sell price %f, setting order price to the stop sell price %f", 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, e.Symbol,
newPrice.Float64(), newPrice.String(),
e.StopPrice.Float64(), e.StopPrice.String(),
e.StopPrice.Float64()) e.StopPrice.String())
newPrice = e.StopPrice newPrice = e.StopPrice
} }
case types.SideTypeBuy: case types.SideTypeBuy:
if newPrice > e.StopPrice { if newPrice.Compare(e.StopPrice) > 0 {
log.Infof("%s order price %f is higher than the stop buy price %f, setting order price to the stop buy price %f", 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, e.Symbol,
newPrice.Float64(), newPrice.String(),
e.StopPrice.Float64(), e.StopPrice.String(),
e.StopPrice.Float64()) e.StopPrice.String())
newPrice = e.StopPrice newPrice = e.StopPrice
} }
} }
} }
minQuantity := fixedpoint.NewFromFloat(e.market.MinQuantity) minQuantity := e.market.MinQuantity
base := e.position.GetBase() 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() { if e.cancelContextIfTargetQuantityFilled() {
return return
} }
} }
if restQuantity < minQuantity { if restQuantity.Compare(minQuantity) < 0 {
return orderForm, fmt.Errorf("can not continue placing orders, rest quantity %f is less than the min quantity %f", restQuantity.Float64(), minQuantity.Float64()) 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 // 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 the rest quantity in the next round is not enough, we should merge the rest quantity into this round
// if there are rest slices // if there are rest slices
nextRestQuantity := restQuantity - e.SliceQuantity nextRestQuantity := restQuantity.Sub(e.SliceQuantity)
if nextRestQuantity > 0 && nextRestQuantity < minQuantity { if nextRestQuantity.Sign() > 0 && nextRestQuantity.Compare(minQuantity) < 0 {
orderQuantity = restQuantity orderQuantity = restQuantity
} }
minNotional := fixedpoint.NewFromFloat(e.market.MinNotional) minNotional := e.market.MinNotional
orderQuantity = AdjustQuantityByMinAmount(orderQuantity, newPrice, minNotional) orderQuantity = AdjustQuantityByMinAmount(orderQuantity, newPrice, minNotional)
switch e.Side { switch e.Side {
@ -179,7 +179,7 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
Symbol: e.Symbol, Symbol: e.Symbol,
Side: e.Side, Side: e.Side,
Type: types.OrderTypeMarket, Type: types.OrderTypeMarket,
Quantity: restQuantity.Float64(), Quantity: restQuantity,
Market: e.market, Market: e.market,
} }
return orderForm, nil return orderForm, nil
@ -191,8 +191,8 @@ func (e *TwapExecution) newBestPriceOrder() (orderForm types.SubmitOrder, err er
Symbol: e.Symbol, Symbol: e.Symbol,
Side: e.Side, Side: e.Side,
Type: types.OrderTypeLimitMaker, Type: types.OrderTypeLimitMaker,
Quantity: orderQuantity.Float64(), Quantity: orderQuantity,
Price: newPrice.Float64(), Price: newPrice,
Market: e.market, Market: e.market,
TimeInForce: "GTC", 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) 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) tickSize := e.market.TickSize
tickSpread := tickSize.MulInt(e.NumOfTicks) numOfTicks := fixedpoint.NewFromInt(int64(e.NumOfTicks))
tickSpread := tickSize.Mul(numOfTicks)
// check and see if we need to cancel the existing active orders // check and see if we need to cancel the existing active orders
for e.activeMakerOrders.NumOfOrders() > 0 { for e.activeMakerOrders.NumOfOrders() > 0 {
@ -227,12 +228,12 @@ func (e *TwapExecution) updateOrder(ctx context.Context) error {
// get the first order // get the first order
order := orders[0] order := orders[0]
orderPrice := fixedpoint.NewFromFloat(order.Price) orderPrice := order.Price
// quantity := fixedpoint.NewFromFloat(order.Quantity) // quantity := fixedpoint.NewFromFloat(order.Quantity)
remainingQuantity := order.Quantity - order.ExecutedQuantity remainingQuantity := order.Quantity.Sub(order.ExecutedQuantity)
if remainingQuantity <= e.market.MinQuantity { if remainingQuantity.Compare(e.market.MinQuantity) <= 0 {
log.Infof("order remaining quantity %f is less than the market minimal quantity %f, skip updating order", remainingQuantity, e.market.MinQuantity) 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 return nil
} }
@ -241,24 +242,24 @@ func (e *TwapExecution) updateOrder(ctx context.Context) error {
// DO NOT UPDATE IF: // DO NOT UPDATE IF:
// tickSpread > 0 AND current order price == second price + tickSpread // tickSpread > 0 AND current order price == second price + tickSpread
// current order price == first price // 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 { switch e.Side {
case types.SideTypeBuy: case types.SideTypeBuy:
if tickSpread > 0 && orderPrice == second.Price+tickSpread { if tickSpread.Sign() > 0 && orderPrice == second.Price.Add(tickSpread) {
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 return nil
} else if orderPrice == first.Price { } 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 return nil
} }
case types.SideTypeSell: case types.SideTypeSell:
if tickSpread > 0 && orderPrice == second.Price-tickSpread { if tickSpread.Sign() > 0 && orderPrice == second.Price.Sub(tickSpread) {
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 return nil
} else if orderPrice == first.Price { } 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 return nil
} }
} }
@ -340,7 +341,7 @@ func (e *TwapExecution) orderUpdater(ctx context.Context) {
func (e *TwapExecution) cancelContextIfTargetQuantityFilled() bool { func (e *TwapExecution) cancelContextIfTargetQuantityFilled() bool {
base := e.position.GetBase() 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") log.Infof("filled target quantity, canceling the order execution context")
e.cancelExecution() e.cancelExecution()
return true return true

View File

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