diff --git a/backtester/common/common_types.go b/backtester/common/common_types.go index 609d25c1..408c8892 100644 --- a/backtester/common/common_types.go +++ b/backtester/common/common_types.go @@ -39,7 +39,8 @@ var ( // ErrNilArguments is a common error response to highlight that nils were passed in // when they should not have been ErrNilArguments = errors.New("received nil argument(s)") - ErrNilEvent = errors.New("nil event received") + // ErrNilEvent is a common error for whenever a nil event occurs when it shouldn't have + ErrNilEvent = errors.New("nil event received") ) // EventHandler interface implements required GetTime() & Pair() return diff --git a/backtester/eventhandlers/strategies/base/base_types.go b/backtester/eventhandlers/strategies/base/base_types.go index b8d7c5e9..09eccc3b 100644 --- a/backtester/eventhandlers/strategies/base/base_types.go +++ b/backtester/eventhandlers/strategies/base/base_types.go @@ -2,6 +2,7 @@ package base import "errors" +// Error vars related to strategies and invalid config settings var ( ErrCustomSettingsUnsupported = errors.New("custom settings not supported") ErrSimultaneousProcessingNotSupported = errors.New("does not support simultaneous processing and could not be loaded") diff --git a/currency/storage_types.go b/currency/storage_types.go index 64020db0..7398ac11 100644 --- a/currency/storage_types.go +++ b/currency/storage_types.go @@ -46,7 +46,7 @@ type Storage struct { // FiatExchangeMarkets defines an interface to access FX data for fiat // currency rates fiatExchangeMarkets *forexprovider.ForexProviders - // CurrencyAnalysis defines a full market analysis suite to receieve and + // CurrencyAnalysis defines a full market analysis suite to receive and // define different fiat currencies, cryptocurrencies and markets currencyAnalysis *coinmarketcap.Coinmarketcap // Path defines the main folder to dump and find currency JSON diff --git a/exchanges/account/account_test.go b/exchanges/account/account_test.go index 9391a4bd..e75ffc18 100644 --- a/exchanges/account/account_test.go +++ b/exchanges/account/account_test.go @@ -73,21 +73,21 @@ func TestHoldings(t *testing.T) { } if u.Accounts[0].ID != "1337" { - t.Errorf("expecting 1337 but receieved %s", u.Accounts[0].ID) + t.Errorf("expecting 1337 but received %s", u.Accounts[0].ID) } if u.Accounts[0].Currencies[0].CurrencyName != currency.BTC { - t.Errorf("expecting BTC but receieved %s", + t.Errorf("expecting BTC but received %s", u.Accounts[0].Currencies[0].CurrencyName) } if u.Accounts[0].Currencies[0].TotalValue != 100 { - t.Errorf("expecting 100 but receieved %f", + t.Errorf("expecting 100 but received %f", u.Accounts[0].Currencies[0].TotalValue) } if u.Accounts[0].Currencies[0].Hold != 20 { - t.Errorf("expecting 20 but receieved %f", + t.Errorf("expecting 20 but received %f", u.Accounts[0].Currencies[0].Hold) } diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index f9fe160b..a12e7b00 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -7,6 +7,7 @@ import ( ) var ( + // ErrNotSupported is an error for an unsupported asset type ErrNotSupported = errors.New("received unsupported asset type") ) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 365efc16..0ed59862 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -2459,12 +2459,12 @@ func TestSetExchangeOrderExecutionLimits(t *testing.T) { err = limit.Conforms(0.000001, 0.1, order.Limit) if !errors.Is(err, order.ErrAmountBelowMin) { - t.Fatalf("expected %v, but receieved %v", order.ErrAmountBelowMin, err) + t.Fatalf("expected %v, but received %v", order.ErrAmountBelowMin, err) } err = limit.Conforms(0.01, 1, order.Limit) if !errors.Is(err, order.ErrPriceBelowMin) { - t.Fatalf("expected %v, but receieved %v", order.ErrPriceBelowMin, err) + t.Fatalf("expected %v, but received %v", order.ErrPriceBelowMin, err) } } diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index ae242131..42405568 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -15,6 +15,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -90,6 +91,10 @@ const ( requestOTCQuote = "/otc/quotes" getOTCQuoteStatus = "/otc/quotes/" acceptOTCQuote = "/otc/quotes/%s/accept" + subaccounts = "/subaccounts" + subaccountsUpdateName = "/subaccounts/update_name" + subaccountsBalance = "/subaccounts/%s/balances" + subaccountsTransfer = "/subaccounts/transfer" // Margin Endpoints marginBorrowRates = "/spot_margin/borrow_rates" @@ -114,7 +119,12 @@ const ( ) var ( - errStartTimeCannotBeAfterEndTime = errors.New("start timestamp cannot be after end timestamp") + errStartTimeCannotBeAfterEndTime = errors.New("start timestamp cannot be after end timestamp") + errSubaccountNameMustBeSpecified = errors.New("a subaccount name must be specified") + errSubaccountUpdateNameInvalid = errors.New("invalid subaccount old/new name") + errCoinMustBeSpecified = errors.New("a coin must be specified") + errSubaccountTransferSizeGreaterThanZero = errors.New("transfer size must be greater than 0") + errSubaccountTransferSourceDestinationMustNotBeEqual = errors.New("subaccount transfer source and destination must not be the same value") ) // GetMarkets gets market data @@ -403,17 +413,17 @@ func (f *FTX) GetCoins() ([]WalletCoinsData, error) { } // GetBalances gets balances of the account -func (f *FTX) GetBalances() ([]BalancesData, error) { +func (f *FTX) GetBalances() ([]WalletBalance, error) { resp := struct { - Data []BalancesData `json:"result"` + Data []WalletBalance `json:"result"` }{} return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getBalances, nil, &resp) } // GetAllWalletBalances gets all wallets' balances -func (f *FTX) GetAllWalletBalances() (AllWalletAccountData, error) { +func (f *FTX) GetAllWalletBalances() (AllWalletBalances, error) { resp := struct { - Data AllWalletAccountData `json:"result"` + Data AllWalletBalances `json:"result"` }{} return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getAllWalletBalances, nil, &resp) } @@ -1105,3 +1115,104 @@ func (f *FTX) GetOTCQuoteStatus(marketName, quoteID string) ([]QuoteStatusData, func (f *FTX) AcceptOTCQuote(quoteID string) error { return f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(acceptOTCQuote, quoteID), nil, nil) } + +// GetSubaccounts returns the users subaccounts +func (f *FTX) GetSubaccounts() ([]Subaccount, error) { + resp := struct { + Data []Subaccount `json:"result"` + }{} + return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, subaccounts, nil, &resp) +} + +// CreateSubaccount creates a new subaccount +func (f *FTX) CreateSubaccount(name string) (*Subaccount, error) { + if name == "" { + return nil, errSubaccountNameMustBeSpecified + } + d := make(map[string]string) + d["nickname"] = name + + resp := struct { + Data Subaccount `json:"result"` + }{} + if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, subaccounts, d, &resp); err != nil { + return nil, err + } + return &resp.Data, nil +} + +// UpdateSubaccountName updates an existing subaccount name +func (f *FTX) UpdateSubaccountName(oldName, newName string) (*Subaccount, error) { + if oldName == "" || newName == "" || oldName == newName { + return nil, errSubaccountUpdateNameInvalid + } + d := make(map[string]string) + d["nickname"] = oldName + d["newNickname"] = newName + + resp := struct { + Data Subaccount `json:"result"` + }{} + if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, subaccountsUpdateName, d, &resp); err != nil { + return nil, err + } + return &resp.Data, nil +} + +// DeleteSubaccountName deletes the specified subaccount name +func (f *FTX) DeleteSubaccount(name string) error { + if name == "" { + return errSubaccountNameMustBeSpecified + } + d := make(map[string]string) + d["nickname"] = name + resp := struct { + Data Subaccount `json:"result"` + }{} + return f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, subaccounts, d, &resp) +} + +// SubaccountBalances returns the user's subaccount balances +func (f *FTX) SubaccountBalances(name string) ([]SubaccountBalance, error) { + if name == "" { + return nil, errSubaccountNameMustBeSpecified + } + resp := struct { + Data []SubaccountBalance `json:"result"` + }{} + if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, fmt.Sprintf(subaccountsBalance, name), nil, &resp); err != nil { + return nil, err + } + return resp.Data, nil +} + +// SubaccountTransfer transfers a desired coin to the specified subaccount +func (f *FTX) SubaccountTransfer(coin currency.Code, source, destination string, size float64) (*SubaccountTransferStatus, error) { + if coin.IsEmpty() { + return nil, errCoinMustBeSpecified + } + if size <= 0 { + return nil, errSubaccountTransferSizeGreaterThanZero + } + if source == destination { + return nil, errSubaccountTransferSourceDestinationMustNotBeEqual + } + d := make(map[string]interface{}) + d["coin"] = coin.Upper().String() + d["size"] = size + if source == "" { + source = "main" + } + d["source"] = source + if destination == "" { + destination = "main" + } + d["destination"] = destination + resp := struct { + Data SubaccountTransferStatus `json:"result"` + }{} + if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, subaccountsTransfer, d, &resp); err != nil { + return nil, err + } + return &resp.Data, nil +} diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index 0f259bec..8c089ec7 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -1,6 +1,7 @@ package ftx import ( + "errors" "log" "os" "reflect" @@ -1450,3 +1451,126 @@ func TestParsingWSOBData2(t *testing.T) { t.Error(err) } } + +func TestGetSubaccounts(t *testing.T) { + t.Parallel() + if !areTestAPIKeysSet() { + t.Skip("skipping test, api keys not set") + } + _, err := f.GetSubaccounts() + if err != nil { + t.Error(err) + } +} + +func TestCreateSubaccount(t *testing.T) { + t.Parallel() + _, err := f.CreateSubaccount("") + 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("subzero") + if err != nil { + t.Fatal(err) + } + if err = f.DeleteSubaccount("subzero"); err != nil { + t.Error(err) + } +} + +func TestUpdateSubaccountName(t *testing.T) { + t.Parallel() + _, err := f.UpdateSubaccountName("", "") + 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("subzero") + if err != nil { + t.Fatal(err) + } + _, err = f.UpdateSubaccountName("subzero", "bizzlebot") + if err != nil { + t.Fatal(err) + } + if err := f.DeleteSubaccount("bizzlebot"); err != nil { + t.Error(err) + } +} + +func TestDeleteSubaccountName(t *testing.T) { + t.Parallel() + if err := f.DeleteSubaccount(""); !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("subzero") + if err != nil { + t.Fatal(err) + } + if err := f.DeleteSubaccount("subzero"); err != nil { + t.Error(err) + } +} + +func TestSubaccountBalances(t *testing.T) { + t.Parallel() + _, err := f.SubaccountBalances("") + 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("non-existent") + if err == nil { + t.Error("expecting non-existent subaccount to return an error") + } + _, err = f.CreateSubaccount("subzero") + if err != nil { + t.Fatal(err) + } + _, err = f.SubaccountBalances("subzero") + if err != nil { + t.Error(err) + } + if err := f.DeleteSubaccount("subzero"); err != nil { + t.Error(err) + } +} + +func TestSubaccountTransfer(t *testing.T) { + 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(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(currency.BTC, "", "test", 0.1) + if err != nil { + t.Error(err) + } +} diff --git a/exchanges/ftx/ftx_types.go b/exchanges/ftx/ftx_types.go index f577b933..4c66683e 100644 --- a/exchanges/ftx/ftx_types.go +++ b/exchanges/ftx/ftx_types.go @@ -234,18 +234,18 @@ type WalletCoinsData struct { Name string `json:"name"` } -// BalancesData stores balances data -type BalancesData struct { - Coin string `json:"coin"` - Free float64 `json:"free"` - Total float64 `json:"total"` +// WalletBalance stores balances data +type WalletBalance struct { + Coin string `json:"coin"` + Free float64 `json:"free"` + Total float64 `json:"total"` + AvailableWithoutBorrow float64 `json:"availableWithoutBorrow"` + USDValue float64 `json:"usdValue"` + SpotBorrow float64 `json:"spotBorrow"` } -// AllWalletAccountData stores account data on all WalletCoins -type AllWalletAccountData struct { - Main []BalancesData `json:"main"` - BattleRoyale []BalancesData `json:"Battle Royale"` -} +// AllWalletBalances stores all the user's account balances +type AllWalletBalances map[string][]WalletBalance // DepositData stores deposit address data type DepositData struct { @@ -771,3 +771,31 @@ type QuoteStatusData struct { type AcceptQuote struct { Success bool `json:"success"` } + +// Subaccount stores subaccount data +type Subaccount struct { + Nickname string `json:"nickname"` + Special bool `json:"special"` + Deletable bool `json:"deletable"` + Editable bool `json:"editable"` + 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"` + Coin string `json:"coin"` + Size float64 `json:"size"` + Time time.Time `json:"time"` + Notes string `json:"notes"` + Status string `json:"status"` +} diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go index 8a93e942..c5df1e18 100644 --- a/exchanges/kline/kline.go +++ b/exchanges/kline/kline.go @@ -444,7 +444,7 @@ func (h *IntervalRangeHolder) VerifyResultsHaveData(c []Candle) error { return nil } -// Set is a simple helper function to set the time twice +// CreateIntervalTime is a simple helper function to set the time twice func CreateIntervalTime(tt time.Time) IntervalTime { return IntervalTime{ Time: tt, diff --git a/exchanges/kline/kline_types.go b/exchanges/kline/kline_types.go index 95230ead..12b7d19a 100644 --- a/exchanges/kline/kline_types.go +++ b/exchanges/kline/kline_types.go @@ -39,6 +39,7 @@ const ( ) var ( + // ErrMissingCandleData is an error for missing candle data ErrMissingCandleData = errors.New("missing candle data") // SupportedIntervals is a list of all supported intervals SupportedIntervals = []Interval{ diff --git a/exchanges/kraken/futures_types.go b/exchanges/kraken/futures_types.go index 95b59f5e..885b9813 100644 --- a/exchanges/kraken/futures_types.go +++ b/exchanges/kraken/futures_types.go @@ -534,7 +534,7 @@ type BatchOrderData struct { Status string `json:"status"` OrderTag string `json:"order_tag"` OrderID string `json:"order_id"` - DateTimeReceived string `json:"dateTimeReceieved"` + DateTimeReceived string `json:"dateTimeReceived"` OrderEvents []struct { OrderPlaced FuturesOrderData `json:"orderPlaced"` ReduceOnly bool `json:"reduceOnly"` diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index c9831eb0..1c819c5a 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -859,7 +859,7 @@ func TestOrderBookPartialChecksumCalculator(t *testing.T) { calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse) if calculatedChecksum != dataResponse.Checksum { - t.Errorf("Expected %v, Receieved %v", dataResponse.Checksum, calculatedChecksum) + t.Errorf("Expected %v, received %v", dataResponse.Checksum, calculatedChecksum) } } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index d642c37e..1234f549 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1672,7 +1672,7 @@ func TestOrderBookPartialChecksumCalculator(t *testing.T) { calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse) if calculatedChecksum != dataResponse.Checksum { - t.Errorf("Expected %v, Receieved %v", dataResponse.Checksum, calculatedChecksum) + t.Errorf("Expected %v, received %v", dataResponse.Checksum, calculatedChecksum) } } diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index ddcd2f18..be3dc081 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -436,7 +436,7 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float vals.Set("fees", strconv.FormatFloat(fees, 'f', -1, 64)) vals.Set("itransfer", strconv.FormatBool(itransfer)) vals.Set("method", "withdraw") - vals.Set("recieveAddr", address) + vals.Set("receiveAddr", address) vals.Set("safePwd", safepassword) var resp response diff --git a/portfolio/withdraw/validate_test.go b/portfolio/withdraw/validate_test.go index 54a427ce..07c2194d 100644 --- a/portfolio/withdraw/validate_test.go +++ b/portfolio/withdraw/validate_test.go @@ -254,7 +254,7 @@ func TestValidateFiat(t *testing.T) { err := test.request.Validate(test.validate) if err != nil { if test.output.(error).Error() != err.Error() { - t.Fatalf("Test Name %s expecting error [%s] but receieved [%s]", test.name, test.output.(error).Error(), err) + t.Fatalf("Test Name %s expecting error [%s] but received [%s]", test.name, test.output.(error).Error(), err) } } })