mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
FTX: Funding rates, payments & stats + order manager tracking (#976)
* Adds basic PoC for calculating/retrieving position data * A very unfortunate day of miscalculations * Adds position summary and funding rate details to RPC * Offline funding rate calculations * More helpers, more stats, refining data, automated retrieval * Adds new rpc server commands and attempts some organisation * lower string, lower stress * Adds ordermanager config. Fleshes outcli. Tracks positions automatically * Adds new separation for funding payments/rates * Combines funding rates and payments * Fun test coverage * ALL THE TESTS... I hope * Fixes * polishes ftx tests. improves perp check. Loops rates * Final touches before nit attax * buff 💪 * Stops NotYetImplemented spam with one simple trick! * Some lovely little niteroos * linteroo * Clarifies a couple of errors to help narrow likely end user problems * Fixes asset type bug, fixes closed position order return, fixes unset status bug * Fixes order manager handling when no rates are available yet * Continues on no funding rates instead. Removes err * Don't show predicted rate if the time is zero * Addresses scenario with no funding rate payments * Bug fixes and commentary before updating maps to use *currency.Item * Adds a pair key type * Polishes pKey, fixes map order bug * key is not a property in the event someone changes the base/quote * Adds improvements to order processing...Breaks it all * Shakes up the design of things by removing a function * Fixes issues with order manager positions. Limits update range * Fixes build issues. Identification of bad tests. * Merges and fixes features from master and this branch * buff linter 💪 * re-gen * proto regen * Addresses some nits. But not all of them. * Fixes issue where funding rates weren't returned 🎉 * completes transition futures tracking to map[*currency.Item]map[*currency.Item] * who did that? not me * removes redundant check on account of being redundant and unnecessary * so buf * addresses nits: duplications, startTime, loops, go tidy, typos * fixes minor mistakes * fixes 🍣 🐻 changes to int64
This commit is contained in:
@@ -163,6 +163,7 @@ func validateSettings(b *Engine, s *Settings, flagSet FlagSet) {
|
||||
b.Settings = *s
|
||||
|
||||
flagSet.WithBool("coinmarketcap", &b.Settings.EnableCoinmarketcapAnalysis, b.Config.Currency.CryptocurrencyProvider.Enabled)
|
||||
flagSet.WithBool("ordermanager", &b.Settings.EnableOrderManager, b.Config.OrderManager.Enabled != nil && *b.Config.OrderManager.Enabled)
|
||||
|
||||
flagSet.WithBool("currencyconverter", &b.Settings.EnableCurrencyConverter, b.Config.Currency.ForexProviders.IsEnabled("currencyconverter"))
|
||||
|
||||
@@ -525,8 +526,9 @@ func (bot *Engine) Start() error {
|
||||
bot.ExchangeManager,
|
||||
bot.CommunicationsManager,
|
||||
&bot.ServicesWG,
|
||||
bot.Settings.EnableFuturesTracking,
|
||||
bot.Settings.Verbose)
|
||||
bot.Config.OrderManager.Verbose,
|
||||
bot.Config.OrderManager.ActivelyTrackFuturesPositions,
|
||||
bot.Config.OrderManager.FuturesTrackingSeekDuration)
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Order manager unable to setup: %s", err)
|
||||
} else {
|
||||
|
||||
@@ -137,8 +137,9 @@ func (bot *Engine) SetSubsystem(subSystemName string, enable bool) error {
|
||||
bot.ExchangeManager,
|
||||
bot.CommunicationsManager,
|
||||
&bot.ServicesWG,
|
||||
bot.Settings.EnableFuturesTracking,
|
||||
bot.Settings.Verbose)
|
||||
bot.Config.OrderManager.Verbose,
|
||||
bot.Config.OrderManager.ActivelyTrackFuturesPositions,
|
||||
bot.Config.OrderManager.FuturesTrackingSeekDuration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -21,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// SetupOrderManager will boot up the OrderManager
|
||||
func SetupOrderManager(exchangeManager iExchangeManager, communicationsManager iCommsManager, wg *sync.WaitGroup, enabledFuturesTracking, verbose bool) (*OrderManager, error) {
|
||||
func SetupOrderManager(exchangeManager iExchangeManager, communicationsManager iCommsManager, wg *sync.WaitGroup, verbose, activelyTrackFuturesPositions bool, futuresTrackingSeekDuration time.Duration) (*OrderManager, error) {
|
||||
if exchangeManager == nil {
|
||||
return nil, errNilExchangeManager
|
||||
}
|
||||
@@ -32,18 +33,28 @@ func SetupOrderManager(exchangeManager iExchangeManager, communicationsManager i
|
||||
return nil, errNilWaitGroup
|
||||
}
|
||||
|
||||
return &OrderManager{
|
||||
shutdown: make(chan struct{}),
|
||||
om := &OrderManager{
|
||||
shutdown: make(chan struct{}),
|
||||
activelyTrackFuturesPositions: activelyTrackFuturesPositions,
|
||||
orderStore: store{
|
||||
Orders: make(map[string][]*order.Detail),
|
||||
exchangeManager: exchangeManager,
|
||||
commsManager: communicationsManager,
|
||||
wg: wg,
|
||||
futuresPositionController: order.SetupPositionController(),
|
||||
trackFuturesPositions: enabledFuturesTracking,
|
||||
},
|
||||
verbose: verbose,
|
||||
}, nil
|
||||
}
|
||||
if activelyTrackFuturesPositions {
|
||||
if futuresTrackingSeekDuration > 0 {
|
||||
futuresTrackingSeekDuration = -futuresTrackingSeekDuration
|
||||
}
|
||||
if futuresTrackingSeekDuration == 0 {
|
||||
futuresTrackingSeekDuration = defaultOrderSeekTime
|
||||
}
|
||||
om.futuresPositionSeekDuration = futuresTrackingSeekDuration
|
||||
}
|
||||
return om, nil
|
||||
}
|
||||
|
||||
// IsRunning safely checks whether the subsystem is running
|
||||
@@ -85,7 +96,7 @@ func (m *OrderManager) gracefulShutdown() {
|
||||
if !m.cfg.CancelOrdersOnShutdown {
|
||||
return
|
||||
}
|
||||
log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...")
|
||||
log.Debugln(log.OrderMgr, "Cancelling any open orders...")
|
||||
exchanges, err := m.orderStore.exchangeManager.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err)
|
||||
@@ -133,7 +144,7 @@ func (m *OrderManager) CancelAllOrders(ctx context.Context, exchanges []exchange
|
||||
continue
|
||||
}
|
||||
for j := range orders {
|
||||
log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.", exchanges[i].GetName())
|
||||
log.Debugf(log.OrderMgr, "Cancelling order(s) for exchange %s.", exchanges[i].GetName())
|
||||
cancel, err := orders[j].DeriveCancel()
|
||||
if err != nil {
|
||||
log.Error(log.OrderMgr, err)
|
||||
@@ -189,7 +200,7 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf(log.OrderMgr, "Order manager: Cancelling order ID %v [%+v]",
|
||||
log.Debugf(log.OrderMgr, "Cancelling order ID %v [%+v]",
|
||||
cancel.OrderID, cancel)
|
||||
|
||||
err = exch.CancelOrder(ctx, cancel)
|
||||
@@ -210,7 +221,7 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.",
|
||||
msg := fmt.Sprintf("Exchange %s order ID=%v cancelled.",
|
||||
od.Exchange, od.OrderID)
|
||||
log.Debugln(log.OrderMgr, msg)
|
||||
m.orderStore.commsManager.PushEvent(base.Event{Type: "order", Message: msg})
|
||||
@@ -219,16 +230,13 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error {
|
||||
|
||||
// GetFuturesPositionsForExchange returns futures positions stored within
|
||||
// the order manager's futures position tracker that match the provided params
|
||||
func (m *OrderManager) GetFuturesPositionsForExchange(exch string, item asset.Item, pair currency.Pair) ([]order.PositionStats, error) {
|
||||
func (m *OrderManager) GetFuturesPositionsForExchange(exch string, item asset.Item, pair currency.Pair) ([]order.Position, error) {
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("order manager %w", ErrNilSubsystem)
|
||||
}
|
||||
if atomic.LoadInt32(&m.started) == 0 {
|
||||
return nil, fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
if m.orderStore.futuresPositionController == nil {
|
||||
return nil, errFuturesTrackerNotSetup
|
||||
}
|
||||
if !item.IsFutures() {
|
||||
return nil, fmt.Errorf("%v %w", item, order.ErrNotFuturesAsset)
|
||||
}
|
||||
@@ -236,6 +244,39 @@ func (m *OrderManager) GetFuturesPositionsForExchange(exch string, item asset.It
|
||||
return m.orderStore.futuresPositionController.GetPositionsForExchange(exch, item, pair)
|
||||
}
|
||||
|
||||
// GetOpenFuturesPosition returns an open futures position stored within
|
||||
// the order manager's futures position tracker that match the provided params
|
||||
func (m *OrderManager) GetOpenFuturesPosition(exch string, item asset.Item, pair currency.Pair) (*order.Position, error) {
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("order manager %w", ErrNilSubsystem)
|
||||
}
|
||||
if atomic.LoadInt32(&m.started) == 0 {
|
||||
return nil, fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
if !item.IsFutures() {
|
||||
return nil, fmt.Errorf("%v %w", item, order.ErrNotFuturesAsset)
|
||||
}
|
||||
if !m.activelyTrackFuturesPositions {
|
||||
return nil, errFuturesTrackingDisabled
|
||||
}
|
||||
return m.orderStore.futuresPositionController.GetOpenPosition(exch, item, pair)
|
||||
}
|
||||
|
||||
// GetAllOpenFuturesPositions returns all open futures positions stored within
|
||||
// the order manager's futures position tracker that match the provided params
|
||||
func (m *OrderManager) GetAllOpenFuturesPositions() ([]order.Position, error) {
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("order manager %w", ErrNilSubsystem)
|
||||
}
|
||||
if atomic.LoadInt32(&m.started) == 0 {
|
||||
return nil, fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
if !m.activelyTrackFuturesPositions {
|
||||
return nil, errFuturesTrackingDisabled
|
||||
}
|
||||
return m.orderStore.futuresPositionController.GetAllOpenPositions()
|
||||
}
|
||||
|
||||
// ClearFuturesTracking will clear existing futures positions for a given exchange,
|
||||
// asset, pair for the event that positions have not been tracked accurately
|
||||
func (m *OrderManager) ClearFuturesTracking(exch string, item asset.Item, pair currency.Pair) error {
|
||||
@@ -245,9 +286,6 @@ func (m *OrderManager) ClearFuturesTracking(exch string, item asset.Item, pair c
|
||||
if atomic.LoadInt32(&m.started) == 0 {
|
||||
return fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
if m.orderStore.futuresPositionController == nil {
|
||||
return errFuturesTrackerNotSetup
|
||||
}
|
||||
if !item.IsFutures() {
|
||||
return fmt.Errorf("%v %w", item, order.ErrNotFuturesAsset)
|
||||
}
|
||||
@@ -265,9 +303,6 @@ func (m *OrderManager) UpdateOpenPositionUnrealisedPNL(e string, item asset.Item
|
||||
if atomic.LoadInt32(&m.started) == 0 {
|
||||
return decimal.Zero, fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
if m.orderStore.futuresPositionController == nil {
|
||||
return decimal.Zero, errFuturesTrackerNotSetup
|
||||
}
|
||||
if !item.IsFutures() {
|
||||
return decimal.Zero, fmt.Errorf("%v %w", item, order.ErrNotFuturesAsset)
|
||||
}
|
||||
@@ -381,7 +416,7 @@ func (m *OrderManager) Modify(ctx context.Context, mod *order.Modify) (*order.Mo
|
||||
res, err := exch.ModifyOrder(ctx, mod)
|
||||
if err != nil {
|
||||
message := fmt.Sprintf(
|
||||
"Order manager: Exchange %s order ID=%v: failed to modify",
|
||||
"Exchange %s order ID=%v: failed to modify",
|
||||
mod.Exchange,
|
||||
mod.OrderID,
|
||||
)
|
||||
@@ -401,9 +436,9 @@ func (m *OrderManager) Modify(ctx context.Context, mod *order.Modify) (*order.Mo
|
||||
// Notify observers.
|
||||
var message string
|
||||
if err != nil {
|
||||
message = "Order manager: Exchange %s order ID=%v: modified on exchange, but failed to modify locally"
|
||||
message = "Exchange %s order ID=%v: modified on exchange, but failed to modify locally"
|
||||
} else {
|
||||
message = "Order manager: Exchange %s order ID=%v: modified successfully"
|
||||
message = "Exchange %s order ID=%v: modified successfully"
|
||||
}
|
||||
m.orderStore.commsManager.PushEvent(base.Event{
|
||||
Type: "order",
|
||||
@@ -551,7 +586,7 @@ func (m *OrderManager) processSubmittedOrder(newOrderResp *order.SubmitResponse)
|
||||
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
log.Warnf(log.OrderMgr, "Order manager: Unable to generate UUID. Err: %s", err)
|
||||
log.Warnf(log.OrderMgr, "Unable to generate UUID. Err: %s", err)
|
||||
}
|
||||
|
||||
detail, err := newOrderResp.DeriveDetail(id)
|
||||
@@ -559,7 +594,7 @@ func (m *OrderManager) processSubmittedOrder(newOrderResp *order.SubmitResponse)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Order manager: Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v quoteAmount=%v side=%v type=%v for time %v.",
|
||||
msg := fmt.Sprintf("Exchange %s submitted order ID=%v [Ours: %v] pair=%v price=%v amount=%v quoteAmount=%v side=%v type=%v for time %v.",
|
||||
detail.Exchange,
|
||||
detail.OrderID,
|
||||
detail.InternalOrderID.String(),
|
||||
@@ -592,28 +627,29 @@ func (m *OrderManager) processOrders() {
|
||||
return
|
||||
}
|
||||
defer atomic.StoreInt32(&m.processingOrders, 0)
|
||||
|
||||
exchanges, err := m.orderStore.exchangeManager.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err)
|
||||
log.Errorf(log.OrderMgr, "order manager cannot get exchanges: %v", err)
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for i := range exchanges {
|
||||
if !exchanges[i].IsRESTAuthenticationSupported() {
|
||||
for x := range exchanges {
|
||||
if !exchanges[x].IsRESTAuthenticationSupported() {
|
||||
continue
|
||||
}
|
||||
log.Debugf(log.OrderMgr,
|
||||
"Order manager: Processing orders for exchange %v.",
|
||||
exchanges[i].GetName())
|
||||
|
||||
enabledAssets := exchanges[i].GetAssetTypes(true)
|
||||
if m.verbose {
|
||||
log.Debugf(log.OrderMgr,
|
||||
"Processing orders for exchange %v",
|
||||
exchanges[x].GetName())
|
||||
}
|
||||
enabledAssets := exchanges[x].GetAssetTypes(true)
|
||||
for y := range enabledAssets {
|
||||
pairs, err := exchanges[i].GetEnabledPairs(enabledAssets[y])
|
||||
var pairs currency.Pairs
|
||||
pairs, err = exchanges[x].GetEnabledPairs(enabledAssets[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr,
|
||||
"Order manager: Unable to get enabled pairs for %s and asset type %s: %s",
|
||||
exchanges[i].GetName(),
|
||||
"Unable to get enabled pairs for %s and asset type %s: %s",
|
||||
exchanges[x].GetName(),
|
||||
enabledAssets[y],
|
||||
err)
|
||||
continue
|
||||
@@ -622,18 +658,18 @@ func (m *OrderManager) processOrders() {
|
||||
if len(pairs) == 0 {
|
||||
if m.verbose {
|
||||
log.Debugf(log.OrderMgr,
|
||||
"Order manager: No pairs enabled for %s and asset type %s, skipping...",
|
||||
exchanges[i].GetName(),
|
||||
"No pairs enabled for %s and asset type %s, skipping...",
|
||||
exchanges[x].GetName(),
|
||||
enabledAssets[y])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
filter := &order.Filter{Exchange: exchanges[i].GetName()}
|
||||
filter := &order.Filter{Exchange: exchanges[x].GetName()}
|
||||
orders := m.orderStore.getActiveOrders(filter)
|
||||
order.FilterOrdersByPairs(&orders, pairs)
|
||||
|
||||
result, err := exchanges[i].GetActiveOrders(context.TODO(), &order.GetOrdersRequest{
|
||||
var result []order.Detail
|
||||
result, err = exchanges[x].GetActiveOrders(context.TODO(), &order.GetOrdersRequest{
|
||||
Side: order.AnySide,
|
||||
Type: order.AnyType,
|
||||
Pairs: pairs,
|
||||
@@ -641,38 +677,141 @@ func (m *OrderManager) processOrders() {
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr,
|
||||
"Order manager: Unable to get active orders for %s and asset type %s: %s",
|
||||
exchanges[i].GetName(),
|
||||
"Unable to get active orders for %s and asset type %s: %s",
|
||||
exchanges[x].GetName(),
|
||||
enabledAssets[y],
|
||||
err)
|
||||
continue
|
||||
}
|
||||
if len(orders) == 0 && len(result) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for z := range result {
|
||||
upsertResponse, err := m.UpsertOrder(&result[z])
|
||||
if err != nil {
|
||||
log.Error(log.OrderMgr, err)
|
||||
} else {
|
||||
for i := range orders {
|
||||
if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID {
|
||||
continue
|
||||
if len(orders) > 0 && len(result) > 0 {
|
||||
for z := range result {
|
||||
var upsertResponse *OrderUpsertResponse
|
||||
upsertResponse, err = m.UpsertOrder(&result[z])
|
||||
if err != nil {
|
||||
log.Error(log.OrderMgr, err)
|
||||
} else {
|
||||
for i := range orders {
|
||||
if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID {
|
||||
continue
|
||||
}
|
||||
orders[i] = orders[len(orders)-1]
|
||||
orders = orders[:len(orders)-1]
|
||||
break
|
||||
}
|
||||
orders[i] = orders[len(orders)-1]
|
||||
orders = orders[:len(orders)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if !exchanges[i].GetBase().GetSupportedFeatures().RESTCapabilities.GetOrder {
|
||||
continue
|
||||
|
||||
if exchanges[x].GetBase().GetSupportedFeatures().RESTCapabilities.GetOrder {
|
||||
wg.Add(1)
|
||||
go m.processMatchingOrders(exchanges[x], orders, &wg)
|
||||
}
|
||||
|
||||
if m.activelyTrackFuturesPositions && enabledAssets[y].IsFutures() {
|
||||
var positions []order.PositionDetails
|
||||
var sd time.Time
|
||||
sd, err = m.orderStore.futuresPositionController.LastUpdated()
|
||||
if err != nil {
|
||||
log.Error(log.OrderMgr, err)
|
||||
return
|
||||
}
|
||||
if sd.IsZero() {
|
||||
sd = time.Now().Add(m.futuresPositionSeekDuration)
|
||||
}
|
||||
positions, err = exchanges[x].GetFuturesPositions(context.TODO(), &order.PositionsRequest{
|
||||
Asset: enabledAssets[y],
|
||||
Pairs: pairs,
|
||||
StartDate: sd,
|
||||
})
|
||||
if err != nil {
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
log.Error(log.OrderMgr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
for z := range positions {
|
||||
if len(positions[z].Orders) == 0 {
|
||||
continue
|
||||
}
|
||||
err = m.processFuturesPositions(exchanges[x], &positions[z])
|
||||
if err != nil {
|
||||
log.Errorf(log.OrderMgr, "unable to process future positions for %v %v %v. err: %v", positions[z].Exchange, positions[z].Asset, positions[z].Pair, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Add(1)
|
||||
go m.processMatchingOrders(exchanges[i], orders, &wg)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
if m.verbose {
|
||||
log.Debugf(log.OrderMgr, "Finished processing orders")
|
||||
}
|
||||
}
|
||||
|
||||
// processFuturesPositions ensures any open position found is kept up to date in the order manager
|
||||
func (m *OrderManager) processFuturesPositions(exch exchange.IBotExchange, position *order.PositionDetails) error {
|
||||
if !m.activelyTrackFuturesPositions {
|
||||
return errFuturesTrackingDisabled
|
||||
}
|
||||
if exch == nil {
|
||||
return fmt.Errorf("%w IBotExchange", common.ErrNilPointer)
|
||||
}
|
||||
if position == nil {
|
||||
return fmt.Errorf("%w PositionDetails", common.ErrNilPointer)
|
||||
}
|
||||
if len(position.Orders) == 0 {
|
||||
return fmt.Errorf("%w position for '%v' '%v' '%v' has no orders", errNilOrder, position.Exchange, position.Asset, position.Pair)
|
||||
}
|
||||
sort.Slice(position.Orders, func(i, j int) bool {
|
||||
return position.Orders[i].Date.Before(position.Orders[j].Date)
|
||||
})
|
||||
var err error
|
||||
for i := range position.Orders {
|
||||
err = m.orderStore.futuresPositionController.TrackNewOrder(&position.Orders[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = m.orderStore.futuresPositionController.GetOpenPosition(position.Exchange, position.Asset, position.Pair)
|
||||
if err != nil {
|
||||
if errors.Is(err, order.ErrPositionNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
tick, err := exch.FetchTicker(context.TODO(), position.Pair, position.Asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w when fetching ticker data for %v %v %v", err, position.Exchange, position.Asset, position.Pair)
|
||||
}
|
||||
_, err = m.UpdateOpenPositionUnrealisedPNL(position.Exchange, position.Asset, position.Pair, tick.Last, tick.LastUpdated)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w when updating unrealised PNL for %v %v %v", err, position.Exchange, position.Asset, position.Pair)
|
||||
}
|
||||
isPerp, err := exch.IsPerpetualFutureCurrency(position.Asset, position.Pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isPerp {
|
||||
return nil
|
||||
}
|
||||
frp, err := exch.GetFundingRates(context.TODO(), &order.FundingRatesRequest{
|
||||
Asset: position.Asset,
|
||||
Pairs: currency.Pairs{position.Pair},
|
||||
StartDate: position.Orders[0].Date,
|
||||
EndDate: time.Now(),
|
||||
IncludePayments: true,
|
||||
IncludePredictedRate: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range frp {
|
||||
err = m.orderStore.futuresPositionController.TrackFundingDetails(&frp[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders []order.Detail, wg *sync.WaitGroup) {
|
||||
@@ -770,7 +909,7 @@ func (m *OrderManager) UpsertOrder(od *order.Detail) (resp *OrderUpsertResponse,
|
||||
upsertResponse, err := m.orderStore.upsert(od)
|
||||
if err != nil {
|
||||
msg = fmt.Sprintf(
|
||||
"Order manager: Exchange %s unable to upsert order ID=%v internal ID=%v pair=%v price=%.8f amount=%.8f side=%v type=%v status=%v: %s",
|
||||
"Exchange %s unable to upsert order ID=%v internal ID=%v pair=%v price=%.8f amount=%.8f side=%v type=%v status=%v: %s",
|
||||
od.Exchange, od.OrderID, od.InternalOrderID, od.Pair, od.Price, od.Amount, od.Side, od.Type, od.Status, err)
|
||||
return nil, err
|
||||
}
|
||||
@@ -779,7 +918,7 @@ func (m *OrderManager) UpsertOrder(od *order.Detail) (resp *OrderUpsertResponse,
|
||||
if upsertResponse.IsNewOrder {
|
||||
status = "added"
|
||||
}
|
||||
msg = fmt.Sprintf("Order manager: Exchange %s %s order ID=%v internal ID=%v pair=%v price=%.8f amount=%.8f side=%v type=%v status=%v.",
|
||||
msg = fmt.Sprintf("Exchange %s %s order ID=%v internal ID=%v pair=%v price=%.8f amount=%.8f side=%v type=%v status=%v.",
|
||||
upsertResponse.OrderDetails.Exchange, status, upsertResponse.OrderDetails.OrderID, upsertResponse.OrderDetails.InternalOrderID,
|
||||
upsertResponse.OrderDetails.Pair, upsertResponse.OrderDetails.Price, upsertResponse.OrderDetails.Amount,
|
||||
upsertResponse.OrderDetails.Side, upsertResponse.OrderDetails.Type, upsertResponse.OrderDetails.Status)
|
||||
@@ -890,7 +1029,7 @@ func (s *store) upsert(od *order.Detail) (*OrderUpsertResponse, error) {
|
||||
}
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
if s.trackFuturesPositions && od.AssetType.IsFutures() {
|
||||
if od.AssetType.IsFutures() {
|
||||
err = s.futuresPositionController.TrackNewOrder(od)
|
||||
if err != nil && !errors.Is(err, order.ErrPositionClosed) {
|
||||
return nil, err
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
@@ -16,9 +18,10 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
)
|
||||
|
||||
// omfExchange aka ordermanager fake exchange overrides exchange functions
|
||||
// omfExchange aka order manager fake exchange overrides exchange functions
|
||||
// we're not testing an actual exchange's implemented functions
|
||||
type omfExchange struct {
|
||||
exchange.IBotExchange
|
||||
@@ -30,6 +33,31 @@ func (f omfExchange) CancelOrder(ctx context.Context, o *order.Cancel) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f omfExchange) FetchTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) {
|
||||
return &ticker.Price{
|
||||
Last: 1337,
|
||||
High: 1337,
|
||||
Low: 1337,
|
||||
Bid: 1337,
|
||||
Ask: 1337,
|
||||
Volume: 1337,
|
||||
QuoteVolume: 1337,
|
||||
PriceATH: 1337,
|
||||
Open: 1337,
|
||||
Close: 1337,
|
||||
Pair: p,
|
||||
ExchangeName: f.GetName(),
|
||||
AssetType: a,
|
||||
LastUpdated: time.Now(),
|
||||
FlashReturnRate: 1337,
|
||||
BidPeriod: 1337,
|
||||
BidSize: 1337,
|
||||
AskPeriod: 1337,
|
||||
AskSize: 1337,
|
||||
FlashReturnRateAmount: 1337,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetOrderInfo overrides testExchange's get order function
|
||||
// to do the bare minimum required with no API calls or credentials required
|
||||
func (f omfExchange) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
|
||||
@@ -92,22 +120,64 @@ func (f omfExchange) ModifyOrder(ctx context.Context, action *order.Modify) (*or
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func (f omfExchange) GetFuturesPositions(ctx context.Context, req *order.PositionsRequest) ([]order.PositionDetails, error) {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]order.PositionDetails, len(req.Pairs))
|
||||
tt := time.Now()
|
||||
for i := range req.Pairs {
|
||||
resp[i] = order.PositionDetails{
|
||||
Exchange: f.GetName(),
|
||||
Asset: req.Asset,
|
||||
Pair: req.Pairs[i],
|
||||
Orders: []order.Detail{
|
||||
{
|
||||
Exchange: f.GetName(),
|
||||
Price: 1337,
|
||||
Amount: 1337,
|
||||
InternalOrderID: id,
|
||||
OrderID: "1337",
|
||||
ClientOrderID: "1337",
|
||||
Type: order.Market,
|
||||
Side: order.Short,
|
||||
Status: order.Open,
|
||||
AssetType: req.Asset,
|
||||
Date: tt,
|
||||
CloseTime: tt,
|
||||
LastUpdated: tt,
|
||||
Pair: req.Pairs[i],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func TestSetupOrderManager(t *testing.T) {
|
||||
_, err := SetupOrderManager(nil, nil, nil, false, false)
|
||||
_, err := SetupOrderManager(nil, nil, nil, false, false, 0)
|
||||
if !errors.Is(err, errNilExchangeManager) {
|
||||
t.Errorf("error '%v', expected '%v'", err, errNilExchangeManager)
|
||||
}
|
||||
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), nil, nil, false, false)
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), nil, nil, false, false, 0)
|
||||
if !errors.Is(err, errNilCommunicationsManager) {
|
||||
t.Errorf("error '%v', expected '%v'", err, errNilCommunicationsManager)
|
||||
}
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, nil, false, false)
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, nil, false, false, 0)
|
||||
if !errors.Is(err, errNilWaitGroup) {
|
||||
t.Errorf("error '%v', expected '%v'", err, errNilWaitGroup)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false)
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, true, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
_, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, true, 1337)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -120,7 +190,7 @@ func TestOrderManagerStart(t *testing.T) {
|
||||
t.Errorf("error '%v', expected '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
m, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false)
|
||||
m, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -141,7 +211,7 @@ func TestOrderManagerIsRunning(t *testing.T) {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
m, err := SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false)
|
||||
m, err := SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -166,7 +236,7 @@ func TestOrderManagerStop(t *testing.T) {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
m, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false)
|
||||
m, err = SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -204,12 +274,11 @@ func OrdersSetup(t *testing.T) *OrderManager {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fakeExchange := omfExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
m, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false)
|
||||
m, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -674,7 +743,7 @@ func TestProcessOrders(t *testing.T) {
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
m, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false)
|
||||
m, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -712,6 +781,11 @@ func TestProcessOrders(t *testing.T) {
|
||||
Enabled: pairs,
|
||||
Available: pairs,
|
||||
},
|
||||
asset.Futures: {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Enabled: pairs,
|
||||
Available: pairs,
|
||||
},
|
||||
},
|
||||
}
|
||||
exch.GetBase().Config = &config.Exchange{
|
||||
@@ -731,6 +805,11 @@ func TestProcessOrders(t *testing.T) {
|
||||
Enabled: pairs,
|
||||
Available: pairs,
|
||||
},
|
||||
asset.Futures: {
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Enabled: pairs,
|
||||
Available: pairs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -773,6 +852,21 @@ func TestProcessOrders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
m.orderStore.futuresPositionController = order.SetupPositionController()
|
||||
if err = m.orderStore.add(&order.Detail{
|
||||
Exchange: testExchange,
|
||||
Pair: pairs[0],
|
||||
AssetType: asset.Futures,
|
||||
Amount: 2.0,
|
||||
Side: order.Short,
|
||||
Status: order.Open,
|
||||
LastUpdated: time.Now().Add(-time.Hour),
|
||||
OrderID: "4",
|
||||
Date: time.Now(),
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
m.processOrders()
|
||||
|
||||
// Order1 is not returned by exch.GetActiveOrders()
|
||||
@@ -1050,10 +1144,6 @@ func TestGetFuturesPositionsForExchange(t *testing.T) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
o.started = 1
|
||||
_, err = o.GetFuturesPositionsForExchange("test", asset.Spot, cp)
|
||||
if !errors.Is(err, errFuturesTrackerNotSetup) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errFuturesTrackerNotSetup)
|
||||
}
|
||||
o.orderStore.futuresPositionController = order.SetupPositionController()
|
||||
_, err = o.GetFuturesPositionsForExchange("test", asset.Spot, cp)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
@@ -1061,8 +1151,8 @@ func TestGetFuturesPositionsForExchange(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = o.GetFuturesPositionsForExchange("test", asset.Futures, cp)
|
||||
if !errors.Is(err, order.ErrPositionsNotLoadedForExchange) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionsNotLoadedForExchange)
|
||||
if !errors.Is(err, order.ErrPositionNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionNotFound)
|
||||
}
|
||||
|
||||
err = o.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
@@ -1101,10 +1191,6 @@ func TestClearFuturesPositionsForExchange(t *testing.T) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
o.started = 1
|
||||
err = o.ClearFuturesTracking("test", asset.Spot, cp)
|
||||
if !errors.Is(err, errFuturesTrackerNotSetup) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errFuturesTrackerNotSetup)
|
||||
}
|
||||
o.orderStore.futuresPositionController = order.SetupPositionController()
|
||||
err = o.ClearFuturesTracking("test", asset.Spot, cp)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
@@ -1112,8 +1198,8 @@ func TestClearFuturesPositionsForExchange(t *testing.T) {
|
||||
}
|
||||
|
||||
err = o.ClearFuturesTracking("test", asset.Futures, cp)
|
||||
if !errors.Is(err, order.ErrPositionsNotLoadedForExchange) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionsNotLoadedForExchange)
|
||||
if !errors.Is(err, order.ErrPositionNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionNotFound)
|
||||
}
|
||||
|
||||
err = o.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
@@ -1156,10 +1242,6 @@ func TestUpdateOpenPositionUnrealisedPNL(t *testing.T) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
o.started = 1
|
||||
_, err = o.UpdateOpenPositionUnrealisedPNL("test", asset.Spot, cp, 1, time.Now())
|
||||
if !errors.Is(err, errFuturesTrackerNotSetup) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errFuturesTrackerNotSetup)
|
||||
}
|
||||
o.orderStore.futuresPositionController = order.SetupPositionController()
|
||||
_, err = o.UpdateOpenPositionUnrealisedPNL("test", asset.Spot, cp, 1, time.Now())
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
@@ -1167,8 +1249,8 @@ func TestUpdateOpenPositionUnrealisedPNL(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = o.UpdateOpenPositionUnrealisedPNL("test", asset.Futures, cp, 1, time.Now())
|
||||
if !errors.Is(err, order.ErrPositionsNotLoadedForExchange) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionsNotLoadedForExchange)
|
||||
if !errors.Is(err, order.ErrPositionNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionNotFound)
|
||||
}
|
||||
|
||||
err = o.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
@@ -1350,3 +1432,214 @@ func TestOrderManagerAdd(t *testing.T) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllOpenFuturesPositions(t *testing.T) {
|
||||
t.Parallel()
|
||||
wg := &sync.WaitGroup{}
|
||||
o, err := SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
o.started = 0
|
||||
_, err = o.GetAllOpenFuturesPositions()
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
o.started = 1
|
||||
o.activelyTrackFuturesPositions = true
|
||||
o.orderStore.futuresPositionController = order.SetupPositionController()
|
||||
_, err = o.GetAllOpenFuturesPositions()
|
||||
if !errors.Is(err, order.ErrNoPositionsFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNoPositionsFound)
|
||||
}
|
||||
|
||||
o = nil
|
||||
_, err = o.GetAllOpenFuturesPositions()
|
||||
if !errors.Is(err, ErrNilSubsystem) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOpenFuturesPosition(t *testing.T) {
|
||||
t.Parallel()
|
||||
wg := &sync.WaitGroup{}
|
||||
o, err := SetupOrderManager(SetupExchangeManager(), &CommunicationManager{}, wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
o.started = 0
|
||||
cp := currency.NewPair(currency.BTC, currency.PERP)
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Spot, cp)
|
||||
if !errors.Is(err, ErrSubSystemNotStarted) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
o.started = 1
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Spot, cp)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName("ftx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
o, err = SetupOrderManager(em, &CommunicationManager{}, wg, false, true, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
o.started = 1
|
||||
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Spot, cp)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Futures, cp)
|
||||
if !errors.Is(err, order.ErrPositionNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionNotFound)
|
||||
}
|
||||
|
||||
err = o.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
AssetType: asset.Futures,
|
||||
OrderID: "123",
|
||||
Pair: cp,
|
||||
Side: order.Buy,
|
||||
Type: order.Market,
|
||||
Date: time.Now(),
|
||||
Amount: 1337,
|
||||
Exchange: testExchange,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Futures, cp)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
o = nil
|
||||
_, err = o.GetOpenFuturesPosition(testExchange, asset.Spot, cp)
|
||||
if !errors.Is(err, ErrNilSubsystem) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrNilSubsystem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessFuturesPositions(t *testing.T) {
|
||||
t.Parallel()
|
||||
o := &OrderManager{}
|
||||
err := o.processFuturesPositions(nil, nil)
|
||||
if !errors.Is(err, errFuturesTrackingDisabled) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errFuturesTrackingDisabled)
|
||||
}
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName("ftx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-perp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cp2, err := currency.NewPairFromString("btc-usd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
var wg sync.WaitGroup
|
||||
o, err = SetupOrderManager(em, &CommunicationManager{}, &wg, false, true, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
o.started = 1
|
||||
|
||||
err = o.processFuturesPositions(fakeExchange, nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received '%v', expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
position := &order.PositionDetails{
|
||||
Exchange: b.Name,
|
||||
Asset: asset.Spot,
|
||||
Pair: cp,
|
||||
Orders: nil,
|
||||
}
|
||||
err = o.processFuturesPositions(fakeExchange, position)
|
||||
if !errors.Is(err, errNilOrder) {
|
||||
t.Errorf("received '%v', expected '%v'", err, errNilOrder)
|
||||
}
|
||||
|
||||
od := &order.Detail{
|
||||
AssetType: asset.Spot,
|
||||
OrderID: "123",
|
||||
Pair: cp,
|
||||
Side: order.Buy,
|
||||
Type: order.Market,
|
||||
Date: time.Now().Add(-time.Hour),
|
||||
Amount: 1337,
|
||||
Exchange: b.Name,
|
||||
}
|
||||
position.Orders = []order.Detail{
|
||||
*od,
|
||||
}
|
||||
err = o.processFuturesPositions(fakeExchange, position)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
position.Orders[0].AssetType = asset.Futures
|
||||
position.Asset = asset.Futures
|
||||
err = o.processFuturesPositions(fakeExchange, position)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ var (
|
||||
|
||||
errNilCommunicationsManager = errors.New("cannot start with nil communications manager")
|
||||
errNilOrder = errors.New("nil order received")
|
||||
errFuturesTrackerNotSetup = errors.New("futures position tracker not setup")
|
||||
|
||||
orderManagerDelay = time.Second * 10
|
||||
errFuturesTrackingDisabled = errors.New("tracking futures positions disabled. enable it via config under orderManager activelyTrackFuturesPositions")
|
||||
orderManagerDelay = time.Second * 10
|
||||
defaultOrderSeekTime = -time.Hour * 24 * 365
|
||||
)
|
||||
|
||||
type orderManagerConfig struct {
|
||||
@@ -38,6 +38,18 @@ type orderManagerConfig struct {
|
||||
OrderSubmissionRetries int64
|
||||
}
|
||||
|
||||
// OrderManager processes and stores orders across enabled exchanges
|
||||
type OrderManager struct {
|
||||
started int32
|
||||
processingOrders int32
|
||||
shutdown chan struct{}
|
||||
orderStore store
|
||||
cfg orderManagerConfig
|
||||
verbose bool
|
||||
activelyTrackFuturesPositions bool
|
||||
futuresPositionSeekDuration time.Duration
|
||||
}
|
||||
|
||||
// store holds all orders by exchange
|
||||
type store struct {
|
||||
m sync.RWMutex
|
||||
@@ -45,18 +57,7 @@ type store struct {
|
||||
commsManager iCommsManager
|
||||
exchangeManager iExchangeManager
|
||||
wg *sync.WaitGroup
|
||||
futuresPositionController *order.PositionController
|
||||
trackFuturesPositions bool
|
||||
}
|
||||
|
||||
// OrderManager processes and stores orders across enabled exchanges
|
||||
type OrderManager struct {
|
||||
started int32
|
||||
processingOrders int32
|
||||
shutdown chan struct{}
|
||||
orderStore store
|
||||
cfg orderManagerConfig
|
||||
verbose bool
|
||||
futuresPositionController order.PositionController
|
||||
}
|
||||
|
||||
// OrderSubmitResponse contains the order response along with an internal order ID
|
||||
|
||||
@@ -70,6 +70,7 @@ var (
|
||||
errCurrencyNotSpecified = errors.New("a currency must be specified")
|
||||
errCurrencyPairInvalid = errors.New("currency provided is not found in the available pairs list")
|
||||
errNoTrades = errors.New("no trades returned from supplied params")
|
||||
errUnexpectedResponseSize = errors.New("unexpected slice size")
|
||||
errNilRequestData = errors.New("nil request data received, cannot continue")
|
||||
errNoAccountInformation = errors.New("account information does not exist")
|
||||
errShutdownNotAllowed = errors.New("shutting down this bot instance is not allowed via gRPC, please enable by command line flag --grpcshutdown or config.json field grpcAllowBotShutdown")
|
||||
@@ -998,10 +999,10 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (
|
||||
Trades: trades,
|
||||
}
|
||||
if !resp[x].Date.IsZero() {
|
||||
o.CreationTime = s.unixTimestamp(resp[x].Date)
|
||||
o.CreationTime = resp[x].Date.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
if !resp[x].LastUpdated.IsZero() {
|
||||
o.UpdateTime = s.unixTimestamp(resp[x].LastUpdated)
|
||||
o.UpdateTime = resp[x].LastUpdated.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
orders[x] = o
|
||||
}
|
||||
@@ -1087,10 +1088,10 @@ func (s *RPCServer) GetManagedOrders(_ context.Context, r *gctrpc.GetOrdersReque
|
||||
Trades: trades,
|
||||
}
|
||||
if !resp[x].Date.IsZero() {
|
||||
o.CreationTime = s.unixTimestamp(resp[x].Date)
|
||||
o.CreationTime = resp[x].Date.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
if !resp[x].LastUpdated.IsZero() {
|
||||
o.UpdateTime = s.unixTimestamp(resp[x].LastUpdated)
|
||||
o.UpdateTime = resp[x].LastUpdated.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
orders[x] = o
|
||||
}
|
||||
@@ -1152,12 +1153,12 @@ func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*g
|
||||
}
|
||||
}
|
||||
|
||||
var creationTime, updateTime int64
|
||||
var creationTime, updateTime string
|
||||
if !result.Date.IsZero() {
|
||||
creationTime = s.unixTimestamp(result.Date)
|
||||
creationTime = result.Date.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
if !result.LastUpdated.IsZero() {
|
||||
updateTime = s.unixTimestamp(result.LastUpdated)
|
||||
updateTime = result.LastUpdated.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
|
||||
return &gctrpc.OrderDetails{
|
||||
@@ -4179,8 +4180,171 @@ func (s *RPCServer) CurrencyStateTradingPair(_ context.Context, r *gctrpc.Curren
|
||||
ai)
|
||||
}
|
||||
|
||||
func (s *RPCServer) buildFuturePosition(position *order.Position, getFundingPayments, includeFundingRates, includeOrders, includePredictedRate bool) *gctrpc.FuturePosition {
|
||||
response := &gctrpc.FuturePosition{
|
||||
Exchange: position.Exchange,
|
||||
Asset: position.Asset.String(),
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: position.Pair.Delimiter,
|
||||
Base: position.Pair.Base.String(),
|
||||
Quote: position.Pair.Quote.String(),
|
||||
},
|
||||
Status: position.Status.String(),
|
||||
OpeningDate: position.OpeningDate.Format(common.SimpleTimeFormatWithTimezone),
|
||||
OpeningDirection: position.OpeningDirection.String(),
|
||||
OpeningPrice: position.OpeningPrice.String(),
|
||||
OpeningSize: position.OpeningSize.String(),
|
||||
CurrentDirection: position.LatestDirection.String(),
|
||||
CurrentPrice: position.LatestPrice.String(),
|
||||
CurrentSize: position.LatestSize.String(),
|
||||
UnrealisedPnl: position.UnrealisedPNL.String(),
|
||||
RealisedPnl: position.RealisedPNL.String(),
|
||||
OrderCount: int64(len(position.Orders)),
|
||||
}
|
||||
if getFundingPayments {
|
||||
var sum decimal.Decimal
|
||||
fundingData := &gctrpc.FundingData{}
|
||||
for i := range position.FundingRates.FundingRates {
|
||||
if includeFundingRates {
|
||||
fundingData.Rates = append(fundingData.Rates, &gctrpc.FundingRate{
|
||||
Date: position.FundingRates.FundingRates[i].Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: position.FundingRates.FundingRates[i].Rate.String(),
|
||||
Payment: position.FundingRates.FundingRates[i].Payment.String(),
|
||||
})
|
||||
}
|
||||
sum = sum.Add(position.FundingRates.FundingRates[i].Payment)
|
||||
}
|
||||
fundingData.PaymentSum = sum.String()
|
||||
response.FundingData = fundingData
|
||||
if includePredictedRate && !position.FundingRates.PredictedUpcomingRate.Time.IsZero() {
|
||||
fundingData.UpcomingRate = &gctrpc.FundingRate{
|
||||
Date: position.FundingRates.PredictedUpcomingRate.Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: position.FundingRates.PredictedUpcomingRate.Rate.String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if includeOrders {
|
||||
for i := range position.Orders {
|
||||
od := &gctrpc.OrderDetails{
|
||||
Exchange: position.Orders[i].Exchange,
|
||||
Id: position.Orders[i].OrderID,
|
||||
ClientOrderId: position.Orders[i].ClientOrderID,
|
||||
BaseCurrency: position.Orders[i].Pair.Base.String(),
|
||||
QuoteCurrency: position.Orders[i].Pair.Quote.String(),
|
||||
AssetType: position.Orders[i].AssetType.String(),
|
||||
OrderSide: position.Orders[i].Side.String(),
|
||||
OrderType: position.Orders[i].Type.String(),
|
||||
CreationTime: position.Orders[i].Date.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Status: position.Orders[i].Status.String(),
|
||||
Price: position.Orders[i].Price,
|
||||
Amount: position.Orders[i].Cost,
|
||||
OpenVolume: position.Orders[i].RemainingAmount,
|
||||
Fee: position.Orders[i].Fee,
|
||||
Cost: position.Orders[i].Cost,
|
||||
}
|
||||
if !position.Orders[i].LastUpdated.IsZero() {
|
||||
od.UpdateTime = position.Orders[i].LastUpdated.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
for j := range position.Orders[i].Trades {
|
||||
od.Trades = append(od.Trades, &gctrpc.TradeHistory{
|
||||
CreationTime: position.Orders[i].Trades[j].Timestamp.Unix(),
|
||||
Id: position.Orders[i].Trades[j].TID,
|
||||
Price: position.Orders[i].Trades[j].Price,
|
||||
Amount: position.Orders[i].Trades[j].Amount,
|
||||
Exchange: position.Orders[i].Trades[j].Exchange,
|
||||
AssetType: position.Orders[i].AssetType.String(),
|
||||
OrderSide: position.Orders[i].Trades[j].Side.String(),
|
||||
Fee: position.Orders[i].Trades[j].Fee,
|
||||
Total: position.Orders[i].Trades[j].Total,
|
||||
})
|
||||
}
|
||||
response.Orders = append(response.Orders, od)
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
// GetManagedPosition returns an open positions from the order manager, no calling any API endpoints to return this information
|
||||
func (s *RPCServer) GetManagedPosition(_ context.Context, r *gctrpc.GetManagedPositionRequest) (*gctrpc.GetManagedPositionsResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w GetManagedPositionRequest", common.ErrNilPointer)
|
||||
}
|
||||
if err := order.CheckFundingRatePrerequisites(r.GetFundingPayments, r.IncludePredictedRate, r.GetFundingPayments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Pair == nil {
|
||||
return nil, fmt.Errorf("%w request pair", common.ErrNilPointer)
|
||||
}
|
||||
var (
|
||||
exch exchange.IBotExchange
|
||||
ai asset.Item
|
||||
cp currency.Pair
|
||||
err error
|
||||
)
|
||||
exch, err = s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exch.IsEnabled() {
|
||||
return nil, fmt.Errorf("%w '%v'", errExchangeDisabled, exch.GetName())
|
||||
}
|
||||
ai, err = asset.New(r.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ai.IsFutures() {
|
||||
return nil, fmt.Errorf("%w '%v'", order.ErrNotFuturesAsset, ai)
|
||||
}
|
||||
cp, err = currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = checkParams(r.Exchange, exch, ai, cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
position, err := s.OrderManager.GetOpenFuturesPosition(r.Exchange, ai, cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gctrpc.GetManagedPositionsResponse{Positions: []*gctrpc.FuturePosition{
|
||||
s.buildFuturePosition(position, r.GetFundingPayments, r.IncludeFullFundingRates, r.IncludeFullOrderData, r.IncludePredictedRate),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// GetAllManagedPositions returns all open positions from the order manager, no calling any API endpoints to return this information
|
||||
func (s *RPCServer) GetAllManagedPositions(_ context.Context, r *gctrpc.GetAllManagedPositionsRequest) (*gctrpc.GetManagedPositionsResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w GetAllManagedPositions", common.ErrNilPointer)
|
||||
}
|
||||
if err := order.CheckFundingRatePrerequisites(r.GetFundingPayments, r.IncludePredictedRate, r.GetFundingPayments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
positions, err := s.OrderManager.GetAllOpenFuturesPositions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(positions, func(i, j int) bool {
|
||||
return positions[i].OpeningDate.Before(positions[j].OpeningDate)
|
||||
})
|
||||
response := make([]*gctrpc.FuturePosition, len(positions))
|
||||
for i := range positions {
|
||||
response[i] = s.buildFuturePosition(&positions[i], r.GetFundingPayments, r.IncludeFullFundingRates, r.IncludeFullOrderData, r.IncludePredictedRate)
|
||||
}
|
||||
|
||||
return &gctrpc.GetManagedPositionsResponse{Positions: response}, nil
|
||||
}
|
||||
|
||||
// GetFuturesPositions returns pnl positions for an exchange asset pair
|
||||
func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuturesPositionsRequest) (*gctrpc.GetFuturesPositionsResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w GetFuturesPositions", common.ErrNilPointer)
|
||||
}
|
||||
if err := order.CheckFundingRatePrerequisites(r.GetFundingPayments, r.IncludePredictedRate, r.GetFundingPayments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -4225,25 +4389,29 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var subErr string
|
||||
var subAccount string
|
||||
if creds.SubAccount != "" {
|
||||
subErr = "for subaccount: " + creds.SubAccount
|
||||
subAccount = "for subaccount: " + creds.SubAccount
|
||||
}
|
||||
orders, err := exch.GetFuturesPositions(ctx, ai, cp, start, end)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w %v", err, subErr)
|
||||
}
|
||||
sort.Slice(orders, func(i, j int) bool {
|
||||
return orders[i].Date.Before(orders[j].Date)
|
||||
positionDetails, err := exch.GetFuturesPositions(ctx, &order.PositionsRequest{
|
||||
Asset: ai,
|
||||
Pairs: currency.Pairs{cp},
|
||||
StartDate: start,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w %v", err, subAccount)
|
||||
}
|
||||
if len(positionDetails) != 1 {
|
||||
return nil, errUnexpectedResponseSize
|
||||
}
|
||||
if r.Overwrite {
|
||||
err = s.OrderManager.ClearFuturesTracking(r.Exchange, ai, cp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w %v", err, subErr)
|
||||
return nil, fmt.Errorf("cannot overwrite %w %v", err, subAccount)
|
||||
}
|
||||
}
|
||||
for i := range orders {
|
||||
_, err = s.OrderManager.UpsertOrder(&orders[i])
|
||||
for i := range positionDetails[0].Orders {
|
||||
err = s.OrderManager.orderStore.futuresPositionController.TrackNewOrder(&positionDetails[0].Orders[i])
|
||||
if err != nil {
|
||||
if !errors.Is(err, order.ErrPositionClosed) {
|
||||
return nil, err
|
||||
@@ -4252,13 +4420,17 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
}
|
||||
pos, err := s.OrderManager.GetFuturesPositionsForExchange(r.Exchange, ai, cp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w %v", err, subErr)
|
||||
return nil, fmt.Errorf("cannot GetFuturesPositionsForExchange %w %v", err, subAccount)
|
||||
}
|
||||
|
||||
response := &gctrpc.GetFuturesPositionsResponse{
|
||||
SubAccount: creds.SubAccount,
|
||||
}
|
||||
var totalRealisedPNL, totalUnrealisedPNL decimal.Decimal
|
||||
for i := range pos {
|
||||
if r.Status != "" && pos[i].Status.String() != strings.ToUpper(r.Status) {
|
||||
continue
|
||||
}
|
||||
if r.PositionLimit > 0 && len(response.Positions) >= int(r.PositionLimit) {
|
||||
break
|
||||
}
|
||||
@@ -4266,18 +4438,34 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
var tick *ticker.Price
|
||||
tick, err = exch.FetchTicker(ctx, pos[i].Pair, pos[i].Asset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w when fetching ticker data for %v %v %v", err, pos[i].Exchange, pos[i].Asset, pos[i].Pair)
|
||||
return nil, fmt.Errorf("%w when fetching ticker data for %v %v %v %v", err, pos[i].Exchange, pos[i].Asset, pos[i].Pair, subAccount)
|
||||
}
|
||||
pos[i].UnrealisedPNL, err = s.OrderManager.UpdateOpenPositionUnrealisedPNL(pos[i].Exchange, pos[i].Asset, pos[i].Pair, tick.Last, tick.LastUpdated)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w when updating unrealised PNL for %v %v %v", err, pos[i].Exchange, pos[i].Asset, pos[i].Pair)
|
||||
return nil, fmt.Errorf("%w when updating unrealised PNL for %v %v %v %v", err, pos[i].Exchange, pos[i].Asset, pos[i].Pair, subAccount)
|
||||
}
|
||||
pos[i].LatestPrice = decimal.NewFromFloat(tick.Last)
|
||||
}
|
||||
response.TotalOrders += int64(len(pos[i].Orders))
|
||||
details := &gctrpc.FuturePosition{
|
||||
Status: pos[i].Status.String(),
|
||||
UnrealisedPnl: pos[i].UnrealisedPNL.String(),
|
||||
RealisedPnl: pos[i].RealisedPNL.String(),
|
||||
Exchange: pos[i].Exchange,
|
||||
Asset: pos[i].Asset.String(),
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: pos[i].Pair.Delimiter,
|
||||
Base: pos[i].Pair.Base.String(),
|
||||
Quote: pos[i].Pair.Quote.String(),
|
||||
},
|
||||
Status: pos[i].Status.String(),
|
||||
OpeningDate: pos[i].OpeningDate.Format(common.SimpleTimeFormatWithTimezone),
|
||||
OpeningDirection: pos[i].OpeningDirection.String(),
|
||||
OpeningPrice: pos[i].OpeningPrice.String(),
|
||||
OpeningSize: pos[i].OpeningSize.String(),
|
||||
CurrentDirection: pos[i].LatestDirection.String(),
|
||||
CurrentPrice: pos[i].LatestPrice.String(),
|
||||
CurrentSize: pos[i].LatestSize.String(),
|
||||
UnrealisedPnl: pos[i].UnrealisedPNL.String(),
|
||||
RealisedPnl: pos[i].RealisedPNL.String(),
|
||||
OrderCount: int64(len(pos[i].Orders)),
|
||||
}
|
||||
if !pos[i].UnrealisedPNL.IsZero() {
|
||||
details.UnrealisedPnl = pos[i].UnrealisedPNL.String()
|
||||
@@ -4296,7 +4484,85 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
}
|
||||
totalRealisedPNL = totalRealisedPNL.Add(pos[i].RealisedPNL)
|
||||
totalUnrealisedPNL = totalUnrealisedPNL.Add(pos[i].UnrealisedPNL)
|
||||
if !r.Verbose {
|
||||
if r.GetPositionStats {
|
||||
var stats *order.PositionSummary
|
||||
stats, err = exch.GetPositionSummary(ctx, &order.PositionSummaryRequest{
|
||||
Asset: pos[i].Asset,
|
||||
Pair: pos[i].Pair,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot GetPositionSummary %w %v", err, subAccount)
|
||||
}
|
||||
details.PositionStats = &gctrpc.FuturesPositionStats{
|
||||
MaintenanceMarginRequirement: stats.MaintenanceMarginRequirement.String(),
|
||||
InitialMarginRequirement: stats.InitialMarginRequirement.String(),
|
||||
CollateralUsed: stats.CollateralUsed.String(),
|
||||
MarkPrice: stats.MarkPrice.String(),
|
||||
CurrentSize: stats.CurrentSize.String(),
|
||||
BreakEvenPrice: stats.BreakEvenPrice.String(),
|
||||
AverageOpenPrice: stats.AverageOpenPrice.String(),
|
||||
RecentPnl: stats.RecentPNL.String(),
|
||||
MarginFraction: stats.MarginFraction.String(),
|
||||
FreeCollateral: stats.FreeCollateral.String(),
|
||||
TotalCollateral: stats.TotalCollateral.String(),
|
||||
}
|
||||
if !stats.EstimatedLiquidationPrice.IsZero() {
|
||||
details.PositionStats.EstimatedLiquidationPrice = stats.EstimatedLiquidationPrice.String()
|
||||
}
|
||||
}
|
||||
if r.GetFundingPayments {
|
||||
var endDate = time.Now()
|
||||
if pos[i].Status == order.Closed {
|
||||
endDate = pos[i].Orders[len(pos[i].Orders)-1].Date
|
||||
}
|
||||
var fundingDetails []order.FundingRates
|
||||
fundingDetails, err = exch.GetFundingRates(ctx, &order.FundingRatesRequest{
|
||||
Asset: pos[i].Asset,
|
||||
Pairs: currency.Pairs{pos[i].Pair},
|
||||
StartDate: pos[i].Orders[0].Date,
|
||||
EndDate: endDate,
|
||||
IncludePayments: r.GetFundingPayments,
|
||||
IncludePredictedRate: r.IncludePredictedRate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case len(fundingDetails) == 0:
|
||||
case len(fundingDetails) == 1:
|
||||
var funding []*gctrpc.FundingRate
|
||||
if r.IncludeFullFundingRates {
|
||||
for j := range fundingDetails[0].FundingRates {
|
||||
funding = append(funding, &gctrpc.FundingRate{
|
||||
Date: fundingDetails[0].FundingRates[j].Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: fundingDetails[0].FundingRates[j].Rate.String(),
|
||||
Payment: fundingDetails[0].FundingRates[j].Payment.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
fundingRates := &gctrpc.FundingData{
|
||||
Rates: funding,
|
||||
PaymentSum: fundingDetails[0].PaymentSum.String(),
|
||||
}
|
||||
if r.IncludeFullFundingRates {
|
||||
fundingRates.LatestRate = funding[len(fundingRates.Rates)-1]
|
||||
}
|
||||
if r.IncludePredictedRate && !fundingDetails[0].PredictedUpcomingRate.Time.IsZero() {
|
||||
fundingRates.UpcomingRate = &gctrpc.FundingRate{
|
||||
Date: fundingDetails[0].PredictedUpcomingRate.Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: fundingDetails[0].PredictedUpcomingRate.Rate.String(),
|
||||
}
|
||||
}
|
||||
details.FundingData = fundingRates
|
||||
err = s.OrderManager.orderStore.futuresPositionController.TrackFundingDetails(&fundingDetails[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w expected 1 set of funding rates, got %d %v", errUnexpectedResponseSize, len(fundingDetails), subAccount)
|
||||
}
|
||||
}
|
||||
if !r.IncludeFullOrderData {
|
||||
response.Positions = append(response.Positions, details)
|
||||
continue
|
||||
}
|
||||
@@ -4324,7 +4590,7 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
AssetType: pos[i].Orders[j].AssetType.String(),
|
||||
OrderSide: pos[i].Orders[j].Side.String(),
|
||||
OrderType: pos[i].Orders[j].Type.String(),
|
||||
CreationTime: pos[i].Orders[j].Date.Unix(),
|
||||
CreationTime: pos[i].Orders[j].Date.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Status: pos[i].Orders[j].Status.String(),
|
||||
Price: pos[i].Orders[j].Price,
|
||||
Amount: pos[i].Orders[j].Amount,
|
||||
@@ -4333,7 +4599,7 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
Trades: trades,
|
||||
}
|
||||
if pos[i].Orders[j].LastUpdated.After(pos[i].Orders[j].Date) {
|
||||
od.UpdateTime = pos[i].Orders[j].LastUpdated.Unix()
|
||||
od.UpdateTime = pos[i].Orders[j].LastUpdated.Format(common.SimpleTimeFormatWithTimezone)
|
||||
}
|
||||
details.Orders = append(details.Orders, od)
|
||||
}
|
||||
@@ -4352,6 +4618,107 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetFundingRates returns the funding rates for a slice of pairs of an exchange, asset
|
||||
func (s *RPCServer) GetFundingRates(ctx context.Context, r *gctrpc.GetFundingRatesRequest) (*gctrpc.GetFundingRatesResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w GetFundingRateRequest", common.ErrNilPointer)
|
||||
}
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a, err := asset.New(r.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !a.IsFutures() {
|
||||
return nil, fmt.Errorf("%s %w", a, order.ErrNotFuturesAsset)
|
||||
}
|
||||
start := time.Now().AddDate(-1, 0, 0)
|
||||
end := time.Now()
|
||||
if r.StartDate != "" {
|
||||
start, err = time.Parse(common.SimpleTimeFormat, r.StartDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.EndDate != "" {
|
||||
end, err = time.Parse(common.SimpleTimeFormat, r.EndDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = common.StartEndTimeCheck(start, end)
|
||||
if err != nil && !errors.Is(err, common.ErrDateUnset) {
|
||||
return nil, err
|
||||
}
|
||||
pairs, err := currency.NewPairsFromStrings(r.Pairs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range pairs {
|
||||
err = checkParams(r.Exchange, exch, a, pairs[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
funding, err := exch.GetFundingRates(ctx, &order.FundingRatesRequest{
|
||||
Asset: a,
|
||||
Pairs: pairs,
|
||||
StartDate: start,
|
||||
EndDate: end,
|
||||
IncludePayments: r.IncludePayments,
|
||||
IncludePredictedRate: r.IncludePredicted,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response gctrpc.GetFundingRatesResponse
|
||||
responses := make([]*gctrpc.FundingData, len(funding))
|
||||
for i := range funding {
|
||||
fundingData := &gctrpc.FundingData{
|
||||
Exchange: r.Exchange,
|
||||
Asset: r.Asset,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: funding[i].Pair.Delimiter,
|
||||
Base: funding[i].Pair.Base.String(),
|
||||
Quote: funding[i].Pair.Quote.String(),
|
||||
},
|
||||
StartDate: start.Format(common.SimpleTimeFormatWithTimezone),
|
||||
EndDate: end.Format(common.SimpleTimeFormatWithTimezone),
|
||||
LatestRate: &gctrpc.FundingRate{
|
||||
Date: funding[i].LatestRate.Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: funding[i].LatestRate.Rate.String(),
|
||||
},
|
||||
}
|
||||
var rates []*gctrpc.FundingRate
|
||||
for j := range funding[i].FundingRates {
|
||||
rate := &gctrpc.FundingRate{
|
||||
Rate: funding[i].FundingRates[j].Rate.String(),
|
||||
Date: funding[i].FundingRates[j].Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
}
|
||||
if r.IncludePayments {
|
||||
rate.Payment = funding[i].FundingRates[j].Payment.String()
|
||||
}
|
||||
rates = append(rates, rate)
|
||||
}
|
||||
if r.IncludePayments {
|
||||
fundingData.PaymentSum = funding[i].PaymentSum.String()
|
||||
}
|
||||
fundingData.Rates = rates
|
||||
if r.IncludePredicted {
|
||||
fundingData.UpcomingRate = &gctrpc.FundingRate{
|
||||
Date: funding[i].PredictedUpcomingRate.Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
Rate: funding[i].PredictedUpcomingRate.Rate.String(),
|
||||
}
|
||||
}
|
||||
responses[i] = fundingData
|
||||
}
|
||||
response.FundingPayments = responses
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetCollateral returns the total collateral for an exchange's asset
|
||||
// as exchanges can scale collateral and represent it in a singular currency,
|
||||
// a user can opt to include a breakdown by currency
|
||||
|
||||
@@ -54,6 +54,90 @@ type fExchange struct {
|
||||
exchange.IBotExchange
|
||||
}
|
||||
|
||||
func (f fExchange) GetPositionSummary(context.Context, *order.PositionSummaryRequest) (*order.PositionSummary, error) {
|
||||
leet := decimal.NewFromInt(1337)
|
||||
return &order.PositionSummary{
|
||||
MaintenanceMarginRequirement: leet,
|
||||
InitialMarginRequirement: leet,
|
||||
EstimatedLiquidationPrice: leet,
|
||||
CollateralUsed: leet,
|
||||
MarkPrice: leet,
|
||||
CurrentSize: leet,
|
||||
BreakEvenPrice: leet,
|
||||
AverageOpenPrice: leet,
|
||||
RecentPNL: leet,
|
||||
MarginFraction: leet,
|
||||
FreeCollateral: leet,
|
||||
TotalCollateral: leet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fExchange) GetFuturesPositions(ctx context.Context, req *order.PositionsRequest) ([]order.PositionDetails, error) {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]order.PositionDetails, len(req.Pairs))
|
||||
tt := time.Now()
|
||||
for i := range req.Pairs {
|
||||
resp[i] = order.PositionDetails{
|
||||
Exchange: f.GetName(),
|
||||
Asset: req.Asset,
|
||||
Pair: req.Pairs[i],
|
||||
Orders: []order.Detail{
|
||||
{
|
||||
Exchange: f.GetName(),
|
||||
Price: 1337,
|
||||
Amount: 1337,
|
||||
InternalOrderID: id,
|
||||
OrderID: "1337",
|
||||
ClientOrderID: "1337",
|
||||
Type: order.Market,
|
||||
Side: order.Short,
|
||||
Status: order.Open,
|
||||
AssetType: req.Asset,
|
||||
Date: tt,
|
||||
CloseTime: tt,
|
||||
LastUpdated: tt,
|
||||
Pair: req.Pairs[i],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (f fExchange) GetFundingRates(ctx context.Context, request *order.FundingRatesRequest) ([]order.FundingRates, error) {
|
||||
leet := decimal.NewFromInt(1337)
|
||||
return []order.FundingRates{
|
||||
{
|
||||
Exchange: f.GetName(),
|
||||
Asset: request.Asset,
|
||||
Pair: request.Pairs[0],
|
||||
StartDate: request.StartDate,
|
||||
EndDate: request.EndDate,
|
||||
LatestRate: order.FundingRate{
|
||||
Time: request.EndDate,
|
||||
Rate: leet,
|
||||
Payment: leet,
|
||||
},
|
||||
PredictedUpcomingRate: order.FundingRate{
|
||||
Time: request.EndDate,
|
||||
Rate: leet,
|
||||
Payment: leet,
|
||||
},
|
||||
FundingRates: []order.FundingRate{
|
||||
{
|
||||
Time: request.EndDate,
|
||||
Rate: leet,
|
||||
Payment: leet,
|
||||
},
|
||||
},
|
||||
PaymentSum: leet,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fExchange) GetHistoricCandles(ctx context.Context, p currency.Pair, a asset.Item, timeStart, _ time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{
|
||||
Exchange: fakeExchangeName,
|
||||
@@ -173,24 +257,6 @@ func (f fExchange) FetchAccountInfo(_ context.Context, a asset.Item) (account.Ho
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFuturesPositions overrides testExchange's GetFuturesPositions function
|
||||
func (f fExchange) GetFuturesPositions(_ context.Context, a asset.Item, cp currency.Pair, _, _ time.Time) ([]order.Detail, error) {
|
||||
return []order.Detail{
|
||||
{
|
||||
Price: 1337,
|
||||
Amount: 1337,
|
||||
Fee: 1.337,
|
||||
Exchange: f.GetName(),
|
||||
OrderID: "test",
|
||||
Side: order.Long,
|
||||
Status: order.Open,
|
||||
AssetType: a,
|
||||
Date: time.Now(),
|
||||
Pair: cp,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CalculateTotalCollateral overrides testExchange's CalculateTotalCollateral function
|
||||
func (f fExchange) CalculateTotalCollateral(context.Context, *order.TotalCollateralCalculator) (*order.TotalCollateralResponse, error) {
|
||||
return &order.TotalCollateralResponse{
|
||||
@@ -1177,7 +1243,7 @@ func TestGetOrders(t *testing.T) {
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
em.Add(exch)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false)
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -1284,7 +1350,7 @@ func TestGetOrder(t *testing.T) {
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
em.Add(exch)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false)
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -1813,7 +1879,7 @@ func TestGetManagedOrders(t *testing.T) {
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
em.Add(exch)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false)
|
||||
om, err := SetupOrderManager(em, engerino.CommunicationsManager, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -2168,7 +2234,7 @@ func TestGetFuturesPositions(t *testing.T) {
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false)
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
@@ -2192,7 +2258,6 @@ func TestGetFuturesPositions(t *testing.T) {
|
||||
Base: cp.Base.String(),
|
||||
Quote: cp.Quote.String(),
|
||||
},
|
||||
Verbose: true,
|
||||
})
|
||||
if !errors.Is(err, exchange.ErrCredentialsAreEmpty) {
|
||||
t.Fatalf("received '%v', expected '%v'", err, exchange.ErrCredentialsAreEmpty)
|
||||
@@ -2206,17 +2271,21 @@ func TestGetFuturesPositions(t *testing.T) {
|
||||
)
|
||||
|
||||
_, err = s.GetFuturesPositions(ctx, &gctrpc.GetFuturesPositionsRequest{
|
||||
Exchange: fakeExchangeName,
|
||||
Exchange: "test",
|
||||
Asset: asset.Futures.String(),
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: currency.DashDelimiter,
|
||||
Base: cp.Base.String(),
|
||||
Quote: cp.Quote.String(),
|
||||
},
|
||||
Verbose: true,
|
||||
IncludeFullOrderData: true,
|
||||
IncludeFullFundingRates: true,
|
||||
IncludePredictedRate: true,
|
||||
GetPositionStats: true,
|
||||
GetFundingPayments: true,
|
||||
})
|
||||
if !errors.Is(err, order.ErrPositionsNotLoadedForExchange) {
|
||||
t.Fatalf("received '%v', expected '%v'", err, order.ErrPositionsNotLoadedForExchange)
|
||||
if !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrExchangeNotFound)
|
||||
}
|
||||
|
||||
od := &order.Detail{
|
||||
@@ -2243,7 +2312,7 @@ func TestGetFuturesPositions(t *testing.T) {
|
||||
Base: cp.Base.String(),
|
||||
Quote: cp.Quote.String(),
|
||||
},
|
||||
Verbose: true,
|
||||
IncludeFullOrderData: true,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received '%v', expected '%v'", err, nil)
|
||||
@@ -2257,7 +2326,6 @@ func TestGetFuturesPositions(t *testing.T) {
|
||||
Base: cp.Base.String(),
|
||||
Quote: cp.Quote.String(),
|
||||
},
|
||||
Verbose: true,
|
||||
})
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
|
||||
@@ -2793,3 +2861,340 @@ func TestGetMarginRatesHistory(t *testing.T) {
|
||||
t.Errorf("received '%v' expected '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFundingRates(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName("ftx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-perp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
om.started = 1
|
||||
s := RPCServer{
|
||||
Engine: &Engine{
|
||||
ExchangeManager: em,
|
||||
currencyStateManager: &CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: em,
|
||||
},
|
||||
OrderManager: om,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = s.GetFundingRates(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
request := &gctrpc.GetFundingRatesRequest{
|
||||
Exchange: "",
|
||||
Asset: "",
|
||||
Pairs: nil,
|
||||
StartDate: "",
|
||||
EndDate: "",
|
||||
IncludePredicted: false,
|
||||
IncludePayments: false,
|
||||
}
|
||||
_, err = s.GetFundingRates(context.Background(), request)
|
||||
if !errors.Is(err, ErrExchangeNameIsEmpty) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
|
||||
}
|
||||
request.Exchange = exch.GetName()
|
||||
_, err = s.GetFundingRates(context.Background(), request)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
request.Asset = asset.Spot.String()
|
||||
_, err = s.GetFundingRates(context.Background(), request)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, order.ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
request.Asset = asset.Futures.String()
|
||||
request.Pairs = []string{cp.String()}
|
||||
request.IncludePredicted = true
|
||||
request.IncludePayments = true
|
||||
_, err = s.GetFundingRates(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetManagedPosition(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName("ftx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-perp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cp2, err := currency.NewPairFromString("btc-usd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
om.started = 1
|
||||
s := RPCServer{
|
||||
Engine: &Engine{
|
||||
ExchangeManager: em,
|
||||
currencyStateManager: &CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: em,
|
||||
},
|
||||
OrderManager: om,
|
||||
},
|
||||
}
|
||||
_, err = s.GetManagedPosition(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received '%v', expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
request := &gctrpc.GetManagedPositionRequest{}
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received '%v', expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
request.Pair = &gctrpc.CurrencyPair{
|
||||
Delimiter: "-",
|
||||
Base: "BTC",
|
||||
Quote: "USD",
|
||||
}
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, ErrExchangeNameIsEmpty) {
|
||||
t.Errorf("received '%v', expected '%v'", err, ErrExchangeNameIsEmpty)
|
||||
}
|
||||
|
||||
request.Exchange = fakeExchangeName
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
request.Asset = asset.Spot.String()
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, order.ErrNotFuturesAsset) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
|
||||
}
|
||||
|
||||
request.Asset = asset.Futures.String()
|
||||
s.OrderManager, err = SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
s.OrderManager.started = 1
|
||||
s.OrderManager.activelyTrackFuturesPositions = true
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, order.ErrPositionNotFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrPositionNotFound)
|
||||
}
|
||||
|
||||
err = s.OrderManager.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
Leverage: 1337,
|
||||
Price: 1337,
|
||||
Amount: 1337,
|
||||
LimitPriceUpper: 1337,
|
||||
LimitPriceLower: 1337,
|
||||
TriggerPrice: 1337,
|
||||
AverageExecutedPrice: 1337,
|
||||
QuoteAmount: 1337,
|
||||
ExecutedAmount: 1337,
|
||||
RemainingAmount: 1337,
|
||||
Cost: 1337,
|
||||
Exchange: fakeExchangeName,
|
||||
OrderID: "1337",
|
||||
Type: order.Market,
|
||||
Side: order.Buy,
|
||||
Status: order.Filled,
|
||||
AssetType: asset.Futures,
|
||||
Date: time.Now(),
|
||||
LastUpdated: time.Now(),
|
||||
Pair: cp2,
|
||||
Trades: []order.TradeHistory{
|
||||
{
|
||||
Timestamp: time.Now(),
|
||||
Side: order.Buy,
|
||||
},
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
_, err = s.GetManagedPosition(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllManagedPositions(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName("ftx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-perp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cp2, err := currency.NewPairFromString("btc-usd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Futures] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
Available: currency.Pairs{cp, cp2},
|
||||
Enabled: currency.Pairs{cp, cp2},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
var wg sync.WaitGroup
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
om.started = 1
|
||||
s := RPCServer{
|
||||
Engine: &Engine{
|
||||
ExchangeManager: em,
|
||||
currencyStateManager: &CurrencyStateManager{
|
||||
started: 1,
|
||||
iExchangeManager: em,
|
||||
},
|
||||
OrderManager: om,
|
||||
},
|
||||
}
|
||||
_, err = s.GetAllManagedPositions(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received '%v', expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
request := &gctrpc.GetAllManagedPositionsRequest{}
|
||||
s.OrderManager, err = SetupOrderManager(em, &CommunicationManager{}, &wg, false, true, time.Hour)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
s.OrderManager.started = 1
|
||||
_, err = s.GetAllManagedPositions(context.Background(), request)
|
||||
if !errors.Is(err, order.ErrNoPositionsFound) {
|
||||
t.Errorf("received '%v', expected '%v'", err, order.ErrNoPositionsFound)
|
||||
}
|
||||
|
||||
err = s.OrderManager.orderStore.futuresPositionController.TrackNewOrder(&order.Detail{
|
||||
Leverage: 1337,
|
||||
Price: 1337,
|
||||
Amount: 1337,
|
||||
LimitPriceUpper: 1337,
|
||||
LimitPriceLower: 1337,
|
||||
TriggerPrice: 1337,
|
||||
AverageExecutedPrice: 1337,
|
||||
QuoteAmount: 1337,
|
||||
ExecutedAmount: 1337,
|
||||
RemainingAmount: 1337,
|
||||
Cost: 1337,
|
||||
Exchange: fakeExchangeName,
|
||||
OrderID: "1337",
|
||||
Type: order.Market,
|
||||
Side: order.Buy,
|
||||
Status: order.Filled,
|
||||
AssetType: asset.Futures,
|
||||
Date: time.Now(),
|
||||
LastUpdated: time.Now(),
|
||||
Pair: cp2,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
request.IncludePredictedRate = true
|
||||
request.GetFundingPayments = true
|
||||
request.IncludeFullFundingRates = true
|
||||
request.IncludeFullOrderData = true
|
||||
_, err = s.GetAllManagedPositions(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestWebsocketRoutineManagerHandleData(t *testing.T) {
|
||||
exch.SetDefaults()
|
||||
em.Add(exch)
|
||||
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false)
|
||||
om, err := SetupOrderManager(em, &CommunicationManager{}, &wg, false, false, 0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("error '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user