diff --git a/bbgo/exchange/max/maxapi/order.go b/bbgo/exchange/max/maxapi/order.go index cd53c025a..3d7d9a369 100644 --- a/bbgo/exchange/max/maxapi/order.go +++ b/bbgo/exchange/max/maxapi/order.go @@ -32,20 +32,20 @@ type OrderService struct { type Order struct { ID uint64 `json:"id,omitempty" db:"exchange_id"` Side string `json:"side" db:"side"` - OrderType string `json:"ord_type" db:"order_type"` + OrderType string `json:"ord_type,omitempty" db:"order_type"` Price string `json:"price" db:"price"` AveragePrice string `json:"avg_price,omitempty" db:"average_price"` State string `json:"state,omitempty" db:"state"` - Market string `json:"market" db:"market"` + Market string `json:"market,omitempty" db:"market"` Volume string `json:"volume" db:"volume"` RemainingVolume string `json:"remaining_volume,omitempty" db:"remaining_volume"` ExecutedVolume string `json:"executed_volume,omitempty" db:"executed_volume"` TradesCount int64 `json:"trades_count,omitempty" db:"trades_count"` GroupID int64 `json:"group_id,omitempty" db:"group_id"` ClientOID string `json:"client_oid,omitempty" db:"client_oid"` - CreatedAt time.Time `db:"created_at"` + CreatedAt time.Time `json:"-" db:"created_at"` CreatedAtMs int64 `json:"created_at_in_ms,omitempty"` - InsertedAt time.Time `db:"inserted_at"` + InsertedAt time.Time `json:"-" db:"inserted_at"` } // All returns all orders for the authenticated account. @@ -182,20 +182,22 @@ func (s *OrderService) Get(orderID uint64) (*Order, error) { // Create multiple order in a single request func (s *OrderService) CreateMulti(market string, orders []Order) ([]Order, error) { var returnOrders []Order - req, err := s.client.newAuthenticatedRequest("POST", "v2/orders/multi", map[string]interface{}{ + req, err := s.client.newAuthenticatedRequest("POST", "v2/orders/multi/onebyone", map[string]interface{}{ "market": market, "orders": orders, }) if err != nil { return returnOrders, errors.Wrapf(err, "failed to create %s orders", market) } + response, err := s.client.sendRequest(req) if err != nil { return returnOrders, err } + if errJson := response.DecodeJSON(&returnOrders); errJson != nil { return returnOrders, errJson } + return returnOrders, err } - diff --git a/bbgo/exchange/max/maxapi/userdata.go b/bbgo/exchange/max/maxapi/userdata.go index 2f357cb27..531be7c39 100644 --- a/bbgo/exchange/max/maxapi/userdata.go +++ b/bbgo/exchange/max/maxapi/userdata.go @@ -3,6 +3,9 @@ package max import ( "github.com/pkg/errors" "github.com/valyala/fastjson" + + "github.com/c9s/bbgo/pkg/bbgo/types" + "github.com/c9s/bbgo/pkg/util" ) type BaseEvent struct { @@ -165,6 +168,24 @@ type BalanceMessage struct { Locked string `json:"l"` } +func (m *BalanceMessage) Balance() (*types.Balance, error) { + available, err := util.ParseFloat(m.Available) + if err != nil { + return nil, err + } + + locked, err := util.ParseFloat(m.Locked) + if err != nil { + return nil, err + } + + return &types.Balance{ + Currency: m.Currency, + Locked: locked, + Available: available, + }, nil +} + func parseBalance(v *fastjson.Value) BalanceMessage { return BalanceMessage{ Currency: string(v.GetStringBytes("cu")), diff --git a/bbgo/fixedpoint/convert.go b/bbgo/fixedpoint/convert.go index 7a245d320..41c00c38c 100644 --- a/bbgo/fixedpoint/convert.go +++ b/bbgo/fixedpoint/convert.go @@ -15,6 +15,10 @@ func (v Value) Float64() float64 { return float64(v) / DefaultPow } +func (v Value) Int64() int64 { + return int64(v) +} + func (v Value) Mul(v2 Value) Value { return NewFromFloat(v.Float64() * v2.Float64()) } @@ -43,3 +47,11 @@ func NewFromString(input string) (Value, error) { func NewFromFloat(val float64) Value { return Value(int64(math.Round(val * DefaultPow))) } + +func NewFromInt(val int) Value { + return Value(int64(val * DefaultPow)) +} + +func NewFromInt64(val int64) Value { + return Value(val * DefaultPow) +} diff --git a/bbgo/types/account.go b/bbgo/types/account.go index 1adca2182..488e9d0da 100644 --- a/bbgo/types/account.go +++ b/bbgo/types/account.go @@ -13,3 +13,13 @@ type Account struct { AccountType string Balances map[string]Balance } + +func (a *Account) UpdateBalance(b Balance) { + a.Balances[b.Currency] = b +} + +func NewAccount() *Account { + return &Account{ + Balances: make(map[string]Balance), + } +} diff --git a/bbgo/types/orderbook.go b/bbgo/types/orderbook.go index 61c4e01ba..e9705951a 100644 --- a/bbgo/types/orderbook.go +++ b/bbgo/types/orderbook.go @@ -3,6 +3,7 @@ package types import ( "fmt" "sort" + "sync" "github.com/c9s/bbgo/pkg/bbgo/fixedpoint" ) @@ -34,14 +35,27 @@ func (slice PriceVolumeSlice) Trim() (pvs PriceVolumeSlice) { } func (slice PriceVolumeSlice) Copy() PriceVolumeSlice { - // this is faster than make + // this is faster than make (however it's only for simple types) return append(slice[:0:0], slice...) } +func (slice PriceVolumeSlice) IndexByVolumeDepth(requiredVolume fixedpoint.Value) int { + var tv int64 = 0 + for x, el := range slice { + tv += el.Volume.Int64() + if tv >= requiredVolume.Int64() { + return x + } + } + + // not deep enough + return -1 +} + func (slice PriceVolumeSlice) InsertAt(idx int, pv PriceVolume) PriceVolumeSlice { rear := append([]PriceVolume{}, slice[idx:]...) - slice = append(slice[:idx], pv) - return append(slice, rear...) + newSlice := append(slice[:idx], pv) + return append(newSlice, rear...) } func (slice PriceVolumeSlice) Remove(price fixedpoint.Value, descending bool) PriceVolumeSlice { @@ -89,13 +103,26 @@ func (slice PriceVolumeSlice) Upsert(pv PriceVolume, descending bool) PriceVolum return slice } +//go:generate callbackgen -type OrderBook type OrderBook struct { Symbol string Bids PriceVolumeSlice Asks PriceVolumeSlice + + loadCallbacks []func(book *OrderBook) + updateCallbacks []func(book *OrderBook) + bidsChangeCallbacks []func(pvs PriceVolumeSlice) + asksChangeCallbacks []func(pvs PriceVolumeSlice) } -func (b *OrderBook) UpdateAsks(pvs PriceVolumeSlice) { +func (b *OrderBook) Copy() (book OrderBook) { + book = *b + book.Bids = b.Bids.Copy() + book.Asks = b.Asks.Copy() + return book +} + +func (b *OrderBook) updateAsks(pvs PriceVolumeSlice) { for _, pv := range pvs { if pv.Volume == 0 { b.Asks = b.Asks.Remove(pv.Price, false) @@ -103,9 +130,11 @@ func (b *OrderBook) UpdateAsks(pvs PriceVolumeSlice) { b.Asks = b.Asks.Upsert(pv, false) } } + + b.EmitAsksChange(b.Asks) } -func (b *OrderBook) UpdateBids(pvs PriceVolumeSlice) { +func (b *OrderBook) updateBids(pvs PriceVolumeSlice) { for _, pv := range pvs { if pv.Volume == 0 { b.Bids = b.Bids.Remove(pv.Price, true) @@ -113,17 +142,29 @@ func (b *OrderBook) UpdateBids(pvs PriceVolumeSlice) { b.Bids = b.Bids.Upsert(pv, true) } } + + b.EmitBidsChange(b.Bids) +} + +func (b *OrderBook) update(book OrderBook) { + b.updateBids(book.Bids) + b.updateAsks(book.Asks) +} + +func (b *OrderBook) Reset() { + b.Bids = nil + b.Asks = nil } func (b *OrderBook) Load(book OrderBook) { - b.Bids = nil - b.Asks = nil - b.Update(book) + b.Reset() + b.update(book) + b.EmitLoad(b) } func (b *OrderBook) Update(book OrderBook) { - b.UpdateBids(book.Bids) - b.UpdateAsks(book.Asks) + b.update(book) + b.EmitUpdate(b) } func (b *OrderBook) Print() { @@ -138,3 +179,36 @@ func (b *OrderBook) Print() { fmt.Printf("- BID: %s\n", bid.String()) } } + +type MutexOrderBook struct { + sync.Mutex + + *OrderBook +} + +func NewMutexOrderBook(symbol string) *MutexOrderBook { + return &MutexOrderBook{ + OrderBook: &OrderBook{Symbol: symbol}, + } +} + +func (b *MutexOrderBook) Load(book OrderBook) { + b.Lock() + defer b.Unlock() + + b.Reset() + b.update(book) + b.EmitLoad(b.OrderBook) +} + +func (b *MutexOrderBook) Get() OrderBook { + return b.OrderBook.Copy() +} + +func (b *MutexOrderBook) Update(book OrderBook) { + b.Lock() + defer b.Unlock() + + b.update(book) + b.EmitUpdate(b.OrderBook) +} diff --git a/bbgo/types/orderbook_callbacks.go b/bbgo/types/orderbook_callbacks.go new file mode 100644 index 000000000..2d3535cfc --- /dev/null +++ b/bbgo/types/orderbook_callbacks.go @@ -0,0 +1,45 @@ +// Code generated by "callbackgen -type OrderBook"; DO NOT EDIT. + +package types + +import () + +func (b *OrderBook) OnLoad(cb func(book *OrderBook)) { + b.loadCallbacks = append(b.loadCallbacks, cb) +} + +func (b *OrderBook) EmitLoad(book *OrderBook) { + for _, cb := range b.loadCallbacks { + cb(book) + } +} + +func (b *OrderBook) OnUpdate(cb func(book *OrderBook)) { + b.updateCallbacks = append(b.updateCallbacks, cb) +} + +func (b *OrderBook) EmitUpdate(book *OrderBook) { + for _, cb := range b.updateCallbacks { + cb(book) + } +} + +func (b *OrderBook) OnBidsChange(cb func(pvs PriceVolumeSlice)) { + b.bidsChangeCallbacks = append(b.bidsChangeCallbacks, cb) +} + +func (b *OrderBook) EmitBidsChange(pvs PriceVolumeSlice) { + for _, cb := range b.bidsChangeCallbacks { + cb(pvs) + } +} + +func (b *OrderBook) OnAsksChange(cb func(pvs PriceVolumeSlice)) { + b.asksChangeCallbacks = append(b.asksChangeCallbacks, cb) +} + +func (b *OrderBook) EmitAsksChange(pvs PriceVolumeSlice) { + for _, cb := range b.asksChangeCallbacks { + cb(pvs) + } +} diff --git a/util/math.go b/util/math.go index a30504abd..631341290 100644 --- a/util/math.go +++ b/util/math.go @@ -22,6 +22,15 @@ func FormatFloat(val float64, prec int) string { return strconv.FormatFloat(val, 'f', prec, 64) } +func ParseFloat(s string) (float64, error) { + if len(s) == 0 { + return 0.0, nil + } + + return strconv.ParseFloat(s, 64) +} + + func MustParseFloat(s string) float64 { if len(s) == 0 { return 0.0