mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 00:05:15 +00:00
fix all the fixedpoint use other than strategy
This commit is contained in:
parent
b8bf2af14d
commit
d9450e823e
|
@ -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),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user