FTX: Add REST staking and basic subaccount functionality (#692)

* Add FTX staking, missing margin APIs and basic subaccount support

* Fix backtester tests and add optional subaccount support to exchange_wrapper_issues tool

* subAccount to subaccount

* Fix TyPo

* Expand test coverage

* Address nitterinos

* Fix typos

* Remove unusued error type
This commit is contained in:
Adrian Gallagher
2021-06-02 15:52:46 +10:00
committed by GitHub
parent 5ea5245afb
commit f234726382
17 changed files with 361 additions and 84 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -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{

View File

@@ -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"`
}

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -1661,4 +1661,6 @@ var (
CRV = NewCode("CRV")
OXT = NewCode("OXT")
BUSD = NewCode("BUSD")
SRM = NewCode("SRM")
FTT = NewCode("FTT")
)

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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"`
}

View File

@@ -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,