diff --git a/backtester/backtest/backtest.go b/backtester/backtest/backtest.go index ae760ed2..ffae1c2a 100644 --- a/backtester/backtest/backtest.go +++ b/backtester/backtest/backtest.go @@ -652,6 +652,9 @@ func loadLiveData(cfg *config.Config, base *gctexchange.Base) error { if cfg.DataSettings.LiveData.API2FAOverride != "" { base.API.Credentials.PEMKey = cfg.DataSettings.LiveData.API2FAOverride } + if cfg.DataSettings.LiveData.APISubaccountOverride != "" { + base.API.Credentials.Subaccount = cfg.DataSettings.LiveData.APISubaccountOverride + } validated := base.ValidateAPICredentials() base.API.AuthenticatedSupport = validated if !validated && cfg.DataSettings.LiveData.RealOrders { diff --git a/backtester/backtest/backtest_test.go b/backtester/backtest/backtest_test.go index c5a0892f..cd48a9f7 100644 --- a/backtester/backtest/backtest_test.go +++ b/backtester/backtest/backtest_test.go @@ -228,11 +228,12 @@ func TestLoadData(t *testing.T) { } cfg.DataSettings.CSVData = nil cfg.DataSettings.LiveData = &config.LiveData{ - APIKeyOverride: "test", - APISecretOverride: "test", - APIClientIDOverride: "test", - API2FAOverride: "test", - RealOrders: true, + APIKeyOverride: "test", + APISecretOverride: "test", + APIClientIDOverride: "test", + API2FAOverride: "test", + APISubaccountOverride: "test", + RealOrders: true, } _, err = bt.loadData(cfg, exch, cp, asset.Spot) if err != nil { @@ -305,10 +306,11 @@ func TestLoadLiveData(t *testing.T) { AuthenticatedWebsocketSupport: false, PEMKeySupport: false, Credentials: struct { - Key string - Secret string - ClientID string - PEMKey string + Key string + Secret string + ClientID string + PEMKey string + Subaccount string }{}, CredentialsValidator: struct { RequiresPEM bool @@ -344,6 +346,7 @@ func TestLoadLiveData(t *testing.T) { cfg.DataSettings.LiveData.APISecretOverride = "1234" cfg.DataSettings.LiveData.APIClientIDOverride = "1234" cfg.DataSettings.LiveData.API2FAOverride = "1234" + cfg.DataSettings.LiveData.APISubaccountOverride = "1234" err = loadLiveData(cfg, b) if err != nil { t.Error(err) diff --git a/backtester/config/README.md b/backtester/config/README.md index 41c75044..5d80d84e 100644 --- a/backtester/config/README.md +++ b/backtester/config/README.md @@ -127,6 +127,7 @@ See below for a set of tables and fields, expected values and what they can do | APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | | APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | | API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` | +| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | | RealOrders | Whether to place real orders. You really should never consider using this. Ever ever. | `true` | ##### Leverage Settings diff --git a/backtester/config/config_test.go b/backtester/config/config_test.go index a928f2a9..a8b16343 100644 --- a/backtester/config/config_test.go +++ b/backtester/config/config_test.go @@ -117,11 +117,12 @@ func TestPrintSettings(t *testing.T) { FullPath: "fake", }, LiveData: &LiveData{ - APIKeyOverride: "", - APISecretOverride: "", - APIClientIDOverride: "", - API2FAOverride: "", - RealOrders: false, + APIKeyOverride: "", + APISecretOverride: "", + APIClientIDOverride: "", + API2FAOverride: "", + APISubaccountOverride: "", + RealOrders: false, }, DatabaseData: &DatabaseData{ StartDate: startDate, @@ -532,11 +533,12 @@ func TestGenerateConfigForDCALiveCandles(t *testing.T) { Interval: kline.OneHour.Duration(), DataType: common.CandleStr, LiveData: &LiveData{ - APIKeyOverride: "", - APISecretOverride: "", - APIClientIDOverride: "", - API2FAOverride: "", - RealOrders: false, + APIKeyOverride: "", + APISecretOverride: "", + APIClientIDOverride: "", + API2FAOverride: "", + APISubaccountOverride: "", + RealOrders: false, }, }, PortfolioSettings: PortfolioSettings{ diff --git a/backtester/config/config_types.go b/backtester/config/config_types.go index 046cedf9..6bd2bee0 100644 --- a/backtester/config/config_types.go +++ b/backtester/config/config_types.go @@ -131,9 +131,10 @@ type DatabaseData struct { // LiveData defines all fields to configure live data type LiveData struct { - APIKeyOverride string `json:"api-key-override"` - APISecretOverride string `json:"api-secret-override"` - APIClientIDOverride string `json:"api-client-id-override"` - API2FAOverride string `json:"api-2fa-override"` - RealOrders bool `json:"real-orders"` + APIKeyOverride string `json:"api-key-override"` + APISecretOverride string `json:"api-secret-override"` + APIClientIDOverride string `json:"api-client-id-override"` + API2FAOverride string `json:"api-2fa-override"` + APISubaccountOverride string `json:"api-subaccount-override"` + RealOrders bool `json:"real-orders"` } diff --git a/backtester/config/configbuilder/main.go b/backtester/config/configbuilder/main.go index 58ecea5b..da54c8a8 100644 --- a/backtester/config/configbuilder/main.go +++ b/backtester/config/configbuilder/main.go @@ -426,13 +426,15 @@ func parseLive(reader *bufio.Reader, cfg *config.Config) { input = quickParse(reader) if input == y || input == yes { fmt.Println("What is the API key?") - cfg.DataSettings.DatabaseData.ConfigOverride.Database = quickParse(reader) + cfg.DataSettings.LiveData.APIKeyOverride = quickParse(reader) fmt.Println("What is the API secret?") - cfg.DataSettings.DatabaseData.ConfigOverride.Database = quickParse(reader) + cfg.DataSettings.LiveData.APISecretOverride = quickParse(reader) fmt.Println("What is the Client ID?") - cfg.DataSettings.DatabaseData.ConfigOverride.Database = quickParse(reader) + cfg.DataSettings.LiveData.APIClientIDOverride = quickParse(reader) fmt.Println("What is the 2FA seed?") - cfg.DataSettings.DatabaseData.ConfigOverride.Database = quickParse(reader) + cfg.DataSettings.LiveData.API2FAOverride = quickParse(reader) + fmt.Println("What is the subaccount to use?") + cfg.DataSettings.LiveData.APISubaccountOverride = quickParse(reader) } } } diff --git a/cmd/documentation/backtester_templates/backtester_config_readme.tmpl b/cmd/documentation/backtester_templates/backtester_config_readme.tmpl index e2c2d97b..8f7a70c3 100644 --- a/cmd/documentation/backtester_templates/backtester_config_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_config_readme.tmpl @@ -109,6 +109,7 @@ See below for a set of tables and fields, expected values and what they can do | APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | | APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | | API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` | +| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | | RealOrders | Whether to place real orders. You really should never consider using this. Ever ever. | `true` | ##### Leverage Settings diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 4979d558..52ab56dc 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -227,6 +227,10 @@ func setExchangeAPIKeys(name string, keys map[string]*config.APICredentialsConfi if keys[lowerExchangeName].OTPSecret != "-" { base.Config.API.Credentials.OTPSecret = keys[lowerExchangeName].OTPSecret } + if keys[lowerExchangeName].Subaccount != "" { + base.API.Credentials.Subaccount = keys[lowerExchangeName].Subaccount + base.Config.API.Credentials.Subaccount = keys[lowerExchangeName].Subaccount + } base.API.AuthenticatedSupport = true base.API.AuthenticatedWebsocketSupport = true diff --git a/cmd/exchange_wrapper_issues/types.go b/cmd/exchange_wrapper_issues/types.go index 5be0967b..156a1fa3 100644 --- a/cmd/exchange_wrapper_issues/types.go +++ b/cmd/exchange_wrapper_issues/types.go @@ -35,14 +35,6 @@ type Config struct { Exchanges map[string]*config.APICredentialsConfig `json:"exchanges"` } -// Key is the format for wrapperconfig.json to store API credentials -type Key struct { - APIKey string `json:"apiKey"` - APISecret string `json:"apiSecret,omitempty"` - ClientID string `json:"clientId,omitempty"` - OTPSecret string `json:"otpSecret,omitempty"` -} - // ExchangeResponses contains all responses // associated with an exchange type ExchangeResponses struct { diff --git a/config/config_types.go b/config/config_types.go index 9a966508..c55052ba 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -276,11 +276,12 @@ type APIEndpointsConfig struct { // APICredentialsConfig stores the API credentials type APICredentialsConfig struct { - Key string `json:"key,omitempty"` - Secret string `json:"secret,omitempty"` - ClientID string `json:"clientID,omitempty"` - PEMKey string `json:"pemKey,omitempty"` - OTPSecret string `json:"otpSecret,omitempty"` + Key string `json:"key,omitempty"` + Secret string `json:"secret,omitempty"` + ClientID string `json:"clientID,omitempty"` + Subaccount string `json:"subaccount,omitempty"` + PEMKey string `json:"pemKey,omitempty"` + OTPSecret string `json:"otpSecret,omitempty"` } // APICredentialsValidatorConfig stores the API credentials validator settings diff --git a/currency/code_types.go b/currency/code_types.go index 70fead58..2ce4f1b7 100644 --- a/currency/code_types.go +++ b/currency/code_types.go @@ -1661,4 +1661,6 @@ var ( CRV = NewCode("CRV") OXT = NewCode("OXT") BUSD = NewCode("BUSD") + SRM = NewCode("SRM") + FTT = NewCode("FTT") ) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index c04af054..b4b2c82e 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -551,6 +551,7 @@ func (b *Base) SetupDefaults(exch *config.ExchangeConfig) error { b.API.AuthenticatedSupport = exch.API.AuthenticatedSupport b.API.AuthenticatedWebsocketSupport = exch.API.AuthenticatedWebsocketSupport + b.API.Credentials.Subaccount = exch.API.Credentials.Subaccount if b.API.AuthenticatedSupport || b.API.AuthenticatedWebsocketSupport { b.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 76b0dacc..b2872478 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -184,10 +184,11 @@ type API struct { Endpoints *Endpoints Credentials struct { - Key string - Secret string - ClientID string - PEMKey string + Key string + Secret string + ClientID string + PEMKey string + Subaccount string } CredentialsValidator struct { diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index bbe915df..a3d25477 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -99,6 +99,7 @@ const ( // Margin Endpoints marginBorrowRates = "/spot_margin/borrow_rates" marginLendingRates = "/spot_margin/lending_rates" + marginLendingHistory = "/spot_margin/history" dailyBorrowedAmounts = "/spot_margin/borrow_summary" marginMarketInfo = "/spot_margin/market_info?market=%s" marginBorrowHistory = "/spot_margin/borrow_history" @@ -107,6 +108,13 @@ const ( marginLendingInfo = "/spot_margin/lending_info" submitLendingOrder = "/spot_margin/offers" + // Staking endpoints + stakes = "/staking/stakes" + unstakeRequests = "/staking/unstake_requests" + stakeBalances = "/staking/balances" + stakingRewards = "/staking/staking_rewards" + serumStakes = "/srm_stakes/stakes" + // Other Consts trailingStopOrderType = "trailingStop" takeProfitOrderType = "takeProfit" @@ -328,20 +336,62 @@ func (f *FTX) GetMarginMarketInfo(market string) ([]MarginMarketInfo, error) { return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, fmt.Sprintf(marginMarketInfo, market), nil, &r) } -// GetMarginBorrowHistory gets margin borrowing history -func (f *FTX) GetMarginBorrowHistory() ([]MarginTransactionHistoryData, error) { +// GetMarginBorrowHistory gets the margin borrow history data +func (f *FTX) GetMarginBorrowHistory(startTime, endTime time.Time) ([]MarginTransactionHistoryData, error) { r := struct { Data []MarginTransactionHistoryData `json:"result"` }{} - return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginBorrowHistory, nil, &r) + + params := url.Values{} + if !startTime.IsZero() && !endTime.IsZero() { + if startTime.After(endTime) { + return nil, errStartTimeCannotBeAfterEndTime + } + params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) + params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) + } + endpoint := common.EncodeURLValues(marginBorrowHistory, params) + return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &r) +} + +// GetMarginMarketLendingHistory gets the markets margin lending rate history +func (f *FTX) GetMarginMarketLendingHistory(coin currency.Code, startTime, endTime time.Time) ([]MarginTransactionHistoryData, error) { + r := struct { + Data []MarginTransactionHistoryData `json:"result"` + }{} + params := url.Values{} + if !coin.IsEmpty() { + params.Set("coin", coin.Upper().String()) + } + if !startTime.IsZero() && !endTime.IsZero() { + if startTime.After(endTime) { + return nil, errStartTimeCannotBeAfterEndTime + } + params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) + params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) + } + endpoint := common.EncodeURLValues(marginLendingHistory, params) + return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, params, &r) } // GetMarginLendingHistory gets margin lending history -func (f *FTX) GetMarginLendingHistory() ([]MarginTransactionHistoryData, error) { +func (f *FTX) GetMarginLendingHistory(coin currency.Code, startTime, endTime time.Time) ([]MarginTransactionHistoryData, error) { r := struct { Data []MarginTransactionHistoryData `json:"result"` }{} - return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendHistory, nil, &r) + params := url.Values{} + if !coin.IsEmpty() { + params.Set("coin", coin.Upper().String()) + } + if !startTime.IsZero() && !endTime.IsZero() { + if startTime.After(endTime) { + return nil, errStartTimeCannotBeAfterEndTime + } + params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) + params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) + } + endpoint := common.EncodeURLValues(marginLendHistory, params) + return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendHistory, endpoint, &r) } // GetMarginLendingOffers gets margin lending offers @@ -361,13 +411,13 @@ func (f *FTX) GetLendingInfo() ([]LendingInfoData, error) { } // SubmitLendingOffer submits an offer for margin lending -func (f *FTX) SubmitLendingOffer(coin string, size, rate float64) error { +func (f *FTX) SubmitLendingOffer(coin currency.Code, size, rate float64) error { resp := struct { Result string `json:"result"` Success bool `json:"success"` }{} req := make(map[string]interface{}) - req["coin"] = strings.ToUpper(coin) + req["coin"] = coin.Upper().String() req["size"] = size req["rate"] = rate @@ -429,11 +479,11 @@ func (f *FTX) GetAllWalletBalances() (AllWalletBalances, error) { } // FetchDepositAddress gets deposit address for a given coin -func (f *FTX) FetchDepositAddress(coin string) (DepositData, error) { +func (f *FTX) FetchDepositAddress(coin currency.Code) (DepositData, error) { resp := struct { Data DepositData `json:"result"` }{} - return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getDepositAddress+strings.ToUpper(coin), nil, &resp) + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getDepositAddress+coin.Upper().String(), nil, &resp) } // FetchDepositHistory gets deposit history @@ -453,9 +503,9 @@ func (f *FTX) FetchWithdrawalHistory() ([]TransactionData, error) { } // Withdraw sends a withdrawal request -func (f *FTX) Withdraw(coin, address, tag, password, code string, size float64) (TransactionData, error) { +func (f *FTX) Withdraw(coin currency.Code, address, tag, password, code string, size float64) (TransactionData, error) { req := make(map[string]interface{}) - req["coin"] = strings.ToUpper(coin) + req["coin"] = coin.Upper().String() req["address"] = address req["size"] = size if code != "" { @@ -851,9 +901,9 @@ func (f *FTX) GetYourQuoteRequests() ([]PersonalQuotesData, error) { } // CreateQuoteRequest sends a request to create a quote -func (f *FTX) CreateQuoteRequest(underlying, optionType, side string, expiry int64, requestExpiry string, strike, size, limitPrice, counterParyID float64, hideLimitPrice bool) (CreateQuoteRequestData, error) { +func (f *FTX) CreateQuoteRequest(underlying currency.Code, optionType, side string, expiry int64, requestExpiry string, strike, size, limitPrice, counterPartyID float64, hideLimitPrice bool) (CreateQuoteRequestData, error) { req := make(map[string]interface{}) - req["underlying"] = strings.ToUpper(underlying) + req["underlying"] = underlying.Upper().String() req["type"] = optionType req["side"] = side req["strike"] = strike @@ -865,8 +915,8 @@ func (f *FTX) CreateQuoteRequest(underlying, optionType, side string, expiry int if requestExpiry != "" { req["requestExpiry"] = requestExpiry } - if counterParyID != 0 { - req["counterParyID"] = counterParyID + if counterPartyID != 0 { + req["counterpartyId"] = counterPartyID } req["hideLimitPrice"] = hideLimitPrice resp := struct { @@ -978,6 +1028,76 @@ func (f *FTX) GetOptionsFills(startTime, endTime time.Time, limit string) ([]Opt return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOptionsFills, req, &resp) } +// GetStakes returns a list of staked assets +func (f *FTX) GetStakes() ([]Stake, error) { + resp := struct { + Data []Stake `json:"result"` + }{} + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, stakes, nil, &resp) +} + +// GetUnstakeRequests returns a collection of unstake requests +func (f *FTX) GetUnstakeRequests() ([]UnstakeRequest, error) { + resp := struct { + Data []UnstakeRequest `json:"result"` + }{} + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, unstakeRequests, nil, &resp) +} + +// GetStakeBalances returns a collection of staked coin balances +func (f *FTX) GetStakeBalances() ([]StakeBalance, error) { + resp := struct { + Data []StakeBalance `json:"result"` + }{} + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, stakeBalances, nil, &resp) +} + +// UnstakeRequest unstakes an existing staked coin +func (f *FTX) UnstakeRequest(coin currency.Code, size float64) (*UnstakeRequest, error) { + resp := struct { + Data UnstakeRequest `json:"result"` + }{} + req := make(map[string]interface{}) + req["coin"] = coin.Upper().String() + req["size"] = strconv.FormatFloat(size, 'f', -1, 64) + return &resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, unstakeRequests, req, &resp) +} + +// CancelUnstakeRequest cancels a pending unstake request +func (f *FTX) CancelUnstakeRequest(requestID int64) (bool, error) { + resp := struct { + Result string + }{} + path := unstakeRequests + "/" + strconv.FormatInt(requestID, 10) + if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, path, nil, &resp); err != nil { + return false, err + } + + if resp.Result != "Cancelled" { + return false, errors.New("failed to cancel unstake request") + } + return true, nil +} + +// GetStakingRewards returns a collection of staking rewards +func (f *FTX) GetStakingRewards() ([]StakeReward, error) { + resp := struct { + Data []StakeReward `json:"result"` + }{} + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, stakingRewards, nil, &resp) +} + +// StakeRequest submits a stake request based on the specified currency and size +func (f *FTX) StakeRequest(coin currency.Code, size float64) (*Stake, error) { + resp := struct { + Data Stake `json:"result"` + }{} + req := make(map[string]interface{}) + req["coin"] = coin.Upper().String() + req["size"] = strconv.FormatFloat(size, 'f', -1, 64) + return &resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, serumStakes, req, &resp) +} + // SendAuthHTTPRequest sends an authenticated request func (f *FTX) SendAuthHTTPRequest(ep exchange.URL, method, path string, data, result interface{}) error { endpoint, err := f.API.Endpoints.GetURL(ep) @@ -1003,6 +1123,9 @@ func (f *FTX) SendAuthHTTPRequest(ep exchange.URL, method, path string, data, re headers["FTX-KEY"] = f.API.Credentials.Key headers["FTX-SIGN"] = crypto.HexEncodeToString(hmac) headers["FTX-TS"] = ts + if f.API.Credentials.Subaccount != "" { + headers["FTX-SUBACCOUNT"] = url.QueryEscape(f.API.Credentials.Subaccount) + } headers["Content-Type"] = "application/json" return f.SendPayload(context.Background(), &request.Item{ Method: method, @@ -1090,13 +1213,13 @@ func (f *FTX) compatibleOrderVars(orderSide, orderStatus, orderType string, amou } // RequestForQuotes requests for otc quotes -func (f *FTX) RequestForQuotes(base, quote string, amount float64) (RequestQuoteData, error) { +func (f *FTX) RequestForQuotes(base, quote currency.Code, amount float64) (RequestQuoteData, error) { resp := struct { Data RequestQuoteData `json:"result"` }{} req := make(map[string]interface{}) - req["fromCoin"] = strings.ToUpper(base) - req["toCoin"] = strings.ToUpper(quote) + req["fromCoin"] = base.Upper().String() + req["toCoin"] = quote.Upper().String() req["size"] = amount return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, requestOTCQuote, req, &resp) } diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index dd0e9670..ca53f2d1 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -23,6 +23,7 @@ import ( const ( apiKey = "" apiSecret = "" + subaccount = "" canManipulateRealOrders = false spotPair = "FTT/BTC" futuresPair = "DOGE-PERP" @@ -55,6 +56,7 @@ func TestMain(m *testing.M) { exchCfg.API.AuthenticatedWebsocketSupport = true exchCfg.API.Credentials.Key = apiKey exchCfg.API.Credentials.Secret = apiSecret + exchCfg.API.Credentials.Subaccount = subaccount f.Websocket = sharedtestvalues.NewTestWebsocket() err = f.Setup(exchCfg) if err != nil { @@ -310,10 +312,32 @@ func TestGetMarginMarketInfo(t *testing.T) { func TestGetMarginBorrowHistory(t *testing.T) { t.Parallel() + + tmNow := time.Now() + _, err := f.GetMarginBorrowHistory(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() + _, err = f.GetMarginBorrowHistory(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(currency.USD, tmNow.AddDate(0, 0, 1), tmNow) + if !errors.Is(err, errStartTimeCannotBeAfterEndTime) { + t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err) + } + + _, err = f.GetMarginMarketLendingHistory(currency.USD, tmNow.AddDate(0, 0, -1), tmNow) if err != nil { t.Error(err) } @@ -321,10 +345,17 @@ func TestGetMarginBorrowHistory(t *testing.T) { func TestGetMarginLendingHistory(t *testing.T) { t.Parallel() + + tmNow := time.Now() + _, err := f.GetMarginLendingHistory(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() + _, err = f.GetMarginLendingHistory(currency.USD, tmNow.AddDate(0, 0, -1), tmNow) if err != nil { t.Error(err) } @@ -357,7 +388,7 @@ func TestSubmitLendingOffer(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip() } - if err := f.SubmitLendingOffer("bTc", 0.1, 500); err != nil { + if err := f.SubmitLendingOffer(currency.NewCode("bTc"), 0.1, 500); err != nil { t.Error(err) } } @@ -367,7 +398,7 @@ func TestFetchDepositAddress(t *testing.T) { if !areTestAPIKeysSet() { t.Skip() } - _, err := f.FetchDepositAddress("tUsD") + _, err := f.FetchDepositAddress(currency.NewCode("tUsD")) if err != nil { t.Error(err) } @@ -400,7 +431,7 @@ func TestWithdraw(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly") } - _, err := f.Withdraw("BtC", core.BitcoinDonationAddress, "", "", "957378", 0.0009) + _, err := f.Withdraw(currency.NewCode("bTc"), core.BitcoinDonationAddress, "", "", "957378", 0.0009) if err != nil { t.Error(err) } @@ -735,7 +766,7 @@ func TestCreateQuoteRequest(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly") } - _, err := f.CreateQuoteRequest(currency.BTC.String(), "call", order.Buy.Lower(), 1593140400, "", 10, 10, 5, 0, false) + _, err := f.CreateQuoteRequest(currency.BTC, "call", order.Buy.Lower(), 1593140400, "", 10, 10, 5, 0, false) if err != nil { t.Error(err) } @@ -1296,7 +1327,7 @@ func TestRequestForQuotes(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly") } - _, err := f.RequestForQuotes("BtC", "UsD", 0.5) + _, err := f.RequestForQuotes(currency.NewCode("BtC"), currency.NewCode("UsD"), 0.5) if err != nil { t.Error(err) } @@ -1564,3 +1595,80 @@ func TestSubaccountTransfer(t *testing.T) { t.Error(err) } } + +func TestGetStakes(t *testing.T) { + if !areTestAPIKeysSet() { + t.Skip("skipping test, api keys not set") + } + _, err := f.GetStakes() + if err != nil { + t.Error(err) + } +} + +func TestGetUnstakeRequests(t *testing.T) { + if !areTestAPIKeysSet() { + t.Skip("skipping test, api keys not set") + } + _, err := f.GetUnstakeRequests() + if err != nil { + t.Error(err) + } +} + +func TestGetStakeBalances(t *testing.T) { + if !areTestAPIKeysSet() { + t.Skip("skipping test, api keys not set") + } + _, err := f.GetStakeBalances() + if err != nil { + t.Error(err) + } +} + +func TestUnstakeRequest(t *testing.T) { + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set") + } + r, err := f.UnstakeRequest(currency.FTT, 0.1) + if err != nil { + t.Fatal(err) + } + + success, err := f.CancelUnstakeRequest(r.ID) + if err != nil || !success { + t.Errorf("unable to cancel unstaking request: %s", err) + } +} + +func TestCancelUnstakeRequest(t *testing.T) { + if !areTestAPIKeysSet() || !canManipulateRealOrders { + t.Skip("skipping test, either api keys or canManipulateRealOrders isn't set") + } + _, err := f.CancelUnstakeRequest(74351) + if err != nil { + t.Error(err) + } +} + +func TestGetStakingRewards(t *testing.T) { + if !areTestAPIKeysSet() { + t.Skip("skipping test, api keys not set") + } + _, err := f.GetStakingRewards() + if err != nil { + t.Error(err) + } +} + +func TestStakeRequest(t *testing.T) { + 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(currency.FTT, 0.1) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/ftx/ftx_types.go b/exchanges/ftx/ftx_types.go index 4c66683e..c66b0530 100644 --- a/exchanges/ftx/ftx_types.go +++ b/exchanges/ftx/ftx_types.go @@ -1,7 +1,6 @@ package ftx import ( - "errors" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -691,8 +690,6 @@ var ( TimeIntervalDay = TimeInterval("86400") ) -var errInvalidInterval = errors.New("invalid interval") - // OrderVars stores side, status and type for any order/trade type OrderVars struct { Side order.Side @@ -781,15 +778,6 @@ type Subaccount struct { Competition bool `json:"competition"` } -// SubaccountBalance stores the user's subaccount balance -type SubaccountBalance struct { - Coin string `json:"coin"` - Free float64 `json:"free"` - Total float64 `json:"total"` - SpotBorrow float64 `json:"spotBorrow"` - AvailableWithoutBorrow float64 `json:"availableWithoutBorrow"` -} - // SubaccountTransferStatus stores the subaccount transfer details type SubaccountTransferStatus struct { ID int64 `json:"id"` @@ -799,3 +787,47 @@ type SubaccountTransferStatus struct { Notes string `json:"notes"` Status string `json:"status"` } + +// SubaccountBalance stores the user's subaccount balance +type SubaccountBalance struct { + Coin string `json:"coin"` + Free float64 `json:"free"` + Total float64 `json:"total"` + SpotBorrow float64 `json:"spotBorrow"` + AvailableWithoutBorrow float64 `json:"availableWithoutBorrow"` +} + +// Stake stores an individual coin stake +type Stake struct { + Coin string `json:"coin"` + CreatedAt time.Time `json:"createdAt"` + ID int64 `json:"id"` + Size float64 `json:"size"` +} + +// UnstakeRequest stores data for an unstake request +type UnstakeRequest struct { + Stake + Status string `json:"status"` + UnlockAt time.Time `json:"unlockAt"` + FractionToGo float64 `json:"fractionToGo"` + Fee float64 `json:"fee"` +} + +// StakeBalance stores an individual coin stake balance +type StakeBalance struct { + Coin string `json:"coin"` + LifetimeRewards float64 `json:"lifetimeRewards"` + ScheduledToUnstake float64 `json:"scheduledToUnstake"` + Staked float64 `json:"staked"` +} + +// StakeReward stores an individual staking reward +type StakeReward struct { + Coin string `json:"coin"` + ID int64 `json:"id"` + Size float64 `json:"size"` + Notes string `json:"notes"` + Status string `json:"status"` + Time time.Time `json:"time"` +} diff --git a/exchanges/ftx/ftx_wrapper.go b/exchanges/ftx/ftx_wrapper.go index 54b16029..cbffd1b8 100644 --- a/exchanges/ftx/ftx_wrapper.go +++ b/exchanges/ftx/ftx_wrapper.go @@ -736,7 +736,7 @@ func (f *FTX) GetOrderInfo(orderID string, pair currency.Pair, assetType asset.I // GetDepositAddress returns a deposit address for a specified currency func (f *FTX) GetDepositAddress(cryptocurrency currency.Code, _ string) (string, error) { - a, err := f.FetchDepositAddress(cryptocurrency.String()) + a, err := f.FetchDepositAddress(cryptocurrency) if err != nil { return "", err } @@ -749,7 +749,7 @@ func (f *FTX) WithdrawCryptocurrencyFunds(withdrawRequest *withdraw.Request) (*w if err := withdrawRequest.Validate(); err != nil { return nil, err } - resp, err := f.Withdraw(withdrawRequest.Currency.String(), + resp, err := f.Withdraw(withdrawRequest.Currency, withdrawRequest.Crypto.Address, withdrawRequest.Crypto.AddressTag, withdrawRequest.TradePassword,