mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1661,4 +1661,6 @@ var (
|
||||
CRV = NewCode("CRV")
|
||||
OXT = NewCode("OXT")
|
||||
BUSD = NewCode("BUSD")
|
||||
SRM = NewCode("SRM")
|
||||
FTT = NewCode("FTT")
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user