Files
gocryptotrader/engine/orders.go
2019-08-09 15:46:24 +10:00

275 lines
6.8 KiB
Go

package engine
import (
"errors"
"fmt"
"sync/atomic"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/communications/base"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
// vars for the fund manager package
var (
OrderManagerDelay = time.Second * 10
ErrOrdersAlreadyExists = errors.New("order already exists")
)
func (o *orderStore) Get() map[string][]exchange.OrderDetail {
o.m.Lock()
defer o.m.Unlock()
return o.Orders
}
func (o *orderStore) exists(order *exchange.OrderDetail) bool {
r, ok := o.Orders[order.Exchange]
if !ok {
return false
}
for x := range r {
if r[x].ID == order.ID {
return true
}
}
return false
}
func (o *orderStore) Add(order *exchange.OrderDetail) error {
o.m.Lock()
defer o.m.Unlock()
if o.exists(order) {
return ErrOrdersAlreadyExists
}
orders := o.Orders[order.Exchange]
orders = append(orders, *order)
o.Orders[order.Exchange] = orders
return nil
}
func (o *orderManager) Started() bool {
return atomic.LoadInt32(&o.started) == 1
}
func (o *orderManager) Start() error {
if atomic.AddInt32(&o.started, 1) != 1 {
return errors.New("order manager already started")
}
log.Debugln(log.OrderBook, "Order manager starting...")
o.shutdown = make(chan struct{})
o.orderStore.Orders = make(map[string][]exchange.OrderDetail)
go o.run()
return nil
}
func (o *orderManager) Stop() error {
if atomic.LoadInt32(&o.started) == 0 {
return errors.New("order manager not started")
}
if atomic.AddInt32(&o.stopped, 1) != 1 {
return errors.New("order manager is already stopped")
}
defer func() {
atomic.CompareAndSwapInt32(&o.stopped, 1, 0)
atomic.CompareAndSwapInt32(&o.started, 1, 0)
}()
log.Debugln(log.OrderBook, "Order manager shutting down...")
close(o.shutdown)
return nil
}
func (o *orderManager) gracefulShutdown() {
if o.cfg.CancelOrdersOnShutdown {
log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...")
orders := o.orderStore.Get()
if orders == nil {
return
}
for k, v := range orders {
log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.\n", k)
for y := range v {
log.Debugf(log.OrderMgr, "order manager: Cancelling order ID %v [%v]",
v[y].ID, v[y])
err := o.Cancel(k, &exchange.OrderCancellation{
OrderID: v[y].ID,
})
if err != nil {
msg := fmt.Sprintf("Order manager: Exchange %s unable to cancel order ID=%v. Err: %s",
k, v[y].ID, err)
log.Debugln(log.OrderBook, msg)
Bot.CommsManager.PushEvent(base.Event{
Type: "order",
Message: msg,
})
continue
}
msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.",
k, v[y].ID)
log.Debugln(log.OrderBook, msg)
Bot.CommsManager.PushEvent(base.Event{
Type: "order",
Message: msg,
})
}
}
}
}
func (o *orderManager) run() {
log.Debugln(log.OrderBook, "Order manager started.")
tick := time.NewTicker(OrderManagerDelay)
Bot.ServicesWG.Add(1)
defer func() {
log.Debugln(log.OrderMgr, "Order manager shutdown.")
tick.Stop()
Bot.ServicesWG.Done()
}()
for {
select {
case <-o.shutdown:
o.gracefulShutdown()
return
case <-tick.C:
o.processOrders()
}
}
}
func (o *orderManager) CancelAllOrders() {}
func (o *orderManager) Cancel(exchName string, order *exchange.OrderCancellation) error {
if exchName == "" {
return errors.New("order exchange name is empty")
}
if order == nil {
return errors.New("order cancel param is nil")
}
if order.OrderID == "" {
return errors.New("order id is empty")
}
exch := GetExchangeByName(exchName)
if exch == nil {
return errors.New("unable to get exchange by name")
}
if order.AssetType.String() != "" && !exch.GetAssetTypes().Contains(order.AssetType) {
return errors.New("order asset type not supported by exchange")
}
return exch.CancelOrder(order)
}
func (o *orderManager) Submit(exchName string, order *exchange.OrderSubmission) (*orderSubmitResponse, error) {
if exchName == "" {
return nil, errors.New("order exchange name must be specified")
}
if order == nil {
return nil, exchange.ErrOrderSubmissionIsNil
}
if err := order.Validate(); err != nil {
return nil, err
}
if o.cfg.EnforceLimitConfig {
if !o.cfg.AllowMarketOrders && order.OrderType == exchange.MarketOrderType {
return nil, errors.New("order market type is not allowed")
}
if o.cfg.LimitAmount > 0 && order.Amount > o.cfg.LimitAmount {
return nil, errors.New("order limit exceeds allowed limit")
}
if len(o.cfg.AllowedExchanges) > 0 &&
!common.StringDataCompareInsensitive(o.cfg.AllowedExchanges, exchName) {
return nil, errors.New("order exchange not found in allowed list")
}
if len(o.cfg.AllowedPairs) > 0 && !o.cfg.AllowedPairs.Contains(order.Pair, true) {
return nil, errors.New("order pair not found in allowed list")
}
}
exch := GetExchangeByName(exchName)
if exch == nil {
return nil, errors.New("unable to get exchange by name")
}
id, err := common.GetV4UUID()
if err != nil {
log.Warnf(log.OrderMgr, "Order manager: Unable to generate UUID. Err: %s\n", err)
}
result, err := exch.SubmitOrder(order)
if err != nil {
return nil, err
}
if result.IsOrderPlaced {
return nil, errors.New("order unable to be placed")
}
msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v side=%v type=%v.",
exchName, result.OrderID, id.String(), order.Pair, order.Price, order.Amount, order.OrderSide, order.OrderType)
log.Debugln(log.OrderMgr, msg)
Bot.CommsManager.PushEvent(base.Event{
Type: "order",
Message: msg,
})
return &orderSubmitResponse{
SubmitOrderResponse: exchange.SubmitOrderResponse{
OrderID: result.OrderID,
},
OurOrderID: id.String(),
}, nil
}
func (o *orderManager) processOrders() {
authExchanges := GetAuthAPISupportedExchanges()
for x := range authExchanges {
log.Debugf(log.OrderMgr, "Order manager: Procesing orders for exchange %v.\n", authExchanges[x])
exch := GetExchangeByName(authExchanges[x])
req := exchange.GetOrdersRequest{
OrderSide: exchange.AnyOrderSide,
OrderType: exchange.AnyOrderType,
}
result, err := exch.GetActiveOrders(&req)
if err != nil {
log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s\n", err)
continue
}
for x := range result {
order := &result[x]
result := o.orderStore.Add(order)
if result != ErrOrdersAlreadyExists {
msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.",
order.Exchange, order.ID, order.CurrencyPair, order.Price, order.Amount, order.OrderSide, order.OrderType)
log.Debugf(log.OrderMgr, "%v\n", msg)
Bot.CommsManager.PushEvent(base.Event{
Type: "order",
Message: msg,
})
continue
}
}
}
}