mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-20 15:10:10 +00:00
* Initial codes for a trade tracker * Moving everything in a broken fashion * Removes tradetracker. Removes some errors for subsystems * Cleans up some subsystems, renames stuttering types. Removes some global Bot usage * More basic subsystem renaming and file moving * Removes engine dependency from events,ntpserver,ordermanager,comms manager * Exports eventManager, fixes rpcserver. puts rpcserver back for now * Removes redundant error message, further removes engine dependencies * experimental end of day interface usage * adds ability to build the application * Withdraw and event manager handling * cleans up apiserver and communications manager * Cleans up some start/setup processes. Though should separate * More consistency with Setup Start Stop IsRunning funcs * Final consistency pass before testing phase * Fixes engine tests. Fixes stop nil issue * api server tests * Communications manager testing * Connection manager tests and nilsubsystem error * End of day currencypairsyncer tests * Adds databaseconnection/databaseconnection_test.go * Adds withdrawal manager tests * Deposit address testing. Moved orderbook sync first as its more important * Adds test for event manager * More full eventmanager testing * Adds testfile. Enables skipped test. * ntp manager tests * Adds ordermanager tests, Extracts a whole new subsystem from engine and fanangles import cycles * Adds websocket routine manager tests * Basic portfolio manager testing * Fixes issue with currency pair sync startup * Fixes issue with event manager startup * Starts the order manager before backtester starts * Fixes fee tests. Expands testing. Doesnt fix races * Fixes most test races * Resolves data races * Fixes subsystem test issues * currency pair syncer coverage tests * Refactors portfolio. Fixes tests. Withdraw validation Portfolio didn't need to exist with a portfolio manager. Now the porfolio manager is in charge how the portfolio is handled and all portfolio functions are attached to the base instead of just exported at the package level Withdrawal validation occurred at the exchange level when it can just be run at the withdrawal manager level. All withdrawal requests go through that endpoint * lint -fix * golang lint fixes * lints and comments everything * Updates GCT logo, adds documentation for some subsystems * More documentation and more logo updates * Fixes backtesting and apiserver errors encountered * Fixes errors and typos from reviewing * More minor fixes * Changes %h verb to %w * reverbs to %s * Humbly begins reverting to more flat engine package The main reasoning for this is that the subsystem split doesn't make sense in a golang environment. The subsystems are only meant to be used with engine and so by placing them in a non-engine area, it does not work and is inconsistent with the rest of the application's package layout. This will begin salvaging the changes made by reverting to a flat engine package, but maintaining the consistent designs introduced. Further, I will look to remove any TestMains and decrease the scope of testing to be more local and decrease the issues that have been caused from our style of testing. * Manages to re-flatten things. Everything is within its own file * mini fixes * Fixes tests and data races and lints * Updates docs tool for engine to create filename readmes * os -> ioutil * remove err * Appveyor version increase test * Removes tCleanup as its unsupported on appveyor * Adds stuff that I thought was in previous merge master commit * Removes cancel from test * Fixes really fun test-exclusive data race * minor nit fixes * niterinos * docs gen * rm;rf test * Remove typoline. expands startstop helper. Splits apiserver * Removes accidental folder * Uses update instead of replace for order upsert * addresses nits. Renames files. Regenerates documentation. * lint and removal of comments * Add new test for default scenario * Fixes typo * regen docs
1219 lines
40 KiB
Go
1219 lines
40 KiB
Go
package ftx
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
// FTX is the overarching type across this package
|
|
type FTX struct {
|
|
exchange.Base
|
|
}
|
|
|
|
const (
|
|
ftxAPIURL = "https://ftx.com/api"
|
|
|
|
// Public endpoints
|
|
getMarkets = "/markets"
|
|
getMarket = "/markets/"
|
|
getOrderbook = "/markets/%s/orderbook?depth=%s"
|
|
getTrades = "/markets/%s/trades"
|
|
getHistoricalData = "/markets/%s/candles"
|
|
getFutures = "/futures"
|
|
getFuture = "/futures/"
|
|
getFutureStats = "/futures/%s/stats"
|
|
getFundingRates = "/funding_rates"
|
|
getIndexWeights = "/indexes/%s/weights"
|
|
getAllWalletBalances = "/wallet/all_balances"
|
|
|
|
// Authenticated endpoints
|
|
getAccountInfo = "/account"
|
|
getPositions = "/positions"
|
|
setLeverage = "/account/leverage"
|
|
getCoins = "/wallet/coins"
|
|
getBalances = "/wallet/balances"
|
|
getDepositAddress = "/wallet/deposit_address/"
|
|
getDepositHistory = "/wallet/deposits"
|
|
getWithdrawalHistory = "/wallet/withdrawals"
|
|
withdrawRequest = "/wallet/withdrawals"
|
|
getOpenOrders = "/orders"
|
|
getOrderHistory = "/orders/history"
|
|
getOpenTriggerOrders = "/conditional_orders"
|
|
getTriggerOrderTriggers = "/conditional_orders/%s/triggers"
|
|
getTriggerOrderHistory = "/conditional_orders/history"
|
|
placeOrder = "/orders"
|
|
placeTriggerOrder = "/conditional_orders"
|
|
modifyOrder = "/orders/%s/modify"
|
|
modifyOrderByClientID = "/orders/by_client_id/%s/modify"
|
|
modifyTriggerOrder = "/conditional_orders/%s/modify"
|
|
getOrderStatus = "/orders/"
|
|
getOrderStatusByClientID = "/orders/by_client_id/"
|
|
deleteOrder = "/orders/"
|
|
deleteOrderByClientID = "/orders/by_client_id/"
|
|
cancelTriggerOrder = "/conditional_orders/"
|
|
getFills = "/fills"
|
|
getFundingPayments = "/funding_payments"
|
|
getLeveragedTokens = "/lt/tokens"
|
|
getTokenInfo = "/lt/"
|
|
getLTBalances = "/lt/balances"
|
|
getLTCreations = "/lt/creations"
|
|
requestLTCreation = "/lt/%s/create"
|
|
getLTRedemptions = "/lt/redemptions"
|
|
requestLTRedemption = "/lt/%s/redeem"
|
|
getListQuotes = "/options/requests"
|
|
getMyQuotesRequests = "/options/my_requests"
|
|
createQuoteRequest = "/options/requests"
|
|
deleteQuote = "/options/requests/"
|
|
endpointQuote = "/options/requests/%s/quotes"
|
|
getMyQuotes = "/options/my_quotes"
|
|
deleteMyQuote = "/options/quotes/"
|
|
acceptQuote = "/options/quotes/%s/accept"
|
|
getOptionsInfo = "/options/account_info"
|
|
getOptionsPositions = "/options/positions"
|
|
getPublicOptionsTrades = "/options/trades"
|
|
getOptionsFills = "/options/fills"
|
|
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"
|
|
marginLendingRates = "/spot_margin/lending_rates"
|
|
dailyBorrowedAmounts = "/spot_margin/borrow_summary"
|
|
marginMarketInfo = "/spot_margin/market_info?market=%s"
|
|
marginBorrowHistory = "/spot_margin/borrow_history"
|
|
marginLendHistory = "/spot_margin/lending_history"
|
|
marginLendingOffers = "/spot_margin/offers"
|
|
marginLendingInfo = "/spot_margin/lending_info"
|
|
submitLendingOrder = "/spot_margin/offers"
|
|
|
|
// Other Consts
|
|
trailingStopOrderType = "trailingStop"
|
|
takeProfitOrderType = "takeProfit"
|
|
closedStatus = "closed"
|
|
spotString = "spot"
|
|
futuresString = "future"
|
|
|
|
ratePeriod = time.Second
|
|
rateLimit = 30
|
|
)
|
|
|
|
var (
|
|
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
|
|
func (f *FTX) GetMarkets() ([]MarketData, error) {
|
|
resp := struct {
|
|
Data []MarketData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, getMarkets, &resp)
|
|
}
|
|
|
|
// GetMarket gets market data for a provided asset type
|
|
func (f *FTX) GetMarket(marketName string) (MarketData, error) {
|
|
resp := struct {
|
|
Data MarketData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, getMarket+marketName,
|
|
&resp)
|
|
}
|
|
|
|
// GetOrderbook gets orderbook for a given market with a given depth (default depth 20)
|
|
func (f *FTX) GetOrderbook(marketName string, depth int64) (OrderbookData, error) {
|
|
result := struct {
|
|
Data TempOBData `json:"result"`
|
|
}{}
|
|
|
|
strDepth := "20" // If we send a zero value we get zero asks from the
|
|
// endpoint
|
|
if depth != 0 {
|
|
strDepth = strconv.FormatInt(depth, 10)
|
|
}
|
|
|
|
var resp OrderbookData
|
|
err := f.SendHTTPRequest(exchange.RestSpot, fmt.Sprintf(getOrderbook, marketName, strDepth), &result)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
resp.MarketName = marketName
|
|
for x := range result.Data.Asks {
|
|
resp.Asks = append(resp.Asks, OData{
|
|
Price: result.Data.Asks[x][0],
|
|
Size: result.Data.Asks[x][1],
|
|
})
|
|
}
|
|
for y := range result.Data.Bids {
|
|
resp.Bids = append(resp.Bids, OData{
|
|
Price: result.Data.Bids[y][0],
|
|
Size: result.Data.Bids[y][1],
|
|
})
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// GetTrades gets trades based on the conditions specified
|
|
func (f *FTX) GetTrades(marketName string, startTime, endTime, limit int64) ([]TradeData, error) {
|
|
if marketName == "" {
|
|
return nil, errors.New("a market pair must be specified")
|
|
}
|
|
|
|
params := url.Values{}
|
|
if limit != 0 {
|
|
params.Set("limit", strconv.FormatInt(limit, 10))
|
|
}
|
|
if startTime > 0 && endTime > 0 {
|
|
if startTime >= (endTime) {
|
|
return nil, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
params.Set("start_time", strconv.FormatInt(startTime, 10))
|
|
params.Set("end_time", strconv.FormatInt(endTime, 10))
|
|
}
|
|
resp := struct {
|
|
Data []TradeData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(fmt.Sprintf(getTrades, marketName), params)
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, endpoint, &resp)
|
|
}
|
|
|
|
// GetHistoricalData gets historical OHLCV data for a given market pair
|
|
func (f *FTX) GetHistoricalData(marketName, timeInterval, limit string, startTime, endTime time.Time) ([]OHLCVData, error) {
|
|
if marketName == "" {
|
|
return nil, errors.New("a market pair must be specified")
|
|
}
|
|
|
|
if timeInterval == "" {
|
|
return nil, errors.New("a time interval must be specified")
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Set("resolution", timeInterval)
|
|
if limit != "" {
|
|
params.Set("limit", limit)
|
|
}
|
|
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))
|
|
}
|
|
resp := struct {
|
|
Data []OHLCVData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(fmt.Sprintf(getHistoricalData, marketName), params)
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, endpoint, &resp)
|
|
}
|
|
|
|
// GetFutures gets data on futures
|
|
func (f *FTX) GetFutures() ([]FuturesData, error) {
|
|
resp := struct {
|
|
Data []FuturesData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, getFutures, &resp)
|
|
}
|
|
|
|
// GetFuture gets data on a given future
|
|
func (f *FTX) GetFuture(futureName string) (FuturesData, error) {
|
|
resp := struct {
|
|
Data FuturesData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, getFuture+futureName, &resp)
|
|
}
|
|
|
|
// GetFutureStats gets data on a given future's stats
|
|
func (f *FTX) GetFutureStats(futureName string) (FutureStatsData, error) {
|
|
resp := struct {
|
|
Data FutureStatsData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, fmt.Sprintf(getFutureStats, futureName), &resp)
|
|
}
|
|
|
|
// GetFundingRates gets data on funding rates
|
|
func (f *FTX) GetFundingRates(startTime, endTime time.Time, future string) ([]FundingRatesData, error) {
|
|
resp := struct {
|
|
Data []FundingRatesData `json:"result"`
|
|
}{}
|
|
params := url.Values{}
|
|
if !startTime.IsZero() && !endTime.IsZero() {
|
|
if startTime.After(endTime) {
|
|
return resp.Data, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
|
|
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
|
|
}
|
|
if future != "" {
|
|
params.Set("future", future)
|
|
}
|
|
endpoint := common.EncodeURLValues(getFundingRates, params)
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, endpoint, &resp)
|
|
}
|
|
|
|
// GetIndexWeights gets index weights
|
|
func (f *FTX) GetIndexWeights(index string) (IndexWeights, error) {
|
|
var resp IndexWeights
|
|
return resp, f.SendHTTPRequest(exchange.RestSpot, fmt.Sprintf(getIndexWeights, index), &resp)
|
|
}
|
|
|
|
// SendHTTPRequest sends an unauthenticated HTTP request
|
|
func (f *FTX) SendHTTPRequest(ep exchange.URL, path string, result interface{}) error {
|
|
endpoint, err := f.API.Endpoints.GetURL(ep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return f.SendPayload(context.Background(), &request.Item{
|
|
Method: http.MethodGet,
|
|
Path: endpoint + path,
|
|
Result: result,
|
|
Verbose: f.Verbose,
|
|
HTTPDebugging: f.HTTPDebugging,
|
|
HTTPRecording: f.HTTPRecording,
|
|
})
|
|
}
|
|
|
|
// GetMarginBorrowRates gets borrowing rates for margin trading
|
|
func (f *FTX) GetMarginBorrowRates() ([]MarginFundingData, error) {
|
|
r := struct {
|
|
Data []MarginFundingData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginBorrowRates, nil, &r)
|
|
}
|
|
|
|
// GetMarginLendingRates gets lending rates for margin trading
|
|
func (f *FTX) GetMarginLendingRates() ([]MarginFundingData, error) {
|
|
r := struct {
|
|
Data []MarginFundingData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendingRates, nil, &r)
|
|
}
|
|
|
|
// MarginDailyBorrowedAmounts gets daily borrowed amounts for margin
|
|
func (f *FTX) MarginDailyBorrowedAmounts() ([]MarginDailyBorrowStats, error) {
|
|
r := struct {
|
|
Data []MarginDailyBorrowStats `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendHTTPRequest(exchange.RestSpot, dailyBorrowedAmounts, &r)
|
|
}
|
|
|
|
// GetMarginMarketInfo gets margin market data
|
|
func (f *FTX) GetMarginMarketInfo(market string) ([]MarginMarketInfo, error) {
|
|
r := struct {
|
|
Data []MarginMarketInfo `json:"result"`
|
|
}{}
|
|
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) {
|
|
r := struct {
|
|
Data []MarginTransactionHistoryData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginBorrowHistory, nil, &r)
|
|
}
|
|
|
|
// GetMarginLendingHistory gets margin lending history
|
|
func (f *FTX) GetMarginLendingHistory() ([]MarginTransactionHistoryData, error) {
|
|
r := struct {
|
|
Data []MarginTransactionHistoryData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendHistory, nil, &r)
|
|
}
|
|
|
|
// GetMarginLendingOffers gets margin lending offers
|
|
func (f *FTX) GetMarginLendingOffers() ([]LendingOffersData, error) {
|
|
r := struct {
|
|
Data []LendingOffersData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendingOffers, nil, &r)
|
|
}
|
|
|
|
// GetLendingInfo gets margin lending info
|
|
func (f *FTX) GetLendingInfo() ([]LendingInfoData, error) {
|
|
r := struct {
|
|
Data []LendingInfoData `json:"result"`
|
|
}{}
|
|
return r.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, marginLendingInfo, nil, &r)
|
|
}
|
|
|
|
// SubmitLendingOffer submits an offer for margin lending
|
|
func (f *FTX) SubmitLendingOffer(coin string, 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["size"] = size
|
|
req["rate"] = rate
|
|
|
|
if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, marginLendingOffers, req, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !resp.Success {
|
|
return errors.New(resp.Result)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAccountInfo gets account info
|
|
func (f *FTX) GetAccountInfo() (AccountInfoData, error) {
|
|
resp := struct {
|
|
Data AccountInfoData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getAccountInfo, nil, &resp)
|
|
}
|
|
|
|
// GetPositions gets the users positions
|
|
func (f *FTX) GetPositions() ([]PositionData, error) {
|
|
resp := struct {
|
|
Data []PositionData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getPositions, nil, &resp)
|
|
}
|
|
|
|
// ChangeAccountLeverage changes default leverage used by account
|
|
func (f *FTX) ChangeAccountLeverage(leverage float64) error {
|
|
req := make(map[string]interface{})
|
|
req["leverage"] = leverage
|
|
return f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, setLeverage, req, nil)
|
|
}
|
|
|
|
// GetCoins gets coins' data in the account wallet
|
|
func (f *FTX) GetCoins() ([]WalletCoinsData, error) {
|
|
resp := struct {
|
|
Data []WalletCoinsData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getCoins, nil, &resp)
|
|
}
|
|
|
|
// GetBalances gets balances of the account
|
|
func (f *FTX) GetBalances() ([]WalletBalance, error) {
|
|
resp := struct {
|
|
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() (AllWalletBalances, error) {
|
|
resp := struct {
|
|
Data AllWalletBalances `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getAllWalletBalances, nil, &resp)
|
|
}
|
|
|
|
// FetchDepositAddress gets deposit address for a given coin
|
|
func (f *FTX) FetchDepositAddress(coin string) (DepositData, error) {
|
|
resp := struct {
|
|
Data DepositData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getDepositAddress+strings.ToUpper(coin), nil, &resp)
|
|
}
|
|
|
|
// FetchDepositHistory gets deposit history
|
|
func (f *FTX) FetchDepositHistory() ([]TransactionData, error) {
|
|
resp := struct {
|
|
Data []TransactionData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getDepositHistory, nil, &resp)
|
|
}
|
|
|
|
// FetchWithdrawalHistory gets withdrawal history
|
|
func (f *FTX) FetchWithdrawalHistory() ([]TransactionData, error) {
|
|
resp := struct {
|
|
Data []TransactionData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getWithdrawalHistory, nil, &resp)
|
|
}
|
|
|
|
// Withdraw sends a withdrawal request
|
|
func (f *FTX) Withdraw(coin, address, tag, password, code string, size float64) (TransactionData, error) {
|
|
req := make(map[string]interface{})
|
|
req["coin"] = strings.ToUpper(coin)
|
|
req["address"] = address
|
|
req["size"] = size
|
|
if code != "" {
|
|
req["code"] = code
|
|
}
|
|
if tag != "" {
|
|
req["tag"] = tag
|
|
}
|
|
if password != "" {
|
|
req["password"] = password
|
|
}
|
|
resp := struct {
|
|
Data TransactionData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, withdrawRequest, req, &resp)
|
|
}
|
|
|
|
// GetOpenOrders gets open orders
|
|
func (f *FTX) GetOpenOrders(marketName string) ([]OrderData, error) {
|
|
params := url.Values{}
|
|
if marketName != "" {
|
|
params.Set("market", marketName)
|
|
}
|
|
resp := struct {
|
|
Data []OrderData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(getOpenOrders, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// FetchOrderHistory gets order history
|
|
func (f *FTX) FetchOrderHistory(marketName string, startTime, endTime time.Time, limit string) ([]OrderData, error) {
|
|
resp := struct {
|
|
Data []OrderData `json:"result"`
|
|
}{}
|
|
params := url.Values{}
|
|
if marketName != "" {
|
|
params.Set("market", marketName)
|
|
}
|
|
if !startTime.IsZero() && !endTime.IsZero() {
|
|
if startTime.After(endTime) {
|
|
return resp.Data, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
|
|
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
|
|
}
|
|
if limit != "" {
|
|
params.Set("limit", limit)
|
|
}
|
|
endpoint := common.EncodeURLValues(getOrderHistory, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// GetOpenTriggerOrders gets trigger orders that are currently open
|
|
func (f *FTX) GetOpenTriggerOrders(marketName, orderType string) ([]TriggerOrderData, error) {
|
|
params := url.Values{}
|
|
if marketName != "" {
|
|
params.Set("market", marketName)
|
|
}
|
|
if orderType != "" {
|
|
params.Set("type", orderType)
|
|
}
|
|
resp := struct {
|
|
Data []TriggerOrderData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(getOpenTriggerOrders, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// GetTriggerOrderTriggers gets trigger orders that are currently open
|
|
func (f *FTX) GetTriggerOrderTriggers(orderID string) ([]TriggerData, error) {
|
|
resp := struct {
|
|
Data []TriggerData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, fmt.Sprintf(getTriggerOrderTriggers, orderID), nil, &resp)
|
|
}
|
|
|
|
// GetTriggerOrderHistory gets trigger orders that are currently open
|
|
func (f *FTX) GetTriggerOrderHistory(marketName string, startTime, endTime time.Time, side, orderType, limit string) ([]TriggerOrderData, error) {
|
|
params := url.Values{}
|
|
if marketName != "" {
|
|
params.Set("market", marketName)
|
|
}
|
|
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))
|
|
}
|
|
if side != "" {
|
|
params.Set("side", side)
|
|
}
|
|
if orderType != "" {
|
|
params.Set("type", orderType)
|
|
}
|
|
if limit != "" {
|
|
params.Set("limit", limit)
|
|
}
|
|
resp := struct {
|
|
Data []TriggerOrderData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(getTriggerOrderHistory, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// Order places an order
|
|
func (f *FTX) Order(marketName, side, orderType, reduceOnly, ioc, postOnly, clientID string, price, size float64) (OrderData, error) {
|
|
req := make(map[string]interface{})
|
|
req["market"] = marketName
|
|
req["side"] = side
|
|
req["price"] = price
|
|
req["type"] = orderType
|
|
req["size"] = size
|
|
if reduceOnly != "" {
|
|
req["reduceOnly"] = reduceOnly
|
|
}
|
|
if ioc != "" {
|
|
req["ioc"] = ioc
|
|
}
|
|
if postOnly != "" {
|
|
req["postOnly"] = postOnly
|
|
}
|
|
if clientID != "" {
|
|
req["clientId"] = clientID
|
|
}
|
|
resp := struct {
|
|
Data OrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, placeOrder, req, &resp)
|
|
}
|
|
|
|
// TriggerOrder places an order
|
|
func (f *FTX) TriggerOrder(marketName, side, orderType, reduceOnly, retryUntilFilled string, size, triggerPrice, orderPrice, trailValue float64) (TriggerOrderData, error) {
|
|
req := make(map[string]interface{})
|
|
req["market"] = marketName
|
|
req["side"] = side
|
|
req["type"] = orderType
|
|
req["size"] = size
|
|
if reduceOnly != "" {
|
|
req["reduceOnly"] = reduceOnly
|
|
}
|
|
if retryUntilFilled != "" {
|
|
req["retryUntilFilled"] = retryUntilFilled
|
|
}
|
|
if orderType == order.Stop.Lower() || orderType == "" {
|
|
req["triggerPrice"] = triggerPrice
|
|
req["orderPrice"] = orderPrice
|
|
}
|
|
if orderType == trailingStopOrderType {
|
|
req["trailValue"] = trailValue
|
|
}
|
|
if orderType == takeProfitOrderType {
|
|
req["triggerPrice"] = triggerPrice
|
|
req["orderPrice"] = orderPrice
|
|
}
|
|
resp := struct {
|
|
Data TriggerOrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, placeTriggerOrder, req, &resp)
|
|
}
|
|
|
|
// ModifyPlacedOrder modifies a placed order
|
|
func (f *FTX) ModifyPlacedOrder(orderID, clientID string, price, size float64) (OrderData, error) {
|
|
req := make(map[string]interface{})
|
|
req["price"] = price
|
|
req["size"] = size
|
|
if clientID != "" {
|
|
req["clientID"] = clientID
|
|
}
|
|
resp := struct {
|
|
Data OrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(modifyOrder, orderID), req, &resp)
|
|
}
|
|
|
|
// ModifyOrderByClientID modifies a placed order via clientOrderID
|
|
func (f *FTX) ModifyOrderByClientID(clientOrderID, clientID string, price, size float64) (OrderData, error) {
|
|
req := make(map[string]interface{})
|
|
req["price"] = price
|
|
req["size"] = size
|
|
if clientID != "" {
|
|
req["clientID"] = clientID
|
|
}
|
|
resp := struct {
|
|
Data OrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(modifyOrderByClientID, clientOrderID), req, &resp)
|
|
}
|
|
|
|
// ModifyTriggerOrder modifies an existing trigger order
|
|
// Choices for ordertype include stop, trailingStop, takeProfit
|
|
func (f *FTX) ModifyTriggerOrder(orderID, orderType string, size, triggerPrice, orderPrice, trailValue float64) (TriggerOrderData, error) {
|
|
req := make(map[string]interface{})
|
|
req["size"] = size
|
|
if orderType == order.Stop.Lower() || orderType == "" {
|
|
req["triggerPrice"] = triggerPrice
|
|
req["orderPrice"] = orderPrice
|
|
}
|
|
if orderType == trailingStopOrderType {
|
|
req["trailValue"] = trailValue
|
|
}
|
|
if orderType == takeProfitOrderType {
|
|
req["triggerPrice"] = triggerPrice
|
|
req["orderPrice"] = orderPrice
|
|
}
|
|
resp := struct {
|
|
Data TriggerOrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(modifyTriggerOrder, orderID), req, &resp)
|
|
}
|
|
|
|
// GetOrderStatus gets the order status of a given orderID
|
|
func (f *FTX) GetOrderStatus(orderID string) (OrderData, error) {
|
|
resp := struct {
|
|
Data OrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOrderStatus+orderID, nil, &resp)
|
|
}
|
|
|
|
// GetOrderStatusByClientID gets the order status of a given clientOrderID
|
|
func (f *FTX) GetOrderStatusByClientID(clientOrderID string) (OrderData, error) {
|
|
resp := struct {
|
|
Data OrderData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOrderStatusByClientID+clientOrderID, nil, &resp)
|
|
}
|
|
|
|
// DeleteOrder deletes an order
|
|
func (f *FTX) DeleteOrder(orderID string) (string, error) {
|
|
resp := struct {
|
|
Result string `json:"result"`
|
|
Success bool `json:"success"`
|
|
}{}
|
|
if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, deleteOrder+orderID, nil, &resp); err != nil {
|
|
return "", err
|
|
}
|
|
if !resp.Success {
|
|
return resp.Result, errors.New("delete order request by ID unsuccessful")
|
|
}
|
|
return resp.Result, nil
|
|
}
|
|
|
|
// DeleteOrderByClientID deletes an order
|
|
func (f *FTX) DeleteOrderByClientID(clientID string) (string, error) {
|
|
resp := struct {
|
|
Result string `json:"result"`
|
|
Success bool `json:"success"`
|
|
}{}
|
|
|
|
if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, deleteOrderByClientID+clientID, nil, &resp); err != nil {
|
|
return "", err
|
|
}
|
|
if !resp.Success {
|
|
return resp.Result, errors.New("delete order request by client ID unsuccessful")
|
|
}
|
|
return resp.Result, nil
|
|
}
|
|
|
|
// DeleteTriggerOrder deletes an order
|
|
func (f *FTX) DeleteTriggerOrder(orderID string) (string, error) {
|
|
resp := struct {
|
|
Result string `json:"result"`
|
|
Success bool `json:"success"`
|
|
}{}
|
|
|
|
if err := f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, cancelTriggerOrder+orderID, nil, &resp); err != nil {
|
|
return "", err
|
|
}
|
|
if !resp.Success {
|
|
return resp.Result, errors.New("delete trigger order request unsuccessful")
|
|
}
|
|
return resp.Result, nil
|
|
}
|
|
|
|
// GetFills gets fills' data
|
|
func (f *FTX) GetFills(market, limit string, startTime, endTime time.Time) ([]FillsData, error) {
|
|
resp := struct {
|
|
Data []FillsData `json:"result"`
|
|
}{}
|
|
params := url.Values{}
|
|
if market != "" {
|
|
params.Set("market", market)
|
|
}
|
|
if limit != "" {
|
|
params.Set("limit", limit)
|
|
}
|
|
if !startTime.IsZero() && !endTime.IsZero() {
|
|
if startTime.After(endTime) {
|
|
return resp.Data, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
|
|
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
|
|
}
|
|
endpoint := common.EncodeURLValues(getFills, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// GetFundingPayments gets funding payments
|
|
func (f *FTX) GetFundingPayments(startTime, endTime time.Time, future string) ([]FundingPaymentsData, error) {
|
|
resp := struct {
|
|
Data []FundingPaymentsData `json:"result"`
|
|
}{}
|
|
params := url.Values{}
|
|
if !startTime.IsZero() && !endTime.IsZero() {
|
|
if startTime.After(endTime) {
|
|
return resp.Data, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
|
|
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
|
|
}
|
|
if future != "" {
|
|
params.Set("future", future)
|
|
}
|
|
endpoint := common.EncodeURLValues(getFundingPayments, params)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, endpoint, nil, &resp)
|
|
}
|
|
|
|
// ListLeveragedTokens lists leveraged tokens
|
|
func (f *FTX) ListLeveragedTokens() ([]LeveragedTokensData, error) {
|
|
resp := struct {
|
|
Data []LeveragedTokensData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getLeveragedTokens, nil, &resp)
|
|
}
|
|
|
|
// GetTokenInfo gets token info
|
|
func (f *FTX) GetTokenInfo(tokenName string) ([]LeveragedTokensData, error) {
|
|
resp := struct {
|
|
Data []LeveragedTokensData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getTokenInfo+tokenName, nil, &resp)
|
|
}
|
|
|
|
// ListLTBalances gets leveraged tokens' balances
|
|
func (f *FTX) ListLTBalances() ([]LTBalanceData, error) {
|
|
resp := struct {
|
|
Data []LTBalanceData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getLTBalances, nil, &resp)
|
|
}
|
|
|
|
// ListLTCreations lists the leveraged tokens' creation requests
|
|
func (f *FTX) ListLTCreations() ([]LTCreationData, error) {
|
|
resp := struct {
|
|
Data []LTCreationData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getLTCreations, nil, &resp)
|
|
}
|
|
|
|
// RequestLTCreation sends a request to create a leveraged token
|
|
func (f *FTX) RequestLTCreation(tokenName string, size float64) (RequestTokenCreationData, error) {
|
|
req := make(map[string]interface{})
|
|
req["size"] = size
|
|
resp := struct {
|
|
Data RequestTokenCreationData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(requestLTCreation, tokenName), req, &resp)
|
|
}
|
|
|
|
// ListLTRedemptions lists the leveraged tokens' redemption requests
|
|
func (f *FTX) ListLTRedemptions() ([]LTRedemptionData, error) {
|
|
resp := struct {
|
|
Data []LTRedemptionData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getLTRedemptions, nil, &resp)
|
|
}
|
|
|
|
// RequestLTRedemption sends a request to redeem a leveraged token
|
|
func (f *FTX) RequestLTRedemption(tokenName string, size float64) (LTRedemptionRequestData, error) {
|
|
req := make(map[string]interface{})
|
|
req["size"] = size
|
|
resp := struct {
|
|
Data LTRedemptionRequestData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(requestLTRedemption, tokenName), req, &resp)
|
|
}
|
|
|
|
// GetQuoteRequests gets a list of quote requests
|
|
func (f *FTX) GetQuoteRequests() ([]QuoteRequestData, error) {
|
|
resp := struct {
|
|
Data []QuoteRequestData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getListQuotes, nil, &resp)
|
|
}
|
|
|
|
// GetYourQuoteRequests gets a list of your quote requests
|
|
func (f *FTX) GetYourQuoteRequests() ([]PersonalQuotesData, error) {
|
|
resp := struct {
|
|
Data []PersonalQuotesData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getMyQuotesRequests, nil, &resp)
|
|
}
|
|
|
|
// 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) {
|
|
req := make(map[string]interface{})
|
|
req["underlying"] = strings.ToUpper(underlying)
|
|
req["type"] = optionType
|
|
req["side"] = side
|
|
req["strike"] = strike
|
|
req["expiry"] = expiry
|
|
req["size"] = size
|
|
if limitPrice != 0 {
|
|
req["limitPrice"] = limitPrice
|
|
}
|
|
if requestExpiry != "" {
|
|
req["requestExpiry"] = requestExpiry
|
|
}
|
|
if counterParyID != 0 {
|
|
req["counterParyID"] = counterParyID
|
|
}
|
|
req["hideLimitPrice"] = hideLimitPrice
|
|
resp := struct {
|
|
Data CreateQuoteRequestData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, createQuoteRequest, req, &resp)
|
|
}
|
|
|
|
// DeleteQuote sends request to cancel a quote
|
|
func (f *FTX) DeleteQuote(requestID string) (CancelQuoteRequestData, error) {
|
|
resp := struct {
|
|
Data CancelQuoteRequestData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, deleteQuote+requestID, nil, &resp)
|
|
}
|
|
|
|
// GetQuotesForYourQuote gets a list of quotes for your quote
|
|
func (f *FTX) GetQuotesForYourQuote(requestID string) (QuoteForQuoteData, error) {
|
|
var resp QuoteForQuoteData
|
|
return resp, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, fmt.Sprintf(endpointQuote, requestID), nil, &resp)
|
|
}
|
|
|
|
// MakeQuote makes a quote for a quote
|
|
func (f *FTX) MakeQuote(requestID, price string) ([]QuoteForQuoteData, error) {
|
|
params := url.Values{}
|
|
params.Set("price", price)
|
|
resp := struct {
|
|
Data []QuoteForQuoteData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(endpointQuote, requestID), nil, &resp)
|
|
}
|
|
|
|
// MyQuotes gets a list of my quotes for quotes
|
|
func (f *FTX) MyQuotes() ([]QuoteForQuoteData, error) {
|
|
resp := struct {
|
|
Data []QuoteForQuoteData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getMyQuotes, nil, &resp)
|
|
}
|
|
|
|
// DeleteMyQuote deletes my quote for quotes
|
|
func (f *FTX) DeleteMyQuote(quoteID string) ([]QuoteForQuoteData, error) {
|
|
resp := struct {
|
|
Data []QuoteForQuoteData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodDelete, deleteMyQuote+quoteID, nil, &resp)
|
|
}
|
|
|
|
// AcceptQuote accepts the quote for quote
|
|
func (f *FTX) AcceptQuote(quoteID string) ([]QuoteForQuoteData, error) {
|
|
resp := struct {
|
|
Data []QuoteForQuoteData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, fmt.Sprintf(acceptQuote, quoteID), nil, &resp)
|
|
}
|
|
|
|
// GetAccountOptionsInfo gets account's options' info
|
|
func (f *FTX) GetAccountOptionsInfo() (AccountOptionsInfoData, error) {
|
|
resp := struct {
|
|
Data AccountOptionsInfoData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOptionsInfo, nil, &resp)
|
|
}
|
|
|
|
// GetOptionsPositions gets options' positions
|
|
func (f *FTX) GetOptionsPositions() ([]OptionsPositionsData, error) {
|
|
resp := struct {
|
|
Data []OptionsPositionsData `json:"result"`
|
|
}{}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOptionsPositions, nil, &resp)
|
|
}
|
|
|
|
// GetPublicOptionsTrades gets options' trades from public
|
|
func (f *FTX) GetPublicOptionsTrades(startTime, endTime time.Time, limit string) ([]OptionsTradesData, error) {
|
|
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))
|
|
}
|
|
if limit != "" {
|
|
params.Set("limit", limit)
|
|
}
|
|
resp := struct {
|
|
Data []OptionsTradesData `json:"result"`
|
|
}{}
|
|
endpoint := common.EncodeURLValues(getPublicOptionsTrades, params)
|
|
return resp.Data, f.SendHTTPRequest(exchange.RestSpot, endpoint, &resp)
|
|
}
|
|
|
|
// GetOptionsFills gets fills data for options
|
|
func (f *FTX) GetOptionsFills(startTime, endTime time.Time, limit string) ([]OptionFillsData, error) {
|
|
resp := struct {
|
|
Data []OptionFillsData `json:"result"`
|
|
}{}
|
|
req := make(map[string]interface{})
|
|
if !startTime.IsZero() && !endTime.IsZero() {
|
|
req["start_time"] = strconv.FormatInt(startTime.Unix(), 10)
|
|
req["end_time"] = strconv.FormatInt(endTime.Unix(), 10)
|
|
if startTime.After(endTime) {
|
|
return resp.Data, errStartTimeCannotBeAfterEndTime
|
|
}
|
|
}
|
|
if limit != "" {
|
|
req["limit"] = limit
|
|
}
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOptionsFills, 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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ts := strconv.FormatInt(time.Now().UnixNano()/1000000, 10)
|
|
var body io.Reader
|
|
var hmac, payload []byte
|
|
if data != nil {
|
|
payload, err = json.Marshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body = bytes.NewBuffer(payload)
|
|
sigPayload := ts + method + "/api" + path + string(payload)
|
|
hmac = crypto.GetHMAC(crypto.HashSHA256, []byte(sigPayload), []byte(f.API.Credentials.Secret))
|
|
} else {
|
|
sigPayload := ts + method + "/api" + path
|
|
hmac = crypto.GetHMAC(crypto.HashSHA256, []byte(sigPayload), []byte(f.API.Credentials.Secret))
|
|
}
|
|
headers := make(map[string]string)
|
|
headers["FTX-KEY"] = f.API.Credentials.Key
|
|
headers["FTX-SIGN"] = crypto.HexEncodeToString(hmac)
|
|
headers["FTX-TS"] = ts
|
|
headers["Content-Type"] = "application/json"
|
|
return f.SendPayload(context.Background(), &request.Item{
|
|
Method: method,
|
|
Path: endpoint + path,
|
|
Headers: headers,
|
|
Body: body,
|
|
Result: result,
|
|
AuthRequest: true,
|
|
Verbose: f.Verbose,
|
|
HTTPDebugging: f.HTTPDebugging,
|
|
HTTPRecording: f.HTTPRecording,
|
|
})
|
|
}
|
|
|
|
// GetFee returns an estimate of fee based on type of transaction
|
|
func (f *FTX) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
|
var fee float64
|
|
switch feeBuilder.FeeType {
|
|
case exchange.OfflineTradeFee:
|
|
fee = getOfflineTradeFee(feeBuilder)
|
|
default:
|
|
feeData, err := f.GetAccountInfo()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch feeBuilder.IsMaker {
|
|
case true:
|
|
fee = feeData.MakerFee * feeBuilder.Amount * feeBuilder.PurchasePrice
|
|
case false:
|
|
fee = feeData.TakerFee * feeBuilder.Amount * feeBuilder.PurchasePrice
|
|
}
|
|
if fee < 0 {
|
|
fee = 0
|
|
}
|
|
}
|
|
return fee, nil
|
|
}
|
|
|
|
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
|
func getOfflineTradeFee(feeBuilder *exchange.FeeBuilder) float64 {
|
|
if feeBuilder.IsMaker {
|
|
return 0.0002 * feeBuilder.PurchasePrice * feeBuilder.Amount
|
|
}
|
|
return 0.0007 * feeBuilder.PurchasePrice * feeBuilder.Amount
|
|
}
|
|
|
|
func (f *FTX) compatibleOrderVars(orderSide, orderStatus, orderType string, amount, filledAmount, avgFillPrice float64) (OrderVars, error) {
|
|
var resp OrderVars
|
|
switch orderSide {
|
|
case order.Buy.Lower():
|
|
resp.Side = order.Buy
|
|
case order.Sell.Lower():
|
|
resp.Side = order.Sell
|
|
}
|
|
switch orderStatus {
|
|
case strings.ToLower(order.New.String()):
|
|
resp.Status = order.New
|
|
case strings.ToLower(order.Open.String()):
|
|
resp.Status = order.Open
|
|
case closedStatus:
|
|
if filledAmount != 0 && filledAmount != amount {
|
|
resp.Status = order.PartiallyCancelled
|
|
}
|
|
if filledAmount == 0 {
|
|
resp.Status = order.Cancelled
|
|
}
|
|
if filledAmount == amount {
|
|
resp.Status = order.Filled
|
|
}
|
|
}
|
|
var feeBuilder exchange.FeeBuilder
|
|
feeBuilder.PurchasePrice = avgFillPrice
|
|
feeBuilder.Amount = amount
|
|
resp.OrderType = order.Market
|
|
if strings.EqualFold(orderType, order.Limit.String()) {
|
|
resp.OrderType = order.Limit
|
|
feeBuilder.IsMaker = true
|
|
}
|
|
fee, err := f.GetFee(&feeBuilder)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
resp.Fee = fee
|
|
return resp, nil
|
|
}
|
|
|
|
// RequestForQuotes requests for otc quotes
|
|
func (f *FTX) RequestForQuotes(base, quote string, 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["size"] = amount
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodPost, requestOTCQuote, req, &resp)
|
|
}
|
|
|
|
// GetOTCQuoteStatus gets quote status of a quote
|
|
func (f *FTX) GetOTCQuoteStatus(marketName, quoteID string) ([]QuoteStatusData, error) {
|
|
resp := struct {
|
|
Data []QuoteStatusData `json:"result"`
|
|
}{}
|
|
params := url.Values{}
|
|
params.Set("market", marketName)
|
|
return resp.Data, f.SendAuthHTTPRequest(exchange.RestSpot, http.MethodGet, getOTCQuoteStatus+quoteID, params, &resp)
|
|
}
|
|
|
|
// AcceptOTCQuote requests for otc quotes
|
|
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
|
|
}
|
|
|
|
// DeleteSubaccount 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
|
|
}
|