Files
gocryptotrader/exchanges/ftx/ftx_test.go
Scott 46cadd6f15 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
2022-08-23 12:16:50 +10:00

2741 lines
71 KiB
Go

package ftx
import (
"context"
"errors"
"log"
"math"
"os"
"sync"
"testing"
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
// Please supply your own keys here to do authenticated endpoint testing
const (
apiKey = ""
apiSecret = ""
subaccount = ""
canManipulateRealOrders = false
spotPairStr = "FTT/BTC"
futuresPair = "DOGE-PERP"
testLeverageToken = "ADAMOON"
validFTTBTCStartTime = 1565445600 // Sat Aug 10 2019 14:00:00 GMT+0000
validFTTBTCEndTime = 1565532000 // Sat Aug 11 2019 14:00:00 GMT+0000
invalidFTTBTCStartTime = 1559881511 // Fri Jun 07 2019 04:25:11 GMT+0000
invalidFTTBTCEndTime = 1559901511 // Fri Jun 07 2019 09:58:31 GMT+0000
authStartTime = validFTTBTCStartTime // Adjust these to test auth requests
authEndTime = validFTTBTCEndTime
)
var (
f FTX
spotPair = currency.NewPair(currency.FTT, currency.BTC)
)
func TestMain(m *testing.M) {
f.SetDefaults()
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal(err)
}
exchCfg, err := cfg.GetExchangeConfig("FTX")
if err != nil {
log.Fatal(err)
}
exchCfg.API.Credentials.Key = apiKey
exchCfg.API.Credentials.Secret = apiSecret
exchCfg.API.Credentials.Subaccount = subaccount
if apiKey != "" && apiSecret != "" {
// Only set auth to true when keys present as fee online calculation requires authentication
exchCfg.API.AuthenticatedSupport = true
exchCfg.API.AuthenticatedWebsocketSupport = true
}
f.Websocket = sharedtestvalues.NewTestWebsocket()
err = f.Setup(exchCfg)
if err != nil {
log.Fatal(err)
}
err = f.CurrencyPairs.EnablePair(asset.Futures, currency.NewPair(currency.BTC, currency.PERP))
if err != nil {
log.Fatal(err)
}
err = f.CurrencyPairs.EnablePair(asset.Futures, currency.NewPair(currency.OKB, currency.PERP))
if err != nil {
log.Fatal(err)
}
f.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
f.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
os.Exit(m.Run())
}
func areTestAPIKeysSet() bool {
return f.ValidateAPICredentials(f.GetDefaultCredentials()) == nil
}
// Implement tests for API endpoints below
func TestStart(t *testing.T) {
t.Parallel()
err := f.Start(nil)
if !errors.Is(err, common.ErrNilPointer) {
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNilPointer)
}
var testWg sync.WaitGroup
err = f.Start(&testWg)
if err != nil {
t.Fatal(err)
}
testWg.Wait()
}
func TestGetMarkets(t *testing.T) {
t.Parallel()
_, err := f.GetMarkets(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetHistoricalIndex(t *testing.T) {
t.Parallel()
_, err := f.GetHistoricalIndex(context.Background(),
"BTC", 3600, time.Now().Add(-time.Hour*2), time.Now().Add(-time.Hour*1))
if err != nil {
t.Error(err)
}
_, err = f.GetHistoricalIndex(context.Background(),
"BTC", 3600, time.Time{}, time.Time{})
if err != nil {
t.Error(err)
}
}
func TestGetMarket(t *testing.T) {
t.Parallel()
_, err := f.GetMarket(context.Background(), spotPairStr)
if err != nil {
t.Error(err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := f.GetOrderbook(context.Background(), spotPairStr, 5)
if err != nil {
t.Error(err)
}
}
func TestGetTrades(t *testing.T) {
t.Parallel()
// test empty market
_, err := f.GetTrades(context.Background(), "", 0, 0, 200)
if err == nil {
t.Error("empty market should return an error")
}
_, err = f.GetTrades(context.Background(),
spotPairStr, validFTTBTCEndTime, validFTTBTCStartTime, 5)
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
// test optional params
var trades []TradeData
trades, err = f.GetTrades(context.Background(), spotPairStr, 0, 0, 0)
if err != nil {
t.Error(err)
}
if len(trades) != 20 {
t.Error("default limit should return 20 items")
}
trades, err = f.GetTrades(context.Background(),
spotPairStr, validFTTBTCStartTime, validFTTBTCEndTime, 5)
if err != nil {
t.Error(err)
}
if len(trades) != 5 {
t.Error("limit of 5 should return 5 items")
}
trades, err = f.GetTrades(context.Background(),
spotPairStr, invalidFTTBTCStartTime, invalidFTTBTCEndTime, 5)
if err != nil {
t.Error(err)
}
if len(trades) != 0 {
t.Error("invalid time range should return 0 items")
}
}
func TestGetHistoricalData(t *testing.T) {
t.Parallel()
// test empty market
_, err := f.GetHistoricalData(context.Background(),
"", 86400, 5, time.Time{}, time.Time{})
if err == nil {
t.Error("empty market should return an error")
}
// test empty resolution
_, err = f.GetHistoricalData(context.Background(),
spotPairStr, 0, 5, time.Time{}, time.Time{})
if err == nil {
t.Error("empty resolution should return an error")
}
_, err = f.GetHistoricalData(context.Background(),
spotPairStr, 86400, 5, time.Unix(validFTTBTCEndTime, 0),
time.Unix(validFTTBTCStartTime, 0))
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
var o []OHLCVData
o, err = f.GetHistoricalData(context.Background(),
spotPairStr, 86400, 5, time.Time{}, time.Time{})
if err != nil {
t.Error(err)
}
if len(o) != 5 {
t.Error("limit of 5 should return 5 items")
}
o, err = f.GetHistoricalData(context.Background(),
spotPairStr, 86400, 5, time.Unix(invalidFTTBTCStartTime, 0),
time.Unix(invalidFTTBTCEndTime, 0))
if err != nil {
t.Error(err)
}
if len(o) != 0 {
t.Error("invalid time range should return 0 items")
}
}
func TestGetFutures(t *testing.T) {
t.Parallel()
_, err := f.GetFutures(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetFuture(t *testing.T) {
t.Parallel()
_, err := f.GetFuture(context.Background(), futuresPair)
if err != nil {
t.Error(err)
}
}
func TestGetFutureStats(t *testing.T) {
t.Parallel()
_, err := f.GetFutureStats(context.Background(), currency.NewPair(currency.BTC, currency.PERP))
if err != nil {
t.Error(err)
}
future, err := f.GetFutureStats(context.Background(), currency.NewPair(currency.BTC, currency.NewCode("MOVE-2021Q4")))
if err != nil {
t.Error(err)
}
if future.Greeks == nil {
t.Fatal("no greeks returned for futures contract")
}
}
func TestFundingRates(t *testing.T) {
t.Parallel()
// optional params
_, err := f.FundingRates(context.Background(), time.Time{}, time.Time{}, currency.EMPTYPAIR, -1)
if err != nil {
t.Error(err)
}
_, err = f.FundingRates(context.Background(),
time.Now().Add(-time.Hour), time.Now(), currency.NewPair(currency.BTC, currency.PERP), 1)
if err != nil {
t.Error(err)
}
}
func TestGetAccountInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetAccountInfo(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetPositions(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetPositions(context.Background(), false)
if err != nil {
t.Error(err)
}
_, err = f.GetPositions(context.Background(), true)
if err != nil {
t.Error(err)
}
}
func TestGetBalances(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetBalances(context.Background(), false, false)
if err != nil {
t.Error(err)
}
_, err = f.GetBalances(context.Background(), true, false)
if err != nil {
t.Error(err)
}
_, err = f.GetBalances(context.Background(), false, true)
if err != nil {
t.Error(err)
}
_, err = f.GetBalances(context.Background(), true, true)
if err != nil {
t.Error(err)
}
}
func TestGetAllWalletBalances(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetAllWalletBalances(context.Background())
if err != nil {
t.Error(err)
}
}
func TestChangeAccountLeverage(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
err := f.ChangeAccountLeverage(context.Background(), 50)
if err != nil {
t.Error(err)
}
}
func TestGetCoins(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetCoins(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetMarginBorrowRates(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetMarginBorrowRates(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetMarginLendingRates(t *testing.T) {
t.Parallel()
_, err := f.GetMarginLendingRates(context.Background())
if err != nil {
t.Error(err)
}
}
func TestMarginDailyBorrowedAmounts(t *testing.T) {
t.Parallel()
_, err := f.MarginDailyBorrowedAmounts(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetMarginMarketInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetMarginMarketInfo(context.Background(), "BTC_USD")
if err != nil {
t.Error(err)
}
}
func TestGetMarginBorrowHistory(t *testing.T) {
t.Parallel()
tmNow := time.Now()
_, err := f.GetMarginBorrowHistory(context.Background(),
tmNow.AddDate(0, 0, 1),
tmNow)
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err)
}
if !areTestAPIKeysSet() {
t.Skip()
}
_, err = f.GetMarginBorrowHistory(context.Background(),
tmNow.AddDate(0, 0, -1),
tmNow)
if err != nil {
t.Error(err)
}
}
func TestGetMarginMarketLendingHistory(t *testing.T) {
t.Parallel()
tmNow := time.Now()
_, err := f.GetMarginMarketLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, 1), tmNow)
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err)
}
_, err = f.GetMarginMarketLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, -1), tmNow)
if err != nil {
t.Error(err)
}
}
func TestGetMarginLendingHistory(t *testing.T) {
t.Parallel()
tmNow := time.Now()
_, err := f.GetMarginLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, 1), tmNow)
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err)
}
if !areTestAPIKeysSet() {
t.Skip()
}
_, err = f.GetMarginLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, -1), tmNow)
if err != nil {
t.Error(err)
}
}
func TestGetMarginLendingOffers(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetMarginLendingOffers(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetLendingInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetLendingInfo(context.Background())
if err != nil {
t.Error(err)
}
}
func TestSubmitLendingOffer(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip()
}
if err := f.SubmitLendingOffer(context.Background(),
currency.NewCode("bTc"), 0.1, 500); err != nil {
t.Error(err)
}
}
func TestFetchDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
r, err := f.FetchDepositAddress(context.Background(), currency.NewCode("UsDt"), "trx")
if err != nil {
t.Fatal(err)
}
if r.Method != "trx" {
t.Error("expected trx method")
}
_, err = f.FetchDepositAddress(context.Background(), currency.NewCode("SUSHIBEAR"), "")
if !errors.Is(err, errDepositAddressDoesNotExist) {
t.Error(err)
}
}
func TestFetchDepositHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.FetchDepositHistory(context.Background())
if err != nil {
t.Error(err)
}
}
func TestFetchWithdrawalHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.FetchWithdrawalHistory(context.Background())
if err != nil {
t.Error(err)
}
}
func TestWithdraw(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.Withdraw(context.Background(),
currency.NewCode("UsDT"),
"TJU9piX2WA8WTvxVKMqpvTzZGhvXQAZKSY",
"",
"",
"trx",
"715913",
-1)
if err != nil {
t.Error(err)
}
}
func TestGetOpenOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetOpenOrders(context.Background(), "")
if err != nil {
t.Error(err)
}
_, err = f.GetOpenOrders(context.Background(), spotPairStr)
if err != nil {
t.Error(err)
}
}
func TestFetchOrderHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.FetchOrderHistory(context.Background(),
"", time.Time{}, time.Time{}, "2")
if err != nil {
t.Error(err)
}
_, err = f.FetchOrderHistory(context.Background(),
spotPairStr, time.Unix(authStartTime, 0), time.Unix(authEndTime, 0), "2")
if err != nil {
t.Error(err)
}
_, err = f.FetchOrderHistory(context.Background(),
spotPairStr, time.Unix(authEndTime, 0), time.Unix(authStartTime, 0), "2")
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}
func TestGetOpenTriggerOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
// optional params
_, err := f.GetOpenTriggerOrders(context.Background(), "", "")
if err != nil {
t.Error(err)
}
_, err = f.GetOpenTriggerOrders(context.Background(), spotPairStr, "")
if err != nil {
t.Error(err)
}
}
func TestGetTriggerOrderTriggers(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetTriggerOrderTriggers(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetTriggerOrderHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetTriggerOrderHistory(context.Background(),
"", time.Time{}, time.Time{}, "", "", "")
if err != nil {
t.Error(err)
}
_, err = f.GetTriggerOrderHistory(context.Background(),
spotPairStr, time.Time{}, time.Time{}, order.Buy.Lower(), "stop", "1")
if err != nil {
t.Error(err)
}
_, err = f.GetTriggerOrderHistory(context.Background(),
spotPairStr,
time.Unix(authStartTime, 0),
time.Unix(authEndTime, 0),
order.Buy.Lower(),
"stop",
"1")
if err != nil {
t.Error(err)
}
_, err = f.GetTriggerOrderHistory(context.Background(),
spotPairStr,
time.Unix(authEndTime, 0),
time.Unix(authStartTime, 0),
order.Buy.Lower(),
"stop",
"1")
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}
func TestOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.Order(context.Background(),
spotPairStr,
order.Buy.Lower(),
"limit",
false, false, false,
"", 0.0001, 500)
if err != nil {
t.Error(err)
}
}
func TestSubmitOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set correctly")
}
currencyPair, err := currency.NewPairFromString(spotPairStr)
if err != nil {
t.Fatal(err)
}
var orderSubmission = &order.Submit{
Exchange: f.Name,
Pair: currencyPair,
Side: order.Sell,
Type: order.Limit,
Price: 100000,
Amount: 1,
AssetType: asset.Spot,
ClientOrderID: "order12345679$$$$$",
}
_, err = f.SubmitOrder(context.Background(), orderSubmission)
if err != nil {
t.Error(err)
}
}
func TestTriggerOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.TriggerOrder(context.Background(),
spotPairStr,
order.Buy.Lower(),
order.Stop.Lower(),
"", "",
500, 0.0004, 0.0001, 0)
if err != nil {
t.Error(err)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set correctly")
}
currencyPair, err := currency.NewPairFromString(spotPairStr)
if err != nil {
t.Fatal(err)
}
c := order.Cancel{
OrderID: "12366984218",
Pair: currencyPair,
AssetType: asset.Spot,
}
if err := f.CancelOrder(context.Background(), &c); err != nil {
t.Error(err)
}
c.ClientOrderID = "1337"
if err := f.CancelOrder(context.Background(), &c); err != nil {
t.Error(err)
}
}
func TestDeleteOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.DeleteOrder(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestDeleteOrderByClientID(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.DeleteOrderByClientID(context.Background(), "clientID123")
if err != nil {
t.Error(err)
}
}
func TestDeleteTriggerOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.DeleteTriggerOrder(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetFills(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now())
if !errors.Is(err, errStartTimeCannotBeAfterEndTime) {
t.Errorf("received '%v' expected '%v'", err, errStartTimeCannotBeAfterEndTime)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Time{}, time.Time{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now())
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = f.GetFills(context.Background(),
spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now())
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
}
func TestFundingPayments(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.FundingPayments(context.Background(), time.Time{}, time.Time{}, currency.EMPTYPAIR, -1)
if err != nil {
t.Error(err)
}
cp, err := currency.NewPairFromString("BTC-PERP")
if err != nil {
t.Fatal(err)
}
_, err = f.FundingPayments(context.Background(), time.Unix(authStartTime, 0), time.Unix(authEndTime, 0), cp, 1)
if err != nil {
t.Error(err)
}
_, err = f.FundingPayments(context.Background(), time.Unix(authEndTime, 0), time.Unix(authStartTime, 0), cp, -1)
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}
func TestListLeveragedTokens(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.ListLeveragedTokens(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetTokenInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetTokenInfo(context.Background(), "")
if err != nil {
t.Error(err)
}
}
func TestListLTBalances(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.ListLTBalances(context.Background())
if err != nil {
t.Error(err)
}
}
func TestListLTCreations(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.ListLTCreations(context.Background())
if err != nil {
t.Error(err)
}
}
func TestRequestLTCreation(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.RequestLTCreation(context.Background(), testLeverageToken, 1)
if err != nil {
t.Error(err)
}
}
func TestListLTRedemptions(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.ListLTRedemptions(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetQuoteRequests(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetQuoteRequests(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetYourQuoteRequests(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetYourQuoteRequests(context.Background())
if err != nil {
t.Error(err)
}
}
func TestCreateQuoteRequest(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.CreateQuoteRequest(context.Background(),
currency.BTC, "call", order.Buy.Lower(), 1593140400, "", 10, 10, 5, 0, false)
if err != nil {
t.Error(err)
}
}
func TestDeleteQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.DeleteQuote(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetQuotesForYourQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetQuotesForYourQuote(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestMakeQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.MakeQuote(context.Background(), "1031", "5")
if err != nil {
t.Error(err)
}
}
func TestMyQuotes(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.MyQuotes(context.Background())
if err != nil {
t.Error(err)
}
}
func TestDeleteMyQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.DeleteMyQuote(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestAcceptQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.AcceptQuote(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetAccountOptionsInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetAccountOptionsInfo(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetOptionsPositions(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetOptionsPositions(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetPublicOptionsTrades(t *testing.T) {
t.Parallel()
// test optional params
result, err := f.GetPublicOptionsTrades(context.Background(),
time.Time{}, time.Time{}, "")
if err != nil {
t.Error(err)
}
if len(result) != 20 {
t.Error("default limit should have returned 20 items")
}
tmNow := time.Now()
result, err = f.GetPublicOptionsTrades(context.Background(),
tmNow.AddDate(-1, 0, 0), tmNow, "5")
if err != nil {
t.Error(err)
}
if len(result) > 5 {
t.Error("limit of 5 should not exceed 5 items")
}
_, err = f.GetPublicOptionsTrades(context.Background(),
time.Unix(validFTTBTCEndTime, 0), time.Unix(validFTTBTCStartTime, 0), "5")
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}
func TestGetOptionsFills(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetOptionsFills(context.Background(), time.Time{}, time.Time{}, "5")
if err != nil {
t.Error(err)
}
_, err = f.GetOptionsFills(context.Background(),
time.Unix(authStartTime, 0), time.Unix(authEndTime, 0), "5")
if err != nil {
t.Error(err)
}
_, err = f.GetOptionsFills(context.Background(),
time.Unix(authEndTime, 0), time.Unix(authStartTime, 0), "5")
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}
func TestUpdateOrderbook(t *testing.T) {
t.Parallel()
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "/")
_, err := f.UpdateOrderbook(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
cp = currency.NewPairWithDelimiter("ALGOBEAR", currency.USD.String(), "/")
_, err = f.UpdateOrderbook(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
cp = currency.NewPairWithDelimiter("ASDBEAR", currency.USD.String(), "/")
_, err = f.UpdateOrderbook(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "/")
_, err := f.UpdateTicker(context.Background(), cp, asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestUpdateTickers(t *testing.T) {
t.Parallel()
err := f.UpdateTickers(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
var orderReq order.GetOrdersRequest
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "/")
orderReq.Pairs = append(orderReq.Pairs, cp)
orderReq.AssetType = asset.Spot
_, err := f.GetActiveOrders(context.Background(), &orderReq)
if err != nil {
t.Fatal(err)
}
}
func TestGetOrderHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
var orderReq order.GetOrdersRequest
cp := currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "/")
orderReq.Pairs = append(orderReq.Pairs, cp)
orderReq.AssetType = asset.Spot
_, err := f.GetOrderHistory(context.Background(), &orderReq)
if err != nil {
t.Fatal(err)
}
}
func TestUpdateAccountHoldings(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.UpdateAccountInfo(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestFetchAccountInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.FetchAccountInfo(context.Background(), asset.Spot)
if err != nil {
t.Error(err)
}
}
func TestGetFee(t *testing.T) {
t.Parallel()
feeBuilder := &exchange.FeeBuilder{
PurchasePrice: 10,
Amount: 1,
IsMaker: true,
}
fee, err := f.GetFee(context.Background(), feeBuilder)
if err != nil {
t.Error(err)
}
if fee <= 0 {
t.Errorf("incorrect maker fee value")
}
feeBuilder.IsMaker = false
if fee, err = f.GetFee(context.Background(), feeBuilder); err != nil {
t.Error(err)
}
if fee <= 0 {
t.Errorf("incorrect maker fee value")
}
feeBuilder.FeeType = exchange.OfflineTradeFee
fee, err = f.GetFee(context.Background(), feeBuilder)
if err != nil {
t.Error(err)
}
if fee <= 0 {
t.Errorf("incorrect maker fee value")
}
feeBuilder.IsMaker = true
fee, err = f.GetFee(context.Background(), feeBuilder)
if err != nil {
t.Error(err)
}
if fee <= 0 {
t.Errorf("incorrect maker fee value")
}
}
func TestGetOfflineTradingFee(t *testing.T) {
t.Parallel()
var f exchange.FeeBuilder
f.PurchasePrice = 10
f.Amount = 1
f.IsMaker = true
fee := getOfflineTradeFee(&f)
if fee != 0.002 {
t.Errorf("incorrect offline maker fee")
}
f.IsMaker = false
fee = getOfflineTradeFee(&f)
if fee != 0.007 {
t.Errorf("incorrect offline taker fee")
}
}
func TestGetOrderStatus(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetOrderStatus(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetOrderStatusByClientID(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetOrderStatusByClientID(context.Background(), "testID")
if err != nil {
t.Error(err)
}
}
func TestRequestLTRedemption(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.RequestLTRedemption(context.Background(), "ETHBULL", 5)
if err != nil {
t.Error(err)
}
}
func TestWithdrawCryptocurrencyFunds(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
var request = new(withdraw.Request)
request.Amount = 5
request.Currency = currency.NewCode("FTT")
var cryptoData withdraw.CryptoRequest
cryptoData.Address = "testaddress123"
cryptoData.AddressTag = "testtag123"
request.Crypto = cryptoData
request.OneTimePassword = 123456
request.TradePassword = "incorrectTradePassword"
_, err := f.WithdrawCryptocurrencyFunds(context.Background(), request)
if err != nil {
t.Error(err)
}
}
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetDepositAddress(context.Background(), currency.NewCode("BTC"), "", "")
if err != nil {
t.Error(err)
}
}
func TestGetFundingHistory(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetFundingHistory(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC/USD")
if err != nil {
t.Fatal(err)
}
start := time.Date(2019, 11, 12, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 2)
_, err = f.GetHistoricCandles(context.Background(),
currencyPair, asset.Spot, start, end, kline.OneDay)
if err != nil {
t.Fatal(err)
}
}
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
currencyPair, err := currency.NewPairFromString("BTC/USD")
if err != nil {
t.Fatal(err)
}
start := time.Date(2019, 11, 12, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 0, 2)
_, err = f.GetHistoricCandlesExtended(context.Background(),
currencyPair, asset.Spot, start, end, kline.OneDay)
if err != nil {
t.Fatal(err)
}
}
func TestGetOTCQuoteStatus(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetOTCQuoteStatus(context.Background(), spotPairStr, "1")
if err != nil {
t.Error(err)
}
}
func TestRequestForQuotes(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.RequestForQuotes(context.Background(),
currency.NewCode("BtC"), currency.NewCode("UsD"), 0.5)
if err != nil {
t.Error(err)
}
}
func TestAcceptOTCQuote(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
err := f.AcceptOTCQuote(context.Background(), "1031")
if err != nil {
t.Error(err)
}
}
func TestGetHistoricTrades(t *testing.T) {
t.Parallel()
assets := f.GetAssetTypes(false)
for i := range assets {
enabledPairs, err := f.GetEnabledPairs(assets[i])
if err != nil {
t.Fatal(err)
}
_, err = f.GetHistoricTrades(context.Background(),
enabledPairs.GetRandomPair(),
assets[i],
time.Now().Add(-time.Minute*15),
time.Now())
if err != nil {
t.Error(err)
}
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
assets := f.GetAssetTypes(false)
for i := range assets {
enabledPairs, err := f.GetEnabledPairs(assets[i])
if err != nil {
t.Fatal(err)
}
_, err = f.GetRecentTrades(context.Background(),
enabledPairs.GetRandomPair(), assets[i])
if err != nil {
t.Error(err)
}
}
}
func TestTimestampFromFloat64(t *testing.T) {
t.Parallel()
constTime := 1592697600.0
checkTime := time.Date(2020, time.June, 21, 0, 0, 0, 0, time.UTC)
timeConst := timestampFromFloat64(constTime)
if timeConst != checkTime {
t.Error("invalid time conversion")
}
}
func TestCompatibleOrderVars(t *testing.T) {
t.Parallel()
orderVars, err := f.compatibleOrderVars(context.Background(),
"buy",
"closed",
"limit",
0.5,
0.5,
9500)
if err != nil {
t.Error(err)
}
if orderVars.Side != order.Buy {
t.Errorf("received %v expected %v", orderVars.Side, order.Buy)
}
if orderVars.OrderType != order.Limit {
t.Errorf("received %v expected %v", orderVars.OrderType, order.Limit)
}
if orderVars.Status != order.Filled {
t.Errorf("received %v expected %v", orderVars.Status, order.Filled)
}
orderVars, err = f.compatibleOrderVars(context.Background(),
"buy",
"closed",
"limit",
0,
0,
9500)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
if orderVars.Status != order.Cancelled {
t.Errorf("received %v expected %v", orderVars.Status, order.Cancelled)
}
orderVars, err = f.compatibleOrderVars(context.Background(),
"buy",
"closed",
"limit",
0.5,
0.2,
9500)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
if orderVars.Status != order.PartiallyCancelled {
t.Errorf("received %v expected %v", orderVars.Status, order.PartiallyCancelled)
}
orderVars, err = f.compatibleOrderVars(context.Background(),
"sell",
"closed",
"limit",
1337,
1337,
9500)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
if orderVars.Status != order.Filled {
t.Errorf("received %v expected %v", orderVars.Status, order.Filled)
}
_, err = f.compatibleOrderVars(context.Background(),
"buy",
"closed",
"limit",
0.1,
0.2,
9500)
if !errors.Is(err, errInvalidOrderAmounts) {
t.Errorf("received %v expected %v", err, errInvalidOrderAmounts)
}
_, err = f.compatibleOrderVars(context.Background(),
"buy",
"fake",
"limit",
0.3,
0.2,
9500)
if !errors.Is(err, errUnrecognisedOrderStatus) {
t.Errorf("received %v expected %v", err, errUnrecognisedOrderStatus)
}
orderVars, err = f.compatibleOrderVars(context.Background(),
"buy",
"new",
"limit",
0.3,
0.2,
9500)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
if orderVars.Status != order.New {
t.Errorf("received %v expected %v", orderVars.Status, order.New)
}
orderVars, err = f.compatibleOrderVars(context.Background(),
"buy",
"open",
"limit",
0.3,
0.2,
9500)
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
if orderVars.Status != order.Open {
t.Errorf("received %v expected %v", orderVars.Status, order.Open)
}
}
func TestGetIndexWeights(t *testing.T) {
t.Parallel()
_, err := f.GetIndexWeights(context.Background(), "SHIT")
if err != nil {
t.Error(err)
}
}
func TestModifyPlacedOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.ModifyPlacedOrder(context.Background(), "1234", "", -0.1, 0.1)
if err != nil {
t.Error(err)
}
}
func TestModifyOrderByClientID(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.ModifyOrderByClientID(context.Background(), "1234", "", -0.1, 0.1)
if err != nil {
t.Error(err)
}
}
func TestModifyTriggerOrder(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.ModifyTriggerOrder(context.Background(),
"1234", "stop", -0.1, 0.1, 0.02, 0)
if err != nil {
t.Error(err)
}
}
func TestGetSubaccounts(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err := f.GetSubaccounts(context.Background())
if err != nil {
t.Error(err)
}
}
func TestCreateSubaccount(t *testing.T) {
t.Parallel()
_, err := f.CreateSubaccount(context.Background(), "")
if !errors.Is(err, errSubaccountNameMustBeSpecified) {
t.Errorf("expected %v, but received: %s", errSubaccountNameMustBeSpecified, err)
}
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
_, err = f.CreateSubaccount(context.Background(), "subzero")
if err != nil {
t.Fatal(err)
}
if err = f.DeleteSubaccount(context.Background(), "subzero"); err != nil {
t.Error(err)
}
}
func TestUpdateSubaccountName(t *testing.T) {
t.Parallel()
_, err := f.UpdateSubaccountName(context.Background(), "", "")
if !errors.Is(err, errSubaccountUpdateNameInvalid) {
t.Errorf("expected %v, but received: %s", errSubaccountUpdateNameInvalid, err)
}
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
_, err = f.CreateSubaccount(context.Background(), "subzero")
if err != nil {
t.Fatal(err)
}
_, err = f.UpdateSubaccountName(context.Background(), "subzero", "bizzlebot")
if err != nil {
t.Fatal(err)
}
if err := f.DeleteSubaccount(context.Background(), "bizzlebot"); err != nil {
t.Error(err)
}
}
func TestDeleteSubaccountName(t *testing.T) {
t.Parallel()
if err := f.DeleteSubaccount(context.Background(), ""); !errors.Is(err, errSubaccountNameMustBeSpecified) {
t.Errorf("expected %v, but received: %s", errSubaccountNameMustBeSpecified, err)
}
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
_, err := f.CreateSubaccount(context.Background(), "subzero")
if err != nil {
t.Fatal(err)
}
if err := f.DeleteSubaccount(context.Background(), "subzero"); err != nil {
t.Error(err)
}
}
func TestSubaccountBalances(t *testing.T) {
t.Parallel()
_, err := f.SubaccountBalances(context.Background(), "")
if !errors.Is(err, errSubaccountNameMustBeSpecified) {
t.Errorf("expected %s, but received: %s", errSubaccountNameMustBeSpecified, err)
}
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err = f.SubaccountBalances(context.Background(), "non-existent")
if err == nil {
t.Error("expecting non-existent subaccount to return an error")
}
_, err = f.CreateSubaccount(context.Background(), "subzero")
if err != nil {
t.Fatal(err)
}
_, err = f.SubaccountBalances(context.Background(), "subzero")
if err != nil {
t.Error(err)
}
if err := f.DeleteSubaccount(context.Background(), "subzero"); err != nil {
t.Error(err)
}
}
func TestSubaccountTransfer(t *testing.T) {
t.Parallel()
tt := []struct {
Coin currency.Code
Source string
Destination string
Size float64
ErrExpected error
}{
{ErrExpected: errCoinMustBeSpecified},
{Coin: currency.BTC, ErrExpected: errSubaccountTransferSizeGreaterThanZero},
{Coin: currency.BTC, Size: 420, ErrExpected: errSubaccountTransferSourceDestinationMustNotBeEqual},
}
for x := range tt {
_, err := f.SubaccountTransfer(context.Background(),
tt[x].Coin, tt[x].Source, tt[x].Destination, tt[x].Size)
if !errors.Is(err, tt[x].ErrExpected) {
t.Errorf("expected %s, but received: %s", tt[x].ErrExpected, err)
}
}
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
_, err := f.SubaccountTransfer(context.Background(),
currency.BTC, "", "test", 0.1)
if err != nil {
t.Error(err)
}
}
func TestGetStakes(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err := f.GetStakes(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetUnstakeRequests(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err := f.GetUnstakeRequests(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetStakeBalances(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err := f.GetStakeBalances(context.Background())
if err != nil {
t.Error(err)
}
}
func TestUnstakeRequest(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
r, err := f.UnstakeRequest(context.Background(), currency.FTT, 0.1)
if err != nil {
t.Fatal(err)
}
success, err := f.CancelUnstakeRequest(context.Background(), r.ID)
if err != nil || !success {
t.Errorf("unable to cancel unstaking request: %s", err)
}
}
func TestCancelUnstakeRequest(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
_, err := f.CancelUnstakeRequest(context.Background(), 74351)
if err != nil {
t.Error(err)
}
}
func TestGetStakingRewards(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
_, err := f.GetStakingRewards(context.Background())
if err != nil {
t.Error(err)
}
}
func TestStakeRequest(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() || !canManipulateRealOrders {
t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set")
}
// WARNING: This will lock up your funds for 14 days
_, err := f.StakeRequest(context.Background(), currency.FTT, 0.1)
if err != nil {
t.Error(err)
}
}
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
err := f.UpdateOrderExecutionLimits(context.Background(), asset.Empty)
if err != nil {
t.Fatal(err)
}
cp := currency.NewPair(currency.BTC, currency.USD)
limit, err := f.GetOrderExecutionLimits(asset.Spot, cp)
if err != nil {
t.Fatal(err)
}
err = limit.Conforms(33000, 0.00001, order.Limit)
if !errors.Is(err, order.ErrAmountBelowMin) {
t.Fatalf("expected error %v but received %v",
order.ErrAmountBelowMin,
err)
}
err = limit.Conforms(33000, 0.0001, order.Limit)
if !errors.Is(err, nil) {
t.Fatalf("expected error %v but received %v",
nil,
err)
}
}
func TestScaleCollateral(t *testing.T) {
t.Parallel()
result, err := f.ScaleCollateral(
context.Background(),
&order.CollateralCalculator{
CollateralCurrency: currency.USDT,
Asset: asset.Spot,
Side: order.Buy,
CalculateOffline: true,
FreeCollateral: decimal.NewFromInt(100000),
USDPrice: decimal.NewFromFloat(1.0003),
})
if err != nil {
t.Error(err)
}
expectedUSDValue := decimal.NewFromFloat(97529.25)
if !result.CollateralContribution.Equal(expectedUSDValue) {
t.Errorf("received %v expected %v", result.CollateralContribution, expectedUSDValue)
}
if !areTestAPIKeysSet() {
return
}
accountInfo, err := f.GetAccountInfo(context.Background())
if err != nil {
t.Error(err)
}
walletInfo, err := f.GetBalances(context.Background(), true, true)
if err != nil {
t.Error(err)
}
localScaling := 0.0
var coverageTested bool
for i := range walletInfo {
coin := walletInfo[i].Coin
if coin.Equal(currency.USD) {
localScaling += walletInfo[i].Total
continue
}
var tick MarketData
usdPrice := walletInfo[i].USDValue / walletInfo[i].Total
tick, err = f.GetMarket(context.Background(), currency.NewPairWithDelimiter(coin.String(), "usd", "/").String())
if err != nil {
if walletInfo[i].USDValue == 0 {
// sometimes spot market for currency/USD doesn't exist and has no value, skip
continue
}
t.Logf("using wallet USD price for %v - %v", coin, usdPrice)
} else {
usdPrice = tick.Price
}
var offlineScaledCollateral *order.CollateralByCurrency
offlineScaledCollateral, err = f.ScaleCollateral(
context.Background(),
&order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
FreeCollateral: decimal.NewFromFloat(walletInfo[i].Total),
USDPrice: decimal.NewFromFloat(usdPrice),
CalculateOffline: true,
})
if err != nil {
if errors.Is(err, errCollateralCurrencyNotFound) ||
errors.Is(err, order.ErrUSDValueRequired) {
continue
}
t.Error(err)
}
localScaling += offlineScaledCollateral.CollateralContribution.InexactFloat64()
if !coverageTested {
_, err = f.ScaleCollateral(context.Background(),
&order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
FreeCollateral: decimal.NewFromFloat(walletInfo[i].Total),
USDPrice: decimal.NewFromFloat(usdPrice),
IsForNewPosition: true,
CalculateOffline: true,
})
if err != nil {
t.Error(err)
}
_, err = f.ScaleCollateral(context.Background(),
&order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
FreeCollateral: decimal.NewFromFloat(walletInfo[i].Total),
IsLiquidating: true,
CalculateOffline: true,
})
if !errors.Is(err, order.ErrUSDValueRequired) {
t.Errorf("received '%v' expected '%v'", err, order.ErrUSDValueRequired)
}
_, err = f.ScaleCollateral(
context.Background(),
&order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
})
if err != nil {
t.Error(err)
}
coverageTested = true
}
}
if accountInfo.Collateral == 0 {
return
}
if (math.Abs((localScaling-accountInfo.Collateral)/accountInfo.Collateral) * 100) > 5 {
t.Errorf("collateral scaling less than 95%% accurate, received '%v'/%v%% expected roughly '%v'", localScaling, math.Abs((localScaling-accountInfo.Collateral)/accountInfo.Collateral)*100, accountInfo.Collateral)
}
}
func TestCalculateTotalCollateral(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
walletInfo, err := f.GetBalances(context.Background(), true, true)
if err != nil {
t.Error(err)
}
scales := make([]order.CollateralCalculator, len(walletInfo))
for i := range walletInfo {
coin := walletInfo[i].Coin
if coin.Equal(currency.USD) {
total := decimal.NewFromFloat(walletInfo[i].Total)
scales[i] = order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
FreeCollateral: total,
CalculateOffline: true,
}
continue
}
var tick MarketData
tick, err = f.GetMarket(context.Background(), currency.NewPairWithDelimiter(coin.String(), "usd", "/").String())
if err != nil {
// some assumed markets don't exist, just don't process them
continue
}
if tick.Price == 0 {
continue
}
scales[i] = order.CollateralCalculator{
CollateralCurrency: coin,
Asset: asset.Spot,
Side: order.Buy,
FreeCollateral: decimal.NewFromFloat(walletInfo[i].Total),
USDPrice: decimal.NewFromFloat(tick.Price),
CalculateOffline: true,
}
}
calc := &order.TotalCollateralCalculator{
CollateralAssets: scales,
FetchPositions: false,
CalculateOffline: true,
}
total, err := f.CalculateTotalCollateral(context.Background(), calc)
if err != nil {
t.Fatal(err)
}
localScaling := total.AvailableCollateral.InexactFloat64()
accountInfo, err := f.GetAccountInfo(context.Background())
if err != nil {
t.Error(err)
}
if accountInfo.Collateral != 0 && (math.Abs((localScaling-accountInfo.Collateral)/accountInfo.Collateral)*100) > 5 {
t.Errorf("collateral scaling less than 95%% accurate, received '%v' expected roughly '%v'", localScaling, accountInfo.Collateral)
}
for i := range scales {
scales[i].CalculateOffline = false
}
calc.CalculateOffline = false
_, err = f.CalculateTotalCollateral(context.Background(), calc)
if err != nil {
t.Error(err)
}
}
func TestCalculateTotalCollateralOnline(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
// nil data
_, err := f.calculateTotalCollateralOnline(context.Background(), nil, nil)
if !errors.Is(err, common.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer)
}
calc := &order.TotalCollateralCalculator{}
// no currency data
_, err = f.calculateTotalCollateralOnline(context.Background(), calc, nil)
if !errors.Is(err, errCollateralCurrencyNotFound) {
t.Errorf("received '%v' expected '%v'", err, errCollateralCurrencyNotFound)
}
calc.CalculateOffline = true
calc.CollateralAssets = []order.CollateralCalculator{
{
CollateralCurrency: currency.BTC,
},
{
CollateralCurrency: currency.USD,
},
}
// offline true
_, err = f.calculateTotalCollateralOnline(context.Background(), calc, nil)
if !errors.Is(err, order.ErrOfflineCalculationSet) {
t.Errorf("received '%v' expected '%v'", err, order.ErrOfflineCalculationSet)
}
calc.CalculateOffline = false
calc.CollateralAssets[0].CalculateOffline = true
// offline true for individual currency
_, err = f.calculateTotalCollateralOnline(context.Background(), calc, nil)
if !errors.Is(err, order.ErrOfflineCalculationSet) {
t.Errorf("received '%v' expected '%v'", err, order.ErrOfflineCalculationSet)
}
// successful run
calc.CollateralAssets[0].CalculateOffline = false
result, err := f.calculateTotalCollateralOnline(context.Background(), calc, nil)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if !result.CollateralCurrency.Equal(currency.USD) {
t.Error("expected USD collateral currency")
}
curr, err := currency.NewPairFromString("BTC-PERP")
if !errors.Is(err, nil) {
t.Fatalf("received '%v' expected '%v'", err, nil)
}
// with position data
pos := []PositionData{
{
CollateralUsed: 5,
Future: curr,
UnrealizedPNL: 10,
},
}
_, err = f.calculateTotalCollateralOnline(context.Background(), calc, pos)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
calc.CollateralAssets = []order.CollateralCalculator{
{
CollateralCurrency: currency.BURST,
},
}
// irrelevant currency
result, err = f.calculateTotalCollateralOnline(context.Background(), calc, pos)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if !result.UnrealisedPNL.IsZero() {
t.Error("expected zero")
}
}
func TestCalculatePNL(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
pair := currency.NewPair(currency.BTC, currency.NewCode("20211231"))
positions, err := f.GetFuturesPositions(context.Background(), &order.PositionsRequest{
Asset: asset.Futures,
Pairs: currency.Pairs{pair},
StartDate: time.Date(2021, 1, 6, 4, 28, 0, 0, time.UTC),
})
if err != nil {
t.Error(err)
}
if len(positions) != 1 {
t.Fatal("expected 1 position")
}
orders := make([]order.Detail, len(positions))
for i := range positions[0].Orders {
orders[i] = order.Detail{
Side: positions[0].Orders[i].Side,
Pair: pair,
OrderID: positions[0].Orders[i].OrderID,
Price: positions[0].Orders[i].Price,
Amount: positions[0].Orders[i].Amount,
AssetType: asset.Futures,
Exchange: f.Name,
Fee: positions[0].Orders[i].Fee,
Date: positions[0].Orders[i].Date,
}
}
exch := f.Name
item := asset.Futures
setup := &order.MultiPositionTrackerSetup{
Exchange: exch,
Asset: item,
Pair: pair,
Underlying: pair.Base,
UseExchangePNLCalculation: true,
ExchangePNLCalculation: &f,
}
p, err := order.SetupMultiPositionTracker(setup)
if err != nil {
t.Error(err)
}
for i := range orders {
err = p.TrackNewOrder(&orders[i])
if err != nil {
t.Error(err)
}
}
results := p.GetPositions()
if len(orders) > 0 && len(results) == 0 {
t.Error("expected position(s) to be generated")
}
}
func TestGetFuturesPositions(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("skipping test, api keys not set")
}
cp := currency.Pairs{currency.NewPair(currency.BTC, currency.PERP)}
start := time.Now().Add(-time.Hour * 24 * 365)
_, err := f.GetFuturesPositions(context.Background(), &order.PositionsRequest{
Asset: asset.Futures,
Pairs: cp,
StartDate: start,
})
if err != nil {
t.Error(err)
}
_, err = f.GetFuturesPositions(context.Background(), &order.PositionsRequest{
Asset: asset.Spot,
Pairs: cp,
StartDate: start,
})
if !errors.Is(err, order.ErrNotFuturesAsset) {
t.Errorf("received '%v' expected '%v'", err, order.ErrNotFuturesAsset)
}
}
func TestLoadCollateralWeightings(t *testing.T) {
t.Parallel()
ff := FTX{}
err := ff.LoadCollateralWeightings(context.Background())
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(ff.collateralWeight) == 0 {
t.Fatal("expected some weight")
}
if !ff.collateralWeight.hasData() {
t.Error("expected loaded weight")
}
if !areTestAPIKeysSet() {
return
}
err = f.LoadCollateralWeightings(context.Background())
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(f.collateralWeight) == 0 {
t.Fatal("expected some weight")
}
}
func TestLoadTotalIMF(t *testing.T) {
t.Parallel()
c := CollateralWeightHolder{}
c.loadTotal("BTC", 1)
if _, ok := c[currency.BTC.Item]; !ok {
t.Error("expected entry")
}
c.loadInitialMarginFraction("btc", 1)
cw, ok := c[currency.BTC.Item]
if !ok {
t.Error("expected entry")
}
if cw.Total != 1 {
t.Errorf("expected '1', received '%v'", cw.Total)
}
if cw.InitialMarginFractionFactor != 1 {
t.Errorf("expected '1', received '%v'", cw.InitialMarginFractionFactor)
}
}
func TestLoadCollateralWeight(t *testing.T) {
t.Parallel()
c := CollateralWeightHolder{}
c.load("DOGE", 1, 2, 3)
cw, ok := c[currency.DOGE.Item]
if !ok {
t.Fatal("expected loaded collateral weight")
}
if cw.Total != 1 {
t.Errorf("expected '1', received '%v'", cw.Total)
}
if cw.Initial != 2 {
t.Errorf("expected '2', received '%v'", cw.Initial)
}
if cw.InitialMarginFractionFactor != 3 {
t.Errorf("expected '3', received '%v'", cw.InitialMarginFractionFactor)
}
}
func TestCollateralWeightHasData(t *testing.T) {
t.Parallel()
c := CollateralWeightHolder{}
if c.hasData() {
t.Error("expected false")
}
c.load("test", 1, 2, 3)
if !c.hasData() {
t.Error("expected true")
}
}
func TestGetExpiredFutures(t *testing.T) {
t.Parallel()
_, err := f.GetExpiredFutures(context.Background())
if err != nil {
t.Error(err)
}
}
func TestGetExpiredFuture(t *testing.T) {
t.Parallel()
_, err := f.GetExpiredFuture(context.Background(), currency.NewPairWithDelimiter("BTC", "20211231", "-"))
if err != nil {
t.Error(err)
}
}
func TestGetCollateral(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetCollateral(context.Background(), false)
if err != nil {
t.Error(err)
}
_, err = f.GetCollateral(context.Background(), true)
if err != nil {
t.Error(err)
}
}
func TestGetMarginRatesHistory(t *testing.T) {
t.Parallel()
type testCase struct {
name string
request *margin.RateHistoryRequest
err error
requiresAuth bool
}
tests := []testCase{
{
name: "nil request",
request: nil,
err: common.ErrNilPointer,
},
{
name: "empty request",
request: &margin.RateHistoryRequest{},
err: currency.ErrCurrencyCodeEmpty,
},
{
name: "disabled currency request",
request: &margin.RateHistoryRequest{
Asset: asset.Futures,
Currency: currency.LUNA,
},
err: currency.ErrCurrencyNotFound,
},
{
name: "empty date request",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
},
err: common.ErrDateUnset,
},
{
name: "nice basic request",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
},
err: nil,
},
{
name: "include predicted rate",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetPredictedRate: true,
},
err: nil,
},
{
name: "include borrowed rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetBorrowRates: true,
},
err: nil,
requiresAuth: true,
},
{
name: "include predicted borrowed rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetBorrowRates: true,
GetPredictedRate: true,
},
err: nil,
requiresAuth: true,
},
{
name: "all you can eat",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 365 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetPredictedRate: true,
GetLendingPayments: true,
GetBorrowCosts: true,
},
err: nil,
requiresAuth: true,
},
{
name: "offline failure, no rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline failure, no fee for lending",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline failure, no fee for borrow",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
GetBorrowCosts: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline pass, lending w fee",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: nil,
},
{
name: "offline pass, borrow w fee",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetBorrowCosts: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
BorrowCost: margin.BorrowCost{Size: decimal.NewFromFloat(1337)},
},
},
},
err: nil,
},
{
name: "offline pass, lending size",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
LendingPayment: margin.LendingPayment{Size: decimal.NewFromFloat(1337)},
},
},
},
err: nil,
},
{
name: "offline failure, cannot predict offline",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
GetPredictedRate: true,
},
err: common.ErrCannotCalculateOffline,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if tt.requiresAuth && !areTestAPIKeysSet() {
t.Skip("requires auth")
}
_, err := f.GetMarginRatesHistory(context.Background(), tt.request)
if !errors.Is(err, tt.err) {
t.Errorf("receieved '%v' expected '%v'", err, tt.err)
}
})
}
if !areTestAPIKeysSet() {
return
}
// test offline calculation against real data
online, err := f.GetMarginRatesHistory(context.Background(), &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetBorrowCosts: true,
})
if !errors.Is(err, nil) {
t.Errorf("receieved '%v' expected '%v'", err, nil)
}
offline, err := f.GetMarginRatesHistory(context.Background(), &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetBorrowCosts: true,
Rates: online.Rates,
TakeFeeRate: online.TakerFeeRate,
CalculateOffline: true,
})
if !errors.Is(err, nil) {
t.Errorf("receieved '%v' expected '%v'", err, nil)
}
if !online.Rates[0].BorrowCost.Cost.Equal(offline.Rates[0].BorrowCost.Cost) {
t.Errorf("expected '%v' received '%v'", online.Rates[0].BorrowCost.Cost, offline.Rates[0].BorrowCost.Cost)
}
}
func TestGetPositionSummary(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
positions, err := f.GetFuturesPositions(
context.Background(),
&order.PositionsRequest{
Asset: asset.Futures,
Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.NewCode("PERP"))},
StartDate: time.Now().Add(-time.Hour * 24 * 365),
})
if err != nil {
t.Error(err)
}
if len(positions) == 0 {
t.Skip("no positions to get summary")
}
if len(positions) != 1 {
t.Fatal("expected 1 position")
}
if len(positions[0].Orders) == 0 {
t.Skip("no positions to get summary")
}
onlineCalculation, err := f.GetPositionSummary(context.Background(), &order.PositionSummaryRequest{Asset: asset.Futures, Pair: positions[0].Pair})
if err != nil {
t.Error(err)
}
if onlineCalculation.CurrentSize.IsZero() {
// you have no positions to calculate offline summary for
return
}
acc, err := f.GetAccountInfo(context.Background())
if err != nil {
t.Error(err)
}
size := decimal.NewFromFloat(positions[0].Orders[0].Amount)
underlyingStr, err := f.FormatSymbol(currency.NewPair(currency.BTC, currency.USD), asset.Spot)
if err != nil {
t.Error(err)
}
underlying, err := f.GetMarket(context.Background(), underlyingStr)
if err != nil {
t.Error(err)
}
offlineCalculation, err := f.GetPositionSummary(context.Background(), &order.PositionSummaryRequest{
Asset: asset.Futures,
Pair: positions[0].Pair,
CalculateOffline: true,
Direction: positions[0].Orders[0].Side,
FreeCollateral: decimal.NewFromFloat(acc.FreeCollateral),
TotalCollateral: decimal.NewFromFloat(acc.Collateral),
OpeningPrice: decimal.NewFromFloat(positions[0].Orders[0].Price),
CurrentPrice: onlineCalculation.MarkPrice,
OpeningSize: size,
CurrentSize: size,
CollateralUsed: onlineCalculation.CollateralUsed,
NotionalPrice: decimal.NewFromFloat(underlying.Last),
Leverage: decimal.NewFromFloat(acc.Leverage),
MaxLeverageForAccount: decimal.NewFromFloat(acc.Leverage),
TotalAccountValue: decimal.NewFromFloat(acc.TotalAccountValue),
TotalOpenPositionNotional: decimal.NewFromFloat(acc.TotalPositionSize),
})
if err != nil {
t.Error(err)
}
if !onlineCalculation.MaintenanceMarginRequirement.Equal(offlineCalculation.MaintenanceMarginRequirement) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.MaintenanceMarginRequirement, offlineCalculation.MaintenanceMarginRequirement)
}
if !onlineCalculation.MarkPrice.Equal(offlineCalculation.MarkPrice) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.MarkPrice, offlineCalculation.MarkPrice)
}
if !onlineCalculation.CurrentSize.Equal(offlineCalculation.CurrentSize) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.CurrentSize, offlineCalculation.CurrentSize)
}
if !onlineCalculation.TotalCollateral.Equal(offlineCalculation.TotalCollateral) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.TotalCollateral, offlineCalculation.TotalCollateral)
}
if !onlineCalculation.FreeCollateral.Equal(offlineCalculation.FreeCollateral) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.FreeCollateral, offlineCalculation.FreeCollateral)
}
if !onlineCalculation.MarginFraction.Equal(offlineCalculation.MarginFraction) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.MarginFraction, offlineCalculation.MarginFraction)
}
if !onlineCalculation.RecentPNL.Equal(offlineCalculation.RecentPNL) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.RecentPNL, offlineCalculation.RecentPNL)
}
if !onlineCalculation.AverageOpenPrice.Equal(offlineCalculation.AverageOpenPrice) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.AverageOpenPrice, offlineCalculation.AverageOpenPrice)
}
if !onlineCalculation.BreakEvenPrice.Equal(offlineCalculation.BreakEvenPrice) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.BreakEvenPrice, offlineCalculation.BreakEvenPrice)
}
if !onlineCalculation.CollateralUsed.Equal(offlineCalculation.CollateralUsed) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.CollateralUsed, offlineCalculation.CollateralUsed)
}
if !onlineCalculation.InitialMarginRequirement.Equal(offlineCalculation.InitialMarginRequirement) {
t.Errorf("expected '%v' received '%v'", onlineCalculation.InitialMarginRequirement, offlineCalculation.InitialMarginRequirement)
}
}
func TestGetFundingRates(t *testing.T) {
t.Parallel()
_, err := f.GetFundingRates(context.Background(), nil)
if !errors.Is(err, common.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer)
}
request := &order.FundingRatesRequest{}
_, err = f.GetFundingRates(context.Background(), request)
if !errors.Is(err, currency.ErrCurrencyPairsEmpty) {
t.Errorf("received '%v' expected '%v'", err, currency.ErrCurrencyPairsEmpty)
}
request.Pairs = currency.Pairs{
currency.NewPair(currency.DOGE, currency.USD),
}
_, err = f.GetFundingRates(context.Background(), request)
if !errors.Is(err, common.ErrDateUnset) {
t.Errorf("received '%v' expected '%v'", err, common.ErrDateUnset)
}
request.StartDate = time.Now().Add(-time.Hour * 24 * 31)
request.EndDate = time.Now()
request.Asset = asset.Spot
_, err = f.GetFundingRates(context.Background(), request)
if !errors.Is(err, currency.ErrPairNotFound) {
t.Errorf("received '%v' expected '%v'", err, currency.ErrPairNotFound)
}
request.Pairs = currency.Pairs{
currency.NewPair(currency.BTC, currency.PERP),
}
request.Asset = asset.Futures
_, err = f.GetFundingRates(context.Background(), request)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if !areTestAPIKeysSet() {
return
}
request.IncludePayments = true
request.IncludePredictedRate = true
resp, err := f.GetFundingRates(context.Background(), request)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(resp) != 1 {
t.Error("expected one result")
}
if resp[0].PredictedUpcomingRate.Time.IsZero() {
t.Error("expected predicted rates")
}
if resp[0].PaymentSum.IsZero() {
t.Log("expected payments, but you may not have had a position open, so not a failure")
}
}
func TestIsPerpetualFutureCurrency(t *testing.T) {
t.Parallel()
cp1 := currency.NewPair(currency.BTC, currency.USD)
cp2 := currency.NewPair(currency.BTC, currency.PERP)
result, err := f.IsPerpetualFutureCurrency(asset.Spot, cp1)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if result {
t.Error("expected false")
}
result, err = f.IsPerpetualFutureCurrency(asset.Spot, cp1)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if result {
t.Error("expected false")
}
_, err = f.IsPerpetualFutureCurrency(asset.Spot, cp2)
if !errors.Is(err, currency.ErrPairNotFound) {
t.Errorf("received '%v' expected '%v'", err, currency.ErrPairNotFound)
}
_, err = f.IsPerpetualFutureCurrency(asset.Spot, cp2)
if !errors.Is(err, currency.ErrPairNotFound) {
t.Errorf("received '%v' expected '%v'", err, currency.ErrPairNotFound)
}
_, err = f.IsPerpetualFutureCurrency(asset.Futures, cp1)
if !errors.Is(err, currency.ErrPairNotFound) {
t.Errorf("received '%v' expected '%v'", err, currency.ErrPairNotFound)
}
result, err = f.IsPerpetualFutureCurrency(asset.Futures, cp2)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if !result {
t.Error("expected true")
}
}
func TestGetFundingPayments(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.getFundingPayments(context.Background(), time.Time{}, time.Time{}, currency.EMPTYPAIR, -1)
if err != nil {
t.Error(err)
}
cp, err := currency.NewPairFromString("BTC-PERP")
if err != nil {
t.Fatal(err)
}
startDate := time.Now().Add(-time.Hour * 24 * 31)
endDate := time.Now()
_, err = f.getFundingPayments(context.Background(), startDate, endDate, cp, 100)
if err != nil {
t.Error(err)
}
_, err = f.getFundingPayments(context.Background(), time.Unix(authEndTime, 0), time.Unix(authStartTime, 0), cp, -1)
if err != errStartTimeCannotBeAfterEndTime {
t.Errorf("should have thrown errStartTimeCannotBeAfterEndTime, got %v", err)
}
}