mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 08:15:15 +00:00
Add more exchange order features
- use uuid for client order id - add stop limit and stop market order types - add order convert functions - improve submit orders
This commit is contained in:
parent
0d570dd4c8
commit
308427416a
|
@ -2,6 +2,7 @@
|
||||||
imports:
|
imports:
|
||||||
- github.com/c9s/bbgo/pkg/strategy/buyandhold
|
- github.com/c9s/bbgo/pkg/strategy/buyandhold
|
||||||
- github.com/c9s/bbgo/pkg/strategy/xpuremaker
|
- github.com/c9s/bbgo/pkg/strategy/xpuremaker
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
slack:
|
slack:
|
||||||
defaultChannel: "bbgo"
|
defaultChannel: "bbgo"
|
||||||
|
@ -40,3 +41,11 @@ exchangeStrategies:
|
||||||
interval: "1m"
|
interval: "1m"
|
||||||
baseQuantity: 0.01
|
baseQuantity: 0.01
|
||||||
minDropPercentage: -0.02
|
minDropPercentage: -0.02
|
||||||
|
- on: max
|
||||||
|
xpuremaker:
|
||||||
|
symbol: MAXUSDT
|
||||||
|
numOrders: 2
|
||||||
|
side: both
|
||||||
|
behindVolume: 1000.0
|
||||||
|
priceTick: 0.01
|
||||||
|
baseQuantity: 100.0
|
||||||
|
|
|
@ -10,6 +10,7 @@ reportTrades:
|
||||||
"ethusdt": "bbgo-ethusdt"
|
"ethusdt": "bbgo-ethusdt"
|
||||||
"bnbusdt": "bbgo-bnbusdt"
|
"bnbusdt": "bbgo-bnbusdt"
|
||||||
"sxpusdt": "bbgo-sxpusdt"
|
"sxpusdt": "bbgo-sxpusdt"
|
||||||
|
"maxusdt": "max-maxusdt"
|
||||||
|
|
||||||
reportPnL:
|
reportPnL:
|
||||||
- averageCostBySymbols:
|
- averageCostBySymbols:
|
||||||
|
|
|
@ -216,7 +216,7 @@ func generateOrders(symbol, side string, price, priceTick, baseVolume fixedpoint
|
||||||
|
|
||||||
orders = append(orders, maxapi.Order{
|
orders = append(orders, maxapi.Order{
|
||||||
Side: side,
|
Side: side,
|
||||||
OrderType: string(maxapi.OrderTypeLimit),
|
OrderType: maxapi.OrderTypeLimit,
|
||||||
Market: symbol,
|
Market: symbol,
|
||||||
Price: util.FormatFloat(price.Float64(), 3),
|
Price: util.FormatFloat(price.Float64(), 3),
|
||||||
Volume: util.FormatFloat(volume, 2),
|
Volume: util.FormatFloat(volume, 2),
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (reporter *TradeReporter) Report(trade types.Trade) {
|
||||||
|
|
||||||
var text = util.Render(`:handshake: {{ .Symbol }} {{ .Side }} Trade Execution @ {{ .Price }}`, trade)
|
var text = util.Render(`:handshake: {{ .Symbol }} {{ .Side }} Trade Execution @ {{ .Price }}`, trade)
|
||||||
if err := reporter.notifier.NotifyTo(channel, text, trade); err != nil {
|
if err := reporter.notifier.NotifyTo(channel, text, trade); err != nil {
|
||||||
log.WithError(err).Error("notifier error")
|
log.WithError(err).Errorf("notifier error, channel=%s", channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
package bbgo
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
package bbgo
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (p *OrderProcessor) Submit(ctx context.Context, order types.SubmitOrder) er
|
||||||
order.QuantityString = market.FormatVolume(quantity)
|
order.QuantityString = market.FormatVolume(quantity)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return p.Exchange.SubmitOrder(ctx, order)
|
return p.Exchange.SubmitOrders(ctx, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustQuantityByMinAmount(quantity float64, currentPrice float64, minAmount float64) float64 {
|
func adjustQuantityByMinAmount(quantity float64, currentPrice float64, minAmount float64) float64 {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (reporter *AverageCostPnLReporter) Of(sessions ...string) *AverageCostPnLRe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reporter *AverageCostPnLReporter) When(specs ...string) *AverageCostPnLReporter {
|
func (reporter *AverageCostPnLReporter) When(specs ...string) *AverageCostPnLReporter {
|
||||||
for _,spec := range specs {
|
for _, spec := range specs {
|
||||||
_, err := reporter.cron.AddJob(spec, reporter)
|
_, err := reporter.cron.AddJob(spec, reporter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -152,15 +152,16 @@ func (trader *Trader) Run(ctx context.Context) error {
|
||||||
|
|
||||||
// load and run session strategies
|
// load and run session strategies
|
||||||
for sessionName, strategies := range trader.exchangeStrategies {
|
for sessionName, strategies := range trader.exchangeStrategies {
|
||||||
|
session := trader.environment.sessions[sessionName]
|
||||||
// we can move this to the exchange session,
|
// we can move this to the exchange session,
|
||||||
// that way we can mount the notification on the exchange with DSL
|
// that way we can mount the notification on the exchange with DSL
|
||||||
orderExecutor := &ExchangeOrderExecutor{
|
orderExecutor := &ExchangeOrderExecutor{
|
||||||
Notifiability: trader.Notifiability,
|
Notifiability: trader.Notifiability,
|
||||||
Exchange: nil,
|
Session: session,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, strategy := range strategies {
|
for _, strategy := range strategies {
|
||||||
err := strategy.Run(ctx, orderExecutor, trader.environment.sessions[sessionName])
|
err := strategy.Run(ctx, orderExecutor, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -325,30 +326,52 @@ type ExchangeOrderExecutionRouter struct {
|
||||||
sessions map[string]*ExchangeSession
|
sessions map[string]*ExchangeSession
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExchangeOrderExecutionRouter) SubmitOrderTo(ctx context.Context, session string, order types.SubmitOrder) error {
|
func (e *ExchangeOrderExecutionRouter) SubmitOrdersTo(ctx context.Context, session string, orders ...types.SubmitOrder) error {
|
||||||
es, ok := e.sessions[session]
|
es, ok := e.sessions[session]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("exchange session %s not found", session)
|
return errors.Errorf("exchange session %s not found", session)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Notify(":memo: Submitting order to %s %s %s %s with quantity: %s", session, order.Symbol, order.Type, order.Side, order.QuantityString, order)
|
for _, order := range orders {
|
||||||
|
market, ok := es.Market(order.Symbol)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("market is not defined: %s", order.Symbol)
|
||||||
|
}
|
||||||
|
|
||||||
order.PriceString = order.Market.FormatVolume(order.Price)
|
order.PriceString = market.FormatPrice(order.Price)
|
||||||
order.QuantityString = order.Market.FormatVolume(order.Quantity)
|
order.QuantityString = market.FormatVolume(order.Quantity)
|
||||||
return es.Exchange.SubmitOrder(ctx, order)
|
e.Notify(":memo: Submitting order to %s %s %s %s with quantity: %s", session, order.Symbol, order.Type, order.Side, order.QuantityString, order)
|
||||||
|
|
||||||
|
if err := es.Exchange.SubmitOrders(ctx, order); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeOrderExecutor is an order executor wrapper for single exchange instance.
|
// ExchangeOrderExecutor is an order executor wrapper for single exchange instance.
|
||||||
type ExchangeOrderExecutor struct {
|
type ExchangeOrderExecutor struct {
|
||||||
Notifiability
|
Notifiability
|
||||||
|
|
||||||
Exchange types.Exchange
|
Session *ExchangeSession
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExchangeOrderExecutor) SubmitOrder(ctx context.Context, order types.SubmitOrder) error {
|
func (e *ExchangeOrderExecutor) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) error {
|
||||||
e.Notify(":memo: Submitting %s %s %s order with quantity: %s", order.Symbol, order.Type, order.Side, order.QuantityString, order)
|
for _, order := range orders {
|
||||||
|
market, ok := e.Session.Market(order.Symbol)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("market is not defined: %s", order.Symbol)
|
||||||
|
}
|
||||||
|
|
||||||
order.PriceString = order.Market.FormatVolume(order.Price)
|
order.Market = market
|
||||||
order.QuantityString = order.Market.FormatVolume(order.Quantity)
|
order.PriceString = market.FormatPrice(order.Price)
|
||||||
return e.Exchange.SubmitOrder(ctx, order)
|
order.QuantityString = market.FormatVolume(order.Quantity)
|
||||||
|
|
||||||
|
e.Notify(":memo: Submitting %s %s %s order with quantity: %s", order.Symbol, order.Type, order.Side, order.QuantityString, order)
|
||||||
|
|
||||||
|
return e.Session.Exchange.SubmitOrders(ctx, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
150
pkg/exchange/binance/convert.go
Normal file
150
pkg/exchange/binance/convert.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package binance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/adshao/go-binance"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
|
||||||
|
switch orderType {
|
||||||
|
case types.OrderTypeLimit:
|
||||||
|
return binance.OrderTypeLimit, nil
|
||||||
|
|
||||||
|
case types.OrderTypeStopLimit:
|
||||||
|
return binance.OrderTypeStopLossLimit, nil
|
||||||
|
|
||||||
|
case types.OrderTypeStopMarket:
|
||||||
|
return binance.OrderTypeStopLoss, nil
|
||||||
|
|
||||||
|
case types.OrderTypeMarket:
|
||||||
|
return binance.OrderTypeMarket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("order type %s not supported", orderType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrder(binanceOrder *binance.Order) (*types.Order, error) {
|
||||||
|
return &types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: binanceOrder.Symbol,
|
||||||
|
Side: toGlobalSideType(binanceOrder.Side),
|
||||||
|
Type: toGlobalOrderType(binanceOrder.Type),
|
||||||
|
Quantity: util.MustParseFloat(binanceOrder.OrigQuantity),
|
||||||
|
Price: util.MustParseFloat(binanceOrder.Price),
|
||||||
|
TimeInForce: string(binanceOrder.TimeInForce),
|
||||||
|
},
|
||||||
|
OrderID: uint64(binanceOrder.OrderID),
|
||||||
|
Status: toGlobalOrderStatus(binanceOrder.Status),
|
||||||
|
ExecutedQuantity: util.MustParseFloat(binanceOrder.ExecutedQuantity),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalTrade(t binance.TradeV3) (*types.Trade, error) {
|
||||||
|
// skip trade ID that is the same. however this should not happen
|
||||||
|
var side types.SideType
|
||||||
|
if t.IsBuyer {
|
||||||
|
side = types.SideTypeBuy
|
||||||
|
} else {
|
||||||
|
side = types.SideTypeSell
|
||||||
|
}
|
||||||
|
|
||||||
|
// trade time
|
||||||
|
mts := time.Unix(0, t.Time*int64(time.Millisecond))
|
||||||
|
|
||||||
|
price, err := strconv.ParseFloat(t.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity, err := strconv.ParseFloat(t.Quantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteQuantity, err := strconv.ParseFloat(t.QuoteQuantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fee, err := strconv.ParseFloat(t.Commission, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Trade{
|
||||||
|
ID: t.ID,
|
||||||
|
Price: price,
|
||||||
|
Symbol: t.Symbol,
|
||||||
|
Exchange: "binance",
|
||||||
|
Quantity: quantity,
|
||||||
|
Side: side,
|
||||||
|
IsBuyer: t.IsBuyer,
|
||||||
|
IsMaker: t.IsMaker,
|
||||||
|
Fee: fee,
|
||||||
|
FeeCurrency: t.CommissionAsset,
|
||||||
|
QuoteQuantity: quoteQuantity,
|
||||||
|
Time: mts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalSideType(side binance.SideType) types.SideType {
|
||||||
|
switch side {
|
||||||
|
case binance.SideTypeBuy:
|
||||||
|
return types.SideTypeBuy
|
||||||
|
|
||||||
|
case binance.SideTypeSell:
|
||||||
|
return types.SideTypeSell
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Errorf("unknown side type: %v", side)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrderType(orderType binance.OrderType) types.OrderType {
|
||||||
|
switch orderType {
|
||||||
|
|
||||||
|
case binance.OrderTypeLimit:
|
||||||
|
return types.OrderTypeLimit
|
||||||
|
|
||||||
|
case binance.OrderTypeMarket:
|
||||||
|
return types.OrderTypeMarket
|
||||||
|
|
||||||
|
case binance.OrderTypeStopLossLimit:
|
||||||
|
return types.OrderTypeStopLimit
|
||||||
|
|
||||||
|
case binance.OrderTypeStopLoss:
|
||||||
|
return types.OrderTypeStopMarket
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Errorf("unsupported order type: %v", orderType)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrderStatus(orderStatus binance.OrderStatusType) types.OrderStatus {
|
||||||
|
switch orderStatus {
|
||||||
|
case binance.OrderStatusTypeNew:
|
||||||
|
return types.OrderStatusNew
|
||||||
|
|
||||||
|
case binance.OrderStatusTypeRejected:
|
||||||
|
return types.OrderStatusRejected
|
||||||
|
|
||||||
|
case binance.OrderStatusTypeCanceled:
|
||||||
|
return types.OrderStatusCanceled
|
||||||
|
|
||||||
|
case binance.OrderStatusTypePartiallyFilled:
|
||||||
|
return types.OrderStatusPartiallyFilled
|
||||||
|
|
||||||
|
case binance.OrderStatusTypeFilled:
|
||||||
|
return types.OrderStatusFilled
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.OrderStatus(orderStatus)
|
||||||
|
}
|
|
@ -3,10 +3,10 @@ package binance
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/adshao/go-binance"
|
"github.com/adshao/go-binance"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
BaseCurrency: symbol.BaseAsset,
|
BaseCurrency: symbol.BaseAsset,
|
||||||
}
|
}
|
||||||
|
|
||||||
if f := symbol.MinNotionalFilter() ; f != nil {
|
if f := symbol.MinNotionalFilter(); f != nil {
|
||||||
market.MinNotional = util.MustParseFloat(f.MinNotional)
|
market.MinNotional = util.MustParseFloat(f.MinNotional)
|
||||||
market.MinAmount = util.MustParseFloat(f.MinNotional)
|
market.MinAmount = util.MustParseFloat(f.MinNotional)
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,14 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
// minQty defines the minimum quantity/icebergQty allowed.
|
// minQty defines the minimum quantity/icebergQty allowed.
|
||||||
// maxQty defines the maximum quantity/icebergQty allowed.
|
// maxQty defines the maximum quantity/icebergQty allowed.
|
||||||
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
|
// stepSize defines the intervals that a quantity/icebergQty can be increased/decreased by.
|
||||||
if f := symbol.LotSizeFilter() ; f != nil {
|
if f := symbol.LotSizeFilter(); f != nil {
|
||||||
market.MinLot = util.MustParseFloat(f.MinQuantity)
|
market.MinLot = util.MustParseFloat(f.MinQuantity)
|
||||||
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
|
market.MinQuantity = util.MustParseFloat(f.MinQuantity)
|
||||||
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
|
market.MaxQuantity = util.MustParseFloat(f.MaxQuantity)
|
||||||
// market.StepSize = util.MustParseFloat(f.StepSize)
|
// market.StepSize = util.MustParseFloat(f.StepSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f := symbol.PriceFilter() ; f != nil {
|
if f := symbol.PriceFilter(); f != nil {
|
||||||
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
|
market.MaxPrice = util.MustParseFloat(f.MaxPrice)
|
||||||
market.MinPrice = util.MustParseFloat(f.MinPrice)
|
market.MinPrice = util.MustParseFloat(f.MinPrice)
|
||||||
market.TickSize = util.MustParseFloat(f.TickSize)
|
market.TickSize = util.MustParseFloat(f.TickSize)
|
||||||
|
@ -268,7 +268,25 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) error {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
|
remoteOrders, err := e.Client.NewListOpenOrdersService().Symbol(symbol).Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, binanceOrder := range remoteOrders {
|
||||||
|
order , err := toGlobalOrder(binanceOrder)
|
||||||
|
if err != nil {
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orders = append(orders, *order)
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) error {
|
||||||
/*
|
/*
|
||||||
limit order example
|
limit order example
|
||||||
|
|
||||||
|
@ -281,40 +299,39 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) err
|
||||||
Price(priceString).
|
Price(priceString).
|
||||||
Do(ctx)
|
Do(ctx)
|
||||||
*/
|
*/
|
||||||
|
for _, order := range orders {
|
||||||
|
orderType, err := toLocalOrderType(order.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
orderType, err := toLocalOrderType(order.Type)
|
clientOrderID := uuid.New().String()
|
||||||
if err != nil {
|
req := e.Client.NewCreateOrderService().
|
||||||
return err
|
Symbol(order.Symbol).
|
||||||
|
Side(binance.SideType(order.Side)).
|
||||||
|
NewClientOrderID(clientOrderID).
|
||||||
|
Type(orderType)
|
||||||
|
|
||||||
|
req.Quantity(order.QuantityString)
|
||||||
|
|
||||||
|
if len(order.PriceString) > 0 {
|
||||||
|
req.Price(order.PriceString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(order.TimeInForce) > 0 {
|
||||||
|
// TODO: check the TimeInForce value
|
||||||
|
req.TimeInForce(binance.TimeInForceType(order.TimeInForce))
|
||||||
|
}
|
||||||
|
|
||||||
|
retOrder, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("order created: %+v", retOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := e.Client.NewCreateOrderService().
|
return nil
|
||||||
Symbol(order.Symbol).
|
|
||||||
Side(binance.SideType(order.Side)).
|
|
||||||
Type(orderType).
|
|
||||||
Quantity(order.QuantityString)
|
|
||||||
|
|
||||||
if len(order.PriceString) > 0 {
|
|
||||||
req.Price(order.PriceString)
|
|
||||||
}
|
|
||||||
if len(order.TimeInForce) > 0 {
|
|
||||||
req.TimeInForce(order.TimeInForce)
|
|
||||||
}
|
|
||||||
|
|
||||||
retOrder, err := req.Do(ctx)
|
|
||||||
log.Infof("order created: %+v", retOrder)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
|
|
||||||
switch orderType {
|
|
||||||
case types.OrderTypeLimit:
|
|
||||||
return binance.OrderTypeLimit, nil
|
|
||||||
|
|
||||||
case types.OrderTypeMarket:
|
|
||||||
return binance.OrderTypeMarket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("order type %s not supported", orderType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryKLines(ctx context.Context, symbol, interval string, options types.KLineQueryOptions) ([]types.KLine, error) {
|
func (e *Exchange) QueryKLines(ctx context.Context, symbol, interval string, options types.KLineQueryOptions) ([]types.KLine, error) {
|
||||||
|
@ -393,7 +410,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range remoteTrades {
|
for _, t := range remoteTrades {
|
||||||
localTrade, err := convertRemoteTrade(*t)
|
localTrade, err := toGlobalTrade(*t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Errorf("can not convert binance trade: %+v", t)
|
log.WithError(err).Errorf("can not convert binance trade: %+v", t)
|
||||||
continue
|
continue
|
||||||
|
@ -406,54 +423,6 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
return trades, nil
|
return trades, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertRemoteTrade(t binance.TradeV3) (*types.Trade, error) {
|
|
||||||
// skip trade ID that is the same. however this should not happen
|
|
||||||
var side string
|
|
||||||
if t.IsBuyer {
|
|
||||||
side = "BUY"
|
|
||||||
} else {
|
|
||||||
side = "SELL"
|
|
||||||
}
|
|
||||||
|
|
||||||
// trade time
|
|
||||||
mts := time.Unix(0, t.Time*int64(time.Millisecond))
|
|
||||||
|
|
||||||
price, err := strconv.ParseFloat(t.Price, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
quantity, err := strconv.ParseFloat(t.Quantity, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
quoteQuantity, err := strconv.ParseFloat(t.QuoteQuantity, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fee, err := strconv.ParseFloat(t.Commission, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.Trade{
|
|
||||||
ID: t.ID,
|
|
||||||
Price: price,
|
|
||||||
Symbol: t.Symbol,
|
|
||||||
Exchange: "binance",
|
|
||||||
Quantity: quantity,
|
|
||||||
Side: side,
|
|
||||||
IsBuyer: t.IsBuyer,
|
|
||||||
IsMaker: t.IsMaker,
|
|
||||||
Fee: fee,
|
|
||||||
FeeCurrency: t.CommissionAsset,
|
|
||||||
QuoteQuantity: quoteQuantity,
|
|
||||||
Time: mts,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Exchange) BatchQueryKLines(ctx context.Context, symbol, interval string, startTime, endTime time.Time) ([]types.KLine, error) {
|
func (e *Exchange) BatchQueryKLines(ctx context.Context, symbol, interval string, startTime, endTime time.Time) ([]types.KLine, error) {
|
||||||
var allKLines []types.KLine
|
var allKLines []types.KLine
|
||||||
|
|
||||||
|
@ -496,3 +465,4 @@ func (e *Exchange) BatchQueryKLineWindows(ctx context.Context, symbol string, in
|
||||||
|
|
||||||
return klineWindows, nil
|
return klineWindows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/adshao/go-binance"
|
||||||
"github.com/valyala/fastjson"
|
"github.com/valyala/fastjson"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
@ -99,7 +100,7 @@ func (e *ExecutionReportEvent) Trade() (*types.Trade, error) {
|
||||||
Price: util.MustParseFloat(e.LastExecutedPrice),
|
Price: util.MustParseFloat(e.LastExecutedPrice),
|
||||||
Quantity: util.MustParseFloat(e.LastExecutedQuantity),
|
Quantity: util.MustParseFloat(e.LastExecutedQuantity),
|
||||||
QuoteQuantity: util.MustParseFloat(e.LastQuoteAssetTransactedQuantity),
|
QuoteQuantity: util.MustParseFloat(e.LastQuoteAssetTransactedQuantity),
|
||||||
Side: e.Side,
|
Side: toGlobalSideType(binance.SideType(e.Side)),
|
||||||
IsBuyer: e.Side == "BUY",
|
IsBuyer: e.Side == "BUY",
|
||||||
IsMaker: e.IsMaker,
|
IsMaker: e.IsMaker,
|
||||||
Time: tt,
|
Time: tt,
|
||||||
|
|
217
pkg/exchange/max/convert.go
Normal file
217
pkg/exchange/max/convert.go
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package max
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/max/maxapi"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toGlobalCurrency(currency string) string {
|
||||||
|
return strings.ToUpper(currency)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLocalCurrency(currency string) string {
|
||||||
|
return strings.ToLower(currency)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLocalSymbol(symbol string) string {
|
||||||
|
return strings.ToLower(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalSymbol(symbol string) string {
|
||||||
|
return strings.ToUpper(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLocalSideType(side types.SideType) string {
|
||||||
|
return strings.ToLower(string(side))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalSideType(v string) types.SideType {
|
||||||
|
switch strings.ToLower(v) {
|
||||||
|
case "bid", "buy":
|
||||||
|
return types.SideTypeBuy
|
||||||
|
|
||||||
|
case "ask", "sell":
|
||||||
|
return types.SideTypeSell
|
||||||
|
|
||||||
|
case "self-trade":
|
||||||
|
return types.SideTypeSelf
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.SideType(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrderStatus(orderStatus max.OrderState, executedVolume, remainingVolume fixedpoint.Value) types.OrderStatus {
|
||||||
|
|
||||||
|
switch orderStatus {
|
||||||
|
|
||||||
|
case max.OrderStateCancel:
|
||||||
|
return types.OrderStatusCanceled
|
||||||
|
|
||||||
|
case max.OrderStateFinalizing, max.OrderStateDone:
|
||||||
|
if executedVolume > 0 && remainingVolume > 0 {
|
||||||
|
return types.OrderStatusPartiallyFilled
|
||||||
|
} else if remainingVolume == 0 {
|
||||||
|
return types.OrderStatusFilled
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.OrderStatusFilled
|
||||||
|
|
||||||
|
case max.OrderStateWait:
|
||||||
|
if executedVolume > 0 && remainingVolume > 0 {
|
||||||
|
return types.OrderStatusPartiallyFilled
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.OrderStatusNew
|
||||||
|
|
||||||
|
case max.OrderStateConvert:
|
||||||
|
if executedVolume > 0 && remainingVolume > 0 {
|
||||||
|
return types.OrderStatusPartiallyFilled
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.OrderStatusNew
|
||||||
|
|
||||||
|
case max.OrderStateFailed:
|
||||||
|
return types.OrderStatusRejected
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Errorf("unknown order status: %v", orderStatus)
|
||||||
|
return types.OrderStatus(orderStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrderType(orderType max.OrderType) types.OrderType {
|
||||||
|
switch orderType {
|
||||||
|
case max.OrderTypeLimit:
|
||||||
|
return types.OrderTypeLimit
|
||||||
|
|
||||||
|
case max.OrderTypeMarket:
|
||||||
|
return types.OrderTypeMarket
|
||||||
|
|
||||||
|
case max.OrderTypeStopLimit:
|
||||||
|
return types.OrderTypeStopLimit
|
||||||
|
|
||||||
|
case max.OrderTypeStopMarket:
|
||||||
|
return types.OrderTypeStopMarket
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Errorf("unknown order type: %v", orderType)
|
||||||
|
return types.OrderType(orderType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLocalOrderType(orderType types.OrderType) (max.OrderType, error) {
|
||||||
|
switch orderType {
|
||||||
|
|
||||||
|
case types.OrderTypeStopLimit:
|
||||||
|
return max.OrderTypeStopLimit, nil
|
||||||
|
|
||||||
|
case types.OrderTypeStopMarket:
|
||||||
|
return max.OrderTypeStopMarket, nil
|
||||||
|
|
||||||
|
case types.OrderTypeLimit:
|
||||||
|
return max.OrderTypeLimit, nil
|
||||||
|
|
||||||
|
case types.OrderTypeMarket:
|
||||||
|
return max.OrderTypeMarket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("order type %s not supported", orderType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalOrder(maxOrder max.Order) (*types.Order, error) {
|
||||||
|
executedVolume, err := fixedpoint.NewFromString(maxOrder.ExecutedVolume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingVolume, err := fixedpoint.NewFromString(maxOrder.RemainingVolume)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: toGlobalSymbol(maxOrder.Market),
|
||||||
|
Side: toGlobalSideType(maxOrder.Side),
|
||||||
|
Type: toGlobalOrderType(maxOrder.OrderType),
|
||||||
|
Quantity: util.MustParseFloat(maxOrder.Volume),
|
||||||
|
Price: util.MustParseFloat(maxOrder.Price),
|
||||||
|
TimeInForce: "GTC", // MAX only supports GTC
|
||||||
|
},
|
||||||
|
OrderID: maxOrder.ID,
|
||||||
|
Status: toGlobalOrderStatus(maxOrder.State, executedVolume, remainingVolume),
|
||||||
|
ExecutedQuantity: executedVolume.Float64(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalTrade(t max.Trade) (*types.Trade, error) {
|
||||||
|
// skip trade ID that is the same. however this should not happen
|
||||||
|
var side = toGlobalSideType(t.Side)
|
||||||
|
|
||||||
|
// trade time
|
||||||
|
mts := time.Unix(0, t.CreatedAtMilliSeconds*int64(time.Millisecond))
|
||||||
|
|
||||||
|
price, err := strconv.ParseFloat(t.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity, err := strconv.ParseFloat(t.Volume, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteQuantity, err := strconv.ParseFloat(t.Funds, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fee, err := strconv.ParseFloat(t.Fee, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Trade{
|
||||||
|
ID: int64(t.ID),
|
||||||
|
Price: price,
|
||||||
|
Symbol: toGlobalSymbol(t.Market),
|
||||||
|
Exchange: "max",
|
||||||
|
Quantity: quantity,
|
||||||
|
Side: side,
|
||||||
|
IsBuyer: t.IsBuyer(),
|
||||||
|
IsMaker: t.IsMaker(),
|
||||||
|
Fee: fee,
|
||||||
|
FeeCurrency: toGlobalCurrency(t.FeeCurrency),
|
||||||
|
QuoteQuantity: quoteQuantity,
|
||||||
|
Time: mts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGlobalDepositStatus(a string) types.DepositStatus {
|
||||||
|
switch a {
|
||||||
|
case "submitting", "submitted", "checking":
|
||||||
|
return types.DepositPending
|
||||||
|
|
||||||
|
case "accepted":
|
||||||
|
return types.DepositSuccess
|
||||||
|
|
||||||
|
case "rejected":
|
||||||
|
return types.DepositRejected
|
||||||
|
|
||||||
|
case "canceled":
|
||||||
|
return types.DepositCancelled
|
||||||
|
|
||||||
|
case "suspect", "refunded":
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.DepositStatus(a)
|
||||||
|
}
|
|
@ -2,11 +2,9 @@ package max
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
@ -46,8 +44,10 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
|
|
||||||
markets := types.MarketMap{}
|
markets := types.MarketMap{}
|
||||||
for _, m := range remoteMarkets {
|
for _, m := range remoteMarkets {
|
||||||
|
symbol := toGlobalSymbol(m.ID)
|
||||||
|
|
||||||
market := types.Market{
|
market := types.Market{
|
||||||
Symbol: toGlobalSymbol(m.ID),
|
Symbol: symbol,
|
||||||
PricePrecision: m.QuoteUnitPrecision,
|
PricePrecision: m.QuoteUnitPrecision,
|
||||||
VolumePrecision: m.BaseUnitPrecision,
|
VolumePrecision: m.BaseUnitPrecision,
|
||||||
QuoteCurrency: toGlobalCurrency(m.QuoteUnit),
|
QuoteCurrency: toGlobalCurrency(m.QuoteUnit),
|
||||||
|
@ -62,7 +62,7 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
TickSize: 0.001,
|
TickSize: 0.001,
|
||||||
}
|
}
|
||||||
|
|
||||||
markets[m.ID] = market
|
markets[symbol] = market
|
||||||
}
|
}
|
||||||
|
|
||||||
return markets, nil
|
return markets, nil
|
||||||
|
@ -72,26 +72,52 @@ func (e *Exchange) NewStream() types.Stream {
|
||||||
return NewStream(e.key, e.secret)
|
return NewStream(e.key, e.secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) error {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
orderType, err := toLocalOrderType(order.Type)
|
maxOrders, err := e.client.OrderService.Open(toLocalSymbol(symbol), maxapi.QueryOrderOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return orders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := e.client.OrderService.NewCreateOrderRequest().
|
for _, maxOrder := range maxOrders {
|
||||||
Market(toLocalSymbol(order.Symbol)).
|
order, err := toGlobalOrder(maxOrder)
|
||||||
OrderType(string(orderType)).
|
if err != nil {
|
||||||
Side(toLocalSideType(order.Side)).
|
return orders, err
|
||||||
Volume(order.QuantityString).
|
}
|
||||||
Price(order.PriceString)
|
|
||||||
|
|
||||||
retOrder, err := req.Do(ctx)
|
orders = append(orders, *order)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("order created: %+v", retOrder)
|
return orders, err
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) error {
|
||||||
|
for _, order := range orders {
|
||||||
|
orderType, err := toLocalOrderType(order.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOrderID := uuid.New().String()
|
||||||
|
req := e.client.OrderService.NewCreateOrderRequest().
|
||||||
|
Market(toLocalSymbol(order.Symbol)).
|
||||||
|
OrderType(string(orderType)).
|
||||||
|
Side(toLocalSideType(order.Side)).
|
||||||
|
ClientOrderID(clientOrderID).
|
||||||
|
Volume(order.QuantityString)
|
||||||
|
|
||||||
|
if len(order.PriceString) > 0 {
|
||||||
|
req.Price(order.PriceString)
|
||||||
|
}
|
||||||
|
|
||||||
|
retOrder, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("order created: %+v", retOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlatformFeeCurrency
|
// PlatformFeeCurrency
|
||||||
|
@ -230,7 +256,7 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
|
||||||
Address: "", // not supported
|
Address: "", // not supported
|
||||||
AddressTag: "", // not supported
|
AddressTag: "", // not supported
|
||||||
TransactionID: d.TxID,
|
TransactionID: d.TxID,
|
||||||
Status: convertDepositState(d.State),
|
Status: toGlobalDepositStatus(d.State),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,27 +266,6 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
|
||||||
return allDeposits, err
|
return allDeposits, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertDepositState(a string) types.DepositStatus {
|
|
||||||
switch a {
|
|
||||||
case "submitting", "submitted", "checking":
|
|
||||||
return types.DepositPending
|
|
||||||
|
|
||||||
case "accepted":
|
|
||||||
return types.DepositSuccess
|
|
||||||
|
|
||||||
case "rejected":
|
|
||||||
return types.DepositRejected
|
|
||||||
|
|
||||||
case "canceled":
|
|
||||||
return types.DepositCancelled
|
|
||||||
|
|
||||||
case "suspect", "refunded":
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.DepositStatus(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) {
|
func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) {
|
||||||
accounts, err := e.client.AccountService.Accounts()
|
accounts, err := e.client.AccountService.Accounts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -301,7 +306,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range remoteTrades {
|
for _, t := range remoteTrades {
|
||||||
localTrade, err := convertRemoteTrade(t)
|
localTrade, err := toGlobalTrade(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Errorf("can not convert trade: %+v", t)
|
logger.WithError(err).Errorf("can not convert trade: %+v", t)
|
||||||
continue
|
continue
|
||||||
|
@ -356,94 +361,3 @@ func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (float6
|
||||||
|
|
||||||
return (util.MustParseFloat(ticker.Sell) + util.MustParseFloat(ticker.Buy)) / 2, nil
|
return (util.MustParseFloat(ticker.Sell) + util.MustParseFloat(ticker.Buy)) / 2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalCurrency(currency string) string {
|
|
||||||
return strings.ToUpper(currency)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocalCurrency(currency string) string {
|
|
||||||
return strings.ToLower(currency)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocalSymbol(symbol string) string {
|
|
||||||
return strings.ToLower(symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toGlobalSymbol(symbol string) string {
|
|
||||||
return strings.ToUpper(symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocalSideType(side types.SideType) string {
|
|
||||||
return strings.ToLower(string(side))
|
|
||||||
}
|
|
||||||
|
|
||||||
func toGlobalSideType(v string) string {
|
|
||||||
switch strings.ToLower(v) {
|
|
||||||
case "bid":
|
|
||||||
return "BUY"
|
|
||||||
|
|
||||||
case "ask":
|
|
||||||
return "SELL"
|
|
||||||
|
|
||||||
case "self-trade":
|
|
||||||
return "SELF"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ToUpper(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocalOrderType(orderType types.OrderType) (maxapi.OrderType, error) {
|
|
||||||
switch orderType {
|
|
||||||
case types.OrderTypeLimit:
|
|
||||||
return maxapi.OrderTypeLimit, nil
|
|
||||||
|
|
||||||
case types.OrderTypeMarket:
|
|
||||||
return maxapi.OrderTypeMarket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("order type %s not supported", orderType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertRemoteTrade(t maxapi.Trade) (*types.Trade, error) {
|
|
||||||
// skip trade ID that is the same. however this should not happen
|
|
||||||
var side = toGlobalSideType(t.Side)
|
|
||||||
|
|
||||||
// trade time
|
|
||||||
mts := time.Unix(0, t.CreatedAtMilliSeconds*int64(time.Millisecond))
|
|
||||||
|
|
||||||
price, err := strconv.ParseFloat(t.Price, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
quantity, err := strconv.ParseFloat(t.Volume, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
quoteQuantity, err := strconv.ParseFloat(t.Funds, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fee, err := strconv.ParseFloat(t.Fee, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.Trade{
|
|
||||||
ID: int64(t.ID),
|
|
||||||
Price: price,
|
|
||||||
Symbol: toGlobalSymbol(t.Market),
|
|
||||||
Exchange: "max",
|
|
||||||
Quantity: quantity,
|
|
||||||
Side: side,
|
|
||||||
IsBuyer: t.IsBuyer(),
|
|
||||||
IsMaker: t.IsMaker(),
|
|
||||||
Fee: fee,
|
|
||||||
FeeCurrency: toGlobalCurrency(t.FeeCurrency),
|
|
||||||
QuoteQuantity: quoteQuantity,
|
|
||||||
Time: mts,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,20 +19,28 @@ const (
|
||||||
type OrderState string
|
type OrderState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OrderStateDone = OrderState("done")
|
OrderStateDone = OrderState("done")
|
||||||
OrderStateCancel = OrderState("cancel")
|
OrderStateCancel = OrderState("cancel")
|
||||||
OrderStateWait = OrderState("wait")
|
OrderStateWait = OrderState("wait")
|
||||||
OrderStateConvert = OrderState("convert")
|
OrderStateConvert = OrderState("convert")
|
||||||
|
OrderStateFinalizing = OrderState("finalizing")
|
||||||
|
OrderStateFailed = OrderState("failed")
|
||||||
)
|
)
|
||||||
|
|
||||||
type OrderType string
|
type OrderType string
|
||||||
|
|
||||||
// Order types that the API can return.
|
// Order types that the API can return.
|
||||||
const (
|
const (
|
||||||
OrderTypeMarket = OrderType("market")
|
OrderTypeMarket = OrderType("market")
|
||||||
OrderTypeLimit = OrderType("limit")
|
OrderTypeLimit = OrderType("limit")
|
||||||
|
OrderTypeStopLimit = OrderType("stop_limit")
|
||||||
|
OrderTypeStopMarket = OrderType("stop_market")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type QueryOrderOptions struct {
|
||||||
|
GroupID int
|
||||||
|
}
|
||||||
|
|
||||||
// OrderService manages the Order endpoint.
|
// OrderService manages the Order endpoint.
|
||||||
type OrderService struct {
|
type OrderService struct {
|
||||||
client *RestClient
|
client *RestClient
|
||||||
|
@ -40,22 +48,52 @@ type OrderService struct {
|
||||||
|
|
||||||
// Order represents one returned order (POST order/GET order/GET orders) on the max platform.
|
// Order represents one returned order (POST order/GET order/GET orders) on the max platform.
|
||||||
type Order struct {
|
type Order struct {
|
||||||
ID uint64 `json:"id,omitempty" db:"exchange_id"`
|
ID uint64 `json:"id,omitempty" db:"exchange_id"`
|
||||||
Side string `json:"side" db:"side"`
|
Side string `json:"side" db:"side"`
|
||||||
OrderType string `json:"ord_type,omitempty" db:"order_type"`
|
OrderType OrderType `json:"ord_type,omitempty" db:"order_type"`
|
||||||
Price string `json:"price" db:"price"`
|
Price string `json:"price" db:"price"`
|
||||||
AveragePrice string `json:"avg_price,omitempty" db:"average_price"`
|
AveragePrice string `json:"avg_price,omitempty" db:"average_price"`
|
||||||
State string `json:"state,omitempty" db:"state"`
|
State OrderState `json:"state,omitempty" db:"state"`
|
||||||
Market string `json:"market,omitempty" db:"market"`
|
Market string `json:"market,omitempty" db:"market"`
|
||||||
Volume string `json:"volume" db:"volume"`
|
Volume string `json:"volume" db:"volume"`
|
||||||
RemainingVolume string `json:"remaining_volume,omitempty" db:"remaining_volume"`
|
RemainingVolume string `json:"remaining_volume,omitempty" db:"remaining_volume"`
|
||||||
ExecutedVolume string `json:"executed_volume,omitempty" db:"executed_volume"`
|
ExecutedVolume string `json:"executed_volume,omitempty" db:"executed_volume"`
|
||||||
TradesCount int64 `json:"trades_count,omitempty" db:"trades_count"`
|
TradesCount int64 `json:"trades_count,omitempty" db:"trades_count"`
|
||||||
GroupID int64 `json:"group_id,omitempty" db:"group_id"`
|
GroupID int64 `json:"group_id,omitempty" db:"group_id"`
|
||||||
ClientOID string `json:"client_oid,omitempty" db:"client_oid"`
|
ClientOID string `json:"client_oid,omitempty" db:"client_oid"`
|
||||||
CreatedAt time.Time `json:"-" db:"created_at"`
|
CreatedAt time.Time `json:"-" db:"created_at"`
|
||||||
CreatedAtMs int64 `json:"created_at_in_ms,omitempty"`
|
CreatedAtMs int64 `json:"created_at_in_ms,omitempty"`
|
||||||
InsertedAt time.Time `json:"-" db:"inserted_at"`
|
InsertedAt time.Time `json:"-" db:"inserted_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns open orders
|
||||||
|
func (s *OrderService) Open(market string, options QueryOrderOptions) ([]Order, error) {
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"market": market,
|
||||||
|
// "state": []OrderState{OrderStateWait, OrderStateConvert},
|
||||||
|
"order_by": "desc",
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.GroupID > 0 {
|
||||||
|
payload["group_id"] = options.GroupID
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.client.newAuthenticatedRequest("GET", "v2/orders", payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := s.client.sendRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var orders []Order
|
||||||
|
if err := response.DecodeJSON(&orders); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// All returns all orders for the authenticated account.
|
// All returns all orders for the authenticated account.
|
||||||
|
@ -281,12 +319,12 @@ func (s *OrderService) NewCreateMultiOrderRequest() *CreateMultiOrderRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateOrderRequestParams struct {
|
type CreateOrderRequestParams struct {
|
||||||
PrivateRequestParams
|
*PrivateRequestParams
|
||||||
|
|
||||||
Market string `json:"market"`
|
Market string `json:"market"`
|
||||||
Volume string `json:"volume"`
|
Volume string `json:"volume"`
|
||||||
Price string `json:"price"`
|
Price string `json:"price,omitempty"`
|
||||||
StopPrice string `json:"stop_price"`
|
StopPrice string `json:"stop_price,omitempty"`
|
||||||
Side string `json:"side"`
|
Side string `json:"side"`
|
||||||
OrderType string `json:"ord_type"`
|
OrderType string `json:"ord_type"`
|
||||||
ClientOrderID string `json:"client_oid,omitempty"`
|
ClientOrderID string `json:"client_oid,omitempty"`
|
||||||
|
@ -335,7 +373,7 @@ func (r *CreateOrderRequest) ClientOrderID(clientOrderID string) *CreateOrderReq
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CreateOrderRequest) Do(ctx context.Context) (order *Order, err error) {
|
func (r *CreateOrderRequest) Do(ctx context.Context) (order *Order, err error) {
|
||||||
req, err := r.client.newAuthenticatedRequest("POST", "v2/orders", r.params)
|
req, err := r.client.newAuthenticatedRequest("POST", "v2/orders", &r.params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return order, errors.Wrapf(err, "order create error")
|
return order, errors.Wrapf(err, "order create error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,8 @@ func (e *BookEvent) Time() time.Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BookEvent) OrderBook() (snapshot types.OrderBook, err error) {
|
func (e *BookEvent) OrderBook() (snapshot types.OrderBook, err error) {
|
||||||
|
snapshot.Symbol = strings.ToUpper(e.Market)
|
||||||
|
|
||||||
for _, bid := range e.Bids {
|
for _, bid := range e.Bids {
|
||||||
pv, err := bid.PriceVolumePair()
|
pv, err := bid.PriceVolumePair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -250,7 +250,6 @@ func getPrivateRequestParamsObject(v interface{}) (*PrivateRequestParams, error)
|
||||||
vt = vt.Elem()
|
vt = vt.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if vt.Kind() != reflect.Struct {
|
if vt.Kind() != reflect.Struct {
|
||||||
return nil, errors.New("reflect error: given object is not a struct" + vt.Kind().String())
|
return nil, errors.New("reflect error: given object is not a struct" + vt.Kind().String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ func NewStream(key, secret string) *Stream {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newbook.Symbol = toGlobalSymbol(e.Market)
|
||||||
|
|
||||||
switch e.Event {
|
switch e.Event {
|
||||||
case "snapshot":
|
case "snapshot":
|
||||||
stream.EmitBookSnapshot(newbook)
|
stream.EmitBookSnapshot(newbook)
|
||||||
|
@ -89,7 +91,7 @@ func NewStream(key, secret string) *Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) Subscribe(channel types.Channel, symbol string, options types.SubscribeOptions) {
|
func (s *Stream) Subscribe(channel types.Channel, symbol string, options types.SubscribeOptions) {
|
||||||
s.websocketService.Subscribe(string(channel), symbol)
|
s.websocketService.Subscribe(string(channel), toLocalSymbol(symbol))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) Connect(ctx context.Context) error {
|
func (s *Stream) Connect(ctx context.Context) error {
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor types.OrderExecutor, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := orderExecutor.SubmitOrder(ctx, types.SubmitOrder{
|
err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: kline.Symbol,
|
Symbol: kline.Symbol,
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
Type: types.OrderTypeMarket,
|
Type: types.OrderTypeMarket,
|
||||||
|
|
|
@ -37,7 +37,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor types.OrderExecutor, s
|
||||||
}
|
}
|
||||||
_ = quoteBalance
|
_ = quoteBalance
|
||||||
|
|
||||||
err := orderExecutor.SubmitOrder(ctx, types.SubmitOrder{
|
err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: kline.Symbol,
|
Symbol: kline.Symbol,
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
Type: types.OrderTypeMarket,
|
Type: types.OrderTypeMarket,
|
||||||
|
|
|
@ -45,6 +45,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor types.OrderExecutor, s
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
s.update(orderExecutor)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -52,10 +54,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor types.OrderExecutor, s
|
||||||
|
|
||||||
case <-s.book.C:
|
case <-s.book.C:
|
||||||
s.book.C.Drain(2*time.Second, 5*time.Second)
|
s.book.C.Drain(2*time.Second, 5*time.Second)
|
||||||
s.update()
|
s.update(orderExecutor)
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.update()
|
s.update(orderExecutor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -89,19 +91,22 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor types.OrderExecutor, s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) update() {
|
func (s *Strategy) update(orderExecutor types.OrderExecutor) {
|
||||||
switch s.Side {
|
switch s.Side {
|
||||||
case "buy":
|
case "buy":
|
||||||
s.updateOrders(types.SideTypeBuy)
|
s.updateOrders(orderExecutor, types.SideTypeBuy)
|
||||||
case "sell":
|
case "sell":
|
||||||
s.updateOrders(types.SideTypeSell)
|
s.updateOrders(orderExecutor, types.SideTypeSell)
|
||||||
case "both":
|
case "both":
|
||||||
s.updateOrders(types.SideTypeBuy)
|
s.updateOrders(orderExecutor, types.SideTypeBuy)
|
||||||
s.updateOrders(types.SideTypeSell)
|
s.updateOrders(orderExecutor, types.SideTypeSell)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Panicf("undefined side: %s", s.Side)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) updateOrders(side types.SideType) {
|
func (s *Strategy) updateOrders(orderExecutor types.OrderExecutor, side types.SideType) {
|
||||||
book := s.book.Copy()
|
book := s.book.Copy()
|
||||||
|
|
||||||
var pvs types.PriceVolumeSlice
|
var pvs types.PriceVolumeSlice
|
||||||
|
@ -118,6 +123,8 @@ func (s *Strategy) updateOrders(side types.SideType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("placing order behind volume: %f", s.BehindVolume.Float64())
|
||||||
|
|
||||||
index := pvs.IndexByVolumeDepth(s.BehindVolume)
|
index := pvs.IndexByVolumeDepth(s.BehindVolume)
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
// do not place orders
|
// do not place orders
|
||||||
|
@ -132,6 +139,10 @@ func (s *Strategy) updateOrders(side types.SideType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infof("submitting %d orders", len(orders))
|
log.Infof("submitting %d orders", len(orders))
|
||||||
|
if err := orderExecutor.SubmitOrders(context.Background(), orders...); err != nil {
|
||||||
|
log.WithError(err).Errorf("order submit error")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateOrders(symbol string, side types.SideType, price, priceTick, baseVolume fixedpoint.Value, numOrders int) (orders []types.SubmitOrder) {
|
func (s *Strategy) generateOrders(symbol string, side types.SideType, price, priceTick, baseVolume fixedpoint.Value, numOrders int) (orders []types.SubmitOrder) {
|
||||||
|
@ -166,7 +177,7 @@ func (s *Strategy) generateOrders(symbol string, side types.SideType, price, pri
|
||||||
Quantity: volume,
|
Quantity: volume,
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Infof("%s order: %.2f @ %.3f", side, volume, price.Float64())
|
log.Infof("%s order: %.2f @ %f", side, volume, price.Float64())
|
||||||
|
|
||||||
if len(orders) >= numOrders {
|
if len(orders) >= numOrders {
|
||||||
break
|
break
|
||||||
|
@ -175,7 +186,7 @@ func (s *Strategy) generateOrders(symbol string, side types.SideType, price, pri
|
||||||
price = price + priceTick
|
price = price + priceTick
|
||||||
declog := math.Log10(math.Abs(priceTick.Float64()))
|
declog := math.Log10(math.Abs(priceTick.Float64()))
|
||||||
expBase += fixedpoint.NewFromFloat(math.Pow10(-int(declog)) * math.Abs(priceTick.Float64()))
|
expBase += fixedpoint.NewFromFloat(math.Pow10(-int(declog)) * math.Abs(priceTick.Float64()))
|
||||||
log.Infof("expBase: %f", expBase.Float64())
|
// log.Infof("expBase: %f", expBase.Float64())
|
||||||
}
|
}
|
||||||
|
|
||||||
return orders
|
return orders
|
||||||
|
|
|
@ -54,7 +54,9 @@ type Exchange interface {
|
||||||
|
|
||||||
QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []Withdraw, err error)
|
QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []Withdraw, err error)
|
||||||
|
|
||||||
SubmitOrder(ctx context.Context, order SubmitOrder) error
|
SubmitOrders(ctx context.Context, orders ...SubmitOrder) error
|
||||||
|
|
||||||
|
QueryOpenOrders(ctx context.Context, symbol string) (orders []Order, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TradeQueryOptions struct {
|
type TradeQueryOptions struct {
|
||||||
|
|
|
@ -26,8 +26,7 @@ type Market struct {
|
||||||
TickSize float64
|
TickSize float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Market) FormatPrice(val float64) string {
|
func (m Market) FormatPriceCurrency(val float64) string {
|
||||||
|
|
||||||
switch m.QuoteCurrency {
|
switch m.QuoteCurrency {
|
||||||
|
|
||||||
case "USD", "USDT":
|
case "USD", "USDT":
|
||||||
|
@ -41,10 +40,19 @@ func (m Market) FormatPrice(val float64) string {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return m.FormatPrice(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Market) FormatPrice(val float64) string {
|
||||||
|
|
||||||
|
p := math.Pow10(m.PricePrecision)
|
||||||
|
val = math.Trunc(val*p) / p
|
||||||
return strconv.FormatFloat(val, 'f', m.PricePrecision, 64)
|
return strconv.FormatFloat(val, 'f', m.PricePrecision, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Market) FormatVolume(val float64) string {
|
func (m Market) FormatVolume(val float64) string {
|
||||||
|
p := math.Pow10(m.PricePrecision)
|
||||||
|
val = math.Trunc(val*p) / p
|
||||||
return strconv.FormatFloat(val, 'f', m.VolumePrecision, 64)
|
return strconv.FormatFloat(val, 'f', m.VolumePrecision, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/adshao/go-binance"
|
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,14 +8,35 @@ import (
|
||||||
type OrderType string
|
type OrderType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OrderTypeLimit OrderType = "LIMIT"
|
OrderTypeLimit OrderType = "LIMIT"
|
||||||
OrderTypeMarket OrderType = "MARKET"
|
OrderTypeMarket OrderType = "MARKET"
|
||||||
|
OrderTypeStopLimit OrderType = "STOP_LIMIT"
|
||||||
|
OrderTypeStopMarket OrderType = "STOP_MARKET"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OrderStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrderStatusNew OrderStatus = "NEW"
|
||||||
|
OrderStatusFilled OrderStatus = "FILLED"
|
||||||
|
OrderStatusPartiallyFilled OrderStatus = "PARTIALLY_FILLED"
|
||||||
|
OrderStatusCanceled OrderStatus = "CANCELED"
|
||||||
|
OrderStatusRejected OrderStatus = "REJECTED"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
SubmitOrder
|
||||||
|
|
||||||
|
OrderID uint64 `json:"orderID"` // order id
|
||||||
|
Status OrderStatus `json:"status"`
|
||||||
|
ExecutedQuantity float64 `json:"executedQuantity"`
|
||||||
|
}
|
||||||
|
|
||||||
type SubmitOrder struct {
|
type SubmitOrder struct {
|
||||||
Symbol string
|
Symbol string
|
||||||
Side SideType
|
Side SideType
|
||||||
Type OrderType
|
Type OrderType
|
||||||
|
|
||||||
Quantity float64
|
Quantity float64
|
||||||
Price float64
|
Price float64
|
||||||
|
|
||||||
|
@ -25,7 +45,7 @@ type SubmitOrder struct {
|
||||||
PriceString string
|
PriceString string
|
||||||
QuantityString string
|
QuantityString string
|
||||||
|
|
||||||
TimeInForce binance.TimeInForceType
|
TimeInForce string `json:"timeInForce"` // GTC, IOC, FOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *SubmitOrder) SlackAttachment() slack.Attachment {
|
func (o *SubmitOrder) SlackAttachment() slack.Attachment {
|
||||||
|
|
|
@ -6,12 +6,14 @@ type SideType string
|
||||||
const (
|
const (
|
||||||
SideTypeBuy = SideType("BUY")
|
SideTypeBuy = SideType("BUY")
|
||||||
SideTypeSell = SideType("SELL")
|
SideTypeSell = SideType("SELL")
|
||||||
|
SideTypeSelf = SideType("SELF")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (side SideType) Color() string {
|
func (side SideType) Color() string {
|
||||||
if side == SideTypeBuy {
|
if side == SideTypeBuy {
|
||||||
return Green
|
return Green
|
||||||
}
|
}
|
||||||
|
|
||||||
if side == SideTypeSell {
|
if side == SideTypeSell {
|
||||||
return Red
|
return Red
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ type Trade struct {
|
||||||
QuoteQuantity float64 `json:"quoteQuantity" db:"quote_quantity"`
|
QuoteQuantity float64 `json:"quoteQuantity" db:"quote_quantity"`
|
||||||
Symbol string `json:"symbol" db:"symbol"`
|
Symbol string `json:"symbol" db:"symbol"`
|
||||||
|
|
||||||
Side string `json:"side" db:"side"`
|
Side SideType `json:"side" db:"side"`
|
||||||
IsBuyer bool `json:"isBuyer" db:"is_buyer"`
|
IsBuyer bool `json:"isBuyer" db:"is_buyer"`
|
||||||
IsMaker bool `json:"isMaker" db:"is_maker"`
|
IsMaker bool `json:"isMaker" db:"is_maker"`
|
||||||
Time time.Time `json:"tradedAt" db:"traded_at"`
|
Time time.Time `json:"tradedAt" db:"traded_at"`
|
||||||
|
|
|
@ -3,10 +3,10 @@ package types
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
type OrderExecutor interface {
|
type OrderExecutor interface {
|
||||||
SubmitOrder(ctx context.Context, order SubmitOrder) error
|
SubmitOrders(ctx context.Context, orders ...SubmitOrder) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderExecutionRouter interface {
|
type OrderExecutionRouter interface {
|
||||||
// SubmitOrderTo submit order to a specific exchange session
|
// SubmitOrderTo submit order to a specific exchange session
|
||||||
SubmitOrderTo(ctx context.Context, session string, order SubmitOrder) error
|
SubmitOrdersTo(ctx context.Context, session string, orders ...SubmitOrder) error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user