mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-20 23:16:49 +00:00
(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:
@@ -30,6 +30,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| BTSE | Yes | Yes | NA |
|
||||
| COINUT | Yes | Yes | NA |
|
||||
| Exmo | Yes | NA | NA |
|
||||
| FTX | Yes | Yes | No |
|
||||
| CoinbasePro | Yes | Yes | No|
|
||||
| Coinbene | Yes | No | No |
|
||||
| GateIO | Yes | Yes | NA |
|
||||
@@ -139,10 +140,10 @@ Binaries will be published once the codebase reaches a stable condition.
|
||||
|
||||
|User|Contribution Amount|
|
||||
|--|--|
|
||||
| [thrasher-](https://github.com/thrasher-) | 639 |
|
||||
| [thrasher-](https://github.com/thrasher-) | 640 |
|
||||
| [shazbert](https://github.com/shazbert) | 191 |
|
||||
| [gloriousCode](https://github.com/gloriousCode) | 169 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 46 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 48 |
|
||||
| [xtda](https://github.com/xtda) | 43 |
|
||||
| [ermalguni](https://github.com/ermalguni) | 14 |
|
||||
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
|
||||
|
||||
@@ -31,8 +31,10 @@ const (
|
||||
backupFile = "backup.json"
|
||||
github = "GitHub Sha Check"
|
||||
htmlScrape = "HTML String Check"
|
||||
pathBinance = "https://binance-docs.github.io/apidocs/spot/en/#change-log"
|
||||
pathOkCoin = "https://www.okcoin.com/docs/en/#change-change"
|
||||
pathOkex = "https://www.okex.com/docs/en/#change-change"
|
||||
pathFTX = "https://github.com/ftexchange/ftx"
|
||||
pathBTSE = "https://www.btse.com/apiexplorer/spot/#btse-spot-api"
|
||||
pathBitfinex = "https://docs.bitfinex.com/docs/changelog"
|
||||
pathBitmex = "https://www.bitmex.com/static/md/en-US/apiChangelog"
|
||||
@@ -448,8 +450,12 @@ func checkChangeLog(htmlData *HTMLScrapingData) (string, error) {
|
||||
var dataStrings []string
|
||||
var err error
|
||||
switch htmlData.Path {
|
||||
case pathBinance:
|
||||
dataStrings, err = htmlScrapeBinance(htmlData)
|
||||
case pathBTSE:
|
||||
dataStrings, err = htmlScrapeBTSE(htmlData)
|
||||
case pathFTX:
|
||||
dataStrings, err = htmlScrapeFTX(htmlData)
|
||||
case pathBitfinex:
|
||||
dataStrings, err = htmlScrapeBitfinex(htmlData)
|
||||
case pathBitmex:
|
||||
@@ -637,8 +643,7 @@ loop:
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
result := r.MatchString(tempStr)
|
||||
if result {
|
||||
if r.MatchString(tempStr) {
|
||||
appendStr := r.FindString(tempStr)
|
||||
resp = append(resp, appendStr)
|
||||
}
|
||||
@@ -686,39 +691,6 @@ loop:
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBitfinex gets the check string for Bitfinex exchange
|
||||
func htmlScrapeBitfinex(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
a, err := ioutil.ReadAll(temp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
str := r.FindAllString(string(a), -1)
|
||||
var resp []string
|
||||
for x := range str {
|
||||
tempStr := strings.Replace(str[x], "section-v-", "", 1)
|
||||
var repeat bool
|
||||
for y := range resp {
|
||||
if tempStr == resp[y] {
|
||||
repeat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !repeat {
|
||||
resp = append(resp, tempStr)
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBitmex gets the check string for Bitmex exchange
|
||||
func htmlScrapeBitmex(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
@@ -813,59 +785,6 @@ func htmlScrapeBTCMarkets(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBitflyer gets the check string for BTCMarkets exchange
|
||||
func htmlScrapeBitflyer(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
var tempArray []string
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
tokenizer := html.NewTokenizer(temp.Body)
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
for {
|
||||
nextToken := tokenizer.Next()
|
||||
switch nextToken {
|
||||
case html.EndTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TokenDataEnd {
|
||||
break loop
|
||||
}
|
||||
case html.StartTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TextTokenData {
|
||||
inner := tokenizer.Next()
|
||||
if inner == html.TextToken {
|
||||
tempStr := string(tokenizer.Text())
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
result := r.MatchString(tempStr)
|
||||
if result {
|
||||
appendStr := r.FindString(tempStr)
|
||||
tempArray = append(tempArray, appendStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resp = append(resp, tempArray[1])
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeOk gets the check string for Okex
|
||||
func htmlScrapeOk(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
@@ -1111,62 +1030,6 @@ func htmlScrapeBitstamp(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeKraken gets the check string for Kraken Exchange
|
||||
func htmlScrapeKraken(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
tokenizer := html.NewTokenizer(temp.Body)
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
inner := tokenizer.Next()
|
||||
if inner == html.TextToken {
|
||||
if string(tokenizer.Text()) == "Get account balance" {
|
||||
loop2:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.EndTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TokenDataEnd {
|
||||
break loop2
|
||||
}
|
||||
case html.StartTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TextTokenData {
|
||||
inside := tokenizer.Next()
|
||||
if inside == html.TextToken {
|
||||
tempStr := string(tokenizer.Text())
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
result := r.MatchString(tempStr)
|
||||
if result {
|
||||
resp = append(resp, tempStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeAlphaPoint gets the check string for Kraken Exchange
|
||||
func htmlScrapeAlphaPoint(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
@@ -1610,3 +1473,288 @@ func trelloCreateNewChecklist() error {
|
||||
}
|
||||
return errors.New("checklist id and name not found, checklist creation failed")
|
||||
}
|
||||
|
||||
// htmlScrapeKraken gets the check string for Kraken Exchange
|
||||
func htmlScrapeKraken(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
tokenizer := html.NewTokenizer(temp.Body)
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
inner := tokenizer.Next()
|
||||
if inner == html.TextToken {
|
||||
if string(tokenizer.Text()) == "Get account balance" {
|
||||
loop2:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.EndTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TokenDataEnd {
|
||||
break loop2
|
||||
}
|
||||
case html.StartTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TextTokenData {
|
||||
inside := tokenizer.Next()
|
||||
if inside == html.TextToken {
|
||||
tempStr := string(tokenizer.Text())
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
result := r.MatchString(tempStr)
|
||||
if result {
|
||||
resp = append(resp, tempStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBitflyer gets the check string for BTCMarkets exchange
|
||||
func htmlScrapeBitflyer(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
var resp []string
|
||||
var tempArray []string
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
tokenizer := html.NewTokenizer(temp.Body)
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
for {
|
||||
nextToken := tokenizer.Next()
|
||||
switch nextToken {
|
||||
case html.EndTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TokenDataEnd {
|
||||
break loop
|
||||
}
|
||||
case html.StartTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data == htmlData.TextTokenData {
|
||||
inner := tokenizer.Next()
|
||||
if inner == html.TextToken {
|
||||
tempStr := string(tokenizer.Text())
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
result := r.MatchString(tempStr)
|
||||
if result {
|
||||
appendStr := r.FindString(tempStr)
|
||||
tempArray = append(tempArray, appendStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resp = append(resp, tempArray[1])
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeFTX gets the check string for FTX exchange
|
||||
func htmlScrapeFTX(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
a := temp.Body
|
||||
tokenizer := html.NewTokenizer(a)
|
||||
var respStr string
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
for _, a := range token.Attr {
|
||||
if a.Key == htmlData.Key && a.Val == htmlData.Val {
|
||||
loop2:
|
||||
for {
|
||||
anotherToken := tokenizer.Next()
|
||||
switch anotherToken {
|
||||
case html.StartTagToken:
|
||||
z := tokenizer.Token()
|
||||
if z.Data == "a" {
|
||||
for _, m := range z.Attr {
|
||||
if m.Key == "title" {
|
||||
switch m.Val {
|
||||
case "rest":
|
||||
loop3:
|
||||
for {
|
||||
nextToken := tokenizer.Next()
|
||||
switch nextToken {
|
||||
case html.StartTagToken:
|
||||
f := tokenizer.Token()
|
||||
if f.Data == "time-ago" {
|
||||
for _, b := range f.Attr {
|
||||
if b.Key == "datetime" {
|
||||
respStr += b.Val
|
||||
}
|
||||
}
|
||||
}
|
||||
case html.EndTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == htmlData.TokenDataEnd {
|
||||
break loop3
|
||||
}
|
||||
}
|
||||
}
|
||||
case "websocket":
|
||||
loop4:
|
||||
for {
|
||||
nextToken := tokenizer.Next()
|
||||
switch nextToken {
|
||||
case html.StartTagToken:
|
||||
f := tokenizer.Token()
|
||||
if f.Data == "time-ago" {
|
||||
for _, b := range f.Attr {
|
||||
if b.Key == "datetime" {
|
||||
respStr += b.Val
|
||||
}
|
||||
}
|
||||
}
|
||||
case html.EndTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == htmlData.TokenDataEnd {
|
||||
break loop4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case html.ErrorToken:
|
||||
break loop2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []string{respStr}, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBitfinex gets the check string for Bitfinex exchange
|
||||
func htmlScrapeBitfinex(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
a, err := ioutil.ReadAll(temp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
str := r.FindAllString(string(a), -1)
|
||||
var resp []string
|
||||
for x := range str {
|
||||
tempStr := strings.Replace(str[x], "section-v-", "", 1)
|
||||
var repeat bool
|
||||
for y := range resp {
|
||||
if tempStr == resp[y] {
|
||||
repeat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !repeat {
|
||||
resp = append(resp, tempStr)
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeBinance gets checkstring for binance exchange
|
||||
func htmlScrapeBinance(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
temp, err := http.Get(htmlData.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
tokenizer := html.NewTokenizer(temp.Body)
|
||||
var resp []string
|
||||
loop:
|
||||
for {
|
||||
next := tokenizer.Next()
|
||||
switch next {
|
||||
case html.ErrorToken:
|
||||
break loop
|
||||
case html.StartTagToken:
|
||||
token := tokenizer.Token()
|
||||
if token.Data == htmlData.TokenData {
|
||||
for _, a := range token.Attr {
|
||||
if a.Key == htmlData.Key && a.Val == htmlData.Val {
|
||||
loop2:
|
||||
for {
|
||||
nextToken := tokenizer.Next()
|
||||
switch nextToken {
|
||||
case html.EndTagToken:
|
||||
nt := tokenizer.Token()
|
||||
if nt.Data == htmlData.TokenDataEnd {
|
||||
break loop2
|
||||
}
|
||||
case html.StartTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == htmlData.TextTokenData {
|
||||
inner := tokenizer.Next()
|
||||
if inner == html.TextToken {
|
||||
tempStr := string(tokenizer.Text())
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if r.MatchString(tempStr) {
|
||||
resp = append(resp, tempStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -148,13 +148,15 @@ func TestCheckChangeLog(t *testing.T) {
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
data2 := HTMLScrapingData{TokenData: "a",
|
||||
Key: "href",
|
||||
Val: "./#change-change",
|
||||
TokenDataEnd: "./#change-",
|
||||
RegExp: `./#change-\d{8}`,
|
||||
Path: "wrongpath"}
|
||||
err := addExch("WrongExch", htmlScrape, data2, false)
|
||||
data2 := HTMLScrapingData{
|
||||
TokenData: "h1",
|
||||
Key: "id",
|
||||
Val: "change-log",
|
||||
TextTokenData: "strong",
|
||||
TokenDataEnd: "p",
|
||||
Path: "incorrectpath",
|
||||
}
|
||||
err := addExch("FalseName", htmlScrape, data2, false)
|
||||
if err == nil {
|
||||
t.Log("expected an error due to invalid path being parsed in")
|
||||
}
|
||||
@@ -418,8 +420,6 @@ func TestHTMLYobit(t *testing.T) {
|
||||
func TestHTMLScrapeLocalBitcoins(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := HTMLScrapingData{TokenData: "div",
|
||||
Key: "class",
|
||||
Val: "col-md-12",
|
||||
RegExp: `col-md-12([\s\S]*?)clearfix`,
|
||||
Path: "https://localbitcoins.com/api-docs/"}
|
||||
_, err := htmlScrapeLocalBitcoins(&data)
|
||||
@@ -701,3 +701,31 @@ func TestTrelloDeleteCheckItems(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLScrapeFTX(t *testing.T) {
|
||||
data := HTMLScrapingData{
|
||||
TokenData: "span",
|
||||
Key: "class",
|
||||
Val: "css-truncate css-truncate-target d-block width-fit",
|
||||
TokenDataEnd: "svg",
|
||||
Path: "https://github.com/ftexchange/ftx"}
|
||||
a, err := htmlScrapeFTX(&data)
|
||||
if err != nil || len(a) != 1 {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLScrapeBinance(t *testing.T) {
|
||||
data := HTMLScrapingData{
|
||||
TokenData: "h1",
|
||||
Key: "id",
|
||||
Val: "change-log",
|
||||
TextTokenData: "strong",
|
||||
TokenDataEnd: "p",
|
||||
Path: "https://binance-docs.github.io/apidocs/spot/en/#change-log",
|
||||
}
|
||||
_, err := htmlScrapeBinance(&data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
430
cmd/apichecker/backup.json
Normal file
430
cmd/apichecker/backup.json
Normal file
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"CardID": "",
|
||||
"ChecklistID": "",
|
||||
"ListID": "",
|
||||
"BoardID": "",
|
||||
"Key": "",
|
||||
"Token": "",
|
||||
"CreateCardName": "",
|
||||
"CreateListName": "",
|
||||
"CreateChecklistName": "",
|
||||
"Exchanges": [
|
||||
{
|
||||
"Name": "Huobi",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "change-log",
|
||||
"TokenDataEnd": "h2",
|
||||
"TextTokenData": "td",
|
||||
"DateFormat": "2006.01.02 15:04",
|
||||
"RegExp": "^20(\\d){2}.(\\d){2}.(\\d){2} (\\d){2}:(\\d){2}$",
|
||||
"CheckString": "2019.12.27 19:00",
|
||||
"Path": "https://huobiapi.github.io/docs/spot/v1/en/#change-log"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Coinbasepro",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "changelog",
|
||||
"TokenDataEnd": "ul",
|
||||
"TextTokenData": "strong",
|
||||
"DateFormat": "01/02/06",
|
||||
"RegExp": "^(\\d){1,2}/(\\d){1,2}/(\\d){2}$",
|
||||
"CheckString": "2/20/20",
|
||||
"Path": "https://docs.pro.coinbase.com/#changelog"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Binance",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "binance-exchange/binance-official-api-docs",
|
||||
"Sha": "4878d48adc0075669ba85033b0e2d40c2876cf56"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bithumb",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "bithumb-pro/bithumb.pro-official-api-docs",
|
||||
"Sha": "6293502c7736ab4971491978225ef4d104bdff31"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bittrex",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "Bittrex/bittrex.github.io",
|
||||
"Sha": "fc1ea9c10c48aa82c4dc2c6be74887ef61b5b31b"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "CoinbeneSpot",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "Coinbene/API-SPOT-v2-Documents",
|
||||
"Sha": "e9135a782ba6016bcf008778be368882ad7c784d"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "CoinbeneSwap",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "Coinbene/API-SWAP-Documents",
|
||||
"Sha": "6b7871dae4d2af028a33dde956fbce101e2f9acd"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Coinut",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "coinut/api",
|
||||
"Sha": "6936dab4d2beba3c8245a603aebf3f545ebcf3f9"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Gateio",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "gateio/gateapi-go",
|
||||
"Sha": "81e2f1bb92c5406853139ae054bbda599483e127"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Lbank",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "LBank-exchange/lbank-official-api-docs",
|
||||
"Sha": "85e7bb83f03d4239c3aad26cabb997a1a2bbb3d1"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "BTSE",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "btse-spot-api",
|
||||
"TokenDataEnd": "blockquote",
|
||||
"TextTokenData": "h1",
|
||||
"RegExp": "^BTSE Spot API v(\\d){1}.(\\d){1}$",
|
||||
"CheckString": "BTSE Spot API v3.0.2",
|
||||
"Path": "https://www.btse.com/apiexplorer/spot/#btse-spot-api"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bitfinex",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "class",
|
||||
"Val": "header-scroll",
|
||||
"TokenDataEnd": "p",
|
||||
"DateFormat": "2006-01-02",
|
||||
"RegExp": "section-v-(2\\d{3}-\\d{1,2}-\\d{1,2})",
|
||||
"CheckString": "2019-08-19",
|
||||
"Path": "https://docs.bitfinex.com/docs/changelog"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "ANX",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"RegExp": "ANX Exchange API v\\d{1}",
|
||||
"CheckString": "ANX Exchange API v3",
|
||||
"Path": "https://anxv3.docs.apiary.io/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Poloniex",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "changelog",
|
||||
"TokenDataEnd": "div",
|
||||
"TextTokenData": "h2",
|
||||
"DateFormat": "2006-01-02",
|
||||
"RegExp": "(2\\d{3}-\\d{1,2}-\\d{1,2})",
|
||||
"CheckString": "2020-03-24",
|
||||
"Path": "https://docs.poloniex.com/#changelog"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "ItBit",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "a",
|
||||
"Key": "href",
|
||||
"RegExp": "^https://api.itbit.com/v\\d{1}/$",
|
||||
"CheckString": "https://api.itbit.com/v1/",
|
||||
"Path": "https://api.itbit.com/docs"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bitmex",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h4",
|
||||
"Key": "id",
|
||||
"DateFormat": "Jan-2-2006",
|
||||
"RegExp": "([A-Z]{1}[a-z]{2}-\\d{1,2}-2\\d{3})",
|
||||
"CheckString": "Dec-16-2019",
|
||||
"Path": "https://www.bitmex.com/static/md/en-US/apiChangelog"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "HitBTC",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "about-companyname-api",
|
||||
"TokenDataEnd": "h2",
|
||||
"TextTokenData": "p",
|
||||
"RegExp": "newest version \\d{1}.\\d{1}",
|
||||
"CheckString": "newest version 2.0",
|
||||
"Path": "https://api.hitbtc.com/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "BTC Markets",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"RegExp": "^version: \\d{1}.\\d{1}.\\d{1}",
|
||||
"CheckString": "version: 3.0.0",
|
||||
"Path": "https://api.btcmarkets.net/openapi/info/index.yaml"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bitflyer",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "p",
|
||||
"TokenDataEnd": "h3",
|
||||
"TextTokenData": "code",
|
||||
"RegExp": "^https://api.bitflyer.com/v\\d{1}/$",
|
||||
"CheckString": "https://api.bitflyer.com/v1/",
|
||||
"Path": "https://lightning.bitflyer.com/docs?lang=en"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "LakeBTC",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "div",
|
||||
"Key": "class",
|
||||
"Val": "flash-message",
|
||||
"TokenDataEnd": "h2",
|
||||
"TextTokenData": "h1",
|
||||
"RegExp": "APIv\\d{1}",
|
||||
"CheckString": "de2491b95ef1f6ea334247b13f0f14f6816fb5961cc63acc0542b07fc0336dd8",
|
||||
"Path": "https://www.lakebtc.com/s/api_v2"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Exmo",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"RegExp": "Last updated on [\\s\\S]*, 20\\d{2}",
|
||||
"CheckString": "Last updated on December, 16th, 2019",
|
||||
"Path": "https://exmo.com/en/api/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Kraken",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h3",
|
||||
"TokenDataEnd": "p",
|
||||
"TextTokenData": "p",
|
||||
"RegExp": "URL: https://api.kraken.com/\\d{1}/private/Balance",
|
||||
"CheckString": "URL: https://api.kraken.com/0/private/Balance",
|
||||
"Path": "https://www.kraken.com/features/api"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bitstamp",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h2",
|
||||
"Key": "class",
|
||||
"Val": "text-subtitle mt48",
|
||||
"TokenDataEnd": "h4",
|
||||
"TextTokenData": "p",
|
||||
"RegExp": "refer to the v\\d{1} API for future references.",
|
||||
"CheckString": "refer to the v2 API for future references.",
|
||||
"Path": "https://www.bitstamp.net/api/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "AlphaPoint",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "introduction",
|
||||
"TokenDataEnd": "blockquote",
|
||||
"TextTokenData": "h3",
|
||||
"RegExp": "revised-calls-\\d{1}-\\d{1}-\\d{1}-gt-\\d{1}-\\d{1}-\\d{1}",
|
||||
"CheckString": "revised-calls-3-3-2-gt-3-3-3",
|
||||
"Path": "https://alphapoint.github.io/slate/#introduction"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Yobit",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h2",
|
||||
"Key": "id",
|
||||
"CheckString": "v3",
|
||||
"Path": "https://www.yobit.net/en/api/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "LocalBitcoins",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "div",
|
||||
"Key": "class",
|
||||
"Val": "col-md-12",
|
||||
"RegExp": "col-md-12([\\s\\S]*?)clearfix",
|
||||
"CheckString": "37a144dc619776b87c098da5a88bef7fed6c8a7cea2d4b9a38c96750726c93ff",
|
||||
"Path": "https://localbitcoins.com/api-docs/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "OkCoin International",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "a",
|
||||
"Key": "href",
|
||||
"Val": "./#change-change",
|
||||
"TokenDataEnd": "./#change-",
|
||||
"RegExp": "./#change-\\d{8}",
|
||||
"CheckString": "20200229",
|
||||
"Path": "https://www.okcoin.com/docs/en/#change-change"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Okex",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "a",
|
||||
"Key": "href",
|
||||
"Val": "./#change-change",
|
||||
"TokenDataEnd": "./#change-",
|
||||
"RegExp": "./#change-\\d{8}",
|
||||
"CheckString": "20200331",
|
||||
"Path": "https://www.okex.com/docs/en/#change-change"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Gemini",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "revision-history",
|
||||
"TokenDataEnd": "table",
|
||||
"TextTokenData": "td",
|
||||
"DateFormat": "2006/01/02",
|
||||
"RegExp": "^20(\\d){2}/(\\d){2}/(\\d){2}$",
|
||||
"CheckString": "2020/03/05",
|
||||
"Path": "https://docs.gemini.com/rest-api/#revision-history"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -47,11 +47,16 @@
|
||||
},
|
||||
{
|
||||
"Name": "Binance",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "binance-exchange/binance-official-api-docs",
|
||||
"Sha": "4878d48adc0075669ba85033b0e2d40c2876cf56"
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "change-log",
|
||||
"TokenDataEnd": "p",
|
||||
"TextTokenData": "strong",
|
||||
"CheckString": "2020-05-06",
|
||||
"Path": "https://binance-docs.github.io/apidocs/spot/en/#change-log"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
@@ -425,6 +430,20 @@
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "FTX",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"Key": "class",
|
||||
"Val": "css-truncate css-truncate-target",
|
||||
"TokenDataEnd": "td",
|
||||
"CheckString": "65e8800b5c6800aad896f888b2a62afc-d29c4f140f6ca068db9970054076ba63e51a9e96bf56d570e62cdb573c86b18526296117-1c388b9ec24e52ca4537240db3f48025ec9fca7a",
|
||||
"Path": "https://github.com/ftexchange/ftx"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -46,15 +46,20 @@
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Binance",
|
||||
"CheckType": "GitHub Sha Check",
|
||||
"Data": {
|
||||
"GitHubData": {
|
||||
"Repo": "binance-exchange/binance-official-api-docs",
|
||||
"Sha": "4878d48adc0075669ba85033b0e2d40c2876cf56"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
"Name": "Binance",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "h1",
|
||||
"Key": "id",
|
||||
"Val": "change-log",
|
||||
"TokenDataEnd": "p",
|
||||
"TextTokenData": "strong",
|
||||
"CheckString": "2020-05-06",
|
||||
"Path": "https://binance-docs.github.io/apidocs/spot/en/#change-log"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Bithumb",
|
||||
@@ -425,6 +430,20 @@
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "FTX",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"Key": "class",
|
||||
"Val": "css-truncate css-truncate-target",
|
||||
"TokenDataEnd": "td",
|
||||
"CheckString": "65e8800b5c6800aad896f888b2a62afc-d29c4f140f6ca068db9970054076ba63e51a9e96bf56d570e62cdb573c86b18526296117-1c388b9ec24e52ca4537240db3f48025ec9fca7a",
|
||||
"Path": "https://github.com/ftexchange/ftx"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,10 @@
|
||||
+ 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
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
|
||||
99
cmd/documentation/exchanges_templates/ftx.tmpl
Normal file
99
cmd/documentation/exchanges_templates/ftx.tmpl
Normal file
@@ -0,0 +1,99 @@
|
||||
{{define "exchanges ftx" -}}
|
||||
{{template "header" .}}
|
||||
## 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
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -31,6 +31,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| BTSE | Yes | Yes | NA |
|
||||
| COINUT | Yes | Yes | NA |
|
||||
| Exmo | Yes | NA | NA |
|
||||
| FTX | Yes | Yes | No |
|
||||
| CoinbasePro | Yes | Yes | No|
|
||||
| Coinbene | Yes | No | No |
|
||||
| GateIO | Yes | Yes | NA |
|
||||
|
||||
0
cmd/exchange_template/readme_temp.tmpl
Normal file
0
cmd/exchange_template/readme_temp.tmpl
Normal file
@@ -298,8 +298,8 @@ func ({{.Variable}} *{{.CapitalName}}) GetFundingHistory() ([]exchange.FundHisto
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetExchangeHistory returns historic trade data since exchange opening.
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType asset.Item) ([]exchange.TradeHistory, error) {
|
||||
// GetExchangeHistory returns historic trade data within the timeframe provided.
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ func testWrappers(e exchange.IBotExchange) []string {
|
||||
funcs = append(funcs, "GetAccountInfo")
|
||||
}
|
||||
|
||||
_, err = e.GetExchangeHistory(p, assetType)
|
||||
_, err = e.GetExchangeHistory(p, assetType, time.Time{}, time.Time{})
|
||||
if err == common.ErrNotYetImplemented {
|
||||
funcs = append(funcs, "GetExchangeHistory")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
@@ -415,14 +416,14 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
})
|
||||
|
||||
var r8 []exchange.TradeHistory
|
||||
r8, err = e.GetExchangeHistory(p, assetTypes[i])
|
||||
r8, err = e.GetExchangeHistory(p, assetTypes[i], time.Now().Add(-time.Minute), time.Now())
|
||||
msg = ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
responseContainer.ErrorCount++
|
||||
}
|
||||
responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{
|
||||
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}),
|
||||
SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], time.Now().Add(-time.Minute), time.Now()}),
|
||||
Function: "GetExchangeHistory",
|
||||
Error: msg,
|
||||
Response: jsonifyInterface([]interface{}{r8}),
|
||||
@@ -506,9 +507,10 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config)
|
||||
})
|
||||
// r13
|
||||
cancelRequest := order.Cancel{
|
||||
Side: testOrderSide,
|
||||
Pair: p,
|
||||
ID: config.OrderSubmission.OrderID,
|
||||
Side: testOrderSide,
|
||||
Pair: p,
|
||||
ID: config.OrderSubmission.OrderID,
|
||||
AssetType: assetTypes[i],
|
||||
}
|
||||
err = e.CancelOrder(&cancelRequest)
|
||||
msg = ""
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
const (
|
||||
// Default number of enabled exchanges. Modify this whenever an exchange is
|
||||
// added or removed
|
||||
defaultEnabledExchanges = 27
|
||||
defaultEnabledExchanges = 28
|
||||
testFakeExchangeName = "Stampbit"
|
||||
testPair = "BTC-USD"
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
)
|
||||
@@ -268,7 +269,17 @@ func (b *BaseCodes) UpdateContract(fullName, symbol, assocExchange string) error
|
||||
|
||||
// Register registers a currency from a string and returns a currency code
|
||||
func (b *BaseCodes) Register(c string) Code {
|
||||
NewUpperCode := strings.ToUpper(c)
|
||||
NewUpperCode := c
|
||||
lower := true
|
||||
for _, r := range c {
|
||||
if !unicode.IsLower(r) {
|
||||
lower = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if lower {
|
||||
NewUpperCode = strings.ToUpper(c)
|
||||
}
|
||||
format := strings.Contains(c, NewUpperCode)
|
||||
|
||||
b.mtx.Lock()
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
// a Pair struct
|
||||
func NewPairDelimiter(currencyPair, delimiter string) Pair {
|
||||
result := strings.Split(currencyPair, delimiter)
|
||||
if len(result) > 2 {
|
||||
result[1] = strings.Join(result[1:], delimiter)
|
||||
}
|
||||
return Pair{
|
||||
Delimiter: delimiter,
|
||||
Base: NewCode(result[0]),
|
||||
|
||||
@@ -337,6 +337,26 @@ func TestNewPairDelimiter(t *testing.T) {
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
|
||||
pair = NewPairDelimiter("BTC-MOVE-0626", "-")
|
||||
actual = pair.String()
|
||||
expected = "BTC-MOVE-0626"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Pair(): %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
|
||||
pair = NewPairDelimiter("fBTC-USDT", "-")
|
||||
actual = pair.String()
|
||||
expected = "fBTC-USDT"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Pair(): %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewPairFromIndex returns a CurrencyPair via a currency string and
|
||||
|
||||
1084
docs/ADD_NEW_EXCHANGE.md
Normal file
1084
docs/ADD_NEW_EXCHANGE.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/coinbene"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/coinut"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/exmo"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ftx"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/gateio"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/gemini"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/hitbtc"
|
||||
@@ -187,6 +188,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
|
||||
exch = new(exmo.EXMO)
|
||||
case "coinbasepro":
|
||||
exch = new(coinbasepro.CoinbasePro)
|
||||
case "ftx":
|
||||
exch = new(ftx.FTX)
|
||||
case "gateio":
|
||||
exch = new(gateio.Gateio)
|
||||
case "gemini":
|
||||
|
||||
@@ -107,7 +107,7 @@ func (h *FakePassingExchange) SetPairs(_ currency.Pairs, _ asset.Item, _ bool) e
|
||||
return nil
|
||||
}
|
||||
func (h *FakePassingExchange) GetAssetTypes() asset.Items { return asset.Items{asset.Spot} }
|
||||
func (h *FakePassingExchange) GetExchangeHistory(_ currency.Pair, _ asset.Item) ([]exchange.TradeHistory, error) {
|
||||
func (h *FakePassingExchange) GetExchangeHistory(_ currency.Pair, _ asset.Item, _, _ time.Time) ([]exchange.TradeHistory, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (h *FakePassingExchange) SupportsAutoPairUpdates() bool { return true }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] = ¤cy.PairStore{
|
||||
Enabled: currency.Pairs{
|
||||
Available: currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USD),
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "-"},
|
||||
|
||||
@@ -115,6 +115,7 @@ type TradeHistory struct {
|
||||
Amount float64
|
||||
Exchange string
|
||||
Type string
|
||||
Side string
|
||||
Fee float64
|
||||
Description string
|
||||
}
|
||||
|
||||
@@ -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
133
exchanges/ftx/README.md
Normal 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">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/ftx)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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
959
exchanges/ftx/ftx.go
Normal 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
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
726
exchanges/ftx/ftx_types.go
Normal 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"`
|
||||
}
|
||||
511
exchanges/ftx/ftx_websocket.go
Normal file
511
exchanges/ftx/ftx_websocket.go
Normal 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
|
||||
}
|
||||
873
exchanges/ftx/ftx_wrapper.go
Normal file
873
exchanges/ftx/ftx_wrapper.go
Normal 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: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "/",
|
||||
},
|
||||
ConfigFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "/",
|
||||
},
|
||||
}
|
||||
futures := currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: "-",
|
||||
},
|
||||
ConfigFormat: ¤cy.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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ var Exchanges = []string{
|
||||
"coinbene",
|
||||
"coinut",
|
||||
"exmo",
|
||||
"ftx",
|
||||
"gateio",
|
||||
"gemini",
|
||||
"hitbtc",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ func TestExchange_Exchanges(t *testing.T) {
|
||||
t.Parallel()
|
||||
x := exchangeTest.Exchanges(false)
|
||||
y := len(x)
|
||||
if y != 27 {
|
||||
t.Fatalf("expected 27 received %v", y)
|
||||
if y != 28 {
|
||||
t.Fatalf("expected 28 received %v", y)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
go.sum
3
go.sum
@@ -160,7 +160,9 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 h1:DQVOxR9qdYEybJUr/c7ku34r3PfajaMYXZwgDM7KuSk=
|
||||
github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -231,6 +233,7 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
||||
57
testdata/configtest.json
vendored
57
testdata/configtest.json
vendored
@@ -2430,6 +2430,63 @@
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FTX",
|
||||
"enabled": true,
|
||||
"verbose": false,
|
||||
"httpTimeout": 0,
|
||||
"websocketResponseCheckTimeout": 0,
|
||||
"websocketResponseMaxLimit": 0,
|
||||
"websocketTrafficTimeout": 0,
|
||||
"websocketOrderbookBufferLimit": 0,
|
||||
"baseCurrencies": "USD",
|
||||
"currencyPairs": {
|
||||
"assetTypes": [
|
||||
"spot",
|
||||
"futures"
|
||||
],
|
||||
"pairs": {
|
||||
"futures": {
|
||||
"enabled": "DOGE-PERP",
|
||||
"available": "ADA-PERP,ADA-0626,ALGO-PERP,ALGO-0626,ALT-PERP,ALT-0626,ATOM-PERP,ATOM-0626,BCH-PERP,BCH-0626,BNB-PERP,BNB-0626,BSV-PERP,BSV-0626,BTC-PERP,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-0626,BTC-MOVE,BTC-0925,BTC-MOVE,BTC-MOVE,BTMX-PERP,BTMX-0626,DOGE-PERP,DOGE-0626,DRGN-PERP,DRGN-0626,EOS-PERP,EOS-0626,ETC-PERP,ETC-0626,ETH-PERP,ETH-0626,EXCH-PERP,EXCH-0626,HT-PERP,HT-0626,LEO-PERP,LEO-0626,LINK-PERP,LINK-0626,LTC-PERP,LTC-0626,MATIC-PERP,MATIC-0626,MID-PERP,MID-0626,OIL100-0525,OKB-PERP,OKB-0626,PAXG-PERP,PAXG-0626,BERNIE,BIDEN,BLOOMBERG,PETE,TRUMP,WARREN,PRIV-PERP,PRIV-0626,SHIT-PERP,SHIT-0626,TOMO-PERP,TOMO-0626,TRX-PERP,TRX-0626,TRYB-PERP,TRYB-0626,USDT-PERP,USDT-0626,XAUT-PERP,XAUT-0626,XRP-PERP,XRP-0626,XTZ-PERP,XTZ-0626",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "-"
|
||||
}
|
||||
},
|
||||
"spot": {
|
||||
"enabled": "BTC/USD",
|
||||
"available": "BCH/USD,BCH/USDT,BNB/USD,BNB/USDT,BTC/USD,BTC/USDT,BTMX/USD,ETH/USD,ETH/USDT,FTT/BTC,FTT/USD,FTT/USDT,LINK/USD,LINK/USDT,LTC/USD,LTC/USDT,PAXG/USD,PAXG/USDT,TRX/USD,TRX/USDT,TRYB/USD,USDT/USD,XAUT/USD,XAUT/USDT,ADABEAR/USD,ADABULL/USD,ADAHALF/USD,ADAHALF/USDT,ADAHEDGE/USD,ALGOBEAR/USD,ALGOBULL/USD,ALGOHALF/USD,ALGOHALF/USDT,ALGOHEDGE/USD,ALTBEAR/USD,ALTBULL/USD,ALTHALF/USD,ALTHALF/USDT,ALTHEDGE/USD,ATOMBEAR/USD,ATOMBULL/USD,ATOMHALF/USD,ATOMHALF/USDT,ATOMHEDGE/USD,BCHBEAR/USD,BCHBEAR/USDT,BCHBULL/USD,BCHBULL/USDT,BCHHALF/USD,BCHHALF/USDT,BCHHEDGE/USD,BEAR/USD,BEAR/USDT,BEARSHIT/USD,BNBBEAR/USD,BNBBEAR/USDT,BNBBULL/USD,BNBBULL/USDT,BNBHALF/USD,BNBHALF/USDT,BNBHEDGE/USD,BSVBEAR/USD,BSVBEAR/USDT,BSVBULL/USD,BSVBULL/USDT,BSVHALF/USD,BSVHALF/USDT,BSVHEDGE/USD,BTMXBEAR/USD,BTMXBEAR/USDT,BTMXBULL/USD,BTMXBULL/USDT,BTMXHALF/USD,BTMXHALF/USDT,BTMXHEDGE/USD,BULL/USD,BULL/USDT,BULLSHIT/USD,BVOL/USD,BVOL/USDT,DOGEBEAR/USD,DOGEBULL/USD,DOGEHALF/USD,DOGEHALF/USDT,DOGEHEDGE/USD,DRGNBEAR/USD,DRGNBULL/USD,DRGNHALF/USD,DRGNHALF/USDT,DRGNHEDGE/USD,EOSBEAR/USD,EOSBEAR/USDT,EOSBULL/USD,EOSBULL/USDT,EOSHALF/USD,EOSHALF/USDT,EOSHEDGE/USD,ETCBEAR/USD,ETCBULL/USD,ETCHALF/USD,ETCHALF/USDT,ETCHEDGE/USD,ETHBEAR/USD,ETHBEAR/USDT,ETHBULL/USD,ETHBULL/USDT,ETHHALF/USD,ETHHALF/USDT,ETHHEDGE/USD,EXCHBEAR/USD,EXCHBULL/USD,EXCHHALF/USD,EXCHHALF/USDT,EXCHHEDGE/USD,HALF/USD,HALF/USDT,HALFSHIT/USD,HALFSHIT/USDT,HEDGE/USD,HEDGESHIT/USD,HTBEAR/USD,HTBULL/USD,HTHALF/USD,HTHALF/USDT,HTHEDGE/USD,IBVOL/USD,IBVOL/USDT,LEOBEAR/USD,LEOBULL/USD,LEOHALF/USD,LEOHALF/USDT,LEOHEDGE/USD,LINKBEAR/USD,LINKBEAR/USDT,LINKBULL/USD,LINKBULL/USDT,LINKHALF/USD,LINKHALF/USDT,LINKHEDGE/USD,LTCBEAR/USD,LTCBEAR/USDT,LTCBULL/USD,LTCBULL/USDT,LTCHALF/USD,LTCHALF/USDT,LTCHEDGE/USD,MATICBEAR/USD,MATICBULL/USD,MATICHALF/USD,MATICHALF/USDT,MATICHEDGE/USD,MIDBEAR/USD,MIDBULL/USD,MIDHALF/USD,MIDHALF/USDT,MIDHEDGE/USD,OKBBEAR/USD,OKBBULL/USD,OKBHALF/USD,OKBHALF/USDT,OKBHEDGE/USD,PAXGBEAR/USD,PAXGBULL/USD,PAXGHALF/USD,PAXGHALF/USDT,PAXGHEDGE/USD,PRIVBEAR/USD,PRIVBULL/USD,PRIVHALF/USD,PRIVHALF/USDT,PRIVHEDGE/USD,TOMOBEAR/USD,TOMOBULL/USD,TOMOHALF/USD,TOMOHALF/USDT,TOMOHEDGE/USD,TRXBEAR/USD,TRXBULL/USD,TRXHALF/USD,TRXHALF/USDT,TRXHEDGE/USD,TRYBBEAR/USD,TRYBBULL/USD,TRYBHALF/USD,TRYBHALF/USDT,TRYBHEDGE/USD,USDTBEAR/USD,USDTBULL/USD,USDTHALF/USD,USDTHALF/USDT,USDTHEDGE/USD,XAUTBEAR/USD,XAUTBULL/USD,XAUTHALF/USD,XAUTHALF/USDT,XAUTHEDGE/USD,XRPBEAR/USD,XRPBEAR/USDT,XRPBULL/USD,XRPBULL/USDT,XRPHALF/USD,XRPHALF/USDT,XRPHEDGE/USD,XTZBEAR/USD,XTZBEAR/USDT,XTZBULL/USD,XTZBULL/USDT,XTZHALF/USD,XTZHALF/USDT,XTZHEDGE/USD",
|
||||
"requestFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": true,
|
||||
"endpoints": {
|
||||
"url": "",
|
||||
"urlSecondary": "",
|
||||
"websocketURL": ""
|
||||
},
|
||||
"credentials": {
|
||||
"key": "Key",
|
||||
"secret": "Secret"
|
||||
}
|
||||
},
|
||||
"features": null
|
||||
}
|
||||
],
|
||||
"bankAccounts": [
|
||||
|
||||
Reference in New Issue
Block a user