(Exchange) Add FTX exchange support with implementation tutorial (#495)

* initial

* wip

* brokenwip

* broken wipzzzz

* more functions

brokenwip

NO API KEYS

* broken wip

* WIP

* wip

* WIP

work in progress

* WIP

* WIP

* wip

* more wip

* wip

* ws wip

* broken wip

* adding new functions for websocket to work

* trying to fix websocket issues

* websocket bug fix wip

* broken websocket implementation

* WS unauth functions + brokenWS auth func

* authentication problems

* authentication problems fixed

* data handling for websocket

* websocket completed

* remove verbose

* minor error fix changes and testing

* reorganising variable declarations and minor errors fixed

* enabled exchanges updated

* enabled exchanges fixed

* remove keys

* glorious nits

* xdta n shazzy nitzzz

* shazzy n thrasher nitz

* nitz wip

* broken wip

* apichecker donee n make code better

* apichecker donee n make code better

* OB update

* wip

* wip

* all nitz done

* merge conflicts

* go mod tidy

* merge conflicts

* PLEASE merge conflicts

* new funcs added n binanceapi check update

NO APIKEYS

* basic tests

* linter fixs

* linter fixs

* remove verbose

* test errors fixed

* remove comented code

* minor changes

* some tests fixed

no apikeys

* documentation work

* documentation

* wip

* ryan nitz

* nits addressed

* unnecessary conversion

* no fail

* remove verbose

* type field checking

* broken

* websocket nits fixed

* some thangs

* remove verbose

* fix function

* linter issues

* test error fixed

* nits

* bumperino fixed

* very small change

* nits

* errors fixing

* errors fixing retry

* linters

* thrasher glorious nits

* more changes

* changes

* 2 more changes to be addressed

* 2 more changes to be addressed

* issues addressed

* whip

* changes

* missed change

* changes

* currency issues

* changes

* unsaved

* int64

* HUGE

* HUGE

* NO NITS PLS

* no more

* YES

* :

* changes

* PLEASE

* n another one

* thanks guys

* ill believe in god if this ever ends

* :D
This commit is contained in:
Adam
2020-07-02 10:38:50 +10:00
committed by GitHub
parent 2351d89b77
commit c2c200cd1b
66 changed files with 6720 additions and 282 deletions

View File

@@ -25,6 +25,10 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
+ Please checkout individual exchange README for more information on
implementation
## Guide for adding a new exchange
+ A guide on implementing API support for a new exchange can be found [here](../docs/ADD_NEW_EXCHANGE.md)
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution

View File

@@ -210,8 +210,8 @@ func (a *Alphapoint) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrNotYetImplemented
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (a *Alphapoint) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -409,8 +409,8 @@ func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Binance) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Binance) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -424,8 +424,8 @@ func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitfinex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -280,8 +280,8 @@ func (b *Bitflyer) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitflyer) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -311,8 +311,8 @@ func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bithumb) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -421,8 +421,8 @@ func (b *Bitmex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrNotYetImplemented
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitmex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -362,8 +362,8 @@ func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bitstamp) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -339,8 +339,8 @@ func (b *Bittrex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *Bittrex) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -369,8 +369,8 @@ func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *BTCMarkets) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -348,8 +348,8 @@ func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -385,8 +385,8 @@ func (c *CoinbasePro) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (c *CoinbasePro) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -461,8 +461,8 @@ func (c *Coinbene) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (c *Coinbene) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (c *Coinbene) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -462,8 +462,8 @@ func (c *COINUT) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (c *COINUT) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -211,10 +211,12 @@ func (e *Base) GetAssetTypes() asset.Items {
}
// GetPairAssetType returns the associated asset type for the currency pair
// This method is only useful for exchanges that have pair names with multiple delimiters (BTC-USD-0626)
// Helpful if the exchange has only a single asset type but in that case the asset type can be hard coded
func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
assetTypes := e.GetAssetTypes()
for i := range assetTypes {
if e.GetEnabledPairs(assetTypes[i]).Contains(c, true) {
if e.GetAvailablePairs(assetTypes[i]).Contains(c, true) {
return assetTypes[i], nil
}
}

View File

@@ -1428,7 +1428,7 @@ func TestGetAssetType(t *testing.T) {
b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot}
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
Enabled: currency.Pairs{
Available: currency.Pairs{
currency.NewPair(currency.BTC, currency.USD),
},
ConfigFormat: &currency.PairFormat{Delimiter: "-"},

View File

@@ -115,6 +115,7 @@ type TradeHistory struct {
Amount float64
Exchange string
Type string
Side string
Fee float64
Description string
}

View File

@@ -348,8 +348,8 @@ func (e *EXMO) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (e *EXMO) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

133
exchanges/ftx/README.md Normal file
View File

@@ -0,0 +1,133 @@
# GoCryptoTrader package Ftx
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/ftx)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
This ftx package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
## FTX Exchange
### Current Features
+ REST Support
+ Websocket Support
### How to enable
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
+ Individual package example below:
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### How to do REST public/private calls
+ If enabled via "configuration".json file the exchange will be added to the
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
the wrapper interface functions for accessing exchange data. View routines.go
for an example of integration usage with GoCryptoTrader. Rudimentary example
below:
main.go
```go
var f exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "FTX" {
f = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := f.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.FetchOrderbook()
if err != nil {
// Handle error
}
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
// set and AuthenticatedAPISupport is set to true
// Fetches current account information
accountInfo, err := f.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := f.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := f.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := f.Trade(...)
if err != nil {
// Handle error
}
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***

959
exchanges/ftx/ftx.go Normal file
View File

@@ -0,0 +1,959 @@
package ftx
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// FTX is the overarching type across this package
type FTX struct {
exchange.Base
WebsocketConn *wshandler.WebsocketConnection
}
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"
// Other Consts
trailingStopOrderType = "trailingStop"
takeProfitOrderType = "takeProfit"
closedStatus = "closed"
spotString = "spot"
futuresString = "future"
ratePeriod = time.Second
rateLimit = 30
)
// GetMarkets gets market data
func (f *FTX) GetMarkets() ([]MarketData, error) {
resp := struct {
Data []MarketData `json:"result"`
}{}
return resp.Data, f.SendHTTPRequest(ftxAPIURL+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(ftxAPIURL+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 := strconv.FormatInt(depth, 10)
var resp OrderbookData
err := f.SendHTTPRequest(fmt.Sprintf(ftxAPIURL+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 time.Time, limit int64) ([]TradeData, error) {
strLimit := strconv.FormatInt(limit, 10)
params := url.Values{}
params.Set("limit", strLimit)
resp := struct {
Data []TradeData `json:"result"`
}{}
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp.Data, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
return resp.Data, f.SendHTTPRequest(fmt.Sprintf(ftxAPIURL+getTrades, marketName)+params.Encode(),
&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) {
resp := struct {
Data []OHLCVData `json:"result"`
}{}
params := url.Values{}
params.Set("resolution", timeInterval)
if limit != "" {
params.Set("limit", limit)
}
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp.Data, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
return resp.Data, f.SendHTTPRequest(fmt.Sprintf(ftxAPIURL+getHistoricalData, marketName)+params.Encode(), &resp)
}
// GetFutures gets data on futures
func (f *FTX) GetFutures() ([]FuturesData, error) {
resp := struct {
Data []FuturesData `json:"result"`
}{}
return resp.Data, f.SendHTTPRequest(ftxAPIURL+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(ftxAPIURL+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(fmt.Sprintf(ftxAPIURL+getFutureStats, futureName), &resp)
}
// GetFundingRates gets data on funding rates
func (f *FTX) GetFundingRates() ([]FundingRatesData, error) {
resp := struct {
Data []FundingRatesData `json:"result"`
}{}
return resp.Data, f.SendHTTPRequest(ftxAPIURL+getFundingRates, &resp)
}
// GetIndexWeights gets index weights
func (f *FTX) GetIndexWeights(index string) (IndexWeights, error) {
var resp IndexWeights
return resp, f.SendHTTPRequest(ftxAPIURL+fmt.Sprintf(getIndexWeights, index), &resp)
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (f *FTX) SendHTTPRequest(path string, result interface{}) error {
return f.SendPayload(context.Background(), &request.Item{
Method: http.MethodGet,
Path: path,
Result: result,
Verbose: f.Verbose,
HTTPDebugging: f.HTTPDebugging,
HTTPRecording: f.HTTPRecording,
})
}
// GetAccountInfo gets account info
func (f *FTX) GetAccountInfo() (AccountInfoData, error) {
resp := struct {
Data AccountInfoData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(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(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(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(http.MethodGet, getCoins, nil, &resp)
}
// GetBalances gets balances of the account
func (f *FTX) GetBalances() ([]BalancesData, error) {
resp := struct {
Data []BalancesData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getBalances, nil, &resp)
}
// GetAllWalletBalances gets all wallets' balances
func (f *FTX) GetAllWalletBalances() (AllWalletAccountData, error) {
resp := struct {
Data AllWalletAccountData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(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(http.MethodGet, getDepositAddress+coin, nil, &resp)
}
// FetchDepositHistory gets deposit history
func (f *FTX) FetchDepositHistory() ([]TransactionData, error) {
resp := struct {
Data []TransactionData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(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(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"] = 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(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"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getOpenOrders+params.Encode(), 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, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
if limit != "" {
params.Set("limit", limit)
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getOrderHistory+params.Encode(), 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"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getOpenTriggerOrders+params.Encode(), 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(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) {
resp := struct {
Data []TriggerOrderData `json:"result"`
}{}
params := url.Values{}
if marketName != "" {
params.Set("market", marketName)
}
if !startTime.IsZero() && !endTime.IsZero() {
if startTime.After(endTime) {
return resp.Data, errors.New("startTime cannot be after endTime")
}
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)
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getTriggerOrderHistory+params.Encode(), 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(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(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(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(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(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(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(http.MethodGet, getOrderStatusByClientID+clientOrderID, nil, &resp)
}
// DeleteOrder deletes an order
func (f *FTX) DeleteOrder(orderID string) (string, error) {
resp := struct {
Data string `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, deleteOrder+orderID, nil, &resp)
}
// DeleteOrderByClientID deletes an order
func (f *FTX) DeleteOrderByClientID(clientID string) (string, error) {
resp := struct {
Data string `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, deleteOrderByClientID+clientID, nil, &resp)
}
// DeleteTriggerOrder deletes an order
func (f *FTX) DeleteTriggerOrder(orderID string) (string, error) {
resp := struct {
Data string `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(http.MethodDelete, cancelTriggerOrder+orderID, nil, &resp)
}
// 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, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getFills+params.Encode(), 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, errors.New("startTime cannot be after endTime")
}
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
if future != "" {
params.Set("future", future)
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getFundingPayments+params.Encode(), nil, &resp)
}
// ListLeveragedTokens lists leveraged tokens
func (f *FTX) ListLeveragedTokens() ([]LeveragedTokensData, error) {
resp := struct {
Data []LeveragedTokensData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(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(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(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(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(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(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(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(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(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"] = 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(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(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(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(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(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(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(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(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(http.MethodGet, getOptionsPositions, nil, &resp)
}
// GetPublicOptionsTrades gets options' trades from public
func (f *FTX) GetPublicOptionsTrades(startTime, endTime time.Time, limit string) ([]OptionsTradesData, error) {
resp := struct {
Data []OptionsTradesData `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, errors.New("startTime cannot be after endTime")
}
}
if limit != "" {
req["limit"] = limit
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getPublicOptionsTrades, req, &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, errors.New("startTime cannot be after endTime")
}
}
if limit != "" {
req["limit"] = limit
}
return resp.Data, f.SendAuthHTTPRequest(http.MethodGet, getOptionsFills, req, &resp)
}
// SendAuthHTTPRequest sends an authenticated request
func (f *FTX) SendAuthHTTPRequest(method, path string, data, result interface{}) error {
ts := strconv.FormatInt(time.Now().UnixNano()/1000000, 10)
var body io.Reader
var hmac, payload []byte
var err error
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: ftxAPIURL + 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 parseInterval(in time.Duration) (TimeInterval, error) {
switch in {
case kline.FifteenSecond:
return TimeIntervalFifteenSeconds, nil
case kline.OneMin:
return TimeIntervalMinute, nil
case kline.FiveMin:
return TimeIntervalFiveMinutes, nil
case kline.FifteenMin:
return TimeIntervalFifteenMinutes, nil
case kline.OneHour:
return TimeIntervalHour, nil
case kline.FourHour:
return TimeIntervalFourHours, nil
case kline.OneDay:
return TimeIntervalDay, nil
default:
return TimeIntervalMinute, errInvalidInterval
}
}
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"] = base
req["toCoin"] = quote
req["size"] = amount
return resp.Data, f.SendAuthHTTPRequest(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(http.MethodGet, getOTCQuoteStatus+quoteID, params, &resp)
}
// AcceptOTCQuote requests for otc quotes
func (f *FTX) AcceptOTCQuote(quoteID string) error {
return f.SendAuthHTTPRequest(http.MethodPost, fmt.Sprintf(acceptOTCQuote, quoteID), nil, nil)
}

1188
exchanges/ftx/ftx_test.go Normal file

File diff suppressed because it is too large Load Diff

726
exchanges/ftx/ftx_types.go Normal file
View File

@@ -0,0 +1,726 @@
package ftx
import (
"errors"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
// MarketData stores market data
type MarketData struct {
Name string `json:"name"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
MarketType string `json:"type"`
Underlying string `json:"underlying"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
QuoteVolume24h float64 `json:"quoteVolume24h"`
Enabled bool `json:"enabled"`
Ask float64 `json:"ask"`
Bid float64 `json:"bid"`
Last float64 `json:"last"`
USDVolume24h float64 `json:"volumeUSD24h"`
MinProvideSize float64 `json:"minProvideSize"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
Restricted bool `json:"restricted"`
}
// OData stores orderdata in orderbook
type OData struct {
Price float64
Size float64
}
// OrderbookData stores orderbook data
type OrderbookData struct {
MarketName string
Asks []OData
Bids []OData
}
// TempOBData stores orderbook data temporarily
type TempOBData struct {
Asks [][2]float64 `json:"asks"`
Bids [][2]float64 `json:"bids"`
}
// TradeData stores data from trades
type TradeData struct {
ID int64 `json:"id"`
Liquidation bool `json:"liquidation"`
Price float64 `json:"price"`
Side string `json:"side"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
}
// OHLCVData stores historical OHLCV data
type OHLCVData struct {
Close float64 `json:"close"`
High float64 `json:"high"`
Low float64 `json:"low"`
Open float64 `json:"open"`
StartTime time.Time `json:"startTime"`
Time float64 `json:"time"`
Volume float64 `json:"volume"`
}
// FuturesData stores data for futures
type FuturesData struct {
Ask float64 `json:"ask"`
Bid float64 `json:"bid"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
VolumeUSD24h float64 `json:"volumeUsd24h"`
Volume float64 `json:"volume"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Expired bool `json:"expired"`
Expiry time.Time `json:"expiry"`
ExpiryDescription string `json:"expiryDescription"`
Group string `json:"group"`
Index float64 `json:"index"`
IMFFactor float64 `json:"imfFactor"`
Last float64 `json:"last"`
LowerBound float64 `json:"lowerBound"`
MarginPrice float64 `json:"marginPrice"`
Mark float64 `json:"mark"`
MoveStart interface{} `json:"moveStart"`
Name string `json:"name"`
Perpetual bool `json:"perpetual"`
PositionLimitWeight float64 `json:"positionLimitWeight"`
PostOnly bool `json:"postOnly"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
Underlying string `json:"underlying"`
UpperBound float64 `json:"upperBound"`
FutureType string `json:"type"`
}
// FutureStatsData stores data on futures stats
type FutureStatsData struct {
Volume float64 `json:"volume"`
NextFundingRate float64 `json:"nextFundingRate"`
NextFundingTime time.Time `json:"nextFundingTime"`
ExpirationPrice float64 `json:"expirationPrice"`
PredictedExpirationPrice float64 `json:"predictedExpirationPrice"`
OpenInterest float64 `json:"openInterest"`
StrikePrice float64 `json:"strikePrice"`
}
// FundingRatesData stores data on funding rates
type FundingRatesData struct {
Future string `json:"future"`
Rate float64 `json:"rate"`
Time time.Time `json:"time"`
}
// IndexWeights stores index weights' data
type IndexWeights struct {
Result map[string]float64 `json:"result"`
}
// PositionData stores data of an open position
type PositionData struct {
Cost float64 `json:"cost"`
EntryPrice float64 `json:"entryPrice"`
Future string `json:"future"`
InitialMarginRequirement float64 `json:"initialMarginRequirement"`
LongOrderSize float64 `json:"longOrderSize"`
MaintenanceMarginRequirement float64 `json:"maintenanceMarginRequirement"`
NetSize float64 `json:"netSize"`
OpenSize float64 `json:"openSize"`
RealisedPnL float64 `json:"realisedPnL"`
ShortOrderSide float64 `json:"shortOrderSide"`
Side string `json:"side"`
Size float64 `json:"size"`
UnrealisedPnL float64 `json:"unrealisedPnL"`
}
// AccountInfoData stores account data
type AccountInfoData struct {
BackstopProvider bool `json:"backstopProvider"`
ChargeInterestOnNegativeUSD bool `json:"chargeInterestOnNegativeUsd"`
Collateral float64 `json:"collateral"`
FreeCollateral float64 `json:"freeCollateral"`
InitialMarginRequirement float64 `json:"initialMarginRequirement"`
Leverage float64 `json:"float64"`
Liquidating bool `json:"liquidating"`
MaintenanceMarginRequirement float64 `json:"maintenanceMarginRequirement"`
MakerFee float64 `json:"makerFee"`
MarginFraction float64 `json:"marginFraction"`
OpenMarginFraction float64 `json:"openMarginFraction"`
PositionLimit float64 `json:"positionLimit"`
PositionLimitUsed float64 `json:"positionLimitUsed"`
SpotLendingEnabled bool `json:"spotLendingEnabled"`
SpotMarginEnabled bool `json:"spotMarginEnabled"`
TakerFee float64 `json:"takerFee"`
TotalAccountValue float64 `json:"totalAccountValue"`
TotalPositionSize float64 `json:"totalPositionSize"`
UseFTTCollateral bool `json:"useFttCollateral"`
Username string `json:"username"`
Positions []PositionData `json:"positions"`
}
// WalletCoinsData stores data about wallet coins
type WalletCoinsData struct {
Bep2Asset interface{} `json:"bep2Asset"`
CanConvert bool `json:"canConvert"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
Collateral bool `json:"collateral"`
CollateralWeight float64 `json:"collateralWeight"`
CreditTo interface{} `json:"creditTo"`
ERC20Contract interface{} `json:"erc20Contract"`
Fiat bool `json:"fiat"`
HasTag bool `json:"hasTag"`
Hidden bool `json:"hidden"`
IsETF bool `json:"isEtf"`
IsToken bool `json:"isToken"`
Methods []interface{}
ID string `json:"id"`
Name string `json:"name"`
}
// BalancesData stores balances data
type BalancesData struct {
Coin string `json:"coin"`
Free float64 `json:"free"`
Total float64 `json:"total"`
}
// AllWalletAccountData stores account data on all WalletCoins
type AllWalletAccountData struct {
Main []BalancesData `json:"main"`
BattleRoyale []BalancesData `json:"Battle Royale"`
}
// DepositData stores deposit address data
type DepositData struct {
Address string `json:"address"`
Tag string `json:"tag"`
}
// TransactionData stores data about deposit history
type TransactionData struct {
Coin string `json:"coin"`
Confirmations int64 `json:"conformations"`
ConfirmedTime time.Time `json:"confirmedTime"`
Fee float64 `json:"fee"`
ID int64 `json:"id"`
SentTime time.Time `json:"sentTime"`
Size float64 `json:"size"`
Status string `json:"status"`
Time time.Time `json:"time"`
TxID string `json:"txid"`
}
// OrderData stores open order data
type OrderData struct {
CreatedAt time.Time `json:"createdAt"`
FilledSize float64 `json:"filledSize"`
Future string `json:"future"`
ID int64 `json:"id"`
Market string `json:"market"`
Price float64 `json:"price"`
AvgFillPrice float64 `json:"avgFillPrice"`
RemainingSize float64 `json:"remainingSize"`
Side string `json:"side"`
Size float64 `json:"size"`
Status string `json:"status"`
OrderType string `json:"type"`
ReduceOnly bool `json:"reduceOnly"`
IOC bool `json:"ioc"`
PostOnly bool `json:"postOnly"`
ClientID string `json:"clientId"`
}
// TriggerOrderData stores trigger order data
type TriggerOrderData struct {
CreatedAt time.Time `json:"createdAt"`
Error string `json:"error"`
Future string `json:"future"`
ID int64 `json:"id"`
Market string `json:"market"`
OrderID int64 `json:"orderId"`
OrderPrice float64 `json:"orderPrice"`
ReduceOnly bool `json:"reduceOnly"`
Side string `json:"side"`
Size float64 `json:"size"`
Status string `json:"status"`
TrailStart float64 `json:"trailStart"`
TrailValue float64 `json:"trailvalue"`
TriggerPrice float64 `json:"triggerPrice"`
TriggeredAt string `json:"triggeredAt"`
OrderType string `json:"type"`
MarketOrLimit string `json:"orderType"`
FilledSize float64 `json:"filledSize"`
AvgFillPrice float64 `json:"avgFillPrice"`
RetryUntilFilled bool `json:"retryUntilFilled"`
}
// TriggerData stores trigger orders' trigger data
type TriggerData struct {
Error string `json:"error"`
FilledSize float64 `json:"filledSize"`
OrderSize float64 `json:"orderSize"`
OrderID int64 `json:"orderId"`
Time time.Time `json:"time"`
}
// FillsData stores fills' data
type FillsData struct {
Fee float64 `json:"fee"`
FeeRate float64 `json:"feeRate"`
Future string `json:"future"`
ID string `json:"id"`
Liquidity string `json:"liquidity"`
Market string `json:"market"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
OrderID string `json:"orderID"`
TradeID string `json:"tradeID"`
Price float64 `json:"price"`
Side string `json:"side"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
OrderType string `json:"type"`
}
// FundingPaymentsData stores funding payments' data
type FundingPaymentsData struct {
Future string `json:"future"`
ID string `json:"id"`
Payment float64 `json:"payment"`
Time time.Time `json:"time"`
Rate float64 `json:"rate"`
}
// LeveragedTokensData stores data of leveraged tokens
type LeveragedTokensData struct {
Basket map[string]interface{} `json:"basket"`
Bep2AssetName string `json:"bep2AssetName"`
Name string `json:"name"`
Description string `json:"description"`
Underlying string `json:"underlying"`
Leverage float64 `json:"leverage"`
Outstanding float64 `json:"outstanding"`
PricePerShare float64 `json:"pricePerShare"`
PositionPerShare float64 `json:"positionPerShare"`
PositionsPerShare interface{} `json:"positionsPerShare"`
TargetComponents []string `json:"targetComponents"`
TotalCollateral float64 `json:"totalCollateral"`
TotalNav float64 `json:"totalNav"`
UnderlyingMark float64 `json:"underlyingMark"`
ContactAddress string `json:"contactAddress"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
}
// LTBalanceData stores balances of leveraged tokens
type LTBalanceData struct {
Token string `json:"token"`
Balance float64 `json:"balance"`
}
// LTCreationData stores token creation requests' data
type LTCreationData struct {
ID string `json:"id"`
Token string `json:"token"`
RequestedSize float64 `json:"requestedSize"`
Pending bool `json:"pending"`
CreatedSize float64 `json:"createdize"`
Price float64 `json:"price"`
Cost float64 `json:"cost"`
Fee float64 `json:"fee"`
RequestedAt time.Time `json:"requestedAt"`
FulfilledAt time.Time `json:"fulfilledAt"`
}
// RequestTokenCreationData stores data of the token creation requested
type RequestTokenCreationData struct {
ID string `json:"id"`
Token string `json:"token"`
RequestedSize float64 `json:"requestedSize"`
Cost float64 `json:"cost"`
Pending bool `json:"pending"`
RequestedAt time.Time `json:"requestedAt"`
}
// LTRedemptionData stores data of the token redemption request
type LTRedemptionData struct {
ID int64 `json:"id"`
Token string `json:"token"`
Size float64 `json:"size"`
Pending bool `json:"pending"`
Price float64 `json:"price"`
Proceeds float64 `json:"proceeds"`
Fee float64 `json:"fee"`
RequestedAt time.Time `json:"requestedAt"`
FulfilledAt time.Time `json:"fulfilledAt"`
}
// LTRedemptionRequestData stores redemption request data for a leveraged token
type LTRedemptionRequestData struct {
ID string `json:"id"`
Token string `json:"token"`
Size float64 `json:"size"`
ProjectedProceeds float64 `json:"projectedProceeds"`
Pending bool `json:"pending"`
RequestedAt time.Time `json:"requestedAt"`
}
// OptionData stores options' data
type OptionData struct {
Underlying string `json:"underlying"`
OptionType string `json:"type"`
Strike float64 `json:"strike"`
Expiry time.Time `json:"expiry"`
}
// QuoteRequestData stores option's quote request data
type QuoteRequestData struct {
ID int64 `json:"id"`
Option OptionData `json:"option"`
Side string `json:"side"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
RequestExpiry string `json:"requestExpiry"`
Status string `json:"status"`
}
// QuoteData stores quote's data
type QuoteData struct {
Collateral float64 `json:"collateral"`
ID int64 `json:"id"`
Price float64 `json:"price"`
QuoteExpiry string `json:"quoteExpiry"`
Status string `json:"status"`
Time time.Time `json:"time"`
}
// PersonalQuotesData stores data of your quotes
type PersonalQuotesData struct {
ID int64 `json:"id"`
Option OptionData `json:"option"`
Side string `json:"side"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
RequestExpiry string `json:"requestExpiry"`
Status string `json:"status"`
HideLimitPrice bool `json:"hideLimitPrice"`
LimitPrice float64 `json:"limitPrice"`
Quotes []QuoteData `json:"quotes"`
}
// CreateQuoteRequestData stores quote data of the request sent
type CreateQuoteRequestData struct {
ID int64 `json:"id"`
Expiry time.Time `json:"expiry"`
Strike float64 `json:"strike"`
OptionType string `json:"type"`
Underlying string `json:"underlying"`
RequestExpiry string `json:"requestExpiry"`
Side string `json:"side"`
Size float64 `json:"size"`
Status string `json:"status"`
Time time.Time `json:"time"`
}
// CancelQuoteRequestData stores cancel quote request data
type CancelQuoteRequestData struct {
ID int64 `json:"id"`
Option OptionData `json:"option"`
RequestExpiry string `json:"requestExpiry"`
Side string `json:"side"`
Size float64 `json:"size"`
Status string `json:"status"`
Time time.Time `json:"time"`
}
// QuoteForQuoteData gets quote data for your quote
type QuoteForQuoteData struct {
Collateral float64 `json:"collateral"`
ID int64 `json:"id"`
Option OptionData `json:"option"`
Price float64 `json:"price"`
QuoteExpiry string `json:"quoteExpiry"`
QuoterSide string `json:"quoterSide"`
RequestID int64 `json:"requestID"`
RequestSide string `json:"requestSide"`
Size float64 `json:"size"`
Status string `json:"status"`
Time time.Time `json:"time"`
}
// AccountOptionsInfoData stores account's options' info data
type AccountOptionsInfoData struct {
USDBalance float64 `json:"usdBalance"`
LiquidationPrice float64 `json:"liquidationPrice"`
Liquidating bool `json:"liquidating"`
}
// OptionsPositionsData stores options positions' data
type OptionsPositionsData struct {
EntryPrice float64 `json:"entryPrice"`
NetSize float64 `json:"netSize"`
Option OptionData `json:"option"`
Side string `json:"side"`
Size float64 `json:"size"`
PessimisticValuation float64 `json:"pessimisticValuation,omitempty"`
PessimisticIndexPrice float64 `json:"pessimisticIndexPrice,omitempty"`
}
// OptionsTradesData stores options' trades' data
type OptionsTradesData struct {
ID int64 `json:"id"`
Option OptionData `json:"option"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
}
// OptionFillsData stores option's fills data
type OptionFillsData struct {
Fee float64 `json:"fee"`
FeeRate float64 `json:"feeRate"`
ID int64 `json:"id"`
Liquidity string `json:"liquidity"`
Option OptionData `json:"option"`
Price float64 `json:"price"`
QuoteID int64 `json:"quoteId"`
Side string `json:"side"`
Size float64 `json:"size"`
Time string `json:"time"`
}
// AuthenticationData stores authentication variables required
type AuthenticationData struct {
Key string `json:"key"`
Sign string `json:"sign"`
Time int64 `json:"time"`
}
// Authenticate stores authentication variables required
type Authenticate struct {
Args AuthenticationData `json:"args"`
Operation string `json:"op"`
}
// WsResponseData stores basic ws response data on being subscribed to a channel successfully
type WsResponseData struct {
ResponseType string `json:"type"`
Channel string `json:"channel"`
Market string `json:"market"`
Data interface{} `json:"data"`
}
// WsTickerData stores ws ticker data
type WsTickerData struct {
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
BidSize float64 `json:"bidSize"`
AskSize float64 `json:"askSize"`
Last float64 `json:"last"`
Time float64 `json:"time"`
}
// WsTradeData stores ws trade data
type WsTradeData struct {
ID int64 `json:"id"`
Price float64 `json:"price"`
Size float64 `json:"size"`
Side string `json:"side"`
Liquidation bool `json:"liquidation"`
Time time.Time `json:"time"`
}
// WsOrderbookData stores ws orderbook data
type WsOrderbookData struct {
Action string `json:"action"`
Bids [][2]float64 `json:"bids"`
Asks [][2]float64 `json:"asks"`
Time float64 `json:"time"`
Checksum int64 `json:"checksum"`
}
// WsOrders stores ws orders' data
type WsOrders struct {
ID int64 `json:"id"`
ClientID string `json:"clientId"`
Market string `json:"market"`
OrderType string `json:"type"`
Side string `json:"side"`
Size float64 `json:"size"`
Price float64 `json:"price"`
ReduceOnly bool `json:"reduceOnly"`
IOC bool `json:"ioc"`
PostOnly bool `json:"postOnly"`
Status string `json:"status"`
FilledSize float64 `json:"filedSize"`
RemainingSize float64 `json:"remainingSize"`
AvgFillPrice float64 `json:"avgFillPrice"`
}
// WsFills stores websocket fills' data
type WsFills struct {
Fee float64 `json:"fee"`
FeeRate float64 `json:"feeRate"`
Future string `json:"future"`
ID int64 `json:"id"`
Liquidity string `json:"liquidity"`
Market string `json:"market"`
OrderID int64 `json:"int64"`
TradeID int64 `json:"tradeID"`
Price float64 `json:"price"`
Side string `json:"side"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
OrderType string `json:"orderType"`
}
// WsSub has the data used to subscribe to a channel
type WsSub struct {
Channel string `json:"channel,omitempty"`
Market string `json:"market,omitempty"`
Operation string `json:"op,omitempty"`
}
// WsTickerDataStore stores ws ticker data
type WsTickerDataStore struct {
Channel string `json:"channel"`
Market string `json:"market"`
MessageType string `json:"type"`
Ticker WsTickerData `json:"data"`
}
// WsOrderbookDataStore stores ws orderbook data
type WsOrderbookDataStore struct {
Channel string `json:"channel"`
Market string `json:"market"`
MessageType string `json:"type"`
OBData WsOrderbookData `json:"data"`
}
// WsTradeDataStore stores ws trades' data
type WsTradeDataStore struct {
Channel string `json:"channel"`
Market string `json:"market"`
MessageType string `json:"type"`
TradeData []WsTradeData `json:"data"`
}
// WsOrderDataStore stores ws orders' data
type WsOrderDataStore struct {
Channel string `json:"channel"`
MessageType string `json:"type"`
OrderData WsOrders `json:"data"`
}
// WsFillsDataStore stores ws fills' data
type WsFillsDataStore struct {
Channel string `json:"channel"`
MessageType string `json:"type"`
FillsData WsFills `json:"fills"`
}
// TimeInterval represents interval enum.
type TimeInterval string
// Vars related to time intervals
var (
TimeIntervalFifteenSeconds = TimeInterval("15")
TimeIntervalMinute = TimeInterval("60")
TimeIntervalFiveMinutes = TimeInterval("300")
TimeIntervalFifteenMinutes = TimeInterval("900")
TimeIntervalHour = TimeInterval("3600")
TimeIntervalFourHours = TimeInterval("14400")
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
Status order.Status
OrderType order.Type
Fee float64
}
// WsMarketsData stores websocket markets data
type WsMarketsData struct {
Data map[string]WsMarketsDataStorage `json:"data"`
}
// WsMarketsDataStorage stores websocket markets data
type WsMarketsDataStorage struct {
Name string `json:"name,omitempty"`
Enabled bool `json:"enabled,omitempty"`
PriceIncrement float64 `json:"priceIncrement,omitempty"`
SizeIncrement float64 `json:"sizeIncrement,omitempty"`
MarketType string `json:"marketType,omitempty"`
BaseCurrency string `json:"baseCurrency,omitempty"`
QuoteCurrency string `json:"quoteCurrency,omitempty"`
Underlying string `json:"underlying,omitempty"`
Restricted bool `json:"restricted,omitempty"`
Future WsMarketsFutureData `json:"future,omitempty"`
}
// WsMarketsFutureData stores websocket markets' future data
type WsMarketsFutureData struct {
Name string `json:"name,omitempty"`
Underlying string `json:"underlying,omitempty"`
Description string `json:"description,omitempty"`
MarketType string `json:"type,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
Perpetual bool `json:"perpetual,omitempty"`
Expired bool `json:"expired,omitempty"`
Enabled bool `json:"enabled,omitempty"`
PostOnly bool `json:"postOnly,omitempty"`
IMFFactor float64 `json:"imfFactor,omitempty"`
UnderlyingDescription string `json:"underlyingDescription,omitempty"`
ExpiryDescription string `json:"expiryDescription,omitempty"`
MoveStart string `json:"moveStart,omitempty"`
PositionLimitWeight float64 `json:"positionLimitWeight,omitempty"`
Group string `json:"group,omitempty"`
}
// WSMarkets stores websocket markets data
type WSMarkets struct {
Channel string `json:"channel"`
MessageType string `json:"type"`
Data WsMarketsData `json:"data"`
Action string `json:"action"`
}
// RequestQuoteData stores data on the requested quote
type RequestQuoteData struct {
QuoteID int64 `json:"quoteId"`
}
// QuoteStatusData stores data of quotes' status
type QuoteStatusData struct {
BaseCoin string `json:"baseCoin"`
Cost float64 `json:"cost"`
Expired bool `json:"expired"`
Filled bool `json:"filled"`
FromCoin string `json:"fromCoin"`
ID int64 `json:"id"`
Price float64 `json:"price"`
Proceeds float64 `json:"proceeds"`
QuoteCoin string `json:"quoteCoin"`
Side string `json:"side"`
ToCoin string `json:"toCoin"`
}
// AcceptQuote stores data of accepted quote
type AcceptQuote struct {
Success bool `json:"success"`
}

View File

@@ -0,0 +1,511 @@
package ftx
import (
"encoding/json"
"errors"
"fmt"
"hash/crc32"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"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/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
ftxWSURL = "wss://ftx.com/ws/"
ftxWebsocketTimer = 13 * time.Second
wsTicker = "ticker"
wsTrades = "trades"
wsOrderbook = "orderbook"
wsMarkets = "markets"
wsFills = "fills"
wsOrders = "orders"
wsUpdate = "update"
wsPartial = "partial"
subscribe = "subscribe"
unsubscribe = "unsubscribe"
)
var obSuccess = make(map[currency.Pair]bool)
// WsConnect connects to a websocket feed
func (f *FTX) WsConnect() error {
if !f.Websocket.IsEnabled() || !f.IsEnabled() {
return errors.New(wshandler.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := f.WebsocketConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
f.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{
MessageType: websocket.PingMessage,
Delay: ftxWebsocketTimer,
})
if f.Verbose {
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", f.Name)
}
f.GenerateDefaultSubscriptions()
go f.wsReadData()
if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
err := f.WsAuth()
if err != nil {
f.Websocket.DataHandler <- err
f.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
f.GenerateAuthSubscriptions()
}
return nil
}
// WsAuth sends an authentication message to receive auth data
func (f *FTX) WsAuth() error {
intNonce := time.Now().UnixNano() / 1000000
strNonce := strconv.FormatInt(intNonce, 10)
hmac := crypto.GetHMAC(
crypto.HashSHA256,
[]byte(strNonce+"websocket_login"),
[]byte(f.API.Credentials.Secret),
)
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{Operation: "login",
Args: AuthenticationData{
Key: f.API.Credentials.Key,
Sign: sign,
Time: intNonce,
},
}
return f.WebsocketConn.SendJSONMessage(req)
}
// Subscribe sends a websocket message to receive data from the channel
func (f *FTX) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
var sub WsSub
switch channelToSubscribe.Channel {
case wsFills, wsOrders, wsMarkets:
sub.Operation = subscribe
sub.Channel = channelToSubscribe.Channel
default:
a, err := f.GetPairAssetType(channelToSubscribe.Currency)
if err != nil {
return err
}
sub.Operation = subscribe
sub.Channel = channelToSubscribe.Channel
sub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String()
}
return f.WebsocketConn.SendJSONMessage(sub)
}
// GenerateDefaultSubscriptions generates default subscription
func (f *FTX) GenerateDefaultSubscriptions() {
var subscriptions []wshandler.WebsocketChannelSubscription
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: wsMarkets,
})
var channels = []string{wsTicker, wsTrades, wsOrderbook}
for a := range f.CurrencyPairs.AssetTypes {
pairs := f.GetEnabledPairs(f.CurrencyPairs.AssetTypes[a])
for z := range pairs {
newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), pairs[z].Quote.String(), "-")
for x := range channels {
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[x],
Currency: newPair,
})
}
}
}
f.Websocket.SubscribeToChannels(subscriptions)
}
// GenerateAuthSubscriptions generates default subscription
func (f *FTX) GenerateAuthSubscriptions() {
var subscriptions []wshandler.WebsocketChannelSubscription
var channels = []string{wsOrders, wsFills}
for x := range channels {
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channels[x],
})
}
f.Websocket.SubscribeToChannels(subscriptions)
}
// wsReadData gets and passes on websocket messages for processing
func (f *FTX) wsReadData() {
f.Websocket.Wg.Add(1)
defer f.Websocket.Wg.Done()
for {
select {
case <-f.Websocket.ShutdownC:
return
default:
resp, err := f.WebsocketConn.ReadMessage()
if err != nil {
f.Websocket.ReadMessageErrors <- err
return
}
f.Websocket.TrafficAlert <- struct{}{}
err = f.wsHandleData(resp.Raw)
if err != nil {
f.Websocket.DataHandler <- err
}
}
}
}
func timestampFromFloat64(ts float64) time.Time {
secs := int64(ts)
nsecs := int64((ts - float64(secs)) * 1e9)
return time.Unix(secs, nsecs).UTC()
}
func (f *FTX) wsHandleData(respRaw []byte) error {
var result map[string]interface{}
err := json.Unmarshal(respRaw, &result)
if err != nil {
return err
}
switch result["type"] {
case wsUpdate:
var p currency.Pair
var a asset.Item
market, ok := result["market"]
if ok {
p = currency.NewPairFromString(market.(string))
a, err = f.GetPairAssetType(p)
if err != nil {
return err
}
}
switch result["channel"] {
case wsTicker:
var resultData WsTickerDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
f.Websocket.DataHandler <- &ticker.Price{
ExchangeName: f.Name,
Bid: resultData.Ticker.Bid,
Ask: resultData.Ticker.Ask,
Last: resultData.Ticker.Last,
LastUpdated: timestampFromFloat64(resultData.Ticker.Time),
Pair: p,
AssetType: a,
}
case wsOrderbook:
var resultData WsOrderbookDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
if len(resultData.OBData.Asks) == 0 && len(resultData.OBData.Bids) == 0 {
return nil
}
err = f.WsProcessUpdateOB(&resultData.OBData, p, a)
if err != nil {
f.wsResubToOB(p)
return err
}
case wsTrades:
var resultData WsTradeDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
for z := range resultData.TradeData {
var oSide order.Side
oSide, err = order.StringToOrderSide(resultData.TradeData[z].Side)
if err != nil {
f.Websocket.DataHandler <- order.ClassificationError{
Exchange: f.Name,
Err: err,
}
}
f.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: resultData.TradeData[z].Time,
CurrencyPair: p,
AssetType: a,
Exchange: f.Name,
Price: resultData.TradeData[z].Price,
Amount: resultData.TradeData[z].Size,
Side: oSide,
}
}
case wsOrders:
var resultData WsOrderDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
pair := currency.NewPairFromString(resultData.OrderData.Market)
var assetType asset.Item
assetType, err = f.GetPairAssetType(pair)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(resultData.OrderData.Side)
if err != nil {
f.Websocket.DataHandler <- order.ClassificationError{
Exchange: f.Name,
Err: err,
}
}
var resp order.Detail
resp.Side = oSide
resp.Amount = resultData.OrderData.Size
resp.AssetType = assetType
resp.ClientOrderID = resultData.OrderData.ClientID
resp.Exchange = f.Name
resp.ExecutedAmount = resultData.OrderData.FilledSize
resp.ID = strconv.FormatInt(resultData.OrderData.ID, 10)
resp.Pair = pair
resp.RemainingAmount = resultData.OrderData.Size - resultData.OrderData.FilledSize
var orderVars OrderVars
orderVars, err = f.compatibleOrderVars(resultData.OrderData.Side,
resultData.OrderData.Status,
resultData.OrderData.OrderType,
resultData.OrderData.FilledSize,
resultData.OrderData.Size,
resultData.OrderData.AvgFillPrice)
if err != nil {
return err
}
resp.Status = orderVars.Status
resp.Side = orderVars.Side
resp.Type = orderVars.OrderType
resp.Fee = orderVars.Fee
f.Websocket.DataHandler <- &resp
case wsFills:
var resultData WsFillsDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
f.Websocket.DataHandler <- resultData.FillsData
default:
f.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: f.Name + wshandler.UnhandledMessage + string(respRaw)}
}
case wsPartial:
switch result["channel"] {
case "orderbook":
var p currency.Pair
var a asset.Item
market, ok := result["market"]
if ok {
p = currency.NewPairFromString(market.(string))
a, err = f.GetPairAssetType(p)
if err != nil {
return err
}
}
var resultData WsOrderbookDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
err = f.WsProcessPartialOB(&resultData.OBData, p, a)
if err != nil {
f.wsResubToOB(p)
return err
}
// reset obchecksum failure blockage for pair
delete(obSuccess, p)
case wsMarkets:
var resultData WSMarkets
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
f.Websocket.DataHandler <- resultData.Data
}
case "error":
f.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: f.Name + wshandler.UnhandledMessage + string(respRaw)}
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
var unSub WsSub
a, err := f.GetPairAssetType(channelToSubscribe.Currency)
if err != nil {
return err
}
unSub.Operation = unsubscribe
unSub.Channel = channelToSubscribe.Channel
unSub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String()
return f.WebsocketConn.SendJSONMessage(unSub)
}
// WsProcessUpdateOB processes an update on the orderbook
func (f *FTX) WsProcessUpdateOB(data *WsOrderbookData, p currency.Pair, a asset.Item) error {
update := wsorderbook.WebsocketOrderbookUpdate{
Asset: a,
Pair: p,
UpdateTime: timestampFromFloat64(data.Time),
}
var err error
for x := range data.Bids {
update.Bids = append(update.Bids, orderbook.Item{
Price: data.Bids[x][0],
Amount: data.Bids[x][1],
})
}
for x := range data.Asks {
update.Asks = append(update.Asks, orderbook.Item{
Price: data.Asks[x][0],
Amount: data.Asks[x][1],
})
}
err = f.Websocket.Orderbook.Update(&update)
if err != nil {
return err
}
updatedOb := f.Websocket.Orderbook.GetOrderbook(p, a)
checksum := f.CalcUpdateOBChecksum(updatedOb)
if checksum != data.Checksum {
log.Warnf(log.ExchangeSys, "%s checksum failure for item %s",
f.Name,
p)
return errors.New("checksum failed")
}
f.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: f.Name,
Asset: a,
Pair: p,
}
return nil
}
func (f *FTX) wsResubToOB(p currency.Pair) {
if ok := obSuccess[p]; ok {
return
}
obSuccess[p] = true
channelToResubscribe := wshandler.WebsocketChannelSubscription{
Channel: wsOrderbook,
Currency: p,
}
f.Websocket.ResubscribeToChannel(channelToResubscribe)
}
// WsProcessPartialOB creates an OB from websocket data
func (f *FTX) WsProcessPartialOB(data *WsOrderbookData, p currency.Pair, a asset.Item) error {
signedChecksum := f.CalcPartialOBChecksum(data)
if signedChecksum != data.Checksum {
return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid",
f.Name,
a,
p)
}
var bids, asks []orderbook.Item
for x := range data.Bids {
bids = append(bids, orderbook.Item{
Price: data.Bids[x][0],
Amount: data.Bids[x][1],
})
}
for x := range data.Asks {
asks = append(asks, orderbook.Item{
Price: data.Asks[x][0],
Amount: data.Asks[x][1],
})
}
newOrderBook := orderbook.Base{
Asks: asks,
Bids: bids,
AssetType: a,
LastUpdated: timestampFromFloat64(data.Time),
Pair: p,
ExchangeName: f.Name,
}
if err := f.Websocket.Orderbook.LoadSnapshot(&newOrderBook); err != nil {
return err
}
f.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: f.Name,
Asset: a,
Pair: p,
}
return nil
}
// CalcPartialOBChecksum calculates checksum of partial OB data received from WS
func (f *FTX) CalcPartialOBChecksum(data *WsOrderbookData) int64 {
var checksum strings.Builder
var price, amount string
for i := 0; i < 100; i++ {
if len(data.Bids)-1 >= i {
price = checksumParseNumber(data.Bids[i][0])
amount = checksumParseNumber(data.Bids[i][1])
checksum.WriteString(price + ":" + amount + ":")
}
if len(data.Asks)-1 >= i {
price = checksumParseNumber(data.Asks[i][0])
amount = checksumParseNumber(data.Asks[i][1])
checksum.WriteString(price + ":" + amount + ":")
}
}
checksumStr := strings.TrimSuffix(checksum.String(), ":")
return int64(crc32.ChecksumIEEE([]byte(checksumStr)))
}
// CalcUpdateOBChecksum calculates checksum of update OB data received from WS
func (f *FTX) CalcUpdateOBChecksum(data *orderbook.Base) int64 {
var checksum strings.Builder
var price, amount string
for i := 0; i < 100; i++ {
if len(data.Bids)-1 >= i {
price = checksumParseNumber(data.Bids[i].Price)
amount = checksumParseNumber(data.Bids[i].Amount)
checksum.WriteString(price + ":" + amount + ":")
}
if len(data.Asks)-1 >= i {
price = checksumParseNumber(data.Asks[i].Price)
amount = checksumParseNumber(data.Asks[i].Amount)
checksum.WriteString(price + ":" + amount + ":")
}
}
checksumStr := strings.TrimSuffix(checksum.String(), ":")
return int64(crc32.ChecksumIEEE([]byte(checksumStr)))
}
func checksumParseNumber(num float64) string {
modifier := byte('f')
if num < 0.0001 {
modifier = 'e'
}
r := strconv.FormatFloat(num, modifier, -1, 64)
if strings.IndexByte(r, '.') == -1 && modifier != 'e' {
r += ".0"
}
return r
}

View File

@@ -0,0 +1,873 @@
package ftx
import (
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
// GetDefaultConfig returns a default exchange config
func (f *FTX) GetDefaultConfig() (*config.ExchangeConfig, error) {
f.SetDefaults()
exchCfg := new(config.ExchangeConfig)
exchCfg.Name = f.Name
exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout
exchCfg.BaseCurrencies = f.BaseCurrencies
err := f.SetupDefaults(exchCfg)
if err != nil {
return nil, err
}
if f.Features.Supports.RESTCapabilities.AutoPairUpdates {
err = f.UpdateTradablePairs(true)
if err != nil {
return nil, err
}
}
return exchCfg, nil
}
// SetDefaults sets the basic defaults for FTX
func (f *FTX) SetDefaults() {
f.Name = "FTX"
f.Enabled = true
f.Verbose = true
f.API.CredentialsValidator.RequiresKey = true
f.API.CredentialsValidator.RequiresSecret = true
f.CurrencyPairs = currency.PairsManager{
AssetTypes: asset.Items{
asset.Spot,
asset.Futures,
},
}
spot := currency.PairStore{
RequestFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: "/",
},
ConfigFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: "/",
},
}
futures := currency.PairStore{
RequestFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: "-",
},
ConfigFormat: &currency.PairFormat{
Uppercase: true,
Delimiter: "-",
},
}
f.CurrencyPairs.Store(asset.Spot, spot)
f.CurrencyPairs.Store(asset.Futures, futures)
f.Features = exchange.Features{
Supports: exchange.FeaturesSupported{
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerFetching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
TradeFee: true,
FiatDepositFee: true,
FiatWithdrawalFee: true,
CryptoWithdrawalFee: true,
},
WebsocketCapabilities: protocol.Features{
OrderbookFetching: true,
TradeFetching: true,
Subscribe: true,
Unsubscribe: true,
GetOrders: true,
GetOrder: true,
},
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
},
Enabled: exchange.FeaturesEnabled{
AutoPairUpdates: true,
},
}
f.Requester = request.New(f.Name,
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
request.WithLimiter(request.NewBasicRateLimit(ratePeriod, rateLimit)))
f.API.Endpoints.URLDefault = ftxAPIURL
f.API.Endpoints.URL = f.API.Endpoints.URLDefault
f.Websocket = wshandler.New()
f.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
f.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
f.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup takes in the supplied exchange configuration details and sets params
func (f *FTX) Setup(exch *config.ExchangeConfig) error {
if !exch.Enabled {
f.SetEnabled(false)
return nil
}
err := f.SetupDefaults(exch)
if err != nil {
return err
}
err = f.Websocket.Setup(
&wshandler.WebsocketSetup{
Enabled: exch.Features.Enabled.Websocket,
Verbose: exch.Verbose,
AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport,
WebsocketTimeout: exch.WebsocketTrafficTimeout,
DefaultURL: ftxWSURL,
ExchangeName: exch.Name,
RunningURL: exch.API.Endpoints.WebsocketURL,
Connector: f.WsConnect,
Subscriber: f.Subscribe,
UnSubscriber: f.Unsubscribe,
Features: &f.Features.Supports.WebsocketCapabilities,
})
if err != nil {
return err
}
f.WebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: f.Name,
URL: f.Websocket.GetWebsocketURL(),
ProxyURL: f.Websocket.GetProxyAddress(),
Verbose: f.Verbose,
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
f.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}
// Start starts the FTX go routine
func (f *FTX) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
f.Run()
wg.Done()
}()
}
// Run implements the FTX wrapper
func (f *FTX) Run() {
if f.Verbose {
log.Debugf(log.ExchangeSys,
"%s Websocket: %s.",
f.Name,
common.IsEnabled(f.Websocket.IsEnabled()))
f.PrintEnabledPairs()
}
if !f.GetEnabledFeatures().AutoPairUpdates {
return
}
err := f.UpdateTradablePairs(false)
if err != nil {
log.Errorf(log.ExchangeSys,
"%s failed to update tradable pairs. Err: %s",
f.Name,
err)
}
}
// FetchTradablePairs returns a list of the exchanges tradable pairs
func (f *FTX) FetchTradablePairs(a asset.Item) ([]string, error) {
if !f.SupportsAsset(a) {
return nil, fmt.Errorf("asset type of %s is not supported by %s", a, f.Name)
}
markets, err := f.GetMarkets()
if err != nil {
return nil, err
}
var pairs []string
switch a {
case asset.Spot:
for x := range markets {
if markets[x].MarketType == spotString {
pairs = append(pairs, markets[x].Name)
}
}
case asset.Futures:
for x := range markets {
if markets[x].MarketType == futuresString {
pairs = append(pairs, markets[x].Name)
}
}
}
return pairs, nil
}
// UpdateTradablePairs updates the exchanges available pairs and stores
// them in the exchanges config
func (f *FTX) UpdateTradablePairs(forceUpdate bool) error {
for x := range f.CurrencyPairs.AssetTypes {
pairs, err := f.FetchTradablePairs(f.CurrencyPairs.AssetTypes[x])
if err != nil {
return err
}
err = f.UpdatePairs(currency.NewPairsFromStrings(pairs),
f.CurrencyPairs.AssetTypes[x], false, forceUpdate)
if err != nil {
return err
}
}
return nil
}
// UpdateTicker updates and returns the ticker for a currency pair
func (f *FTX) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
allPairs := f.GetEnabledPairs(assetType)
if !allPairs.Contains(p, true) {
allPairs = append(allPairs, p)
}
markets, err := f.GetMarkets()
if err != nil {
return nil, err
}
for a := range allPairs {
for x := range markets {
if markets[x].Name != f.FormatExchangeCurrency(allPairs[a], assetType).String() {
continue
}
var resp ticker.Price
resp.Pair = currency.NewPairFromString(markets[x].Name)
resp.Last = markets[x].Last
resp.Bid = markets[x].Bid
resp.Ask = markets[x].Ask
resp.LastUpdated = time.Now()
err = ticker.ProcessTicker(f.Name, &resp, assetType)
if err != nil {
return nil, err
}
}
}
return ticker.GetTicker(f.Name, p, assetType)
}
// FetchTicker returns the ticker for a currency pair
func (f *FTX) FetchTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
tickerNew, err := ticker.GetTicker(f.Name, p, assetType)
if err != nil {
return f.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// FetchOrderbook returns orderbook base on the currency pair
func (f *FTX) FetchOrderbook(currency currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
ob, err := orderbook.Get(f.Name, currency, assetType)
if err != nil {
return f.UpdateOrderbook(currency, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (f *FTX) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
orderBook := new(orderbook.Base)
tempResp, err := f.GetOrderbook(f.FormatExchangeCurrency(p, assetType).String(), 0)
if err != nil {
return orderBook, err
}
for x := range tempResp.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{
Amount: tempResp.Bids[x].Size,
Price: tempResp.Bids[x].Price})
}
for y := range tempResp.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{
Amount: tempResp.Asks[y].Size,
Price: tempResp.Asks[y].Price})
}
orderBook.Pair = p
orderBook.ExchangeName = f.Name
orderBook.AssetType = assetType
err = orderBook.Process()
if err != nil {
return orderBook, err
}
return orderbook.Get(f.Name, p, assetType)
}
// UpdateAccountInfo retrieves balances for all enabled currencies
func (f *FTX) UpdateAccountInfo() (account.Holdings, error) {
var resp account.Holdings
data, err := f.GetBalances()
if err != nil {
return resp, err
}
var acc account.SubAccount
for i := range data {
c := currency.NewCode(data[i].Coin)
hold := data[i].Total - data[i].Free
total := data[i].Total
acc.Currencies = append(acc.Currencies,
account.Balance{CurrencyName: c,
TotalValue: total,
Hold: hold})
}
resp.Accounts = append(resp.Accounts, acc)
resp.Exchange = f.Name
err = account.Process(&resp)
if err != nil {
return account.Holdings{}, err
}
return resp, nil
}
// FetchAccountInfo retrieves balances for all enabled currencies
func (f *FTX) FetchAccountInfo() (account.Holdings, error) {
acc, err := account.GetHoldings(f.Name)
if err != nil {
return f.UpdateAccountInfo()
}
return acc, nil
}
// GetFundingHistory returns funding history, deposits and
// withdrawals
func (f *FTX) GetFundingHistory() ([]exchange.FundHistory, error) {
var resp []exchange.FundHistory
depositData, err := f.FetchDepositHistory()
if err != nil {
return resp, err
}
for x := range depositData {
var tempData exchange.FundHistory
tempData.Fee = depositData[x].Fee
tempData.Timestamp = depositData[x].Time
tempData.ExchangeName = f.Name
tempData.CryptoTxID = depositData[x].TxID
tempData.Status = depositData[x].Status
tempData.Amount = depositData[x].Size
tempData.Currency = depositData[x].Coin
tempData.TransferID = strconv.FormatInt(depositData[x].ID, 10)
resp = append(resp, tempData)
}
withdrawalData, err := f.FetchWithdrawalHistory()
if err != nil {
return resp, err
}
for y := range withdrawalData {
var tempData exchange.FundHistory
tempData.Fee = depositData[y].Fee
tempData.Timestamp = depositData[y].Time
tempData.ExchangeName = f.Name
tempData.CryptoTxID = depositData[y].TxID
tempData.Status = depositData[y].Status
tempData.Amount = depositData[y].Size
tempData.Currency = depositData[y].Coin
tempData.TransferID = strconv.FormatInt(depositData[y].ID, 10)
resp = append(resp, tempData)
}
return resp, nil
}
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (f *FTX) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
marketName := f.FormatExchangeCurrency(p, assetType).String()
var resp []exchange.TradeHistory
trades, err := f.GetTrades(marketName, time.Unix(timestampStart.Unix(), 0), time.Unix(timestampEnd.Unix(), 0), 100)
if err != nil {
return nil, err
}
for {
var tempResp exchange.TradeHistory
if len(trades) > 0 {
tempResp.Amount = trades[0].Size
tempResp.Price = trades[0].Price
tempResp.Exchange = f.Name
tempResp.Timestamp = trades[0].Time
tempResp.TID = strconv.FormatInt(trades[0].ID, 10)
tempResp.Side = trades[0].Side
resp = append(resp, tempResp)
}
for y := 1; y < len(trades); y++ {
tempResp.Amount = trades[y].Size
tempResp.Price = trades[y].Price
tempResp.Exchange = f.Name
tempResp.Timestamp = trades[y].Time
tempResp.TID = strconv.FormatInt(trades[y].ID, 10)
tempResp.Side = trades[y].Side
resp = append(resp, tempResp)
}
if len(trades) != 100 {
break
}
trades, err = f.GetTrades(marketName, time.Unix(timestampStart.Unix(), 0), time.Unix(trades[len(trades)-1].Time.Unix(), 0), 100)
if err != nil {
return resp, err
}
}
return resp, nil
}
// SubmitOrder submits a new order
func (f *FTX) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
var resp order.SubmitResponse
if err := s.Validate(); err != nil {
return resp, err
}
if s.Side == order.Sell {
s.Side = order.Ask
}
if s.Side == order.Buy {
s.Side = order.Bid
}
tempResp, err := f.Order(f.FormatExchangeCurrency(s.Pair, s.AssetType).String(),
s.Side.String(),
s.Type.String(),
"",
"",
"",
s.ClientOrderID,
s.Price,
s.Amount)
if err != nil {
return resp, err
}
resp.IsOrderPlaced = true
resp.OrderID = strconv.FormatInt(tempResp.ID, 10)
return resp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to
// market conversion
func (f *FTX) ModifyOrder(action *order.Modify) (string, error) {
if action.TriggerPrice != 0 {
a, err := f.ModifyTriggerOrder(action.ID,
action.Type.String(),
action.Amount,
action.TriggerPrice,
action.Price,
0)
if err != nil {
return "", err
}
return strconv.FormatInt(a.ID, 10), err
}
var o OrderData
var err error
switch action.ID {
case "":
o, err = f.ModifyOrderByClientID(action.ClientOrderID, action.ClientOrderID, action.Price, action.Amount)
if err != nil {
return "", err
}
default:
o, err = f.ModifyPlacedOrder(action.ID, action.ClientOrderID, action.Price, action.Amount)
if err != nil {
return "", err
}
}
return strconv.FormatInt(o.ID, 10), err
}
// CancelOrder cancels an order by its corresponding ID number
func (f *FTX) CancelOrder(order *order.Cancel) error {
_, err := f.DeleteOrder(order.ID)
return err
}
// CancelAllOrders cancels all orders associated with a currency pair
func (f *FTX) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
var resp order.CancelAllResponse
tempMap := make(map[string]string)
orders, err := f.GetOpenOrders(f.FormatExchangeCurrency(orderCancellation.Pair, orderCancellation.AssetType).String())
if err != nil {
return resp, err
}
for x := range orders {
_, err := f.DeleteOrder(strconv.FormatInt(orders[x].ID, 10))
if err != nil {
tempMap[strconv.FormatInt(orders[x].ID, 10)] = "Cancellation Failed"
continue
}
tempMap[strconv.FormatInt(orders[x].ID, 10)] = "Success"
}
resp.Status = tempMap
return resp, nil
}
// GetCompatible gets compatible variables for order vars
func (s *OrderData) GetCompatible(f *FTX) (OrderVars, error) {
var resp OrderVars
switch s.Side {
case order.Buy.Lower():
resp.Side = order.Buy
case order.Sell.Lower():
resp.Side = order.Sell
}
switch s.Status {
case strings.ToLower(order.New.String()):
resp.Status = order.New
case strings.ToLower(order.Open.String()):
resp.Status = order.Open
case closedStatus:
if s.FilledSize != 0 && s.FilledSize != s.Size {
resp.Status = order.PartiallyCancelled
}
if s.FilledSize == 0 {
resp.Status = order.Cancelled
}
if s.FilledSize == s.Size {
resp.Status = order.Filled
}
}
var feeBuilder exchange.FeeBuilder
feeBuilder.PurchasePrice = s.AvgFillPrice
feeBuilder.Amount = s.Size
resp.OrderType = order.Market
if strings.EqualFold(s.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
}
// GetOrderInfo returns information on a current open order
func (f *FTX) GetOrderInfo(orderID string) (order.Detail, error) {
var resp order.Detail
orderData, err := f.GetOrderStatus(orderID)
if err != nil {
return resp, err
}
p := currency.NewPairFromString(orderData.Market)
assetType, err := f.GetPairAssetType(p)
if err != nil {
return resp, err
}
resp.ID = strconv.FormatInt(orderData.ID, 10)
resp.Amount = orderData.Size
resp.ClientOrderID = orderData.ClientID
resp.Date = orderData.CreatedAt
resp.Exchange = f.Name
resp.ExecutedAmount = orderData.Size - orderData.RemainingSize
resp.Pair = p
resp.AssetType = assetType
resp.Price = orderData.Price
resp.RemainingAmount = orderData.RemainingSize
orderVars, err := orderData.GetCompatible(f)
if err != nil {
return resp, err
}
resp.Status = orderVars.Status
resp.Side = orderVars.Side
resp.Type = orderVars.OrderType
resp.Fee = orderVars.Fee
return resp, nil
}
// 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())
if err != nil {
return "", err
}
return a.Address, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
// submitted
func (f *FTX) WithdrawCryptocurrencyFunds(withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
var address, addressTag string
if withdrawRequest.Crypto != nil {
address = withdrawRequest.Crypto.Address
addressTag = withdrawRequest.Crypto.AddressTag
}
resp := withdraw.ExchangeResponse{}
a, err := f.Withdraw(withdrawRequest.Currency.String(),
address,
addressTag,
withdrawRequest.TradePassword,
strconv.FormatInt(withdrawRequest.OneTimePassword, 10),
withdrawRequest.Amount)
if err != nil {
return &resp, err
}
resp.ID = strconv.FormatInt(a.ID, 10)
resp.Status = a.Status
return &resp, nil
}
// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
// submitted
func (f *FTX) WithdrawFiatFunds(_ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
return nil, common.ErrFunctionNotSupported
}
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
// withdrawal is submitted
func (f *FTX) WithdrawFiatFundsToInternationalBank(_ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
return nil, common.ErrFunctionNotSupported
}
// GetWebsocket returns a pointer to the exchange websocket
func (f *FTX) GetWebsocket() (*wshandler.Websocket, error) {
return f.Websocket, nil
}
// GetActiveOrders retrieves any orders that are active/open
func (f *FTX) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
var resp []order.Detail
for x := range getOrdersRequest.Pairs {
assetType, err := f.GetPairAssetType(getOrdersRequest.Pairs[x])
if err != nil {
return resp, err
}
var tempResp order.Detail
orderData, err := f.GetOpenOrders(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String())
if err != nil {
return resp, err
}
for y := range orderData {
tempResp.ID = strconv.FormatInt(orderData[y].ID, 10)
tempResp.Amount = orderData[y].Size
tempResp.AssetType = assetType
tempResp.ClientOrderID = orderData[y].ClientID
tempResp.Date = orderData[y].CreatedAt
tempResp.Exchange = f.Name
tempResp.ExecutedAmount = orderData[y].Size - orderData[y].RemainingSize
tempResp.Pair = currency.NewPairFromString(orderData[y].Market)
tempResp.Price = orderData[y].Price
tempResp.RemainingAmount = orderData[y].RemainingSize
var orderVars OrderVars
orderVars, err = f.compatibleOrderVars(orderData[y].Side,
orderData[y].Status,
orderData[y].OrderType,
orderData[y].FilledSize,
orderData[y].Size,
orderData[y].AvgFillPrice)
if err != nil {
return resp, err
}
tempResp.Status = orderVars.Status
tempResp.Side = orderVars.Side
tempResp.Type = orderVars.OrderType
tempResp.Fee = orderVars.Fee
resp = append(resp, tempResp)
}
triggerOrderData, err := f.GetOpenTriggerOrders(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(), getOrdersRequest.Type.String())
if err != nil {
return resp, err
}
for z := range triggerOrderData {
tempResp.ID = strconv.FormatInt(triggerOrderData[z].ID, 10)
tempResp.Amount = triggerOrderData[z].Size
tempResp.AssetType = assetType
tempResp.Date = triggerOrderData[z].CreatedAt
tempResp.Exchange = f.Name
tempResp.ExecutedAmount = triggerOrderData[z].FilledSize
tempResp.Pair = currency.NewPairFromString(triggerOrderData[z].Market)
tempResp.Price = triggerOrderData[z].AvgFillPrice
tempResp.RemainingAmount = triggerOrderData[z].Size - triggerOrderData[z].FilledSize
tempResp.TriggerPrice = triggerOrderData[z].TriggerPrice
orderVars, err := f.compatibleOrderVars(triggerOrderData[z].Side,
triggerOrderData[z].Status,
triggerOrderData[z].OrderType,
triggerOrderData[z].FilledSize,
triggerOrderData[z].Size,
triggerOrderData[z].AvgFillPrice)
if err != nil {
return resp, err
}
tempResp.Status = orderVars.Status
tempResp.Side = orderVars.Side
tempResp.Type = orderVars.OrderType
tempResp.Fee = orderVars.Fee
resp = append(resp, tempResp)
}
}
return resp, nil
}
// GetOrderHistory retrieves account order information
// Can Limit response to specific order status
func (f *FTX) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
var resp []order.Detail
for x := range getOrdersRequest.Pairs {
var tempResp order.Detail
assetType, err := f.GetPairAssetType(getOrdersRequest.Pairs[x])
if err != nil {
return resp, err
}
orderData, err := f.FetchOrderHistory(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(),
getOrdersRequest.StartTicks, getOrdersRequest.EndTicks, "")
if err != nil {
return resp, err
}
for y := range orderData {
tempResp.ID = strconv.FormatInt(orderData[y].ID, 10)
tempResp.Amount = orderData[y].Size
tempResp.AssetType = assetType
tempResp.ClientOrderID = orderData[y].ClientID
tempResp.Date = orderData[y].CreatedAt
tempResp.Exchange = f.Name
tempResp.ExecutedAmount = orderData[y].Size - orderData[y].RemainingSize
tempResp.Pair = currency.NewPairFromString(orderData[y].Market)
tempResp.Price = orderData[y].Price
tempResp.RemainingAmount = orderData[y].RemainingSize
var orderVars OrderVars
orderVars, err = f.compatibleOrderVars(orderData[y].Side,
orderData[y].Status,
orderData[y].OrderType,
orderData[y].FilledSize,
orderData[y].Size,
orderData[y].AvgFillPrice)
if err != nil {
return resp, err
}
tempResp.Status = orderVars.Status
tempResp.Side = orderVars.Side
tempResp.Type = orderVars.OrderType
tempResp.Fee = orderVars.Fee
resp = append(resp, tempResp)
}
triggerOrderData, err := f.GetTriggerOrderHistory(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(),
getOrdersRequest.StartTicks, getOrdersRequest.EndTicks, strings.ToLower(getOrdersRequest.Side.String()), strings.ToLower(getOrdersRequest.Type.String()), "")
if err != nil {
return resp, err
}
for z := range triggerOrderData {
tempResp.ID = strconv.FormatInt(triggerOrderData[z].ID, 10)
tempResp.Amount = triggerOrderData[z].Size
tempResp.AssetType = assetType
tempResp.Date = triggerOrderData[z].CreatedAt
tempResp.Exchange = f.Name
tempResp.ExecutedAmount = triggerOrderData[z].FilledSize
tempResp.Pair = currency.NewPairFromString(triggerOrderData[z].Market)
tempResp.Price = triggerOrderData[z].AvgFillPrice
tempResp.RemainingAmount = triggerOrderData[z].Size - triggerOrderData[z].FilledSize
tempResp.TriggerPrice = triggerOrderData[z].TriggerPrice
orderVars, err := f.compatibleOrderVars(triggerOrderData[z].Side,
triggerOrderData[z].Status,
triggerOrderData[z].OrderType,
triggerOrderData[z].FilledSize,
triggerOrderData[z].Size,
triggerOrderData[z].AvgFillPrice)
if err != nil {
return resp, err
}
tempResp.Status = orderVars.Status
tempResp.Side = orderVars.Side
tempResp.Type = orderVars.OrderType
tempResp.Fee = orderVars.Fee
resp = append(resp, tempResp)
}
}
return resp, nil
}
// GetFeeByType returns an estimate of fee based on the type of transaction
func (f *FTX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) {
return f.GetFee(feeBuilder)
}
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func (f *FTX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
f.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func (f *FTX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
f.Websocket.RemoveSubscribedChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func (f *FTX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
return f.Websocket.GetSubscriptions(), nil
}
// AuthenticateWebsocket sends an authentication message to the websocket
func (f *FTX) AuthenticateWebsocket() error {
return f.WsAuth()
}
// ValidateCredentials validates current credentials used for wrapper
// functionality
func (f *FTX) ValidateCredentials() error {
_, err := f.UpdateAccountInfo()
return f.CheckTransientError(err)
}
// GetHistoricCandles returns candles between a time period for a set time interval
func (f *FTX) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
intervalToString, err := parseInterval(interval)
if err != nil {
return kline.Item{}, err
}
var resp kline.Item
ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(pair, a).String(),
string(intervalToString), "", start, end)
if err != nil {
return resp, err
}
resp.Exchange = f.Name
resp.Asset = a
resp.Pair = pair
for x := range ohlcData {
var tempData kline.Candle
tempData.Open = ohlcData[x].Open
tempData.High = ohlcData[x].High
tempData.Low = ohlcData[x].Low
tempData.Close = ohlcData[x].Close
tempData.Volume = ohlcData[x].Volume
tempData.Time = ohlcData[x].StartTime
resp.Candles = append(resp.Candles, tempData)
}
return resp, nil
}

View File

@@ -408,8 +408,8 @@ func (g *Gateio) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (g *Gateio) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -330,8 +330,8 @@ func (g *Gemini) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (g *Gemini) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -386,8 +386,8 @@ func (h *HitBTC) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (h *HitBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -498,8 +498,8 @@ func (h *HUOBI) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (h *HUOBI) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -39,7 +39,7 @@ type IBotExchange interface {
GetAuthenticatedAPISupport(endpoint uint8) bool
SetPairs(pairs currency.Pairs, a asset.Item, enabled bool) error
GetAssetTypes() asset.Items
GetExchangeHistory(p currency.Pair, a asset.Item) ([]TradeHistory, error)
GetExchangeHistory(p currency.Pair, a asset.Item, startTime, endTime time.Time) ([]TradeHistory, error)
SupportsAutoPairUpdates() bool
SupportsRESTTickerBatchUpdates() bool
GetFeeByType(f *FeeBuilder) (float64, error)

View File

@@ -304,8 +304,8 @@ func (i *ItBit) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (i *ItBit) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -9,19 +9,20 @@ import (
// Consts here define basic time intervals
const (
OneMin = time.Minute
ThreeMin = 3 * time.Minute
FiveMin = 5 * time.Minute
FifteenMin = 15 * time.Minute
ThirtyMin = 30 * time.Minute
OneHour = 1 * time.Hour
TwoHour = 2 * time.Hour
FourHour = 4 * time.Hour
SixHour = 6 * time.Hour
TwelveHour = 12 * time.Hour
OneDay = 24 * time.Hour
ThreeDay = 72 * time.Hour
OneWeek = 168 * time.Hour
FifteenSecond = 15 * time.Second
OneMin = time.Minute
ThreeMin = 3 * time.Minute
FiveMin = 5 * time.Minute
FifteenMin = 15 * time.Minute
ThirtyMin = 30 * time.Minute
OneHour = 1 * time.Hour
TwoHour = 2 * time.Hour
FourHour = 4 * time.Hour
SixHour = 6 * time.Hour
TwelveHour = 12 * time.Hour
OneDay = 24 * time.Hour
ThreeDay = 72 * time.Hour
OneWeek = 168 * time.Hour
)
// Item holds all the relevant information for internal kline elements

View File

@@ -450,8 +450,8 @@ func (k *Kraken) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (k *Kraken) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -335,8 +335,8 @@ func (l *LakeBTC) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (l *LakeBTC) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -304,8 +304,8 @@ func (l *Lbank) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (l *Lbank) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (l *Lbank) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrFunctionNotSupported
}

View File

@@ -285,8 +285,8 @@ func (l *LocalBitcoins) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (l *LocalBitcoins) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
@@ -261,8 +262,8 @@ func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) {
return resp, err
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -43,6 +43,7 @@ type Submit struct {
ID string
AccountID string
ClientID string
ClientOrderID string
WalletAddress string
Type Type
Side Side
@@ -83,6 +84,7 @@ type Modify struct {
Exchange string
InternalOrderID string
ID string
ClientOrderID string
AccountID string
ClientID string
WalletAddress string
@@ -122,6 +124,7 @@ type Detail struct {
Exchange string
InternalOrderID string
ID string
ClientOrderID string
AccountID string
ClientID string
WalletAddress string
@@ -145,6 +148,7 @@ type Cancel struct {
Amount float64
Exchange string
ID string
ClientOrderID string
AccountID string
ClientID string
WalletAddress string

View File

@@ -370,8 +370,8 @@ func (p *Poloniex) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (p *Poloniex) GetExchangeHistory(currencyPair currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -27,6 +27,7 @@ var Exchanges = []string{
"coinbene",
"coinut",
"exmo",
"ftx",
"gateio",
"gemini",
"hitbtc",

View File

@@ -321,8 +321,8 @@ func (y *Yobit) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (y *Yobit) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -374,8 +374,8 @@ func (z *ZB) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
// GetExchangeHistory returns historic trade data within the timeframe provided.
func (z *ZB) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}